2012-05-24 32 views
7

Tôi có thể giải thích điều này ở đâu?Java Generics: thêm sai loại trong bộ sưu tập

Tôi có những cặp vợ chồng các lớp:

abstract class Animal { 
    public void eat() { 
     System.out.println("Animal is eating"); 
    } 
} 

class Dog extends Animal { 
    public void woof() { 
     System.out.println("woof"); 
    } 
} 

class Cat extends Animal { 
    public void meow() { 
     System.out.println("meow"); 
    } 
} 

Và đây là hành động:

import java.util.ArrayList; 
import java.util.List; 

public class TestClass { 

    public static void main(String[] args) { 
     new TestClass().go(); 
    } 

    public void go() { 
     List<Dog> animals = new ArrayList<Dog>(); 
     animals.add(new Dog()); 
     animals.add(new Dog()); 
     doAction(animals); 
    } 

    public <T extends Animal> void doAction(List<T> animals) { 

     animals.add((T) new Cat()); // why is it possible? 
            // Variable **animals** is List<Dog>, 
            // it is wrong, that I can add a Cat! 

     for (Animal animal: animals) { 
      if (animal instanceof Cat) { 
       ((Cat)animal).meow(); 
      } 
      if (animal instanceof Dog) { 
       ((Dog)animal).woof(); 
      } 
     } 
    } 
} 

Ví dụ này biên dịch mà không có lỗi, và đầu ra là:

woof 
woof 
meow 

Nhưng làm thế nào thế nào tôi có thể thêm vào danh sách Dog a Cat? Và làm thế nào Mèo được đúc cho chó?

Tôi sử dụng: phiên bản java "1.6.0_24". Môi trường chạy OpenJDK (IcedTea6 1.11.1) (6b24-1.11.1-4ubuntu3)

+1

Có cảnh báo trình biên dịch, không? –

+1

Chắc chắn sẽ có một cảnh báo trình biên dịch trên animals.add ((T) new Cat()) ;. – tjg184

+1

Và vì vậy hãy bắt đầu đường mòn của nước mắt đó là Generics Java. –

Trả lời

6

Ok đây là thỏa thuận với Generics (bất cứ điều gì có sử dụng đúc hackery có thể không được an toàn khi chạy, vì Generics tác phẩm của erasure):

Bạn có thể gán một subtype parameterised theo cùng một cách như

List<Animal> l = new ArrayList<Animal>(); 

và bạn có thể thêm các mục là loại thông số này hoặc các lớp con của nó, ví dụ:

l.add(new Cat()); 
l.add(new Dog()); 

nhưng bạn chỉ có thể thoát ra khỏi công ty pe của tham số:

Animal a = l.get(0); 
Cat c = l.get(0); //disallowed 
Dog d = l.get(1); //disallowed 

Bây giờ, bạn có thể sử dụng một thẻ hoang dã để thiết lập một giới hạn trên các tham số kiểu

List<? extends Animal> l = new ArrayList<Animal>(); 
List<? extends Animal> l = new ArrayList<Cat>(); 
List<? extends Animal> l = new ArrayList<Dog>(); 

Nhưng bạn không thể thêm các mục mới vào danh sách này

l.add(new Cat()); // disallowed 
l.add(new Dog()); // disallowed 

Trong trường hợp của bạn, bạn có List<T> để có phương thức add(T t) để bạn có thể thêm nếu bạn truyền tới T. Nhưng T có loại bị chặn ở trên bởi Animal vì vậy bạn thậm chí không nên cố gắng thêm vào danh sách này, nhưng nó được coi là loại cụ thể và đó là lý do tại sao nó cho phép truyền. Tuy nhiên, điều này có thể ném một số ClassCastException.

Và bạn chỉ có thể lấy vật phẩm là loại ràng buộc trên

Animal a = l.get(0); 
Cat c = l.get(0); //disallowed 
Dog d = l.get(1); //disallowed 

Hoặc bạn có thể thiết lập các tham số kiểu thấp hơn ràng buộc

List<? super Animal> l1 = new ArrayList<Object>(); 
List<? super Animal> l1 = new ArrayList<Animal>(); 
List<? super Cat> l2 = new ArrayList<Animal>(); 
List<? super Cat> l2 = new ArrayList<Cat>(); 
List<? super Dog> l3 = new ArrayList<Animal>(); 
List<? super Dog> l3 = new ArrayList<Dog>(); 

Và bạn có thể thêm các đối tượng được phân nhóm của mức thấp hơn loại ràng buộc

l1.add(new Cat()); 
l1.add(new Dog()); 
l1.add(new Object()); //disallowed 

Nhưng tất cả các đối tượng được truy lục thuộc loại Object

Object o = l1.get(0); 
Animal a = l1.get(0); //disallowed 
Cat c = l2.get(0); //disallowed 
Dog d = l3.get(0); //disallowed 
+0

+1 để đưa ra các ví dụ đó. – dragon66

0

Generics chỉ hoạt động cho thời gian biên dịch an toàn. Trong trường hợp của bạn, làm thế nào có thể trình biên dịch biết rằng một cái gì đó xấu sẽ xảy ra? Nó giả định định nghĩa kiểu của bạn, và tiến hành điều đó. Để làm nhiều hơn sẽ có nghĩa là công việc phức tạp hơn nhiều cho trình biên dịch, nhưng có các bộ kiểm tra tĩnh mở rộng và các công cụ khác có thể bắt được thời gian chạy trước này.

1

Điều này liên quan đến loại tẩy xoá. Loại không được bảo quản trong thời gian chạy. Thực sự, Danh sách trở thành Danh sách đối tượng kiểu khi chạy. Đây là lý do tại sao bạn nhận được một cảnh báo trình biên dịch hoặc nên được trên animals.add ((T) new Cat()); Vào thời gian biên dịch, Cat mở rộng động vật thuộc loại T. Tuy nhiên, nó không thể thực thi một Dog trong danh sách tại thời điểm đó, do đó cảnh báo trình biên dịch.

0

Đủ để nói rằng có một số T mà tác vụ có thể thành công. Đối với mã được biên dịch, nó sẽ chính xác giống như khi bạn viết animals.add((Animal)new Cat());

2

Đừng mong đợi các generics để thực hiện kiểm tra kiểu thời gian chạy. Trong quá trình biên dịch, Java thực hiện tất cả các suy luận kiểu, instantiates tất cả các loại, ... và sau đó xóa tất cả dấu vết của các kiểu generic từ mã. Khi chạy, loại là Danh sách, không phải Danh sách < T> hoặc Danh sách < Chó>.

Câu hỏi chính, tại sao nó cho phép bạn truyền new Cat() để nhập T extends Animal, chỉ với cảnh báo về chuyển đổi không được kiểm tra, hợp lệ. Một số tính năng không rõ ràng của hệ thống kiểu làm cho việc hợp pháp hoá các khuôn mẫu đáng ngờ như vậy là cần thiết.

Nếu bạn muốn trình biên dịch để ngăn chặn việc bổ sung các bất cứ điều gì vào danh sách, bạn nên sử dụng một ký tự đại diện:

public void doAction(List< ? extends Animal > animals) { 
    animals.add(new Cat()); // disallowed by compiler 
    animals.add((Animal)new Cat()); // disallowed by compiler 

    for (Animal animal: animals) { 
     if (animal instanceof Cat) { 
      ((Cat)animal).meow(); 
     } 
     if (animal instanceof Dog) { 
      ((Dog)animal).woof(); 
     } 
    } 
} 

T.B. Các downcasts đáng ngờ trong cơ thể vòng lặp là một ví dụ hoàn hảo về cách Java lame dành cho các loại phân tách (biến thể).

+0

1. bạn vẫn có thể truyền tới 'Danh sách ' và thêm nội dung vào nó 2.bạn vẫn có thể chuyển danh sách này sang phương thức 'doAction' ban đầu từ trên (tham số hóa với' T') mà vẫn có thể thêm vào nó – newacct

0

Chức năng của bạn doAction được parametrized theo loại T kéo dài lớp Animal. Vì vậy, nó có thể là đối tượng thuộc loại Dog hoặc Cat.
Bạn cũng nên lưu ý sự khác biệt giữa thông số chính thứcthông số hiệu quả.
Thông số chính thức là thông số bạn sử dụng khi xác định phương thức, trong trường hợp của bạn là List <T> (sử dụng nó làm lối tắt).
Thông số hiệu quả là thông số bạn "cung cấp" cho phương thức của bạn khi gọi, tại đây List<Dog>.
.
Hãy xem animals.add((T) new Cat()) trong doAction(). động vật là danh sách các thành phần thuộc loại TDog hoặc Cat, do đó, không có lỗi, vì đó là loại thông số chính thức.
Vì vậy, đó là lý do. Đây là một trong những lợi ích của việc sử dụng các lớp parametrize.

+1

Java không nhận ra rằng chỉ Động vật là Chó và Mèo. Thật vậy mà không bao giờ có thể thực sự là trường hợp, bởi vì người ta luôn có thể thêm một người khác. Điều đang xảy ra là các diễn viên đang được kiểm tra (bởi trình biên dịch) độc lập với suy luận kiểu xảy ra khi nó được gọi với một 'Danh sách < Dog >'. Rõ ràng quy tắc là dàn diễn viên được phép (với một cảnh báo) nếu nó có thể * bao giờ * hợp lệ. –

+0

Những gì tôi nói là dựa trên mã mà người đăng tải đưa ra. Và vâng, tất nhiên, người ta luôn có thể thêm người khác. Quan điểm của tôi là 'Cát' kéo dài 'Động vật' để thêm nó vào danh sách các yếu tố mở rộng Động vật không sai. –

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