web-dev-qa-db-ja.com

スーパータイプのリストをサブタイプのリストにどのようにキャストしますか?

たとえば、2つのクラスがあるとします。

public class TestA {}
public class TestB extends TestA{}

List<TestA>を返すメソッドがあり、そのリスト内のすべてのオブジェクトをTestBにキャストして、List<TestB>になるようにします。

219
Jeremy Logan

List<TestB>にキャストするだけでほとんど機能します。ただし、1つのパラメーターのジェネリック型を別のパラメーターにキャストできないため、機能しません。ただし、中間のワイルドカードタイプを介してキャストでき、許可されます(チェックされていない警告があれば、ワイルドカードタイプとの間でキャストできるため)。

List<TestB> variable = (List<TestB>)(List<?>) collectionOfListA;
540
newacct

ジェネリックのキャストはできませんが、別の方法でリストを定義すると、TestBをリストに保存できます。

List<? extends TestA> myList = new ArrayList<TestA>();

リスト内のオブジェクトを使用しているときは、タイプチェックを行う必要があります。

110
Salandur

本当にできない*:

このJavaチュートリアル からの例

B extends AのようなABの2つのタイプがあると仮定します。次に、次のコードが正しいです。

    B b = new B();
    A a = b;

BAのサブクラスであるため、前のコードは有効です。さて、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")を追加することで、これを抑制することができます。

39
Steve Kuo

Java 8では、実際に次のことができます。

List<TestB> variable = collectionOfListA
    .stream()
    .map(e -> (TestB) e)
    .collect(Collectors.toList());
27
fracz

ただし、間違った方向にキャストしていると思います...メソッドがTestAオブジェクトのリストを返す場合、それらをTestBにキャストするのは本当に安全ではありません。

基本的には、コンパイラに、それらをサポートしていないタイプTestBに対してTestA操作を実行するように依頼しています。

18
jerryjvl

これは広く参照されている質問であり、現在の回答は主にそれが機能しない理由を説明しているため(または本番コードでは決して見たくないハッキング、危険なソリューションを提案します)、別の答えを追加するのが適切だと思います落とし穴、および可能な解決策。


これが一般に機能しない理由は、他の回答で既に指摘されています:変換が実際に有効かどうかは、元のリストに含まれるオブジェクトのタイプに依存します。リストにタイプが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]
8
Marco13

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オブジェクト)をリストに追加するというものです。それがあなたの役に立つことを願っています。

6
martinatime

最も安全な方法は、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
}
6
Igor Zubchenok

オブジェクト参照をキャストすると、オブジェクトのタイプではなく、参照のタイプのみがキャストされます。キャストしても、オブジェクトの実際のタイプは変更されません。

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));
}

これは、直接サポートされている言語よりも冗長ですが、機能するため、頻繁に行う必要はありません。

3
Peter Lawrey

私が知っている唯一の方法はコピーすることです:

List<TestB> list = new ArrayList<TestB> (
    Arrays.asList (
        testAList.toArray(new TestB[0])
    )
);
3
Peter Heusch

これは、タイプの消去により可能です。あなたはそれを見つけるでしょう

List<TestA> x = new ArrayList<TestA>();
List<TestB> y = new ArrayList<TestB>();
x.getClass().equals(y.getClass()); // true

内部的には両方のリストはList<Object>型です。そのため、一方を他方にキャストすることはできません-キャストするものは何もありません。

2
n3rd

クラスTestAのオブジェクトがある場合、TestBにキャストできません。すべてのTestBTestAですが、他の方法ではありません。

次のコードで:

TestA a = new TestA();
TestB b = (TestB) a;

2行目はClassCastExceptionをスローします。

オブジェクト自体がTestAである場合にのみTestB参照をキャストできます。例えば:

TestA a = new TestB();
TestB b = (TestB) a;

そのため、常にTestAのリストをTestBのリストにキャストするとは限りません。

2
cd1
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の代わりに使用します。

1
Alex Po

問題は、メソッドに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;

私にとっては問題なくコンパイルできるようです。

1
Bill K

Eclipse CollectionsselectInstancesメソッドを使用できます。ただし、これには新しいコレクションの作成が含まれるため、キャストを使用する承認済みのソリューションほど効率的ではありません。

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のコミッターです。

1
Donald Raab

これは動作するはずです

List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));
0
anonymouse