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


244

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

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

私はを返すメソッドがありList<TestA>、そのリスト内のすべてのオブジェクトをにキャストして、でTestB終わるようにしたいと思いますList<TestB>

回答:


578

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

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

88
すべての敬意を払って、これは一種のハックなソリューションだと思います-Javaが提供しようとしている型の安全性を回避しています。少なくともこのJavaチュートリアル(docs.oracle.com/javase/tutorial/java/generics/subtyping.html)を見て、この方法で修正する前に、この問題が発生する理由を検討してください。
jfritz42 2012年

63
@ jfritz42私はそれを「回避」型安全とは呼びません。この場合、Javaにはない知識があります。それがキャスティングの目的です。
Planky、2014

3
これにより、次のように記述できます。List <String> myList =(List <String>)(List <?>)(new ArrayList <Integer>()); これは実行時にクラッシュします。List <?Number> myList = new ArrayList <Integer>();を拡張します。
Bentaye 2017年

1
私は正直に@ jfritz42に同意します。同じ問題があり、彼が提供したURLを見て、キャストせずに私の問題を解決することができました。
Sam Orozco、

@ jfritz42これは、コンパイラによって許可されているオブジェクトを整数にキャストすることと機能的には異なりません
kcazllerraf

116

ジェネリックのキャストは不可能ですが、別の方法でリストを定義すると、TestBをそこに格納できます。

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

リスト内のオブジェクトを使用している場合でも、型チェックを行う必要があります。


14
これは有効なJava構文でもありません。
newacct 2013

2
それは本当ですが、それは実際に正しいというよりは説明的
でした

次のメソッドがあります。createSingleString(List <?extends Object> objects)このメソッド内で、String.valueOf(Object)を呼び出して、リストを1つの文字列に折りたたみます。入力がList <Long>、List <Boolean>などの場合にうまく機能します。ありがとう!
Chris Sprague

2
TestAがインターフェイスの場合はどうなりますか?
Suvitruf-Andrei Apanasik 2015

8
これが実際に機能するMCVEを提供できますか?わかりますCannot instantiate the type ArrayList<? extends TestA>
Nateowami 2015

42

あなたは本当にできない*:

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

2種類があると仮定AしてB、その結果B extends A。その後、次のコードは正しいです:

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

Bはのサブクラスであるため、前のコードは有効ですA。では、List<A>and で何が起こりList<B>ますか?

それはのList<B>サブクラスではないことが判明したList<A>ので、私たち書くことができません

    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<?>例えば。以下有効です。

    List<B> b = new ArrayList<>();
    List<?> t = (List<B>)b;
    List<A> a = (List<A>)t;

ただし、警告が表示されます。@SuppressWarnings("unchecked")メソッドに追加することで抑制できます。


2
また、コンパイラが変換全体を解読できるため、このリンクの素材を使用すると、チェックされていない変換を回避できます。
Ryan H

29

Java 8では、実際にできること

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

32
それは実際にリストをキャストしていますか?これは、リスト全体を反復処理する一連の操作に似ているため、各要素をキャストし、それらを目的のタイプの新しくインスタンス化されたリストに追加します。言い換えれば、Java7でこれを正確に行うことはできませんnew List<TestB>か?
Patrick M

5
OPから、「そのリストのすべてのオブジェクトをキャストしたい」-実際、これは、ここで閲覧している人の質問が後でなくても、前述の質問に答える数少ない回答の1つです。
ルークアッシャーウッド2018年

17

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

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


11

これは広く参照されている質問であり、現在の回答は主になぜそれが機能しないのかを説明している(または私が製品コードで見たくないハックで危険な解決策を提案している)ので、別の回答を追加して、落とし穴、および可能な解決策。


これが一般に機能しない理由は、他の回答ですでに指摘されています。変換が実際に有効かどうかは、元のリストに含まれているオブジェクトのタイプによって異なります。そこタイプではないタイプのものであり、リスト内のオブジェクトである場合はTestB、しかし、別のサブクラスでTestAは、キャストがあるではない有効な。


もちろん、キャスト有効です。コンパイラーで使用できないタイプに関する情報がある場合があります。これらの場合、リストをキャストすることは可能ですが、一般的には推奨されません

いずれか...

  • ...リスト全体をキャストするか、
  • ...リストのすべての要素をキャストします

(現在受け入れられている回答に対応する)最初のアプローチの意味は微妙です。一見すると正常に動作しているように見えるかもしれません。しかし、入力リストに間違ったタイプがある場合ClassCastException、おそらくコードの完全に異なる場所にスローされ、これをデバッグして、間違った要素がリストにどこに落ちたのかを見つけるのは難しいかもしれません。最悪の問題は、リストがキャストされた後に誰かが無効な要素を追加して、デバッグをさらに困難にする可能性があることです。

これらのスプリアスをデバッグする問題は、一連のメソッドでClassCastExceptions軽減できますCollections#checkedCollection


タイプに基づくリストのフィルタリング

aからa List<Supertype> に変換するよりタイプセーフな方法List<Subtype>は、実際にリストをフィルタリングし、特定のタイプの要素のみを含む新しいリストを作成することです。このようなメソッドの実装にはある程度の自由度があります(たとえば、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]

7

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


なぜこれは反対票が投じられたのですか?それは私にとって有用でした、そして私は反対票を投じる理由を知りません。
zod

7
確かにこの投稿は不正確ではありません。しかし、質問をした人にはようやくTestBのリストが必要になります。その場合、この答えは誤解を招くものです。List <TestA> .addAll(List <TestB>)を呼び出すことにより、List <TestB>のすべての要素をList <TestA>に追加できます。ただし、List <TestB> .addAll(List <TestA>);は使用できません。
12

6

最良の安全な方法は、実装で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();
            }
        };
    }
}

castmethodを使用して、リスト内のオブジェクトを盲目的にキャストし、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
}

4

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

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

2
かなり奇妙なことに、これはコンパイラの警告を出さないと思います。
Simon Forsberg

これは配列では奇妙です。ため息、Java配列は役に立たない。しかし、私はそれが他の答えよりも確かに悪くなく、読みやすいと思います。キャストほど危険ではないようです。
Thufir

3

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

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

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


2

クラスのオブジェクトがある場合、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


1
私は彼が次のようなものについて話していると思います、次のように作成されたメソッド引数として「a」を取得します-TestA a = new TestB()、次にTestBオブジェクトを取得し、それをキャストする必要があります-TestB b =( TestB)a;
samsamara 2013

2

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

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コレクションのコミッターです。


1

これは型消去のために可能です。あなたはそれを見つけるでしょう

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

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


「キャストするものは何もない」と「一方を他方にキャストすることはできません」は少し矛盾しています。Integer j = 42; Integer i = (Integer) j;正常に動作します。どちらも整数であり、どちらも同じクラスを持っているため、「キャストするものは何もありません」。
Simon Forsberg 2013年

1

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


0

これはうまくいくでしょう

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

1
これにより、不要なコピーが作成されます。
defnull

0

手動でリストをキャストすることは、次のようなものを実装するいくつかのツールボックスではまだ提供されていないのはかなり奇妙です:

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends E, E> List<T> cast(List<E> list) {
    return (List) list;
}

もちろん、これはアイテムを1つずつチェックしませんが、実装がサブタイプしか提供していないことがわかっている場合は、ここでそれを避けたいのです。

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