2009-07-15 45 views
7

Tại sao các Generics java lại quá khó? Tôi nghĩ cuối cùng tôi đã hiểu, nhưng nhật thực cho tôi một lỗi ở dòng trong somOtherMethod dưới đây bằng cách sử dụng một trong các phương thức getOuterList bên dưới.danh sách chung đơn giản trong java

protected List<?> getOuterList() { 
    // blah blah 
} 

protected List<? extends Object> getOuterList() { 
    // blah blah 
} 

protected void someOtherMethod() { 
    ... 
    getOuterList().add((MyObject)myObject); //compile error 
    ... 
} 

CẬP NHẬT: ok - vì vậy tôi hiểu được những lỗi ngay bây giờ. Đó là sự thiếu hiểu biết về một phần của tôi về những gì List<?> hoặc List<? extends SomeObject> thực sự có nghĩa là. Trong trường hợp trước đây, tôi nghĩ nó có nghĩa là một danh sách có thể chứa bất cứ thứ gì. Trong trường hợp sau, tôi cho rằng đó là danh sách một loạt các đối tượng mở rộng SomeObject. Sự thể hiện chính xác của sự hiểu biết của tôi sẽ chỉ là List<Object>List<SomeObject> (w/out the extends). Tôi nghĩ rằng việc mở rộng đã giúp tôi giải quyết một vấn đề mà họ không làm. Vì vậy, đây là nơi vấn đề thực sự của tôi nằm:

public interface DogKennel { 
    public List<Dog> getDogs(); 
} 

public class GreyHoundKennel implements DogKennel { 

    protected List<GreyHound> greyHounds; 

    public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
    } 

    public List<Dog> getDogs() { 
    // Is there no way to handle this with generics 
    // w/out creating a new List? 
    return getGreyHounds(); //compiler error 
    } 

} 
+0

Lỗi là gì? – jjnguy

Trả lời

1

Bạn đang vấp ngã trên thực tế là Java Generics không đa hình trên tham số kiểu.

Nói qua đoạn mã của bạn, hãy kéo ví dụ ngoài:

protected List<GreyHound> greyHounds; // List<GreyHound> is fine 

/** This method returns a lovely List of GreyHounds */ 
public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
} 

/** Here is the problem. A List<GreyHound> is not a List<Dog> */ 
public List<Dog> getDogs() { 
    return getGreyHounds(); //compiler error 
} 

Vì vậy, bình luận ban đầu của bạn là đúng. Hai Danh sách chắc chắn khác nhau và không có sự kế thừa giữa chúng. Vì vậy, tôi khuyên bạn nên điều tra hai tùy chọn sau:

  1. Thử trả lại danh sách mới như bạn đề xuất trong nhận xét của bạn. Ví dụ: return new ArrayList<Dog>(this.greyHounds);

  2. Bạn có thực sự cần giữ danh sách một giống chó cụ thể không? Có lẽ bạn nên xác định thành viên dữ liệu là List<Dog> mà bạn thêm GreyHounds cụ thể của mình. Tức là, protected List<Dog> greyHoundsOnly; nơi bạn quản lý con chó nào được phép trong cũi thông qua giao diện bên ngoài của đối tượng.

Trừ khi bạn có một lý do chính đáng để giữ một danh sách loại cụ thể, tôi sẽ suy nghĩ nghiêm túc về tùy chọn 2.

EDIT: fleshing ra lựa chọn đề nghị của tôi trên:

Lựa chọn 1: Trở lại danh sách mới. Ưu điểm: Đơn giản, đơn giản, bạn nhận được một danh sách đã nhập và nó loại bỏ một vấn đề an toàn luồng (không phơi bày một tham chiếu nội bộ với thế giới). Nhược điểm: dường như là một chi phí hiệu suất.

// Original code starts here. 
public interface DogKennel { 
    public List<Dog> getDogs(); 
} 

public class GreyHoundKennel implements DogKennel { 

    protected List<GreyHound> greyHounds; 

    public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
    } 
// Original code ends here 

    public List<Dog> getDogs() { 
    // This line eliminates the thread safety issue in returning 
    // an internal reference. It does use additional memory + cost 
    // CPU time required to copy the elements. Unless this list is 
    // very large, it will be hard to notice this cost. 
    return new ArrayList<Dog>(this.greyHounds); 
    } 

} 

Phương án 2: Sử dụng một biểu diễn dữ liệu khác nhau. Ưu điểm: chơi đẹp hơn với đa hình, trả về danh sách chung chung là mục tiêu ban đầu. Nhược điểm: đó là một kiến ​​trúc hơi khác nhau có thể không phù hợp với nhiệm vụ ban đầu.

public abstract class DogKennel { 
    protected List<Dog> dogs = new ArrayList<Dog>(); 
} 

public class GreyHoundKennel extends DogKennel { 

    // Force an interface that only allows what I want to allow 
    public void addDog(GreyHound greyHound) { dogs.add(greyHound); } 

    public List<Dog> getDogs() { 
    // Greatly reduces risk of side-effecting and thread safety issues 
    // Plus, you get the generic list that you were hoping for 
    return Collections.unmodifiableList(this.dogs); 
    } 

} 
+0

tùy chọn # 1: đây là lần truy cập hiệu suất. tốt hơn không sử dụng Generics. tùy chọn # 2: "Tôi có thực sự cần giữ danh sách giống chó cụ thể không?" Không, nhưng tôi nghĩ rằng đó là những gì danh sách đánh máy đã mua cho bạn? Nếu tôi không thể làm điều đó, thì điểm của generics là gì. Kết luận của tôi: Generics là một sự lãng phí thời gian. – andersonbd1

+0

@ andersonbd1, Bạn thành thật nghĩ rằng tùy chọn 1 là một hit hiệu suất? Bạn đang theo dõi bao nhiêu con chó cùng một lúc? Trừ khi đó là hơn một ngàn, bạn sẽ có một thời gian rất khó khăn để đo lường chi phí, đặc biệt là khi bạn so sánh nó với sự tiện lợi mã hóa của danh sách độc đáo đánh máy được trả lại. Nếu bạn thực sự muốn một danh sách đánh máy, điều đó sẽ cung cấp cho bạn (và đó là tùy chọn mà tôi thường sử dụng cho khá nhiều dữ liệu). Nếu bạn nghĩ rằng generics là một sự lãng phí thời gian, bạn đang sai lầm nặng. –

+0

@BobCross - tùy chọn # 1 - Chắc chắn cho 90% Danh sách không phải là lần truy cập hiệu suất, nhưng chúng tôi đang nói về một ngôn ngữ mục đích chung ở đây. Tôi, là một nhà phát triển ứng dụng, phải làm việc xung quanh ngôn ngữ như thế này? Có, tôi tin rằng generics là một sự lãng phí thời gian (trừ khi tất nhiên bạn đang thực sự viết các loại lớp mẫu), nhưng đó là mở ra toàn bộ cuộc tranh luận tĩnh động :-) Đối với tôi, tôi sẽ quay trở lại bằng cách sử dụng và '@SuppressWarnings (" không được chọn ")'. – andersonbd1

0

loại chung? có nghĩa là "một số loại cụ thể, nhưng tôi không biết cái nào". bất cứ điều gì bằng cách sử dụng một? về cơ bản là chỉ đọc bởi vì bạn không thể viết cho nó w/out biết loại thực tế.

3

Bạn đang nói rằng phương thức trả về "List của một số loại không xác định" (mà bạn không thể thêm vào vì bạn không thể đảm bảo rằng thứ bạn đang thêm là loại phụ của loại đó). Bạn thực sự muốn nói, một "List của bất cứ loại bạn muốn", vì vậy bạn cần phải thực hiện các phương pháp chung:

protected <T> List<T> getOuterList() { 
    // blah blah 
} 

Được rồi, tôi chỉ nhìn cập nhật của bạn:

Tất cả phụ thuộc vào những gì bạn có ý định làm được với kết quả của getDogs(). Nếu bạn không có ý định thêm bất kỳ mục nào vào danh sách, thì getDogs() phải trả về loại List<? extends Dog> và sau đó vấn đề sẽ được giải quyết.

Nếu bạn có ý định để có thể thêm những thứ với nó, và bởi các loại List<Dog> nó có nghĩa là bạn có thể thêm bất kỳ loại Dog với nó, sau đó một cách hợp lý trong danh sách này có thể không phải là danh sách giống như greyHounds, vì greyHounds có kiểu List<GreyHound> và do đó Dog các đối tượng không nên đi vào trong đó.

Điều đó có nghĩa là bạn phải tạo danh sách mới. Lưu ý rằng tất cả các thay đổi đối với danh sách mới sẽ không được phản ánh trong danh sách gốc greyHouds.

+0

Điều đó không hữu ích lắm trừ khi nó trả về một danh sách trống (hoặc một danh sách 'null'). –

+0

Chúc mừng, Generics là những gì ** I ** đang tìm kiếm. –

3

khai này:

List<?> getOuterList() { } 

đang nói với trình biên dịch "Tôi thực sự không biết những gì loại danh sách tôi sẽ trở lại". Sau đó, về cơ bản, bạn thực thi

list<dunno-what-this-is>.add((MyObject)myObject) 

Không thể thêm MyObject vào Danh sách thứ gì đó mà không biết loại đó là gì.

khai này:

protected List<? extends Object> getOuterList() { ... } 

nói với trình biên dịch "Đây là một danh sách những thứ mà phân nhóm của Object". Vì vậy, một lần nữa, tất nhiên bạn không thể chuyển sang "MyObject" và sau đó thêm vào danh sách các đối tượng. Bởi vì tất cả các trình biên dịch biết là danh sách có thể chứa các đối tượng.

Bạn thể Tuy nhiên, làm điều gì đó như thế này:

List<? super MyObject>.getOuterList() { ... } 

và sau đó thêm thành công một MyObject. Đó là bởi vì bây giờ trình biên dịch biết Danh sách là một danh sách MyObject, hoặc bất kỳ siêu kiểu của MyObject, vì vậy nó chắc chắn có thể chấp nhận MyObject.

Edit: Như ví dụ DogKennel của bạn, đoạn mã này, tôi nghĩ rằng những gì bạn muốn:

protected List<GreyHound> greyHounds; 

// We only want a List of GreyHounds here: 
public List<GreyHound> getGreyHounds() { 
    return this.greyHounds; 
} 

// The list returned can be a List of any type of Dog: 
public List<? extends Dog> getDogs() { 
    return getGreyHounds(); 
} 
0

Đã có câu trả lời được chấp nhận, tuy nhiên, hãy xem xét sửa đổi mã sau đây.

public interface DogKernel { 
    public List<? extends Dog> getDogs(); 
} 

public class GreyHoundKennel implements DogKernel { 
    protected List<GreyHound> greyHounds; 

    public List<GreyHound> getGreyHounds() { 
     return this.greyHounds; 
    } 

    public List<? extends Dog> getDogs() { 
     return getGreyHounds(); // no compilation error 
    } 

    public static void main(String[] args) { 
    GreyHoundKennel inst = new GreyHoundKennel(); 
    List<? extends Dog> dogs = inst.getDogs(); 
    } 
} 

Generics Java thực sự bị hỏng, nhưng không bị hỏng. BTW Scala sửa lỗi này một cách rất thanh lịch bằng cách cung cấp xử lý phương sai.

CẬP NHẬT ----------

Vui lòng xem xét đoạn mã được cập nhật.

public interface DogKennel<T extends Dog> { 
    public List<T> getDogs(); 
} 

public class GreyHoundKennel implements DogKennel<GreyHound> { 
    private List<GreyHound> greyHounds; 

    public List<GreyHound> getDogs() { 
     return greyHounds; // no compilation error 
    } 

    public static void main(String[] args) { 
     GreyHoundKennel inst = new GreyHoundKennel(); 
     inst.getDogs().add(new GreyHound()); // no compilation error 
    } 
} 
+0

nhưng bạn không thể thêm vào danh sách được trả về từ getDogs - đó là những gì bắt đầu cuộc thảo luận này. – andersonbd1

+0

Lời xin lỗi của tôi - Tôi đã tập trung nhiều hơn vào phần thứ hai của bài đăng của bạn. Xin vui lòng xem xét một cách tiếp cận thay thế trong phần UPDATE. Xin lưu ý rằng theo nguyên tắc chung, tốt hơn là không nên trực tiếp thay đổi thuộc tính thu thập. Thay vào đó, chúng tôi khuyên bạn nên cung cấp các phương thức mutator riêng biệt như addDog, removeDog và cho getter để trả về bộ sưu tập không thể sửa đổi như được đề xuất trong câu trả lời được chấp nhận. – 01es

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