답변:
tl; dr : "PECS"는 컬렉션의 관점에서 나온 것입니다. 당신이하는 경우 에만 제네릭 컬렉션 항목을 당겨, 그것은 프로듀서 그리고 당신은 사용해야합니다 extends
; 당신이하는 경우 에만 항목을 채우고, 그것은 소비자 그리고 당신은 사용해야합니다 super
. 당신이 모두 동일한 컬렉션 할 경우, 당신은 하나를 사용하지 않아야 extends
또는 super
.
매개 변수로 사물 모음을 취하는 메소드가 있다고 가정하지만을 수락하는 것보다 더 유연하게 만들고 싶다고 가정하십시오 Collection<Thing>
.
사례 1 : 컬렉션을 살펴보고 각 항목으로 작업을 수행하려고합니다.
그런 다음 목록은 생산자이므로을 사용해야합니다 Collection<? extends Thing>
.
추론은의 Collection<? extends Thing>
모든 하위 유형을 보유 할 수 Thing
있기 때문에 Thing
작업을 수행 할 때 각 요소가로 작동 합니다. (실제로 컬렉션 의 특정 하위 유형이 보유하고 있음을 Collection<? extends Thing>
알 수 없기 때문에 실제로는 아무것도 추가 할 수 없습니다 .)Thing
사례 2 : 컬렉션에 항목을 추가하려고합니다.
그런 다음 목록은 소비자이므로을 사용해야합니다 Collection<? super Thing>
.
여기에서의 논리는 달리 있다는 것입니다 Collection<? extends Thing>
, Collection<? super Thing>
항상을 보유 수 Thing
에 상관없이 실제 파라미터 화 된 형태가 무엇인지. 여기에 a Thing
를 추가 할 수있는 한 이미 목록에있는 내용은 상관하지 않습니다 . 이것이 ? super Thing
보장하는 것입니다.
doSomethingWithList(List list)
, 당신이 소모 목록을 그래서 공분산이 필요합니다 / 확장 (또는 불변 목록). 반면에 방법이 List doSomethingProvidingList
인 경우 목록을 생성 하는 중이며 불균형 / 슈퍼 (또는 불변 목록)가 필요합니다.
const
C ++에서 메서드 매개 변수로 참조를 사용 하여 메서드가 인수를 수정하지 않음을 나타내는 것과 비슷한 가독성을 향상시키는 수단 입니까?
컴퓨터 과학에서 이것의 배후 원리는
? extends MyClass
,? super MyClass
및MyClass
아래 그림은 개념을 설명해야합니다. 사진 제공 : Andrey Tyukin
PECS (생산자 extends
및 소비자 super
)
니모닉 → Get and Put 원칙.
이 원칙은 다음과 같이 말합니다.
자바 예제 :
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
}
Liskov 대체 원리 : S가 T의 하위 유형 인 경우 T 유형의 오브젝트는 S 유형의 오브젝트로 대체 될 수 있습니다.
프로그래밍 언어의 타이핑 시스템 내에서 타이핑 규칙
이 일반적인 현상을 설명하기 위해 배열 유형을 고려하십시오. Animal 유형의 경우 Animal [] 유형을 만들 수 있습니다
자바 예제 :
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
경계 (예 : 어딘가로 향함) 와일드 카드 : 와일드 카드 에는 세 가지 맛이 있습니다.
?
나 ? extends Object
- 무제한으로 와일드 카드. 모든 유형의 가족을 나타냅니다. 둘 다 넣을 때 사용하십시오.? extends T
(하위 유형의 모든 유형의 패밀리 T
)- 상한이 있는 와일드 카드 . T
는 IS 상단 상속 계층 구조 - 대부분의 클래스는. 사용 extends
만 할 때 와일드 카드를 얻기 구조에서 값을.? super T
(슈퍼 타입 인 모든 유형의 패밀리 T
)- 하한이 있는 와일드 카드 . T
는 IS 낮은 상속 계층 구조 - 대부분의 클래스는. 구조에 값을 넣을super
때만 와일드 카드를 사용하십시오 .참고 : 와일드 카드 ?
는 0 회 또는 1 회를 의미 하며 알 수없는 유형을 나타냅니다. 와일드 카드는 일반적인 메소드 호출, 제네릭 클래스의 인스턴스 생성에 대한 형식 인수로 사용되지 않는 매개 변수의 유형으로 사용할 수있다. (즉, 때 우리가 사용하는 같은 참조 프로그램의 다른 곳에서 사용하지 않는 것이 사용 와일드 카드 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.
List <?> 또는 List <?에 요소를 추가 할 수 없습니다. Object>를 확장하므로 왜 그런지 이해할 수 없습니다 Use when you both get and put
.
?
"제한되지 않은 와일드 카드"-불일치와 정확히 반대입니다. 다음 문서를 참조하십시오 : docs.oracle.com/javase/tutorial/java/generics/… 다음 내용 : 코드가 변수에 "in"및 "out"변수로 액세스해야하는 경우 와일드 카드를 사용하지 마십시오. ( "get"및 "put"과 동의어로 "in"및 "out"을 사용하고 있습니다). 를 제외하고는 null
매개 변수화 된 컬렉션에 추가 할 수 없습니다 ?
.
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
B와 B를 확장하는 모든 것을 의미합니다.
내가 설명 할 것처럼 내 대답은 다른 질문에, PECS 도움으로 조쉬 블로흐에 의해 생성 된 기억을 돕는 장치가 기억입니다 P의 roducer extends
, C onsumer을 super
.
이는 매개 변수화 된 유형이 메소드에 전달되면의 인스턴스를 생성
T
할 때 (어떤 방식으로 검색)? extends T
서브 클래스의 모든 인스턴스T
가 또한 이므로 사용되어야 함을 의미 합니다T
.파라미터 화 된 형태가하는 방법에 전달되는 경우 소비 의 인스턴스를
T
(그들은 뭔가를 그에게 건네집니다)? super T
의 인스턴스 때문에 사용되어야한다T
일부 수퍼 타입을 허용하는 모든 메소드에 법적으로 전달할 수T
. 예를 들어Comparator<Number>
에 A를 사용할 수 있습니다Collection<Integer>
.? extends T
에서 작동Comparator<Integer>
할 수 없기 때문에 작동하지 않습니다Collection<Number>
.
일반적으로 당신은 사용해야합니다 ? extends T
? super T
일부 메소드의 매개 변수 및 매개 변수 . 메소드는 T
일반 리턴 유형에서 유형 매개 변수로 사용해야 합니다.
간단히 말해서 PECS를 기억하는 세 가지 쉬운 규칙 :
<? extends T>
유형의 오브젝트를 검색해야하는 경우 와일드 카드를 사용하십시오 T
.<? super T>
유형의 객체를 넣어야하는 경우 와일드 카드를 사용하십시오.T
컬렉션 .이 계층 구조를 가정 해 봅시다 :
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
PE를 명확히합시다-생산자 확장 :
List<? extends Shark> sharks = new ArrayList<>();
이 목록에서 "상어"를 확장하는 개체를 추가 할 수없는 이유는 무엇입니까? 처럼:
sharks.add(new HammerShark());//will result in compilation error
런타임에 A, B 또는 C 유형이 될 수있는 목록이 있기 때문에 java에서 허용되지 않는 조합으로 끝날 수 있으므로 A, B 또는 C 유형의 오브젝트를 추가 할 수 없습니다.
실제로 컴파일러는 컴파일 타임에 B를 추가하는 것을 실제로 볼 수 있습니다.
sharks.add(new HammerShark());
...하지만 런타임에 B가 목록 유형의 하위 유형 또는 상위 유형인지 여부를 알 수있는 방법이 없습니다. 런타임시 목록 유형은 A, B, C 유형 중 하나 일 수 있습니다. 따라서 예를 들어 DeadHammerShark 목록에 HammerSkark (슈퍼 유형)를 추가 할 수 없습니다.
* 당신은 말할 것입니다 : "하지만 가장 작은 타입이기 때문에 왜 HammerSkark를 추가 할 수 없습니까?". 답변 : 그것은 가장 작은 당신 알고있다. 그러나 HammerSkark는 다른 사람에 의해 확장 될 수 있으며 같은 시나리오에서 끝납니다.
CS-Consumer Super를 명확히하자 :
동일한 계층에서 우리는 이것을 시도 할 수 있습니다 :
List<? super Shark> sharks = new ArrayList<>();
이 목록에 무엇을 왜 추가 할 수 있습니까?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
상어 (A, B, C) 아래의 항목은 항상 상어 (X, Y, Z) 이상인 하위 유형이므로 위의 유형의 객체를 추가 할 수 있습니다. 이해하기 쉬운.
당신은 할 수 있기 때문에, 상어 위의 유형을 추가 런타임에 추가 개체의 유형 목록 (X, Y, Z)의 선언 된 유형과 계층 구조에서 높을 수있다. 이것은 허용되지 않습니다.
그러나 왜이 목록에서 읽을 수 없습니까? (요소를 가져올 수는 있지만 Object o 이외의 다른 요소에는 할당 할 수 없습니다) :
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
런타임시 목록 유형은 A : X, Y, Z 등의 모든 유형이 될 수 있습니다. 컴파일러는 할당 문을 컴파일 할 수 있지만 (올바른 것처럼 보이지만) 런타임에는 에는 s (동물) 유형이 더 낮을 수 있습니다. 선언 된 유형의 목록보다 계층 구조 (크리쳐 이상일 수 있음). 이것은 허용되지 않습니다.
요약하자면
<? super T>
유형이 같은 객체를에 추가 하는 데 사용 T
합니다 List
. 읽을 수 없습니다.
우리 는 목록에서 <? extends T>
유형이 같은 객체를 읽는 데 사용 T
합니다. 요소를 추가 할 수 없습니다.
(Generics 와일드 카드가 포함 된 예제가 충분하지 않기 때문에 답변 추가)
// 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);
}
}
이것이 확장 대 슈퍼를 생각하는 가장 명확하고 간단한 방법입니다.
extends
을위한 독서
super
입니다 쓰기
"PECS"는 누가 "생산자"이고 누가 "소비자"인지에 대해 생각할 수있는 명백한 방법입니다. "PECS는"의 관점에서 정의 된 데이터 수집 자체 개체가 기록되는 경우 컬렉션 "소비하는"- 에 (이 호출 코드에서 오브젝트를 소모)이,와 개체가 읽는 경우는 "생산" 에서 (이 그것을 호출 코드에 객체를 생성하는 중). 이것은 다른 모든 것이 어떻게 명명되는지에 반합니다. 표준 Java API는 콜렉션 자체가 아니라 호출 코드의 관점에서 명명됩니다. 예를 들어, java.util.List 의 콜렉션 중심 뷰 에는 "add ()"대신 "receive ()"라는 메소드 가 있어야합니다.요소이지만 목록 자체 는 요소를 받습니다 .
컬렉션과 상호 작용하는 코드의 관점에서 사물을 생각하는 것이 더 직관적이고 자연스럽고 일관성이 있다고 생각합니다. 코드가 컬렉션을 "읽거나"쓰나요? 그런 다음 컬렉션에 쓰는 코드 는 "제작자"가되고 컬렉션 에서 읽는 코드 는 "소비자"가됩니다.
src
and와 같은 모음을 생성 / 소비하는 이름이 있습니다 dst
. 그래서 당신은 코드와 컨테이너를 동시에 다루고 있으며, "컨테이너를 소비하는"코드와 소비하는 컨테이너를위한 "코드 생성"이라는 라인을 따라 생각했습니다.
PECS "규칙"은 다음을 합법적으로 보장합니다.
?
법적으로 참조 할 수 있습니다 T
?
법적으로 참조 할 수 있습니다. T
라인을 따라 일반적인 쌍을 이루는 List<? extends T> producer, List<? super T> consumer
것은 컴파일러가 표준 "IS-A"상속 관계 규칙을 시행 할 수 있도록하는 것입니다. 우리가 합법적으로 그렇게 할 수 있다면, 말하기가 더 쉬울 수도 있습니다 <T extends ?>, <? extends T>
(또는 위에서 볼 수 있듯이 Scala에서는 더 좋습니다) [-T], [+T]
. 불행히도 우리가 할 수있는 최선은입니다 <? super T>, <? extends 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);
}
위한 할당과 유사하게 측면에서 본를 도시 과제의 "좌측"- - 및 기준 와일드 (알 형)은 어떤 것을 보장 하고, "-A는" - 할당 될 수 있기 때문 은과 (와) 같은 유형 입니다.consumer
?
<? super T>
?
T
?
T
?
T
위해 producer
우려 그냥 거꾸로 것 같습니다 : producer
'의 ?
와일드 카드 (알 수없는 타입)는 인 지시 대상 - 할당의 "오른쪽"- 그리고 <? extends T>
어떤 보장 ?
이다 ?
"-A는" T
- 있음 이 할당 할 수 있습니다 A를을T
때문에 ?
같은 서브 타입 (또는 적어도 동일 유형)이다 T
.
예를 보자
public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }
제네릭을 사용하면 안전한 방식으로 유형 작업을 동적으로 수행 할 수 있습니다
//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);
Java의 Collection은 결과적으로 참조 유형이므로 다음 문제가 있습니다.
문제 # 1
//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;
* Swift의 generic에는 Collection이 Value type
[About] 이므로 새 컬렉션이 만들어 지기 때문에 이러한 문제가 없습니다.
문제 # 2
//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;
와일드 카드는 참조 유형 기능이며 직접 인스턴스화 할 수 없습니다
솔루션 # 1
<? super A>
일명 하한 (contravariance) 일명 소비자는 A와 모든 수퍼 클래스에 의해 운영됨을 보장하므로 추가 하는 것이 안전 합니다.
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);
해결책 # 2
<? extends A>
일명 상단은 안전 이유 즉, 그것은 모든 서브 클래스에 의해 작동되는 일명 공분산 일명 생산자 보증을 구속 얻을 캐스트
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
super
있지만 부분 을 설명 하지만 다른 아이디어를 제공합니다.