PECS (Nhà sản xuất mở rộng siêu tiêu dùng) là gì?


729

Tôi tình cờ thấy PECS (viết tắt của Nhà sản xuất extendsvà Người tiêu dùngsuper ) trong khi đọc về thuốc generic.

Ai đó có thể giải thích cho tôi cách sử dụng PECS để giải quyết nhầm lẫn giữa extendssuperkhông?


3
Một lời giải thích rất hay với một ví dụ @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630 giải thích supermột phần nhưng, đưa ra ý tưởng về phần khác.
lupchiazoem

Câu trả lời:


843

tl; dr: "PECS" là từ quan điểm của bộ sưu tập. Nếu bạn chỉ lấy các mục từ một bộ sưu tập chung, thì đó là nhà sản xuất và bạn nên sử dụng extends; nếu bạn chỉ nhồi các mặt hàng vào thì đó là người tiêu dùng và bạn nên sử dụng super. Nếu bạn làm cả hai với cùng một bộ sưu tập, bạn không nên sử dụng extendshoặc super.


Giả sử bạn có một phương thức lấy tham số của nó là tập hợp các thứ, nhưng bạn muốn nó linh hoạt hơn là chỉ chấp nhận a Collection<Thing>.

Trường hợp 1: Bạn muốn đi qua bộ sưu tập và làm mọi thứ với từng mục.
Sau đó, danh sách là một nhà sản xuất , vì vậy bạn nên sử dụng a Collection<? extends Thing>.

Lý do là một Collection<? extends Thing>có thể chứa bất kỳ kiểu con nào Thing, và do đó mỗi phần tử sẽ hoạt động như một Thingkhi bạn thực hiện thao tác của mình. (Bạn thực sự không thể thêm bất cứ điều gì vào a Collection<? extends Thing>, bởi vì bạn không thể biết trong thời gian chạy mà kiểu con cụ thể của Thingbộ sưu tập giữ.)

Trường hợp 2: Bạn muốn thêm những thứ vào bộ sưu tập.
Sau đó, danh sách là một người tiêu dùng , vì vậy bạn nên sử dụng a Collection<? super Thing>.

Lý do ở đây là không giống như Collection<? extends Thing>, Collection<? super Thing>luôn có thể giữ Thingbất kể loại tham số thực tế là gì. Ở đây bạn không quan tâm những gì đã có trong danh sách miễn là nó sẽ cho phép Thingthêm vào; Đây là những gì ? super Thingđảm bảo.


142
Tôi luôn cố gắng nghĩ về nó theo cách này: Một nhà sản xuất được phép sản xuất một cái gì đó cụ thể hơn, do đó mở rộng , người tiêu dùng được phép chấp nhận một cái gì đó chung chung hơn, do đó siêu .
Feuermurmel

10
Một cách khác để ghi nhớ sự khác biệt của nhà sản xuất / người tiêu dùng là nghĩ về chữ ký phương thức. Nếu bạn có một phương thức doSomethingWithList(List list), bạn đang sử dụng danh sách và do đó sẽ cần hiệp phương sai / mở rộng (hoặc Danh sách bất biến). Mặt khác, nếu phương thức của bạn là List doSomethingProvidingList, thì bạn đang tạo Danh sách và sẽ cần chống chỉ định / siêu (hoặc Danh sách bất biến).
Raman

3
@MichaelMyer: Tại sao chúng ta không thể sử dụng loại tham số cho cả hai trường hợp này? Có bất kỳ lợi thế cụ thể nào của việc sử dụng các ký tự đại diện ở đây không, hay nó chỉ là một phương tiện để cải thiện khả năng đọc tương tự như, sử dụng các tham chiếu đến constnhư các tham số phương thức trong C ++ để biểu thị rằng phương thức không sửa đổi các đối số?
Chatterjee

7
@Raman, tôi nghĩ bạn chỉ nhầm lẫn nó. Trong doSthWithList (bạn có thể có Danh sách <? Super Thing>), vì bạn là người tiêu dùng, bạn có thể sử dụng siêu (hãy nhớ, CS). Tuy nhiên, đó là Danh sách <? mở rộng Thing> getList () vì bạn được phép trả lại một cái gì đó cụ thể hơn khi sản xuất (PE).
masterxilo

4
@AZ_ Tôi chia sẻ tình cảm của bạn. Nếu một phương thức lấy get () từ danh sách, phương thức đó sẽ được coi là Người tiêu dùng <T> và danh sách được coi là nhà cung cấp; nhưng quy tắc của PECS là từ quan điểm của danh sách, do đó, 'mở rộng' được yêu cầu. Nó phải là GEPS: được gia hạn; đặt siêu.
Cá cây Zhang

561

Các nguyên tắc đằng sau này trong khoa học máy tính được gọi là

  • Hiệp phương sai : ? extends MyClass,
  • Chống chỉ định: ? super MyClass
  • Bất biến / không phương sai: MyClass

Hình dưới đây sẽ giải thích khái niệm. Hình ảnh lịch sự: Andrey Tyukin

Hiệp phương sai và chống chỉ định


143
Nè mọi người. Tôi là Andrey Tyukin, tôi chỉ muốn xác nhận rằng động vật phù du & DaoWen đã liên lạc với tôi và được tôi cho phép sử dụng bản phác thảo, nó được cấp phép theo (CC) -BY-SA. Thx @ Anoop đã mang đến cho nó một cuộc sống thứ hai phương sai tại chỗ ... Có lẽ tôi nên viết một câu trả lời chi tiết hơn cho thấy rõ cách phác họa này áp dụng cho Java ...
Andrey Tyukin

3
Đây là một trong những giải thích đơn giản và rõ ràng nhất cho Hiệp phương sai và Chống chỉ định mà tôi từng tìm thấy!
cs4r

@Andrey Tyukin Xin chào, tôi cũng muốn sử dụng hình ảnh này. Tôi có thể liên hệ với bạn bằng cách nào?
sloc

Nếu bạn có bất kỳ câu hỏi nào về hình minh họa này, chúng tôi có thể thảo luận về chúng trong phòng chat: chat.stackoverflow.com/rooms/145734/ Kẻ
Andrey Tyukin


49

PECS (Nhà sản xuất extendsvà Người tiêu dùng super)

nguyên tắc → Nhận và đặt nguyên tắc.

Nguyên tắc này nói rằng:

  • Sử dụng một ký tự đại diện mở rộng khi bạn chỉ nhận được các giá trị từ một cấu trúc.
  • Sử dụng một siêu ký tự khi bạn chỉ đặt các giá trị vào một cấu trúc.
  • Và đừng sử dụng ký tự đại diện khi cả hai bạn nhận và đặt.

Ví dụ trong Java:

class Super {

    Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
    void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}

class Sub extends Super {

    @Override
    String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object) 
    @Override
    void testContraVariance(String parameter){} //doesn't support even though String is subtype of Object

}

Nguyên tắc thay thế Liskov: nếu S là một kiểu con của T, thì các đối tượng thuộc loại T có thể được thay thế bằng các đối tượng loại S.

Trong hệ thống loại ngôn ngữ lập trình, quy tắc gõ

  • covariant nếu nó duy trì thứ tự các loại (≤), thứ tự các loại từ cụ thể hơn đến chung chung hơn;
  • chống chỉ định nếu nó đảo ngược thứ tự này;
  • bất biến hoặc không biến đổi nếu cả hai không áp dụng.

Hiệp phương sai và chống chỉ định

  • Các kiểu dữ liệu chỉ đọc (nguồn) có thể là covariant ;
  • kiểu dữ liệu chỉ ghi (bồn) có thể được contravariant .
  • Các kiểu dữ liệu có thể thay đổi đóng vai trò là cả nguồn và mức chìm sẽ là bất biến .

Để minh họa hiện tượng chung này, hãy xem xét kiểu mảng. Đối với loại Động vật, chúng ta có thể tạo loại Động vật []

  • covariant : một con mèo [] là một con vật [];
  • chống chỉ định : một con vật [] là một con mèo [];
  • bất biến : một con vật [] không phải là con mèo [] và con mèo [] không phải là con vật [].

Ví dụ Java:

Object name= new String("prem"); //works
List<Number> numbers = new ArrayList<Integer>();//gets compile time error

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
myNumber[0] = 3.14; //attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)

List<String> list=new ArrayList<>();
list.add("prem");
List<Object> listObject=list; //Type mismatch: cannot convert from List<String> to List<Object> at Compiletime  

thêm ví dụ

ràng buộc (tức là hướng tới một nơi nào đó) ký tự đại diện : Có 3 hương vị khác nhau của ký tự đại diện:

  • In-sai / Non-sai: ?hoặc ? extends Object- bị chặn Wildcard. Nó là viết tắt của gia đình của tất cả các loại. Sử dụng khi cả hai bạn nhận và đặt.
  • Co-variance: ? extends T(họ của tất cả các loại là kiểu con của T) - một ký tự đại diện có giới hạn trên . Ttrên lớp -most trong hệ thống phân cấp thừa kế. Sử dụng extendský tự đại diện khi bạn chỉ Nhận giá trị ra khỏi cấu trúc.
  • Contra-variance: ? super T(họ của tất cả các loại là siêu kiểu của T) - một ký tự đại diện có giới hạn dưới . Tthấp hơn lớp -most trong hệ thống phân cấp thừa kế. Sử dụng superký tự đại diện khi bạn chỉ Đặt giá trị vào cấu trúc.

Lưu ý: ký tự đại diện ?có nghĩa là không hoặc một lần , đại diện cho một loại không xác định. Ký tự đại diện có thể được sử dụng làm kiểu của một tham số, không bao giờ được sử dụng làm đối số kiểu cho một lời gọi phương thức chung, một tạo cá thể lớp chung. (Tức là khi sử dụng ký tự đại diện mà tham chiếu không được sử dụng trong các chương trình khác như chúng ta sử dụng T)

nhập mô tả hình ảnh ở đây

class Shape { void draw() {}}

class Circle extends Shape {void draw() {}}

class Square extends Shape {void draw() {}}

class Rectangle extends Shape {void draw() {}}

public class Test {
 /*
   * Example for an upper bound wildcard (Get values i.e Producer `extends`)
   * 
   * */  

    public void testCoVariance(List<? extends Shape> list) {
        list.add(new Shape()); // Error:  is not applicable for the arguments (Shape) i.e. inheritance is not supporting
        list.add(new Circle()); // Error:  is not applicable for the arguments (Circle) i.e. inheritance is not supporting
        list.add(new Square()); // Error:  is not applicable for the arguments (Square) i.e. inheritance is not supporting
        list.add(new Rectangle()); // Error:  is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
        Shape shape= list.get(0);//compiles so list act as produces only

        /*You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape> 
         * You can get an object and know that it will be an Shape
         */         
    }
      /* 
* Example for  a lower bound wildcard (Put values i.e Consumer`super`)
* */
    public void testContraVariance(List<? super Shape> list) {
        list.add(new Shape());//compiles i.e. inheritance is supporting
        list.add(new Circle());//compiles i.e. inheritance is  supporting
        list.add(new Square());//compiles i.e. inheritance is supporting
        list.add(new Rectangle());//compiles i.e. inheritance is supporting
        Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
        Object object= list.get(0); // gets an object, but we don't know what kind of Object it is.

        /*You can add a Shape,Circle,Square,Rectangle to a List<? super Shape> 
        * You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
        */  
    }
}

thuốc genericví dụ


Này, tôi chỉ muốn biết ý của bạn với câu cuối cùng: "Nếu bạn nghĩ rằng sự tương tự của tôi là sai xin vui lòng cập nhật". Bạn có nghĩa là nếu nó sai về mặt đạo đức (đó là chủ quan) hoặc nếu nó sai trong bối cảnh lập trình (đó là khách quan: không, nó không sai)? Tôi muốn thay thế nó bằng một ví dụ trung lập hơn, có thể chấp nhận được độc lập với các chuẩn mực văn hóa và niềm tin đạo đức; Nếu đó là OK với bạn.
Neuron

cuối cùng tôi có thể có được nó. Giải thích tốt đẹp.
Oleg Kuts

2
@Premraj ,, In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.tôi không thể thêm phần tử vào Danh sách <?> Hoặc Danh sách <? mở rộng Object>, vì vậy tôi không hiểu tại sao nó có thể Use when you both get and put.
LiuWenbin_NO.

1
@LiuWenbin_NO. - Đó là một phần của câu trả lời là sai lệch. ?- "ký tự đại diện không giới hạn" - tương ứng với sự đối nghịch chính xác của tính bất biến. Vui lòng tham khảo tài liệu sau: docs.oracle.com/javase/tutorial/java/generics/ Khăn trong đó nêu rõ: Trong trường hợp mã cần truy cập vào biến là cả biến "in" và biến "out", hãy làm không sử dụng ký tự đại diện. (Họ đang sử dụng "vào" và "ra" đồng nghĩa với "nhận" và "đặt"). Ngoại trừ nullbạn không thể thêm vào Bộ sưu tập được tham số hóa với ?.
mouselabs

29
public class Test {

    public class A {}

    public class B extends A {}

    public class C extends B {}

    public void testCoVariance(List<? extends B> myBlist) {
        B b = new B();
        C c = new C();
        myBlist.add(b); // does not compile
        myBlist.add(c); // does not compile
        A a = myBlist.get(0); 
    }

    public void testContraVariance(List<? super B> myBlist) {
        B b = new B();
        C c = new C();
        myBlist.add(b);
        myBlist.add(c);
        A a = myBlist.get(0); // does not compile
    }
}

Vì vậy, "? Extends B" nên được hiểu là "? B extends". Đó là thứ mà B mở rộng để bao gồm tất cả các siêu hạng B lên đến Object, ngoại trừ chính B. Cảm ơn mã!
Saurabh Patil

3
@SaurabhPatil Không, ? extends Bcó nghĩa là B và bất cứ điều gì kéo dài B.
vào

24

Như tôi đã giải thích trong câu trả lời của mình cho một câu hỏi khác, PECS là một thiết bị ghi nhớ được tạo bởi Josh Bloch để giúp ghi nhớ P Roducer extends, C onsumer super.

Điều này có nghĩa là khi một kiểu tham số được truyền cho một phương thức sẽ tạo ra các thể hiện của T(chúng sẽ được lấy từ nó theo một cách nào đó) ? extends T, vì bất kỳ trường hợp nào của một lớp con Tcũng là a T.

Khi một kiểu tham số được truyền cho một phương thức sẽ tiêu thụ các thể hiện của T(chúng sẽ được truyền cho nó để làm một cái gì đó), ? super Tnên được sử dụng vì một thể hiện của Thợp pháp có thể được truyền cho bất kỳ phương thức nào chấp nhận một số siêu kiểu T. A Comparator<Number>có thể được sử dụng trên một Collection<Integer>, ví dụ. ? extends Tsẽ không hoạt động, bởi vì Comparator<Integer>không thể hoạt động trên a Collection<Number>.

Lưu ý rằng nhìn chung bạn chỉ nên sử dụng ? extends T? super Tcho các tham số của một số phương thức. Các phương thức chỉ nên sử dụng Tlàm tham số kiểu trên kiểu trả về chung.


1
Liệu nguyên tắc này chỉ giữ cho Bộ sưu tập? Nó có ý nghĩa khi một người cố gắng tương quan nó với một danh sách. Nếu bạn nghĩ về chữ ký sắp xếp (Danh sách <T>, Bộ so sánh <? Super T>) ---> ở đây Bộ so sánh sử dụng siêu nên có nghĩa đó là người tiêu dùng trong ngữ cảnh PECS. Khi bạn xem việc triển khai chẳng hạn như: public int so sánh (Người a, Người b) {return a.age <b.age? -1: a.age == b.age? 0: 1; } Tôi cảm thấy như Người không tiêu thụ bất cứ thứ gì ngoài việc tạo ra tuổi tác. Điều đó khiến tôi bối rối. Có một lỗ hổng trong lý luận của tôi hay PECS chỉ giữ cho Bộ sưu tập?
Fatih Arslan

23

Tóm lại, ba quy tắc dễ nhớ PECS:

  1. Sử dụng <? extends T>ký tự đại diện nếu bạn cần truy xuất đối tượng loại Ttừ bộ sưu tập.
  2. Sử dụng <? super T>ký tự đại diện nếu bạn cần đặt các đối tượng loại Ttrong một bộ sưu tập.
  3. Nếu bạn cần thỏa mãn cả hai điều, tốt, đừng sử dụng bất kỳ ký tự đại diện nào. Đơn giản vậy thôi.

10

hãy giả sử hệ thống phân cấp này:

class Creature{}// X
class Animal extends Creature{}// Y
class Fish extends Animal{}// Z
class Shark extends Fish{}// A
class HammerSkark extends Shark{}// B
class DeadHammerShark extends HammerSkark{}// C

Hãy làm rõ PE - Nhà sản xuất mở rộng:

List<? extends Shark> sharks = new ArrayList<>();

Tại sao bạn không thể thêm các đối tượng mở rộng "Shark" trong danh sách này? giống:

sharks.add(new HammerShark());//will result in compilation error

Vì bạn có một danh sách có thể thuộc loại A, B hoặc C trong thời gian chạy , bạn không thể thêm bất kỳ đối tượng nào thuộc loại A, B hoặc C trong đó vì bạn có thể kết thúc bằng một kết hợp không được phép trong java.
Trong thực tế, trình biên dịch thực sự có thể thấy tại compXLime mà bạn thêm B:

sharks.add(new HammerShark());

... nhưng không có cách nào để biết nếu trong thời gian chạy, B của bạn sẽ là một kiểu con hoặc siêu kiểu của danh sách. Trong thời gian chạy, loại danh sách có thể là bất kỳ loại A, B, C. Vì vậy, cuối cùng bạn không thể thêm HammerSkark (siêu loại) vào danh sách của Dead HammerShark.

* Bạn sẽ nói: "OK, nhưng tại sao tôi không thể thêm HammerSkark vào vì nó là loại nhỏ nhất?". Trả lời: Đó là nhỏ nhất bạn biết. Nhưng HammerSkark cũng có thể được mở rộng bởi người khác và bạn kết thúc trong cùng một kịch bản.

Hãy làm rõ CS - Tiêu dùng siêu:

Trong cùng một hệ thống phân cấp, chúng ta có thể thử điều này:

List<? super Shark> sharks = new ArrayList<>();

Điều gì và tại sao bạn có thể thêm vào danh sách này?

sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());

Bạn có thể thêm các loại đối tượng ở trên bởi vì bất kỳ thứ gì bên dưới cá mập (A, B, C) sẽ luôn là các kiểu con của bất cứ thứ gì trên cá mập (X, Y, Z). Dễ hiểu.

Bạn không thể thêm các loại trên Shark, vì trong thời gian chạy , loại đối tượng được thêm có thể có thứ bậc cao hơn loại khai báo của danh sách (X, Y, Z). Điều này không được phép.

Nhưng tại sao bạn không thể đọc từ danh sách này? (Ý tôi là bạn có thể lấy một phần tử từ nó, nhưng bạn không thể gán nó cho bất cứ thứ gì ngoài Object o):

Object o;
o = sharks.get(2);// only assignment that works

Animal s;
s = sharks.get(2);//doen't work

Trong thời gian chạy, loại danh sách có thể là bất kỳ loại nào ở trên A: X, Y, Z, ... Trình biên dịch có thể biên dịch câu lệnh gán của bạn (có vẻ đúng) nhưng, trong thời gian chạy , loại s (Animal) có thể thấp hơn trong phân cấp hơn loại khai báo của danh sách (có thể là Sinh vật hoặc cao hơn). Điều này không được phép.

Tóm lại

Chúng tôi sử dụng <? super T>để thêm các đối tượng của các loại bằng hoặc dưới Tvới List. Chúng ta không thể đọc từ nó.
Chúng tôi sử dụng <? extends T>để đọc các đối tượng của các loại bằng hoặc dưới Tdanh sách. Chúng ta không thể thêm yếu tố vào nó.


9

(thêm câu trả lời vì không bao giờ đủ ví dụ với ký tự đại diện)

       // Source 
       List<Integer> intList = Arrays.asList(1,2,3);
       List<Double> doubleList = Arrays.asList(2.78,3.14);
       List<Number> numList = Arrays.asList(1,2,2.78,3.14,5);

       // Destination
       List<Integer> intList2 = new ArrayList<>();
       List<Double> doublesList2 = new ArrayList<>();
       List<Number> numList2 = new ArrayList<>();

        // Works
        copyElements1(intList,intList2);         // from int to int
        copyElements1(doubleList,doublesList2);  // from double to double


     static <T> void copyElements1(Collection<T> src, Collection<T> dest) {
        for(T n : src){
            dest.add(n);
         }
      }


     // Let's try to copy intList to its supertype
     copyElements1(intList,numList2); // error, method signature just says "T"
                                      // and here the compiler is given 
                                      // two types: Integer and Number, 
                                      // so which one shall it be?

     // PECS to the rescue!
     copyElements2(intList,numList2);  // possible



    // copy Integer (? extends T) to its supertype (Number is super of Integer)
    private static <T> void copyElements2(Collection<? extends T> src, 
                                          Collection<? super T> dest) {
        for(T n : src){
            dest.add(n);
        }
    }

4

Đây là cách rõ ràng nhất, đơn giản nhất để tôi nghĩ về việc kéo dài so với siêu:

  • extendslà để đọc

  • superlà để viết

Tôi thấy "PECS" là một cách không rõ ràng để nghĩ về những điều liên quan đến ai là "nhà sản xuất" và ai là "người tiêu dùng". "PECS" được định nghĩa theo quan điểm của chính bộ sưu tập dữ liệu - bộ sưu tập "tiêu thụ" nếu các đối tượng được ghi vào nó (nó đang tiêu thụ các đối tượng từ mã gọi) và nó "tạo ra" nếu các đối tượng được đọc từ nó (nó đang sản xuất các đối tượng cho một số mã gọi). Điều này phản lại cách mọi thứ khác được đặt tên mặc dù. API Java tiêu chuẩn được đặt tên theo quan điểm của mã gọi, không phải chính bộ sưu tập. Ví dụ: chế độ xem tập trung vào java.util.List nên có một phương thức có tên là "receive ()" thay vì "add ()" - sau tất cả,phần tử, nhưng bản thân danh sách nhận phần tử.

Tôi nghĩ rằng sẽ trực quan hơn, tự nhiên và nhất quán hơn khi nghĩ về mọi thứ từ quan điểm của mã tương tác với bộ sưu tập - mã có "đọc từ" hoặc "ghi vào" bộ sưu tập không? Theo đó, bất kỳ mã nào được viết vào bộ sưu tập sẽ là "nhà sản xuất" và bất kỳ mã nào được đọc từ bộ sưu tập sẽ là "người tiêu dùng".


Tôi đã gặp phải sự va chạm tinh thần tương tự và sẽ có xu hướng đồng ý ngoại trừ việc PECS không chỉ định việc đặt tên mã và chính các ranh giới loại được đặt trên các khai báo Bộ sưu tập. Ngoài ra, liên quan đến việc đặt tên, bạn thường có tên để sản xuất / tiêu thụ Bộ sưu tập như srcdst. Vì vậy, bạn đang xử lý cả mã và bộ chứa cùng một lúc và cuối cùng tôi đã nghĩ về nó dọc theo các dòng đó - "tiêu thụ mã" tiêu thụ từ một thùng chứa sản xuất và "sản xuất mã" sản xuất cho một thùng chứa tiêu thụ.
mouselabs

4

"Quy tắc" PECS chỉ đảm bảo rằng những điều sau đây là hợp pháp:

  • Người tiêu dùng: bất kể ?là gì , nó có thể tham chiếu hợp pháp T
  • Nhà sản xuất: bất kể ?là gì , nó có thể được gọi một cách hợp pháp bởi T

Việc ghép nối điển hình dọc theo các dòng List<? extends T> producer, List<? super T> consumerchỉ đơn giản là đảm bảo rằng trình biên dịch có thể thực thi các quy tắc quan hệ thừa kế "IS-A" tiêu chuẩn. Nếu chúng ta có thể làm như vậy một cách hợp pháp, có thể nói đơn giản hơn <T extends ?>, <? extends T>(hoặc tốt hơn là trong Scala, như bạn có thể thấy ở trên, thật [-T], [+T]không may, điều tốt nhất chúng ta có thể làm là <? super T>, <? extends T>.

Khi tôi lần đầu tiên gặp phải điều này và phá vỡ nó trong đầu, cơ học có ý nghĩa nhưng bản thân mã tiếp tục gây khó hiểu cho tôi - tôi cứ nghĩ "có vẻ như giới hạn không cần phải đảo ngược như thế" - mặc dù tôi đã rõ ràng ở trên - rằng nó chỉ đơn giản là về việc đảm bảo tuân thủ các quy tắc tham chiếu tiêu chuẩn.

Điều đã giúp tôi là nhìn vào nó bằng cách sử dụng phép gán thông thường như một sự tương tự.

Xem xét các mã đồ chơi sau (không sẵn sàng sản xuất):

// copies the elements of 'producer' into 'consumer'
static <T> void copy(List<? extends T> producer, List<? super T> consumer) {
   for(T t : producer)
       consumer.add(t);
}

Minh họa điều này theo cách tương tự bài tập, đối consumervới ?ký tự đại diện (loại không xác định) là tham chiếu - "phía bên trái" của bài tập - và <? super T>đảm bảo rằng bất cứ điều gì ?, T"IS-A" ?- Tcó thể được gán cho nó, bởi vì ?là một siêu loại (hoặc nhiều nhất là cùng loại) với T.

Đối với producermối quan tâm là như nhau nó chỉ đảo ngược: producer's ?ký tự đại diện (loại chưa biết) là referent - những 'phía bên tay phải' của công việc - và <? extends T>Đảm bảo rằng bất cứ ?là, ?'IS-A' T- đó có thể được gán cho mộtT , bởi vì ?là một loại phụ (hoặc ít nhất là cùng loại) như T.


2

Nhớ điều này:

Người tiêu dùng ăn bữa tối (siêu); Nhà sản xuất mở rộng nhà máy của cha mẹ mình


1

Sử dụng ví dụ thực tế (với một số đơn giản hóa):

  1. Hãy tưởng tượng một chuyến tàu chở hàng với những chiếc xe chở hàng giống như một danh sách.
  2. Bạn có thể đặt hàng hóa trong xe chở hàng nếu hàng hóa có cùng kích thước hoặc nhỏ hơn xe chở hàng =<? super FreightCarSize>
  3. Bạn có thể dỡ hàng hóa từ một chiếc xe chở hàng nếu bạn có đủ chỗ (nhiều hơn kích thước của hàng hóa) trong kho của bạn =<? extends DepotSize>

1

Hiệp phương sai : chấp nhận các
kiểu con Contravariance : chấp nhận siêu kiểu

Các loại covariant là chỉ đọc, trong khi các loại contravariant là chỉ ghi.


0

Hãy xem ví dụ

public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }

Generics cho phép bạn làm việc với các loại động một cách an toàn

//ListA
List<A> listA = new ArrayList<A>();

//add
listA.add(new A());
listA.add(new B());
listA.add(new C());

//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListB
List<B> listB = new ArrayList<B>();

//add
listB.add(new B());

//get
B b0 = listB.get(0);

Vấn đề

Do Bộ sưu tập của Java là loại tham chiếu do đó chúng tôi có các vấn đề tiếp theo:

Vấn đề # 1

//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;

* Chung của Swift không có vấn đề như vậy vì Bộ sưu tập là Value type[Giới thiệu] do đó bộ sưu tập mới được tạo

Vấn đề # 2

//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;

Giải pháp - Ký tự đại diện

Wildcard là một tính năng loại tham chiếu và nó không thể được khởi tạo trực tiếp

Giải pháp số 1 <? super A> hay còn gọi là ràng buộc thấp hơn là chống chỉ định hay còn gọi là người tiêu dùng đảm bảo rằng nó được vận hành bởi A và tất cả các siêu lớp, đó là lý do tại sao nó an toàn để thêm

List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();

//add
listSuperA.add(new A());
listSuperA.add(new B());

//get
Object o0 = listSuperA.get(0);

Giải pháp số 2

<? extends A>aka giới hạn trên hay còn gọi là hiệp phương sai aka các nhà sản xuất đảm bảo rằng nó được vận hành bởi A và tất cả các lớp con, đó là lý do tại sao nó an toàn để lấy và sử dụng

List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;

//get
A a0 = listExtendsA.get(0);

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.