以下に、_List<Object>
_をパラメーターとして受け入れるメソッドdoSomething(List<Object>)
を考えます。
_private void doSomething(List<Object> list) {
// do something
}
_
次に、doSomething()
を呼び出そうとする以下のコードスニペットを考えます。ここで、_List<String>
_をdoSomething()
に渡そうとします。
_List<Object> objectList;
List<String> stringList;
doSomething(stringList); // compilation error incompatible types
doSomething(objectList); // works fine
_
以下のコードでもコンパイルエラーが発生します
_objectList = stringList; // compilation error incompatible types
_
私の質問は、なぜ_List<String>
_が_List<Object>
_を受け入れるメソッドに渡されないのかということです。
Java=のこの一般的な質問は、一見するとStringがオブジェクトのように見えるため、List<String>
をList<Object>
は必須ですが、これは正しくありません。コンパイルエラーが発生します。
List<Object>
はString
、Integer
などを含むすべてのものを格納できるため、List<String>
はStrings
のみを保存できます。
また見てください: なぜList <T>から継承しないのですか?
String
はObject
を拡張しますが、List<String>
はList<Object>
を拡張しません
更新:
一般的に、Foo
がBar
のサブタイプ(サブクラスまたはサブインターフェース)であり、G
がいくつかのジェネリック型宣言である場合、それは当てはまりませんG<Foo>
はG<Bar>
のサブタイプです。
これは、コレクションが変更されるためです。あなたの場合、List<String>
がList<Object>
のサブタイプである場合、次のように、スーパータイプを使用してリストが参照されるときに、String
以外のタイプを追加できます。
List<String> stringList = new ArrayList<String>;
List<Object> objectList = stringList;// this does compile only if List<String> where subtypes of List<Object>
objectList.add(new Object());
String s = stringList.get(0);// attempt to assign an Object to a String :O
Javaコンパイラはこれらのケースを防ぐ必要があります。
this Javaチュートリアルページの詳細。
間違ったタイプのオブジェクトをリストに入れることができます[〜#〜] if [〜#〜]これはうまくいきました:
private void doSomething(List<Object> list) {
list.add(new Integer(123)); // Should be fine, it's an object
}
List<String> stringList = new ArrayList<String>();
doSomething(stringList); // If this worked....
String s = stringList.get(0); // ... you'd receive a ClassCastException here
これらの制限の理由は、分散の考慮に関係しています。
次のコードを見てください。
_public void doSomething(List<Object> objects)
{
objects.add(new Object());
}
_
あなたの例を拡張すると、次のことを試すことができます:
_List<String> strings = new ArrayList<String>();
string.add("S1");
doSomething(strings);
for (String s : strings)
{
System.out.println(s.length);
}
_
うまくいけば、コンパイラがこのコードのコンパイルを許可した場合(そうしない場合)にこれが失敗する理由は明らかです。ClassCastException
をObject
にキャストしようとすると、リストの2番目の項目でString
が発生します。
一般化されたコレクション型を渡すことができるようにするには、これを行う必要があります:
_public void doSomething(List<?> objects)
{
for (Object obj : objects)
{
System.out.println(obj.toString);
}
}
_
繰り返しますが、コンパイラはあなたの背中を見守っていて、_System.out
_をobjects.add(new Object())
で置き換えようとすると、objects
が_List<String>
_として作成された可能性があるため、コンパイラはこれを許可しません。
分散の詳細については、Wikipediaのアーティカルを参照してください 共分散と反分散
Object
はString
のスーパータイプであるため、List<Object>
はList<String>
のスーパータイプであると予想される場合があります。
この期待は、配列に対してこのような型の関係が存在するという事実から生じています。
Object
はString
のスーパータイプであるため、Object[]
はString[]
のスーパータイプです。 (このタイプの関係は共分散と呼ばれます。)
コンポーネントタイプのスーパーサブタイプの関係は、対応する配列タイプに拡張されます。
ジェネリック型のインスタンス化には、そのような型の関係はありません。 (パラメーター化された型は共変ではありません。)
詳細は こちら を確認してください
From Javaジェネリックのチュートリアル:
ジェネリックの理解度をテストしてみましょう。次のコードスニペットは合法ですか?
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
1行目は確かに合法です。質問のトリッキーな部分は2行目です。これは質問に要約されます:オブジェクトのリストである文字列のリストです。ほとんどの人は本能的に「確かに」と答えます。
さて、次の数行を見てください:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!
ここでは、lsとloのエイリアスを設定しています。 Stringのリストであるlsにアクセスし、別名loを介して、任意のオブジェクトを挿入できます。その結果、lsは文字列だけを保持しなくなり、そこから何かを取得しようとすると、失礼な驚きが生じます。
Javaコンパイラはこれが発生するのを防ぎます。2行目はコンパイル時エラーを引き起こします。
出典: ジェネリックとサブタイピング
どのデータ型を取るかわからない場合は、次のようにJavaでGenericsを使用できます。
public static void doSomething(List<?> data) {
}
public static void main(String [] args) {
List<Object> objectList = new ArrayList<Object>();
List<String> stringList = new ArrayList<String>();
doSomething(objectList);
doSomething(stringList);
}
ただし、データを使用するときは、適切なデータ型をタイプキャストとして指定する必要があります。