<의 차이점은 무엇입니까? 슈퍼 E>와 <? E>를 확장합니까?


147

차이점은 무엇이며 <? super E>그리고 <? extends E>?

예를 들어 클래스를 살펴보면 java.util.concurrent.LinkedBlockingQueue생성자에 대해 다음과 같은 서명이 있습니다.

public LinkedBlockingQueue(Collection<? extends E> c)

그리고 방법에 대한 하나 :

public int drainTo(Collection<? super E> c)

답변:


182

첫 번째는 그것이 "E의 조상 인 어떤 유형"이라고 말합니다. 두 번째는 "E의 서브 클래스 인 일부 유형"이라고 말합니다. (두 경우 모두 E 자체는 괜찮습니다.)

생성자가 사용하는 그래서 ? extends E그것이 때 보장 있도록 양식을 가져옵니다 컬렉션에서 값을, 그들은 모두 E 또는 일부 서브 클래스 (즉 그것은 호환). drainTo있어서 값을 넣어 시도 컬렉션의 요소 타입 가져야하므로, 수집 E 또는 수퍼 클래스 .

예를 들어, 다음과 같은 클래스 계층 구조가 있다고 가정하십시오.

Parent extends Object
Child extends Parent

그리고 LinkedBlockingQueue<Parent>. List<Child>모든 Child것이 부모 이기 때문에 모든 요소를 ​​안전하게 복사 하는 전달을 구성 할 수 있습니다 . 당신은 전달할 수없는 List<Object>몇 가지 요소와 호환되지 않을 수 있기 때문입니다Parent .

마찬가지로 List<Object>모든 Parent것이 Object... 이기 때문에 대기열을 비울 수는 있지만 모든 요소가와 호환되기를 기대 List<Child>하기 때문에 비울 수 없었 List<Child>습니다 Child.


25
+1. 그것은 실제로 실질적인 차이입니다. 가져 오기로 확장하고 삽입하려면 슈퍼로 확장합니다.
Yishai

1
@Jon 첫 번째 단락에서 (두 경우 모두 E 자체는 괜찮습니다.) 무엇을 의미합니까?
Geek

2
@ 괴짜 : 나는 당신이 ? extends InputStream또는 비슷한 것을 가지고 있다면 논쟁으로 ? super InputStream사용할 수 있음을 의미합니다 InputStream.
Jon Skeet

Josh Java가 효과적인 Java로 PECS 설명을 한 번도 얻지 못했습니다. 그러나 @Yishai, 이것은 기억하는 데 도움이되는 방법입니다. 아마도 우리는 새로운 니모닉 SAGE를 제안 할 수 있습니다.
Super-

따라서 올바르게 읽으면 "<? extends E>"에는 "?"가 필요합니다. "E"의 하위 클래스이고 "<? super E>"는 "E"가 "?"의 하위 클래스 여야합니까?
El Suscriptor Justiciero 2018 년

130

그 이유는 Java가 제네릭을 구현하는 방법을 기반으로합니다.

배열 예제

배열을 사용하면이 작업을 수행 할 수 있습니다 (배열은 공변량입니다)

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

그러나 이렇게하면 어떻게 될까요?

myNumber[0] = 3.14; //attempt of heap pollution

이 마지막 줄은 잘 컴파일되지만이 코드를 실행하면 ArrayStoreException . 숫자 참조를 통해 액세스하는 것과 상관없이 정수 배열에 double을 넣으려고하기 때문에.

즉, 컴파일러를 속일 수는 있지만 런타임 유형 시스템을 속일 수는 없습니다. 배열은 우리가 reifiable 타입 이라고 부르기 때문 입니다. 즉, 런타임시 Java는이 배열이 실제로 유형의 참조를 통해 액세스되는 정수 배열로 인스턴스화되었음을 알고 있습니다.Number[] .

보시다시피, 하나는 객체의 실제 유형이고 다른 하나는 객체에 액세스하는 데 사용하는 참조 유형입니다.

자바 제네릭의 문제점

이제 Java 제네릭 형식의 문제점은 형식 정보가 컴파일러에 의해 삭제되어 런타임에 사용할 수 없다는 것입니다. 이 과정을 유형 삭제 라고 합니다 . Java에서 이와 같은 제네릭을 구현해야 할 충분한 이유가 있지만, 그것은 긴 이야기이며, 무엇보다도 기존 코드와 바이너리 호환성을 가지고 수행해야합니다 (제네릭을 얻는 방법 참조) ).

그러나 여기서 중요한 점은 런타임에 유형 정보가 없기 때문에 힙 오염을 저 지르지 않도록 보장 할 방법이 없다는 것입니다.

예를 들어

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap pollution

Java 컴파일러가이를 수행하지 못하게하는 경우 런타임시 시스템이이 목록이 정수 목록으로 만 가정되었음을 판별 할 방법이 없기 때문에 런타임을 중지 할 수 없습니다. Java 런타임을 사용하면 정수만 포함해야 할 때이 목록에 원하는 것을 넣을 수 있습니다. 생성 될 때 정수 목록으로 선언 되었기 때문입니다.

따라서 Java 디자이너는 컴파일러를 속일 수 없도록했습니다. 컴파일러를 속일 수 없다면 (배열로 할 수있는 것처럼) 런타임 유형 시스템을 속일 수 없습니다.

따라서 제네릭 형식은 수정할 수 없다고 말합니다. .

분명히 이것은 다형성을 방해 할 것입니다. 다음 예제를 고려하십시오.

static long sum(Number[] numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

이제 다음과 같이 사용할 수 있습니다.

Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};

System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));

그러나 일반 컬렉션으로 동일한 코드를 구현하려고하면 성공하지 못합니다.

static long sum(List<Number> numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

시도하면 컴파일러 오류가 발생합니다 ...

List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);

System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error

해결책은 공분산 및 공분산으로 알려진 Java 제네릭의 두 가지 강력한 기능을 사용하는 방법을 배우는 것입니다.

공분산

공분산을 사용하면 구조에서 항목을 읽을 수 있지만 아무 것도 쓸 수 없습니다. 이들은 모두 유효한 선언입니다.

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>();
List<? extends Number> myNums = new ArrayList<Double>();

그리고 당신은 읽을 수 있습니다 myNums:

Number n = myNums.get(0); 

실제 목록에 포함 된 것을 확실하게 알 수 있기 때문에 Number로 업 캐스트 될 수 있습니다 (Number를 확장하는 모든 것이 숫자입니다)?

그러나 공변량 구조에 어떤 것도 넣을 수 없습니다.

myNumst.add(45L); //compiler error

Java는 일반 구조에서 객체의 실제 유형을 보증 할 수 없으므로 허용되지 않습니다. Number를 확장하는 것은 무엇이든 될 수 있지만 컴파일러는 확신 할 수 없습니다. 따라서 읽을 수는 있지만 쓸 수는 없습니다.

공분산

불균형을 사용하면 반대의 작업을 수행 할 수 있습니다. 일반적인 구조에 물건을 넣을 수는 있지만 읽을 수는 없습니다.

List<Object> myObjs = new List<Object>();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

이 경우 객체의 실제 특성은 객체 목록이며, 불균형을 통해 기본적으로 모든 숫자에 공통 조상으로 Object가 있으므로 Number를 넣을 수 있습니다. 따라서 모든 숫자는 개체이므로 유효합니다.

그러나 숫자를 얻는다고 가정하면이 반 변형 구조에서 아무것도 읽을 수 없습니다.

Number myNum = myNums.get(0); //compiler-error

보시다시피, 컴파일러에서이 줄을 쓸 수 있으면 런타임에 ClassCastException이 발생합니다.

원리를 얻는다

따라서 구조에서 일반 값만 가져 오려는 경우 공분산을 사용하고 구조에 일반 값을 넣을 때만 대비를 사용하고 두 가지를 모두 수행하려는 경우 정확한 일반 유형을 사용하십시오.

내가 가진 가장 좋은 예는 한 목록에서 다른 목록으로 모든 종류의 숫자를 복사하는 다음입니다. 소스 에서만 항목을 가져오고 대상 에만 항목을 넣습니다 .

public static void copy(List<? extends Number> source, List<? super Number> target) {
    for(Number number : source) {
        target(number);
    }
}

공분산과 공분산의 힘으로 인해 다음과 같은 경우에 작동합니다.

List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);

27
이 답변은 맨 위로 올라갑니다. 좋은 설명입니다.
Suresh Atta

1
@edwindalorzo, Contravariance에서 수정하려는 작은 오타가 있습니다. 당신은 List<Object> myObjs = new List<Object();( >두 번째에 대한 마감이 누락되었습니다) 라고 말합니다 Object.

이 미묘한 개념의 환상적이고 쉽고 명확한 예!
db1234

다른 사람들이 기억하는 것을 돕기 위해 추가 할 수있는 것. 수퍼 클래스에서 메소드를 호출하려면을 사용하십시오 super.methodName. 를 사용할 때 방향의 무언가와 반대되는 "방향의 <? super E>무언가"를 의미 super합니다 extends. 예 : Objectsuper방향 Number(이 수퍼 클래스이므로) 및 Integer이다 extends(그 이후 연장 방향 Number).
BrainStorm.exe 2019

59

<? extends E>E상한으로 정의합니다 . "E .

<? super E>E하한값으로 정의합니다 : " E이것에 캐스트 할 수 있습니다."


6
이것은 내가 본 차이점에 대한 가장 간단하고 실용적인 요약 중 하나입니다.
JAB

1
들어 수십 년 나는 "위"관념의 본능적 인 반전을 싸우고 "낮은"했습니다 (OOP와) 지금. 악화! 나에게, Object궁극적 인 슈퍼 클래스 (UML 또는 유사한 상속 트리에서 수직으로 그려 짐)에도 불구하고 본질적으로 저급 클래스입니다. 나는 시도의 노력에도 불구하고 이것을 취소 할 수 없었습니다.

3
@ tgm1024 "superclass"와 "subclass"는 많은 문제를 야기해야합니다.
David Moles

@DavidMoles, 왜? 당신은 내가 말하는 것을 전혀 따르지 않습니다 . "superclass"는 "superset"과 유사합니다. 전문화의 개념은 IS-A 관계 하에서 적용 가능성의 감소이다. 애플 IS-A 과일. Fruit (슈퍼 클래스)은 Apple (서브 클래스)을 하위 세트로 포함하는 수퍼 세트입니다. 그 구두 관계는 괜찮습니다. 내가 말하는 것은 "상위"와 "하위"가 "슈퍼 셋"과 "서브셋"에 대한 고유 한 매핑을 가지고 있다는 개념입니다. OO의 용어로 상한과 하한을 피해야합니다.

1
@ tgm1024 "Super-"는 라틴 수퍼 "위, 위"에서, "sub-"는 라틴 하위 "아래, 아래" 에서 나옵니다 . 즉, 어원 적으로 슈퍼는 일어나고 서브는 내려 갔다.
David Moles

12

나는 이것을 시도하고 대답 할 것입니다. 그러나 정답을 얻으려면 Joshua Bloch의 책 Effective Java (2 판)를 확인해야합니다. 그는 "프로듀서 확장, 소비자 슈퍼"를 의미하는 니모닉 PECS에 대해 설명합니다.

아이디어는 코드에서 객체의 일반 값을 소비하는 경우 extends를 사용해야한다는 것입니다. 그러나 제네릭 형식의 새 값을 생성하는 경우 super를 사용해야합니다.

예를 들어 :

public void pushAll(Iterable<? extends E> src) {
  for (E e: src) 
    push(e);
}

public void popAll(Collection<? super E> dst) {
  while (!isEmpty())
    dst.add(pop())
}

그러나 실제로이 책을 확인해야합니다 : http://java.sun.com/docs/books/effective/


12

<? super E> 방법 any object including E that is parent of E

<? extends E> 방법 any object including E that is child of E .


짧고 달콤한 답변.
chirag soni

7

반도체 ( ) 및 공분산 ( ) 이라는 용어를 Google에 표시 할 수 있습니다 . 제네릭을 이해할 때 가장 유용한 것은 다음의 메소드 서명을 이해하는 것입니다 .<? super E><? extends E>Collection.addAll

public interface Collection<T> {
    public boolean addAll(Collection<? extends T> c);
}

그냥 당신은 추가 할 수 있도록하려는 것 같은 StringA를 List<Object>:

List<Object> lo = ...
lo.add("Hello")

또한 메소드 를 통해 List<String>(또는 Strings 모음) 을 추가 할 수 있어야합니다 addAll.

List<String> ls = ...
lo.addAll(ls)

그러나 a List<Object>와 a List<String>는 동등하지 않으며 후자는 전자의 하위 클래스도 아닙니다. 공변량 유형 매개 변수 의 개념이 필요합니다. 즉<? extends T> 비트 .

이것을 가지고 나면, 반공산 도 를 원하는 시나리오를 생각하는 것은 간단합니다 ( Comparable인터페이스를 확인하십시오 ).


4

대답하기 전에; 명확하게

  1. 제네릭은 TYPE_SAFETY를 보장하기 위해 컴파일 시간 기능 만 제공하며 RUNTIME 동안에는 사용할 수 없습니다.
  2. 제네릭을 사용한 참조 만 형식 안전성을 강제합니다. 참조가 제네릭으로 선언되지 않으면 safty 유형없이 작동합니다.

예:

List stringList = new ArrayList<String>();
stringList.add(new Integer(10)); // will be successful.

이것이 와일드 카드를보다 명확하게 이해하는 데 도움이되기를 바랍니다.

//NOTE CE - Compilation Error
//      4 - For

class A {}

class B extends A {}

public class Test {

    public static void main(String args[]) {

        A aObj = new A();
        B bObj = new B();

        //We can add object of same type (A) or its subType is legal
        List<A> list_A = new ArrayList<A>();
        list_A.add(aObj);
        list_A.add(bObj); // A aObj = new B(); //Valid
        //list_A.add(new String()); Compilation error (CE);
        //can't add other type   A aObj != new String();


        //We can add object of same type (B) or its subType is legal
        List<B> list_B = new ArrayList<B>();
        //list_B.add(aObj); CE; can't add super type obj to subclass reference
        //Above is wrong similar like B bObj = new A(); which is wrong
        list_B.add(bObj);



        //Wild card (?) must only come for the reference (left side)
        //Both the below are wrong;   
        //List<? super A> wildCard_Wrongly_Used = new ArrayList<? super A>();
        //List<? extends A> wildCard_Wrongly_Used = new ArrayList<? extends A>();


        //Both <? extends A>; and <? super A> reference will accept = new ArrayList<A>
        List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<A>();
                        list_4__A_AND_SuperClass_A = new ArrayList<Object>();
                      //list_4_A_AND_SuperClass_A = new ArrayList<B>(); CE B is SubClass of A
                      //list_4_A_AND_SuperClass_A = new ArrayList<String>(); CE String is not super of A  
        List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<A>();
                          list_4__A_AND_SubClass_A = new ArrayList<B>();
                        //list_4__A_AND_SubClass_A = new ArrayList<Object>(); CE Object is SuperClass of A


        //CE; super reference, only accepts list of A or its super classes.
        //List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<String>(); 

        //CE; extends reference, only accepts list of A or its sub classes.
        //List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<Object>();

        //With super keyword we can use the same reference to add objects
        //Any sub class object can be assigned to super class reference (A)                  
        list_4__A_AND_SuperClass_A.add(aObj);
        list_4__A_AND_SuperClass_A.add(bObj); // A aObj = new B();
        //list_4__A_AND_SuperClass_A.add(new Object()); // A aObj != new Object(); 
        //list_4__A_AND_SuperClass_A.add(new String()); CE can't add other type

        //We can't put anything into "? extends" structure. 
        //list_4__A_AND_SubClass_A.add(aObj); compilation error
        //list_4__A_AND_SubClass_A.add(bObj); compilation error
        //list_4__A_AND_SubClass_A.add("");   compilation error

        //The Reason is below        
        //List<Apple> apples = new ArrayList<Apple>();
        //List<? extends Fruit> fruits = apples;
        //fruits.add(new Strawberry()); THIS IS WORNG :)

        //Use the ? extends wildcard if you need to retrieve object from a data structure.
        //Use the ? super wildcard if you need to put objects in a data structure.
        //If you need to do both things, don't use any wildcard.


        //Another Solution
        //We need a strong reference(without wild card) to add objects 
        list_A = (ArrayList<A>) list_4__A_AND_SubClass_A;
        list_A.add(aObj);
        list_A.add(bObj);

        list_B = (List<B>) list_4__A_AND_SubClass_A;
        //list_B.add(aObj); compilation error
        list_B.add(bObj);

        private Map<Class<? extends Animal>, List<? extends Animal>> animalListMap;

        public void registerAnimal(Class<? extends Animal> animalClass, Animal animalObject) {

            if (animalListMap.containsKey(animalClass)) {
                //Append to the existing List
                 /*    The ? extends Animal is a wildcard bounded by the Animal class. So animalListMap.get(animalObject);
                 could return a List<Donkey>, List<Mouse>, List<Pikachu>, assuming Donkey, Mouse, and Pikachu were all sub classes of Animal. 
                 However, with the wildcard, you are telling the compiler that you don't care what the actual type is as long as it is a sub type of Animal.      
                 */   
                //List<? extends Animal> animalList = animalListMap.get(animalObject);
                //animalList.add(animalObject);  //Compilation Error because of List<? extends Animal>
                List<Animal> animalList = animalListMap.get(animalObject);
                animalList.add(animalObject);      


            } 
    }

    }
}



1
코드에 대한 좋은 설명. 그러나 코드 블록 외부의 코드에서 주석을 사용한 경우보다 읽기 쉽고 읽기 쉽습니다.
Prabhu

3

상한이있는 와일드 카드는 "? extends Type"처럼 보이고 Type의 하위 유형 인 Type Type이 포함 된 모든 유형의 패밀리를 나타냅니다. 유형을 상한이라고합니다.

하한이있는 와일드 카드는 "? super Type"과 비슷하며 Type의 수퍼 타입 ​​인 Type Type이 포함 된 모든 유형의 패밀리를 나타냅니다. 유형을 하한이라고합니다.


1

Parent 클래스와 Parent 클래스에서 상속 된 Child 클래스가 있습니다. Parent 클래스는 GrandParent 클래스라는 다른 클래스에서 상속되므로 상속 순서는 GrandParent> Parent> Child입니다. 이제 <? extends Parent>-Parent 클래스 또는 Child 클래스 <?를 허용합니다. super Parent>-부모 클래스 또는 GrandParent 클래스를 허용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.