たとえば、2つのクラスがあるとします。
public class TestA {}
public class TestB extends TestA{}
List<TestA>
を返すメソッドがあり、そのリスト内のすべてのオブジェクトをTestB
にキャストして、List<TestB>
になるようにします。
List<TestB>
にキャストするだけでほとんど機能します。ただし、1つのパラメーターのジェネリック型を別のパラメーターにキャストできないため、機能しません。ただし、中間のワイルドカードタイプを介してキャストでき、許可されます(チェックされていない警告があれば、ワイルドカードタイプとの間でキャストできるため)。
List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
ジェネリックのキャストはできませんが、別の方法でリストを定義すると、TestBをリストに保存できます。
List<? extends TestA> myList = new ArrayList<TestA>();
リスト内のオブジェクトを使用しているときは、タイプチェックを行う必要があります。
本当にできない*:
このJavaチュートリアル からの例
B extends A
のようなA
とB
の2つのタイプがあると仮定します。次に、次のコードが正しいです。
B b = new B();
A a = b;
B
はA
のサブクラスであるため、前のコードは有効です。さて、List<A>
とList<B>
はどうなりますか?
List<B>
はList<A>
のサブクラスではないため、cannotと書く
List<B> b = new ArrayList<>();
List<A> a = b; // error, List<B> is not of type List<A>
さらに、私たちはできない
List<B> b = new ArrayList<>();
List<A> a = (List<A>)b; // error, List<B> is not of type List<A>
*:キャストを可能にするには、List<A>
とList<B>
の両方に共通の親が必要です:たとえばList<?>
。次のisは有効です。
List<B> b = new ArrayList<>();
List<?> t = (List<B>)b;
List<A> a = (List<A>)t;
ただし、警告が表示されます。メソッドに@SuppressWarnings("unchecked")
を追加することで、これを抑制することができます。
Java 8では、実際に次のことができます。
List<TestB> variable = collectionOfListA
.stream()
.map(e -> (TestB) e)
.collect(Collectors.toList());
ただし、間違った方向にキャストしていると思います...メソッドがTestA
オブジェクトのリストを返す場合、それらをTestB
にキャストするのは本当に安全ではありません。
基本的には、コンパイラに、それらをサポートしていないタイプTestB
に対してTestA
操作を実行するように依頼しています。
これは広く参照されている質問であり、現在の回答は主にそれが機能しない理由を説明しているため(または本番コードでは決して見たくないハッキング、危険なソリューションを提案します)、別の答えを追加するのが適切だと思います落とし穴、および可能な解決策。
これが一般に機能しない理由は、他の回答で既に指摘されています:変換が実際に有効かどうかは、元のリストに含まれるオブジェクトのタイプに依存します。リストにタイプがTestB
ではなく、TestA
の異なるサブクラスのオブジェクトがある場合、キャストは not 有効です。
もちろん、キャスト /が有効な場合があります。コンパイラーで使用できないタイプに関する情報がある場合があります。これらの場合、リストをキャストすることは可能ですが、一般的には、推奨されません:
どちらか...
最初のアプローチ(現在受け入れられている答えに対応)の意味は微妙です。一見正常に動作しているように見えるかもしれません。ただし、入力リストに間違った型がある場合、おそらくコード内の完全に異なる場所にClassCastException
がスローされます。これをデバッグして、間違った要素がリスト内にスリップした場所を見つけるのは難しいかもしれません。最悪の問題は、リストがキャストされた後、 /が無効な要素を追加する可能性があり、デバッグがさらに困難になることです。
これらの偽のClassCastExceptions
をデバッグする問題は、 Collections#checkedCollection
ファミリーのメソッドで軽減できます。
List<Supertype>
からList<Subtype>
に変換するよりタイプセーフな方法は、実際にリストを filter し、特定のタイプの要素のみを含む新しいリストを作成することです。このようなメソッドの実装にはある程度の自由度があります(たとえば、null
エントリの処理に関して)が、1つの可能な実装は次のようになります。
/**
* Filter the given list, and create a new list that only contains
* the elements that are (subtypes) of the class c
*
* @param listA The input list
* @param c The class to filter for
* @return The filtered list
*/
private static <T> List<T> filter(List<?> listA, Class<T> c)
{
List<T> listB = new ArrayList<T>();
for (Object a : listA)
{
if (c.isInstance(a))
{
listB.add(c.cast(a));
}
}
return listB;
}
このメソッドは、次の例のように、任意のリストをフィルタリングするために使用できます(タイプパラメータに関するサブタイプとスーパータイプの関係だけでなく)。
// A list of type "List<Number>" that actually
// contains Integer, Double and Float values
List<Number> mixedNumbers =
new ArrayList<Number>(Arrays.asList(12, 3.4, 5.6f, 78));
// Filter the list, and create a list that contains
// only the Integer values:
List<Integer> integers = filter(mixedNumbers, Integer.class);
System.out.println(integers); // Prints [12, 78]
Steve Kuoが言及しているようにList<TestB>
をList<TestA>
にキャストすることはできませんが、List<TestA>
の内容をList<TestB>
にダンプできます。以下を試してください:
List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);
私はこのコードを試したことがないので、おそらく間違いがありますが、アイデアは、データオブジェクトを反復処理して要素(TestBオブジェクト)をリストに追加するというものです。それがあなたの役に立つことを願っています。
最も安全な方法は、AbstractList
を実装し、実装でアイテムをキャストすることです。 ListUtil
ヘルパークラスを作成しました。
public class ListUtil
{
public static <TCastTo, TCastFrom extends TCastTo> List<TCastTo> convert(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
public static <TCastTo, TCastFrom> List<TCastTo> cast(final List<TCastFrom> list)
{
return new AbstractList<TCastTo>() {
@Override
public TCastTo get(int i)
{
return (TCastTo)list.get(i);
}
@Override
public int size()
{
return list.size();
}
};
}
}
cast
メソッドを使用して、リスト内のオブジェクトを盲目的にキャストし、安全にキャストするためにconvert
メソッドを使用できます。例:
void test(List<TestA> listA, List<TestB> listB)
{
List<TestB> castedB = ListUtil.cast(listA); // all items are blindly casted
List<TestB> convertedB = ListUtil.<TestB, TestA>convert(listA); // wrong cause TestA does not extend TestB
List<TestA> convertedA = ListUtil.<TestA, TestB>convert(listB); // OK all items are safely casted
}
オブジェクト参照をキャストすると、オブジェクトのタイプではなく、参照のタイプのみがキャストされます。キャストしても、オブジェクトの実際のタイプは変更されません。
Javaには、オブジェクト型を変換するための暗黙のルールがありません。 (プリミティブとは異なります)
代わりに、あるタイプを別のタイプに変換し、手動で呼び出す方法を提供する必要があります。
public class TestA {}
public class TestB extends TestA{
TestB(TestA testA) {
// build a TestB from a TestA
}
}
List<TestA> result = ....
List<TestB> data = new List<TestB>();
for(TestA testA : result) {
data.add(new TestB(testA));
}
これは、直接サポートされている言語よりも冗長ですが、機能するため、頻繁に行う必要はありません。
私が知っている唯一の方法はコピーすることです:
List<TestB> list = new ArrayList<TestB> (
Arrays.asList (
testAList.toArray(new TestB[0])
)
);
これは、タイプの消去により可能です。あなたはそれを見つけるでしょう
List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true
内部的には両方のリストはList<Object>
型です。そのため、一方を他方にキャストすることはできません-キャストするものは何もありません。
クラスTestA
のオブジェクトがある場合、TestB
にキャストできません。すべてのTestB
はTestA
ですが、他の方法ではありません。
次のコードで:
TestA a = new TestA();
TestB b = (TestB) a;
2行目はClassCastException
をスローします。
オブジェクト自体がTestA
である場合にのみTestB
参照をキャストできます。例えば:
TestA a = new TestB();
TestB b = (TestB) a;
そのため、常にTestA
のリストをTestB
のリストにキャストするとは限りません。
class MyClass {
String field;
MyClass(String field) {
this.field = field;
}
}
@Test
public void testTypeCast() {
List<Object> objectList = Arrays.asList(new MyClass("1"), new MyClass("2"));
Class<MyClass> clazz = MyClass.class;
List<MyClass> myClassList = objectList.stream()
.map(clazz::cast)
.collect(Collectors.toList());
assertEquals(objectList.size(), myClassList.size());
assertEquals(objectList, myClassList);
}
このテストは、List<Object>
をList<MyClass>
にキャストする方法を示しています。ただし、objectList
にはMyClass
と同じ型のインスタンスが含まれている必要があることに注意する必要があります。また、List<T>
を使用する場合、この例は考慮できます。この目的のために、コンストラクタでフィールドClass<T> clazz
を取得し、MyClass.class
の代わりに使用します。
問題は、メソッドにTestBが含まれている場合、TestAのリストが返されないため、正しく入力された場合はどうなるのでしょうか?次に、このキャスト:
class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;
期待通りに動作します(Eclipseは、あなたがやっていることとまったく同じである未チェックのキャストについて警告します)。これを使用して問題を解決できますか?実際にあなたはこれのためにできます:
List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist; // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!
まさにあなたが求めたものですよね?または本当に正確に言うと:
List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;
私にとっては問題なくコンパイルできるようです。
Eclipse Collections でselectInstances
メソッドを使用できます。ただし、これには新しいコレクションの作成が含まれるため、キャストを使用する承認済みのソリューションほど効率的ではありません。
List<CharSequence> parent =
Arrays.asList("1","2","3", new StringBuffer("4"));
List<String> strings =
Lists.adapt(parent).selectInstancesOf(String.class);
Assert.assertEquals(Arrays.asList("1","2","3"), strings);
StringBuffer
を例に含めて、selectInstances
が型をダウンキャストするだけでなく、コレクションに混合型が含まれている場合にフィルターをかけることも示しています。
注:私はEclipse Collectionsのコミッターです。
これは動作するはずです
List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));