How do you cast a List of supertypes to a List of subtypes?

Viewed 322726

For example, lets say you have two classes:

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

I have a method that returns a List<TestA> and I would like to cast all the objects in that list to TestB so that I end up with a List<TestB>.

19 Answers

Simply casting to List<TestB> almost works; but it doesn't work because you can't cast a generic type of one parameter to another. However, you can cast through an intermediate wildcard type and it will be allowed (since you can cast to and from wildcard types, just with an unchecked warning):

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

Casting of generics is not possible, but if you define the list in another way it is possible to store TestB in it:

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

You still have type checking to do when you are using the objects in the list.

You really can't*:

Example is taken from this Java tutorial

Assume there are two types A and B such that B extends A. Then the following code is correct:

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

The previous code is valid because B is a subclass of A. Now, what happens with List<A> and List<B>?

It turns out that List<B> is not a subclass of List<A> therefore we cannot write

    List<B> b = new ArrayList<>();
    List<A> a = b; // error, List<B> is not of type List<A>

Furthermore, we can't even write

    List<B> b = new ArrayList<>();
    List<A> a = (List<A>)b; // error, List<B> is not of type List<A>

*: To make the casting possible we need a common parent for both List<A> and List<B>: List<?> for example. The following is valid:

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

You will, however, get a warning. You can suppress it by adding @SuppressWarnings("unchecked") to your method.

I think you are casting in the wrong direction though... if the method returns a list of TestA objects, then it really isn't safe to cast them to TestB.

Basically you are asking the compiler to let you perform TestB operations on a type TestA that does not support them.

You cannot cast List<TestB> to List<TestA> as Steve Kuo mentions BUT you can dump the contents of List<TestA> into List<TestB>. Try the following:

List<TestA> result = new List<TestA>();
List<TestB> data = new List<TestB>();
result.addAll(data);

I've not tried this code so there are probably mistakes but the idea is that it should iterate through the data object adding the elements (TestB objects) into the List. I hope that works for you.

When you cast an object reference you are just casting the type of the reference, not the type of the object. casting won't change the actual type of the object.

Java doesn't have implicit rules for converting Object types. (Unlike primitives)

Instead you need to provide how to convert one type to another and call it manually.

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

This is more verbose than in a language with direct support, but it works and you shouldn't need to do this very often.

if you have an object of the class TestA, you can't cast it to TestB. every TestB is a TestA, but not the other way.

in the following code:

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

the second line would throw a ClassCastException.

you can only cast a TestA reference if the object itself is TestB. for example:

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

so, you may not always cast a list of TestA to a list of TestB.

You can use the selectInstances method in Eclipse Collections. This will involved creating a new collection however so will not be as efficient as the accepted solution which uses casting.

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

I included StringBuffer in the example to show that selectInstances not only downcasts the type, but will also filter if the collection contains mixed types.

Note: I am a committer for Eclipse Collections.

Answering in 2022

Casting a List of supertypes to a List of subtypes is nonsensical and nobody should be attempting or even contemplating doing such a thing. If you think your code needs to do this, you need to rewrite your code so that it does not need to do this.

Most visitors to this question are likely to want to do the opposite, which does actually make sense:

Cast a list of subtypes to a list of supertypes.

The best way I have found is as follows:

List<TestA> testAs = List.copyOf( testBs );

This has the following advantages:

  • It is a neat one-liner
  • It produces no warnings
  • It does not make a copy if your list was created with List.of() !!!
  • Most importantly: it does the right thing.

Why is this the right thing?

If you look at the source code of List.copyOf() you will see that it works as follows:

  • If your list was created with List.of(), then it will do the cast and return it without copying it.
  • Otherwise, (e.g. if your list is an ArrayList(),) it will create a copy and return it.

If your List<TestB> is an ArrayList<TestB> then a copy of the ArrayList must be made. If you were to cast the ArrayList<TestB> as List<TestA>, you would be opening up the possibility of inadvertently adding a TestA into that List<TestA>, which would then cause your original ArrayList<TestB> to contain a TestA among the TestBs, which is memory corruption: attempting to iterate all the TestBs in the original ArrayList<TestB> would throw a ClassCastException.

On the other hand, if your List<TestB> has been created using List.of(), then it is unchangeable(*1), so nobody can inadvertently add a TestA to it, so it is okay to just cast it to List<TestA>.


(*1) when these lists were first introduced they were called "immutable"; later they realized that it is wrong to call them immutable, because a collection cannot be immutable, since it cannot vouch for the immutability of the elements that it contains; so they changed the documentation to call them "unmodifiable" instead; however, "unmodifiable" already had a meaning before these lists were introduced, and it meant "an unmodifiable to you view of my list which I am still free to mutate as I please, and the mutations will be very visible to you". So, neither immutable or unmodifiable is correct. I like to call them "superficially immutable" in the sense that they are not deeply immutable, but that may ruffle some feathers, so I just called them "unchangeable" as a compromise.

This is possible due to type erasure. You will find that

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

Internally both lists are of type List<Object>. For that reason you can't cast one to the other - there is nothing to cast.

Quite strange that manually casting a list is still not provided by some tool box implementing something like:

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

Of course, this won't check items one by one, but that is precisely what we want to avoid here, if we well know that our implementation only provides the sub-type.

Simple answer

You can't directly type cast.

Workaround

 public <T> List<T> getSubItemList(List<IAdapterImage> superList, Class<T> clazz) {
        return superList.stream()
                .map(item -> clazz.isInstance(item) ? clazz.cast(item) : null)
                .collect(Collectors.toList());
    }

Usage

private final List<IAdapterImage> myList = new ArrayList<>();

List<SubType> subTypeList = getSubItemList(myList,SubType.class);

So this is simple workaround I use to convert my list with super type into list with subtype. We are using stream api which was introduced in java 8 here, using map on our super list we are simply checking if passed argument is instance of our super type the returning the item. At last we are collecting into a new list. Of course we have to get result into a new list here.

Related