2016-05-04 26 views
11

Tôi đã chơi xung quanh với Generics và thấy rằng, trước sự ngạc nhiên của tôi, đoạn mã sau biên dịch:Tại sao không phải là loại an toàn java khi suy ra các loại mảng?

class A {} 
class B extends A {} 

class Generic<T> { 
    private T instance; 
    public Generic(T instance) { 
     this.instance = instance; 
    } 
    public T get(){ return instance; } 
} 

public class Main { 
    public static void main(String[] args) { 
     fArray(new B[1], new Generic<A>(new A())); // <-- No error here 
    } 

    public static <T> void fArray(T[] a, Generic<? extends T> b) { 
     a[0] = b.get(); 
    } 
} 

Tôi mong chờ T được suy ra B. A không mở rộng B. Vậy tại sao trình biên dịch không phàn nàn về nó?

T dường như được suy ra là Object, vì tôi cũng có thể vượt qua một số Generic<Object>.

Hơn nữa, khi thực sự chạy mã, nó ném một số ArrayStoreException trên đường dây a[0] = b.get();.

Tôi không sử dụng bất kỳ loại chung chung nào. Tôi cảm thấy ngoại lệ này có thể tránh được với lỗi thời gian biên dịch hoặc ít nhất là cảnh báo, nếu T thực sự được phỏng đoán là B.


Khi thử nghiệm thêm với List<...> tương đương:

public static void main(String[] args) { 
    fList(new ArrayList<B>(), new Generic<A>(new A())); // <-- Error, as expected 
} 

public static <T> void fList(List<T> a, Generic<? extends T> b) { 
    a.add(b.get()); 
} 

này sản xuất và lỗi:

The method fList(List<T>, Generic<? extends T>) in the type Main is not applicable for the arguments (ArrayList<B>, Generic<A>) 

Như hiện các trường hợp chung chung hơn:

public static <T> void fList(List<? extends T> a, Generic<? extends T> b) { 
    a.add(b.get()); // <-- Error here 
} 

Các trình biên dịch nhận ra chính xác rằng ? đầu tiên có thể tiếp tục xuống cấp bậc thừa kế hơn so với số thứ hai ?.

ví dụ: Nếu số ? đầu tiên là B và số ? thứ hai là A thì đây không phải là loại an toàn.


Vậy tại sao ví dụ đầu tiên không tạo ra lỗi trình biên dịch tương tự? Nó chỉ là một sự giám sát? Hoặc là có một giới hạn kỹ thuật?

Cách duy nhất tôi có thể tạo ra một lỗi bằng cách cung cấp một cách rõ ràng loại:

Main.<B>fArray(new B[1], new Generic<A>(new A())); // <-- Not applicable 

Tôi không thực sự tìm thấy bất cứ điều gì thông qua nghiên cứu của riêng tôi, trừ this article từ năm 2005 (trước khi generics) , nói về sự nguy hiểm của hiệp phương sai mảng.

Phương sai hiệp phương mảng dường như gợi ý về một lời giải thích, nhưng tôi không thể nghĩ ra.


jdk hiện tại là 1.8.0.0_91

+0

Tôi tự hỏi, nếu bạn có thể gặp phải sự cố xóa kiểu Java trong Generics? Chỉ là một suy nghĩ, tôi không có thời gian để nhìn vào máy ATM gần hơn. – Ukko

Trả lời

4

Hãy xem xét ví dụ sau:

class A {} 
class B extends A {} 

class Generic<T> { 
    private T instance; 
    public Generic(T instance) { 
     this.instance = instance; 
    } 
    public T get(){ return instance; } 
} 

public class Main { 
    public static void main(String[] args) { 
     fArray(new B[1], new Generic<A>(new A())); // <-- No error here 
    } 

    public static <T> void fArray(T[] a, Generic<? extends T> b) { 
     List<T> list = new ArrayList<>(); 
     list.add(a[0]); 
     list.add(b.get()); 
     System.out.println(list); 
    } 
} 

Như bạn có thể thấy, chữ ký sử dụng để suy ra các thông số loại giống hệt nhau, điều duy nhất đó là khác nhau là fArray() chỉ đọc các phần tử mảng thay vì viết họ, làm cho T -> A suy luận hoàn toàn có thể xác minh được khi chạy.

Và không có cách nào để trình biên dịch cho biết tham chiếu mảng của bạn sẽ được sử dụng để thực hiện phương thức.

+0

@ JornVernee Vâng, tôi đã vượt quá nó nhưng bạn sẽ có được ý tưởng chung. Không có lý do gì để trình biên dịch chọn 'T -> B' khi' T -> A' thỏa mãn tất cả các tiêu chí. – biziclop

+0

Vâng, điều này khá hay. Nhưng vấn đề là bạn muốn thêm an toàn kiểu bổ sung bằng cách nói rằng '' '?' '' Phải luôn luôn mở rộng '' 'T''' (tức là kiểu mảng). Cũng giống như bạn làm '' 'A a = new A(); A1 = a; '' 'thay vì có' '' Object a = new A(); A2 = (A) a; '' 'Đôi khi số 2 chỉ tốt (khi đúc), nhưng để chắc chắn chúng ta sử dụng cách thứ nhất, bởi vì nó có an toàn kiểu. –

+0

@JornVernee Đủ công bằng. Làm thế nào về bây giờ? :) – biziclop

2

I would expect T to be inferred to B. A does not extend B. So why doesn't the compiler complain about it?

T không được suy ra được B, nó được suy ra được A.Kể từ khi B mở rộng A, B[] là một loại phụ của A[] và do đó cuộc gọi phương thức là chính xác.

Trái với generics, loại phần tử của một mảng có sẵn khi chạy (chúng là reified). Vì vậy, khi bạn cố gắng làm

a[0] = b.get(); 

môi trường runtime biết rằng a thực sự là một mảng B và không thể tổ chức một A.

Vấn đề ở đây là Java đang mở rộng động. Các mảng có từ phiên bản Java đầu tiên, trong khi các Generics chỉ được thêm vào trong Java 1.5. Nói chung, Oracle cố gắng làm cho các phiên bản Java mới tương thích ngược, và do đó các lỗi được thực hiện trong các phiên bản trước đó (ví dụ hiệp phương sai mảng) không được sửa chữa trong các phiên bản mới hơn.

+0

Như tôi đã nói: _ "' '' T''' dường như được suy ra cho '' 'Object''', vì tôi cũng có thể chuyển' '' Generic '' '. _ Nhưng bạn có thể cho tôi biết _why_ '' 'T''' được suy ra là' '' Object'''? Hoặc bạn có thể chỉ cho tôi một trường hợp thất bại nếu '' 'T''' được giả thiết suy ra là' '' B''' thay thế? –

+0

'T' không được suy ra dưới dạng' Đối tượng', vì sau đó bạn sẽ không thể chuyển một phương thức 'Generic ' vào phương thức của mình, vì 'Generic ' không phải là loại phụ của 'Generic '. Tất nhiên, nếu bạn thực sự chuyển 'Generic ' thay vì 'Generic ', thì 'T' được suy ra là một' Đối tượng'. – Hoopje

+0

Nếu '' 'T''' là' '' Đối tượng''' thì '' 'Chung ' '' thỏa mãn '' 'Generic ' ''. Nó được chứng minh thêm bằng cách gọi '' 'Main. fArray (mới B [1], mới chung (mới A())); '' ', biên dịch không có lỗi. –

Các vấn đề liên quan