Câu trả lời:
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 extends
hoặ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 Thing
khi 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 Thing
bộ 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ữ Thing
bấ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 Thing
thêm vào; Đây là những gì ? super Thing
đảm bảo.
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).
const
như 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ố?
Các nguyên tắc đằng sau này trong khoa học máy tính được gọi là
? extends MyClass
,? super MyClass
vàMyClass
Hình dưới đây sẽ giải thích khái niệm. Hình ảnh lịch sự: Andrey Tyukin
PECS (Nhà sản xuất extends
và 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:
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õ
Hiệp phương sai và chống chỉ định
Để 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 []
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
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:
?
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.? 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 . T
là trên lớp -most trong hệ thống phân cấp thừa kế. Sử dụng extends
ký tự đại diện khi bạn chỉ Nhận giá trị ra khỏi cấu trúc.? 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 . T
là thấp hơn lớp -most trong hệ thống phân cấp thừa kế. Sử dụng super
ký 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
)
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.
*/
}
}
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
.
?
- "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ừ null
bạn không thể thêm vào Bộ sưu tập được tham số hóa với ?
.
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
}
}
? extends B
có nghĩa là B và bất cứ điều gì kéo dài B.
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 conT
cũng là aT
.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 T
nên được sử dụng vì một thể hiện củaT
hợ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ểuT
. AComparator<Number>
có thể được sử dụng trên mộtCollection<Integer>
, ví dụ.? extends T
sẽ không hoạt động, bởi vìComparator<Integer>
không thể hoạt động trên aCollection<Number>
.
Lưu ý rằng nhìn chung bạn chỉ nên sử dụng ? extends T
và ? super T
cho 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 T
làm tham số kiểu trên kiểu trả về chung.
Tóm lại, ba quy tắc dễ nhớ PECS:
<? extends T>
ký tự đại diện nếu bạn cần truy xuất đối tượng loại T
từ bộ sưu tập.<? super T>
ký tự đại diện nếu bạn cần đặt các đối tượng loại T
trong một bộ sưu tập.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 T
vớ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 T
danh sách. Chúng ta không thể thêm yếu tố vào nó.
(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);
}
}
Đâ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:
extends
là để đọc
super
là để 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".
src
và dst
. 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ụ.
"Quy tắc" PECS chỉ đảm bảo rằng những điều sau đây là hợp pháp:
?
là gì , nó có thể tham chiếu hợp pháp T
?
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> consumer
chỉ đơ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 consumer
vớ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" ?
- T
có 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 producer
mố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
- đó nó 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
.
Sử dụng ví dụ thực tế (với một số đơn giản hóa):
<? super FreightCarSize>
<? extends DepotSize>
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.
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);
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;
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);
super
một phần nhưng, đưa ra ý tưởng về phần khác.