<の違いは何ですか?スーパー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の祖先であるあるタイプ」であると言います。2番目は、「Eのサブクラスであるタイプ」であると述べています。(どちらの場合も、E自体は問題ありません。)

そのため、コンストラクタは? extends Eフォームを使用して、コレクションから値をフェッチするときに、すべてがEまたはサブクラスになることを保証します(つまり、互換性があります)。drainToこの方法は、値を入れしようとしているコレクションはの要素の型持たなければならないので、コレクションE やスーパークラスを

例として、次のようなクラス階層があるとします。

Parent extends Object
Child extends Parent

LinkedBlockingQueue<Parent>List<Child>すべてChildが親であるため、すべての要素を安全にコピーする、この受け渡しを構築できます。List<Object>一部の要素はと互換性がない可能性があるため、aを渡すことができませんでしたParent

同様に、List<Object>すべてParentObject...であるため、そのキューをに排出できますがList<Child>List<Child>はそのすべての要素がと互換性があると想定しているため、キューをに排出できませんでしたChild


25
+1。これが実際の違いです。フェッチに拡張、挿入にスーパー。
Yishai

1
@Jon最初の段落の意味(どちらの場合もE自体は大丈夫です。)
Geek

2
@Geek:私はあなたのようなものを持っている場合ことを意味し? extends InputStreamたり? super InputStream、あなたが使用できるInputStream引数として。
Jon Skeet 2013年

効果的なJavaでは、Josh BlockによるPECSの説明は本当に得られませんでした。ただし@Yishai、これは覚えておくと便利な方法です。おそらく、新しいニーモニックを提案できます
。SAGE:Super-

したがって、正しく読むと、「<?extends E>」には「?」が必要です。は「E」のサブクラスであり、「<?super E>」では、「E」が「?」のサブクラスである必要があります。
El Suscriptor Justiciero

130

この理由は、Javaがジェネリックを実装する方法に基づいています。

配列の例

配列でこれを行うことができます(配列は共変です)

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

しかし、これを行おうとするとどうなりますか?

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

この最後の行は問題なくコンパイルされますが、このコードを実行すると、を取得できますArrayStoreException。(数値参照を通じてアクセスされているかどうかに関係なく)整数配列にdoubleを入れようとしているためです。

これは、コンパイラをだますことができても、ランタイム型システムをだますことができないことを意味します。これは、配列がreifiable型と呼ばれるものだからです。つまり、実行時にJavaは、この配列が実際には整数の配列としてインスタンス化され、たまたまtypeの参照を通じてアクセスされることを知っていますNumber[]

ご覧のとおり、1つはオブジェクトの実際のタイプで、もう1つはオブジェクトへのアクセスに使用する参照のタイプです。

Java Genericsの問題

現在、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ジェネリックの2つの強力な機能の使い方を学ぶことです。

共分散

共分散を使用すると、構造から項目を読み取ることはできますが、構造に何かを書き込むことはできません。これらはすべて有効な宣言です。

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を拡張するものはすべて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を持っているため、反変によってNumbersをそこに入れることができます。そのため、すべての数値はオブジェクトであるため、これは有効です。

ただし、数値を取得することを前提として、この反変構造から何も安全に読み取ることはできません。

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

ご覧のように、コンパイラがこの行の記述を許可した場合、実行時にClassCastExceptionが発生します。

Get / Put Principle

そのため、構造体から総称値のみを取得する場合は共分散を使用し、総称値を構造体に配置するだけの場合は反変を使用し、両方を実行する場合は正確な総称型を使用します。

私が持っている最も良い例は、あるリストから別のリストにあらゆる種類の数値をコピーする次のとおりです。それが唯一の取得源からのアイテムを、そしてそれだけ置くターゲットにアイテムを。

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 2014

1
@edwindalorzo、反変の下で修正したい小さなタイプミスがあります。あなたは言うList<Object> myObjs = new List<Object();(これは>2番目の終わりを欠いているObject)。

これらの微妙な概念の素晴らしい、簡単で明確な例!
db1234 2018

他の人が物事を覚えやすくするために追加できるもの。スーパークラスからメソッドを呼び出す場合は、を使用しますsuper.methodName。を使用する場合<? super E>super方向の何かではなく、「方向の何か」を意味しextendsます。例は:Objectであるsuperの方向Number(それはスーパークラスであるため)とIntegerしているextends(それが延びているので方向Number)。
BrainStorm.exe 2019

59

<? extends E>E上限として定義します:「これはにキャストできますE」。

<? super E>E下限として定義します:「Eこれにキャストできます。」


6
これは、私が見た違いの最も単純で実用的な要約の1つです。
JAB

1
以下のために数十年(OOP付き)今私は「上」と「下」の概念の本能的な反転を戦ってきました。悪化!私にとって、Objectは本質的に下位レベルのクラスですが、それは究極のスーパークラス(およびUMLまたは同様の継承ツリーで垂直方向に描画)としての位置にもかかわらずです。何年も試しても、これを元に戻すことはできませんでした。

3
@ tgm1024「スーパークラス」と「サブクラス」は多くの問題を引き起こします。
David Moles

@DavidMoles、なぜ?あなたは明らかに私が言っていることをまったく守っていません。「スーパークラス」は「スーパーセット」に似ています。専門化の概念は、IS-A関係の下での適用性の低下です。アップルIS-Aフルーツ。Fruit(スーパークラス)は、Apple(サブクラス)をサブセットとして含むスーパーセットです。その言葉の関係は結構です。私が言っているのは、「上位」と「下位」に「スーパーセット」と「サブセット」への固有のマッピングがあるという概念です。上および下はOOの用語として避けてください。

1
@ tgm1024 "Super-"はラテン語のスーパー "above、on"から、 "sub-"はラテン語のsub "under、beneath" から来ています。つまり、語源的に、スーパーはアップ、サブはダウンです。
David Moles

12

これに答えてみます。しかし、本当に良い答えを得るには、Joshua Blochの著書「Effective Java(2nd Edition)」をチェックする必要があります。彼は「Producer Extends、Consumer Super」の略であるニーモニック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

反変()と共分散()という用語についてグーグル することをお勧めします。ジェネリックスを理解するときに最も役立つのは、次のメソッドのシグネチャを理解することでした。<? super E><? extends E>Collection.addAll

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

Stringにa を追加できるようにしたいのと同じようにList<Object>

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

メソッドを介してList<String>(またはの任意のコレクションString)を追加することもできますaddAll

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

ただし、a List<Object>とa List<String>は同等ではなく、後者は前者のサブクラスではないことを理解する必要があります。必要なのは、共変型パラメーターの概念です。つまり、<? extends T>ビットです。

これができたら、反変も必要シナリオを考えるのは簡単です(Comparableインターフェースを確認してください)。


4

答えの前に; 明確にしてください

  1. ジェネリックはTYPE_SAFETYを保証するためのコンパイル時機能のみであり、RUNTIMEの間は使用できません。
  2. Genericsでの参照のみがタイプセーフを強制します。参照がジェネリックで宣言されていない場合は、型の安全性がなくても機能します。

例:

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 2014

3

上限付きのワイルドカードは「?extends Type」のように見え、TypeのサブタイプであるTypeが含まれるすべてのタイプのファミリーを表します。タイプは上限と呼ばれます。

下限のあるワイルドカードは「?super Type」のように見え、Typeのスーパータイプであるすべてのタイプのファミリーを表します。タイプTypeが含まれます。タイプは下限と呼ばれます。


1

親クラスから継承された親クラスと子クラスがあります。親クラスは、GrandParent Classと呼ばれる別のクラスから継承されます。したがって、継承の順序は、GrandParent> Parent> Childです。今、<?Parent>を拡張します-これは、ParentクラスまたはChildクラス<?super Parent>-これは、ParentクラスまたはGrandParentクラスのいずれかを受け入れます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.