2017-09-12 17 views
7

Tôi đang cố gắng hiểu các kiểu generic trong Java, và về lý thuyết có vẻ dễ hiểu, nhưng khi tôi cần áp dụng nó vào mã thực tôi có vấn đề. Tôi muốn khai báo phương thức trừu tượng sẽ trả về kiểu generic. Giả sử rằng tôi có một số giao diện rỗng gọi là Magicable và 2 class thực hiện nó: Magican và Witch. Bây giờ tôi tự hỏi sự khác biệt giữa những 3 tờ khai là gì:Sự khác biệt giữa việc sử dụng các ký tự đại diện và khai báo kiểu generic trong phương thức trừu tượng trong Java

/*1*/protected abstract <T extends Magicable> List<T> getMagicables(); 
/*2*/protected abstract List<? extends Magicable> getMagicables(); 
/*3*/protected abstract List<Magicable> getMagicables(); 
  1. Trong trường hợp đầu tiên tôi có vấn đề khi tôi muốn thực hiện cơ thể của phương pháp này trong một số lớp mà mở rộng lớp trừu tượng:

    @Override 
    protected List<Magican> getMagicable() {..} 
    

    tôi có thông điệp cảnh báo:

    loại an toàn: Các kiểu trả về Danh sách <Magican> cho getMagicable() từ loại MagicanService cần chuyển đổi không được kiểm tra để phù hợp với Danh sách <Magicable> từ loại MagicableService.

  2. Trong trường hợp thứ hai tôi không có cảnh báo này, nhưng tôi có vấn đề trong lớp trừu tượng trong đó tôi tuyên bố trên phương pháp trừu tượng:

    public void <T extends Magicable> T getOneFromList() { 
         List<T> list = getMagicables(); 
         //..... 
        } 
    

    Trong trường hợp này tôi có lỗi biên dịch trong getMagicables () gọi:

    Loại không khớp: không thể chuyển đổi từ Danh sách < chụp # 2-of? mở rộng Magicable > vào danh sách <T>

  3. trường hợp thứ ba gây ra lỗi biên dịch ở cả hai nơi nêu trên mã. Tôi không nghĩ nếu nó là giải pháp đúng trong trường hợp của tôi.

+0

'Danh sách phù thủy = getMagicables (Witch .class); 'là doable - với 1.' (Class loại) .... type.cast (obj) ... '. –

+0

Rất khó có khả năng rằng # 1 là một dạng phương pháp mà mọi người sẽ sử dụng. Vì đối số kiểu cho 'T' được cho bởi mã gọi phương thức và vì chúng ta không thể tạo một thể hiện mới của' T', điều duy nhất mà phương thức có thể làm mà không có một số loại bỏ chọn được trả về một danh sách rỗng . Do đó, tại sao ví dụ: ['Collections.emptyList'] (http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#emptyList--) và tương tự là những nơi duy nhất bạn sẽ tìm thấy phương pháp. – Radiodef

Trả lời

2
  1. Trường hợp đầu tiên

Chỉ cần khai báo phương pháp của bạn với:

@Override 
    protected <T extends Magicable> List<T> getMagicables() { 
     List<T> list = ... 
     return list 
    } 

Nếu bạn thực sự muốn điều này:

@Override 
    protected List<Magican> getMagicable() {..} 

bạn có thể phải tuyên bố của bạn gen ric T vào lớp defintion

 public abstract class AbstractKlass<T extends Magicable> { 
     protected abstract List<T> getMagicables(); 
    } 

sau đó trong Subclass của bạn:

 public class MySubClass extends AbstractKlass<Magican> { 

     @Override 
     protected List<Magican> getMagicables() { 
      ... 
     } 
    } 
  1. trường hợp thứ hai

Các lỗi biên dịch là bình thường bởi vì <? extends Magicable> từ chữ ký của phương pháp có nghĩa là bạn không quan tâm những gì bên trong danh sách của bạn từ thời điểm bạn ca n xem xét những yếu tố đó như Magicable. Khi thực hiện cuộc gọi

List<T> list = getMagicables(); 

Bạn muốn quản lý loại T mà không biết. Nói cách khác, có 3 trường hợp sử dụng: T là Magicable (OK), T là Magician (Sai ​​vì getMagicables có thể trả về một danh sách Witch) và T là Witch (Sai ​​quá).

  1. Tại sao tôi sử dụng ? extends Magicable thay vì chỉ Magicable trong danh sách

List<Magician> là một subtype của List<? extends Magicable> nhưng không phải là một subtype của List<Magicable>. Điều này hữu ích cho các tham số của phương thức.

public void doIt(List<? extends Magicable> list) { 
     // you can't add a Magician here 
    } 

có thể được sử dụng như

List<Witch> list = ... 
    doIt(list); 

Nhưng nếu bạn có

public void doIt(List<Magicable> list) { 
     // you can add a Magician here 
    } 

Bạn không thể sử dụng nó như

List<Witch> list = ... 
    doIt(list); // compile error 
1

Đối với một phần của sự cố, bạn đã cho chúng tôi biết, phương pháp/* 3 */là đủ, bạn không cần generics cho phần đó của mã. Nhưng bạn cần phải tôn trọng khả năng thay thế:

Bạn gặp lỗi trong # 1 vì phương pháp loại phụ đang hạn chế phạm vi của kiểu trả về: a MagicanMagicable nhưng không ngược lại. Siêu loại Magicable được phép trong loại phụ. Phương thức loại phụ phải được thay thế cho phương thức siêu loại, mà không phải là trường hợp trong ví dụ của bạn.

Lỗi trong # 2 là do bản chất của ký tự đại diện ?: ? extends MagicableT extends Magicable không cần phải cùng loại. Nếu T được khai báo trong phạm vi lớp học, ví dụ: lớp Magican<T> implements Magicable<T> (tất nhiên giao diện của bạn cần khai báo T trong trường hợp này) tất cả các lần xuất hiện của T trong loại của bạn sẽ tham chiếu đến cùng một lớp.

1
public abstract class AbstractMagicable<T extends Magicable> { 

    abstract List<T> getMagicables1(); 

    abstract List<? extends Magicable> getMagicables2(); 

    abstract List<Magicable> getMagicables3(); 
} 

class MagicableWitch extends AbstractMagicable<Witch> { 

    @Override 
    List<Witch> getMagicables1() { 
     return null; 
    } 

    @Override 
    List<? extends Magicable> getMagicables2() { 
     return getMagicables1(); 
    } 

    @Override 
    List<Magicable> getMagicables3() { 
     return Collections.singletonList(new Witch()); 
    } 
} 

class MagicableMagician extends AbstractMagicable<Magician> { 

    @Override 
    List<Magician> getMagicables1() { 
     return null; 
    } 

    @Override 
    List<? extends Magicable> getMagicables2() { 
     return getMagicables1(); 
    } 

    @Override 
    List<Magicable> getMagicables3() { 
     return Collections.singletonList(new Magician()); 
    } 
} 

1) T được sử dụng khi bạn muốn thay thế bằng tên thật trong khi sử dụng. Ví dụ: class MagicableWitch extends AbstractMagicable<Witch>.

Ở đây Witch đã thay thế T và do đó abstract List<T> getMagicables1(); được đổi thành List<Witch> getMagicables1() trong lớp bê tông của nó.

2)? được sử dụng khi bạn lớp sẽ được thay thế sẽ có sẵn khi chạy.

3) List<Magicable>List<Witch> khác nhau mặc dù Witch implements Magicable. Việc triển khai được hiển thị trong getMagicables3 .

1

Trong trường hợp đầu tiên, phương thức trừu tượng được khai báo là sử dụng loại chung <T extends Magicable> có nghĩa là phương pháp của bạn có thể trả về danh sách Magicable hoặc bất kỳ loại nào thực hiện nó. Trong quá trình thực hiện của bạn, bạn đang trả về loại ma cụ Magican là một Magicable. Bạn có thể bỏ qua cảnh báo một cách an toàn và thêm @SuppressWarning("unchecked") để tắt cảnh báo. Điều cần lưu ý là bất kỳ lớp nào mở rộng lớp học của bạn, sẽ bị hạn chế chỉ trả về danh sách của Magican.

Trong trường hợp thứ hai, khai báo List<T> list = getMagicables(); sẽ phát sinh lỗi vì phương thức của bạn không trả lại List<T> mà là một số List<? extends Magicable' không giống nhau. Bởi vì cách generics hoạt động, khi bạn khai báo kiểu trả về sử dụng ký tự đại diện không ràng buộc, bất kỳ mã nào gọi phương thức của bạn phải có loại đối sánh được chấp nhận, trong trường hợp của bạn như List<? extends Magicable> hoặc List<?>.

Về trường hợp thứ ba, phương thức trừu tượng của bạn trả lại một List<Magicable> trong khi triển khai của bạn trả lại List<Magic>. Điều này có vẻ phản trực giác, nhưng bạn không thể làm một cái gì đó như thế này với Generics trong Java: List<Magicable> list = ArrayList<Magic>. Điều này có vẻ lạ vì mảng cho phép bạn khai báo một cái gì đó như Magicable[] magics = new Magican[3];. Đây là một quan niệm sai lầm phổ biến bởi vì mảng là biến thể trong khi generics là bất biến. Điều gì covariant có nghĩa là nếu bạn có hai lớp SuperSub extends Super, Sub[] is a subtype of Super[]. Đối với generics, bởi vì chúng là bất biến, không có mối quan hệ giữa hai loại này, một số List<Sub> không phải là một kiểu con của List<Super>.

Nếu bạn muốn trả lại kiểu chung, chỉ cần sử dụng cùng khai báo kiểu như trường hợp đầu tiên protected <T extends Magicable> List<T> getMagicable() trong các lớp mở rộng lớp trừu tượng của bạn. Đó là một ý tưởng rất xấu khi sử dụng các ký tự đại diện trong kiểu trả về vì bạn buộc người dùng lớp của bạn sử dụng các ký tự đại diện trong các khai báo biến List của họ.

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