最初に簡単なシナリオを考えてみましょう( ideone.comの完全なソースを参照 ):
_import Java.util.*;
public class TwoListsOfUnknowns {
static void doNothing(List<?> list1, List<?> list2) { }
public static void main(String[] args) {
List<String> list1 = null;
List<Integer> list2 = null;
doNothing(list1, list2); // compiles fine!
}
}
_
2つのワイルドカードは無関係です。そのため、doNothing
を_List<String>
_および_List<Integer>
_で呼び出すことができます。言い換えると、2つの_?
_は完全に異なるタイプを参照できます。したがって、以下はコンパイルされません。これは予想されることです( ideone.comでも ):
_import Java.util.*;
public class TwoListsOfUnknowns2 {
static void doSomethingIllegal(List<?> list1, List<?> list2) {
list1.addAll(list2); // DOES NOT COMPILE!!!
// The method addAll(Collection<? extends capture#1-of ?>)
// in the type List<capture#1-of ?> is not applicable for
// the arguments (List<capture#2-of ?>)
}
}
_
これまでのところ良いですが、ここで物事が非常に混乱し始めます( ideone.comで見られるように ):
_import Java.util.*;
public class LOLUnknowns1 {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
}
_
上記のコードは、Eclipseおよびideone.comの_Sun-jdk-1.6.0.17
_でコンパイルされますが、コンパイルする必要がありますか? _List<List<Integer>> lol
_と_List<String> list
_、TwoListsOfUnknowns
からの類似した2つの無関係なワイルドカード状況がある可能性はありませんか?
実際、その方向に向けた次のわずかな変更はコンパイルされません。これは予想されることです( ideone.comで見られるように ):
_import Java.util.*;
public class LOLUnknowns2 {
static void rightfullyIllegal(
List<List<? extends Number>> lol, List<?> list) {
lol.add(list); // DOES NOT COMPILE! As expected!!!
// The method add(List<? extends Number>) in the type
// List<List<? extends Number>> is not applicable for
// the arguments (List<capture#1-of ?>)
}
}
_
したがって、コンパイラがその仕事をしているように見えますが、次のようになります( ideone.comで見られるように ):
_import Java.util.*;
public class LOLUnknowns3 {
static void probablyIllegalAgain(
List<List<? extends Number>> lol, List<? extends Number> list) {
lol.add(list); // compiles fine!!! how come???
}
}
_
繰り返しますが、例えば_List<List<Integer>> lol
_と_List<Float> list
_なので、コンパイルしないでくださいね。
実際、より単純な_LOLUnknowns1
_(2つの無制限のワイルドカード)に戻って、実際にprobablyIllegal
を何らかの方法で呼び出すことができるかどうかを確認してみましょう。最初に「簡単な」ケースを試して、2つのワイルドカードに同じタイプを選択しましょう( ideone.comで見られるように ):
_import Java.util.*;
public class LOLUnknowns1a {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
public static void main(String[] args) {
List<List<String>> lol = null;
List<String> list = null;
probablyIllegal(lol, list); // DOES NOT COMPILE!!
// The method probablyIllegal(List<List<?>>, List<?>)
// in the type LOLUnknowns1a is not applicable for the
// arguments (List<List<String>>, List<String>)
}
}
_
これは意味がありません!ここでは、2つの異なるタイプを使用しようとさえしておらず、コンパイルもされていません。 _List<List<Integer>> lol
_および_List<String> list
_にすると、同様のコンパイルエラーが発生します。実際、私の実験から、コードをコンパイルする唯一の方法は、最初の引数が明示的なnull
型である場合です( ideone.comで見られるように ):
_import Java.util.*;
public class LOLUnknowns1b {
static void probablyIllegal(List<List<?>> lol, List<?> list) {
lol.add(list); // this compiles!! how come???
}
public static void main(String[] args) {
List<String> list = null;
probablyIllegal(null, list); // compiles fine!
// throws NullPointerException at run-time
}
}
_
したがって、質問は、_LOLUnknowns1
_、_LOLUnknowns1a
_、および_LOLUnknowns1b
_に関して次のようになります。
probablyIllegal
はどのような種類の引数を受け入れますか?lol.add(list);
をコンパイルする必要がありますか?タイプセーフですか?誰かが興味を持っている場合、これはうまくコンパイルされます( ideone.comで見られるように ):
_import Java.util.*;
public class DoubleLOL {
static void omg2xLOL(List<List<?>> lol1, List<List<?>> lol2) {
// compiles just fine!!!
lol1.addAll(lol2);
lol2.addAll(lol1);
}
}
_
さらなる調査は、おそらく複数のワイルドカードが問題とは何の関係もないことを示していますが、むしろネストされたワイルドカードが混乱の原因です。
_import Java.util.*;
public class IntoTheWild {
public static void main(String[] args) {
List<?> list = new ArrayList<String>(); // compiles fine!
List<List<?>> lol = new ArrayList<List<String>>(); // DOES NOT COMPILE!!!
// Type mismatch: cannot convert from
// ArrayList<List<String>> to List<List<?>>
}
}
_
したがって、おそらく_List<List<String>>
_は_List<List<?>>
_ではないように見えます。実際、_List<E>
_は_List<?>
_ですが、_List<List<E>>
_は_List<List<?>>
_のようには見えません( ideone.comで見られるように =):
_import Java.util.*;
public class IntoTheWild2 {
static <E> List<?> makeItWild(List<E> list) {
return list; // compiles fine!
}
static <E> List<List<?>> makeItWildLOL(List<List<E>> lol) {
return lol; // DOES NOT COMPILE!!!
// Type mismatch: cannot convert from
// List<List<E>> to List<List<?>>
}
}
_
次に、新しい質問が発生します。_List<List<?>>
_とは何ですか?
付録Bが示すように、これは複数のワイルドカードとは関係ありませんが、_List<List<?>>
_が実際に何を意味するのかを誤解しています。
まず、Javaジェネリックスが不変であることの意味を思い出してみましょう:
Integer
はNumber
ですList<Integer>
_は[〜#〜] not [〜#〜] a _List<Number>
_List<Integer>
_ [〜#〜] is [〜#〜] a _List<? extends Number>
_ネストされたリストの状況に同じ引数を適用するだけです。 (詳細については、付録を参照してください):
List<String>
_は_List<?>
_です(キャプチャ可能)List<List<String>>
_は[〜#〜] not [〜#〜](キャプチャ可能)_List<List<?>>
_List<List<String>>
_ [〜#〜] is [〜#〜](キャプチャ可能)_List<? extends List<?>>
_この理解により、質問のすべてのスニペットを説明できます。混乱は、_List<List<?>>
_のようなタイプが_List<List<String>>
_、_List<List<Integer>>
_などのタイプをキャプチャできると(誤って)信じることで発生します。これは[〜#〜] not [〜# 〜] true。
つまり、_List<List<?>>
_:
List<? extends List<?>>
_になります上記のポイントを説明するためのスニペットを次に示します。
_List<List<?>> lolAny = new ArrayList<List<?>>();
lolAny.add(new ArrayList<Integer>());
lolAny.add(new ArrayList<String>());
// lolAny = new ArrayList<List<String>>(); // DOES NOT COMPILE!!
List<? extends List<?>> lolSome;
lolSome = new ArrayList<List<String>>();
lolSome = new ArrayList<List<Integer>>();
_
ネストされたワイルドカードが制限されている別の例を次に示します。
_List<List<? extends Number>> lolAnyNum = new ArrayList<List<? extends Number>>();
lolAnyNum.add(new ArrayList<Integer>());
lolAnyNum.add(new ArrayList<Float>());
// lolAnyNum.add(new ArrayList<String>()); // DOES NOT COMPILE!!
// lolAnyNum = new ArrayList<List<Integer>>(); // DOES NOT COMPILE!!
List<? extends List<? extends Number>> lolSomeNum;
lolSomeNum = new ArrayList<List<Integer>>();
lolSomeNum = new ArrayList<List<Float>>();
// lolSomeNum = new ArrayList<List<String>>(); // DOES NOT COMPILE!!
_
質問のスニペットに戻ると、次のように動作します( ideone.comで見られるように ):
_public class LOLUnknowns1d {
static void nowDefinitelyIllegal(List<? extends List<?>> lol, List<?> list) {
lol.add(list); // DOES NOT COMPILE!!!
// The method add(capture#1-of ? extends List<?>) in the
// type List<capture#1-of ? extends List<?>> is not
// applicable for the arguments (List<capture#3-of ?>)
}
public static void main(String[] args) {
List<Object> list = null;
List<List<String>> lolString = null;
List<List<Integer>> lolInteger = null;
// these casts are valid
nowDefinitelyIllegal(lolString, list);
nowDefinitelyIllegal(lolInteger, list);
}
}
_
lol.add(list);
は、_List<List<String>> lol
_と_List<Object> list
_がある可能性があるため、違法です。実際、問題のあるステートメントをコメントアウトすると、コードがコンパイルされ、それがmain
の最初の呼び出しで得られるものとまったく同じです。
問題のprobablyIllegal
メソッドはすべて違法ではありません。それらはすべて完全に合法でタイプセーフです。コンパイラにバグは一切ありません。それはまさにそれがすることになっていることをしている。
List<Animal> animals = new ArrayList<Dog>()
ができない理由を簡単に説明する方法はありますか?(これは回答の最初の改訂で取り上げられました。これは、型不変の引数を補足する価値があります。)
5.1.10キャプチャ変換
[〜#〜] g [〜#〜]ジェネリック型宣言にn正式な型パラメーターA1…an対応する境界を持つ1…Un。 G <Tからのキャプチャ変換が存在します1…tn>からG <S1…sn>、ここで、1 <= i <= n:
- もしT私は、_
?
_の形式のワイルドカード型引数です。その後…- もしT私は、_
? extends
_ Bの形式のワイルドカード型引数です。私、次に…- もしT私は、_
? super
_ Bの形式のワイルドカード型引数です。私、次に…- それ以外の場合S私 = T私。
キャプチャ変換は再帰的に適用されません。
このセクションは、特にキャプチャ変換の非再帰的アプリケーション(ここでは[〜#〜] cc [〜#〜])に関して混乱する可能性がありますが、重要なのはすべての_?
_がCCできるわけではありません。表示される場所によって異なります。ルール4には再帰的な適用はありませんが、ルール2または3が適用される場合、それぞれのB私それ自体がCCの結果である可能性があります。
いくつかの簡単な例を見てみましょう。
List<?>
_はCC _List<String>
_ ?
_はルール1でCCできますList<? extends Number>
_はCC _List<Integer>
_ ?
_はルール2でCCできますNumber
ですList<? extends Number>
_ can [〜#〜] not [〜#〜] CC _List<String>
_ ?
_はルール2でCCできますが、互換性のないタイプが原因でコンパイル時エラーが発生しますそれでは、ネストを試してみましょう。
List<List<?>>
_ can [〜#〜] not [〜#〜] CC _List<List<String>>
_ ?
_は[〜#〜] not [〜#〜] CCを実行できます。List<? extends List<?>>
_はCC _List<List<String>>
_ ?
_はルール2でCCできますList<?>
_になり、CC _List<String>
_になります。?
_はCCできますList<? extends List<? extends Number>>
_はCC _List<List<Integer>>
_ ?
_はルール2でCCできますList<? extends Number>
_になり、CC _List<Integer>
_になります。?
_はCCできますList<? extends List<? extends Number>>
_ can [〜#〜] not [〜#〜] CC _List<List<Integer>>
_ ?
_はルール2でCCできますList<? extends Number>
_になり、CCに変換できますが、_List<Integer>
_に適用するとコンパイル時エラーが発生します?
_はCCできます一部の_?
_がCCでき、他の__ができない理由をさらに説明するために、次のルールを検討してください。ワイルドカードタイプを直接インスタンス化できる[〜#〜] not [〜#〜]。つまり、次の場合、コンパイル時エラーが発生します。
_ // WildSnippet1
new HashMap<?,?>(); // DOES NOT COMPILE!!!
new HashMap<List<?>, ?>(); // DOES NOT COMPILE!!!
new HashMap<?, Set<?>>(); // DOES NOT COMPILE!!!
_
ただし、以下は問題なくコンパイルされます。
_ // WildSnippet2
new HashMap<List<?>,Set<?>>(); // compiles fine!
new HashMap<Map<?,?>, Map<?,Map<?,?>>>(); // compiles fine!
_
_WildSnippet2
_がコンパイルされる理由は、上で説明したように、_?
_のいずれもCCできないためです。 _WildSnippet1
_では、_HashMap<K,V>
_のK
またはV
(あるいはその両方)のいずれかがCCになり、new
を介した直接インスタンス化が違法になります。
引数なしジェネリックス付きは受け入れられるべきです。 _LOLUnknowns1b
_の場合、最初の引数がnull
として入力されたかのようにList
が受け入れられます。たとえば、これはコンパイルされます:
_List lol = null;
List<String> list = null;
probablyIllegal(lol, list);
_
IMHO lol.add(list);
はコンパイルすらすべきではありませんが、lol.add()
にはタイプ_List<?>
_の引数が必要であり、リストが_List<?>
_に収まるので機能します。
この理論を思い起こさせる奇妙な例は次のとおりです。
_static void probablyIllegalAgain(List<List<? extends Number>> lol, List<? extends Integer> list) {
lol.add(list); // compiles fine!!! how come???
}
_
lol.add()
にはタイプ_List<? extends Number>
_の引数が必要で、リストは_List<? extends Integer>
_と入力され、適合します。一致しない場合は機能しません。ダブルLOLやその他のネストされたワイルドカードについても同じことが言えます。最初のキャプチャが2番目のキャプチャと一致する限り、すべてが問題ありません(そして魂はそうではありません)。
繰り返しますが、よくわかりませんが、実際にはバグのようです。
lol
変数を常に使用しているのは私だけではないことを嬉しく思います。
リソース:
http://www.angelikalanger.com 、a FAQジェネリック医薬品について
編集:
専門家ではありませんが、理解できると思います。
例を同等のものに変更しましょう。ただし、より特徴的なタイプを使用します。
static void probablyIllegal(List<Class<?>> x, Class<?> y) {
x.add(y); // this compiles!! how come???
}
リストを[]に変更して、よりわかりやすくしましょう。
static void probablyIllegal(Class<?>[] x, Class<?> y) {
x.add(y); // this compiles!! how come???
}
現在、xはnotsomeタイプのクラスの配列です。これは、anyタイプのクラスの配列です。 Class<String>
およびClass<Int>
を含めることができます。これは通常のタイプパラメータでは表現できません。
static<T> void probablyIllegal(Class<T>[] x //homogeneous! not the same!
Class<?>
は、anyT
のClass<T>
のスーパータイプです。 typeがオブジェクトのセットであると考えると、setClass<?>
は、すべてのT
のClass<T>
のすべてのsetsの和集合です。 (それ自体が含まれていますか?私は知りません...)