2016-01-26 11 views
7

Tôi đã xem xét một trong các đường mòn của Oracle trên các Generics Java, mang tên "Effects of Type Erasure and Bridge Methods" và tôi không thể thuyết phục bản thân mình về một lời giải thích được đưa ra. Tò mò, tôi đã thử nghiệm mã cục bộ và tôi thậm chí không thể tái tạo hành vi mà đường mòn giải thích. Đây là mã có liên quan:Vấn đề tiềm ẩn với một trong những đường mòn của Oracle trên các hình khối Java

public class Node<T> { 
    public T data; 

    public Node(T data) { this.data = data; } 

    public void setData(T data) { 
     System.out.println("Node.setData"); 
     this.data = data; 
    } 
} 

public class MyNode extends Node<Integer> { 
    public MyNode(Integer data) { super(data); } 

    public void setData(Integer data) { 
     System.out.println("MyNode.setData"); 
     super.setData(data); 
    } 
} 

Đường mòn Oracle tuyên bố những hành vi sau đây cho đoạn mã này:

MyNode mn = new MyNode(5); 
Node n = mn;   // A raw type - compiler throws an unchecked warning 
n.setData("Hello");  
Integer x = mn.data; // Causes a ClassCastException to be thrown. 

Đoạn mã này sẽ trông giống như sau sau khi loại tẩy xoá:

MyNode mn = new MyNode(5); 
Node n = (MyNode)mn;   // A raw type - compiler throws an unchecked warning 
n.setData("Hello"); 
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown. 

Tôi không hiểu các phôi được sử dụng ở đây hoặc hành vi. Khi tôi cố gắng chạy mã này tại địa phương sử dụng IntelliJ với Java 7, tôi có hành vi này:

MyNode mn = new MyNode(5); 
Node n = mn;   // A raw type - compiler throws an unchecked warning 
n.setData("Hello");  // Causes a ClassCastException to be thrown. 
Integer x = mn.data; 

Nói cách khác, các JVM sẽ không cho phép một String được sử dụng với setData(). Điều này thực sự trực quan với tôi, và nó đồng ý với sự hiểu biết của tôi về Generics. Vì MyNodemn được xây dựng với Integer, trình biên dịch phải truyền mọi cuộc gọi đến setData() với Integer để đảm bảo an toàn loại (tức là Integer đang được chuyển vào).

Ai đó có thể làm sáng tỏ một số lỗi rõ ràng này trong đường mòn Oracle không?

Trả lời

3

Bạn đã đọc sai trang Oracle. Nếu bạn đọc tất cả các cách để kết thúc, bạn sẽ tìm thấy nó nói rằng những gì xảy ra là những gì bạn mô tả.

Đây không phải là trang được viết tốt; tác giả nói "blah xảy ra" khi ý nghĩa của chúng là "NẾU CÓ TRƯỜNG HỢP NÀO thì blah xảy ra NHƯNG CHÚNG TÔI CÓ THỂ KHÔNG CÓ TRƯỜNG HỢP". Chúng quá lỏng lẻo với ngôn ngữ của chúng.

Điểm của phương pháp cầu nối trang - là giải thích cách thực hiện hành vi thực tế như bạn đã quan sát, khi hành vi được dự đoán (dựa trên thiết kế Generical acutal + implementation) là những gì họ "đề xuất" ngay từ đầu.

+0

Tôi nghĩ rằng trang này là lỏng lẻo với ngôn ngữ của nó là cho nó tín dụng quá nhiều. Vì vậy, những gì bạn đang nói là các cuộc gọi 'n.setData (" Hello ")' thực sự nhấn phương pháp cầu được tạo ra bởi trình biên dịch, mà sẽ đúc tất cả các đối tượng được truyền cho nó để 'Integer' trước khi gọi phương pháp siêu. Vâng điều này giải thích tất cả mọi thứ tôi cho rằng, cảm ơn cho những hiểu biết. –

+1

Tôi có xu hướng ghê tởm về tài liệu xấu, nhưng tôi đã quen với việc xuất bản quân đoàn xuất bản tài liệu khủng khiếp, đặc biệt là cho các API. Trớ trêu thay, Java đã có một số tài liệu hay nhất từ ​​trước đến nay, khi nó còn trẻ. Ngày nay ... Tôi cố gắng giữ sự khinh miệt của mình cho bản thân mình :). – Adam

1

Vâng, nó được giải thích trong đường nhỏ.

Trong lý thuyết, khi lớp học Node được biên dịch, loại cơ sở T bị xóa thành Object.

Vì vậy, trong thực tế, nó được biên dịch vào một cái gì đó giống như

class Node { 
    public Object data; 

    public Node(Object data) {this.data = data; } 

    public void setData(Object data) { 
     System.out.println("Node.setData"); 
     this.data = data; 
    } 
} 

Sau đó, bạn tạo ra một lớp con MyNode, có riêng của mình setData(Integer data). Theo như Java là có liên quan, đây là một quá tải quá tải của phương pháp setData, không phải là ghi đè lên nó. Mỗi đối tượng MyNode có hai phương thức setData. Một là setData(Object) nó được kế thừa từ Node và một là setData(Integer). Vì vậy, về cơ bản, nếu bạn sử dụng kiểu thô, và bạn gọi setData với bất kỳ tham chiếu nào không phải là Integer, cách giải thích thông thường của Java về điều này sẽ là gọi quá tải setData(Object).

Điều này sẽ không gây ra sự cố khi gán, bởi vì data được khai báo là Object, không phải là Integer. Vấn đề sẽ chỉ xảy ra khi bạn cố gắng gán dữ liệu trở lại tham chiếu Integer. Hành vi đơn giản này của Java sẽ khiến đối tượng MyNode bị "ô nhiễm" với dữ liệu không phù hợp. Tuy nhiên, như dấu vết nói, trình biên dịch thêm một phương thức "cầu nối" để làm cho lớp con cư xử giống như cách bạn nghĩ về nó một cách trực giác. Nó thêm ghi đè của setData(Object) đến MyNode, để bạn không thể gọi số an toàn, không an toàn Node.setData(Object) ban đầu. Trong phương thức cầu quan trọng này, có một dàn diễn viên rõ ràng để Integer đảm bảo rằng bạn sẽ không thể chỉ định tham chiếu không phải số nguyên cho số data.

Và đó là hành vi bạn thấy khi bạn thực sự biên dịch và chạy ví dụ.

Nếu bạn chạy javap -p vào file MyNode.class, bạn sẽ thấy rằng trên thực tế, nó có hai phương pháp setData:

class MyNode extends Node<java.lang.Integer> { 
    public MyNode(java.lang.Integer); 
    public void setData(java.lang.Integer); 
    public void setData(java.lang.Object); 
} 
Các vấn đề liên quan