How do you cast a List of supertypes to a List of subtypes?
Asked Answered
W

20

312

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>.

Wear answered 1/6, 2009 at 3:25 Comment(0)
K
669

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;
Koan answered 1/6, 2009 at 4:52 Comment(6)
With all due respect, I think this is kind of a hacky solution -- you're dodging the type safety Java is trying to provide you. At least look at this Java tutorial (docs.oracle.com/javase/tutorial/java/generics/subtyping.html) and consider why you have this problem before fixing it this way.Controller
@Controller I wouldn't call it "dodging" type safety. In this case you have knowledge that Java doesn't have. That's what casting is for.Restoration
This lets me write: List<String> myList = (List<String>)(List<?>)(new ArrayList<Integer>()); This would crash at runtime. I prefer something like List<? extends Number> myList = new ArrayList<Integer>();Friulian
I honestly agree with @Controller I was having the same issue and looked at the URL he provided and was able to solve my problem without the casting.Passageway
@Controller this is not functionally different from casting an Object to an Integer, which is allowed by the compilerAhriman
Is this any different from List<TestB> variable = (List<TestB>)(Object) collectionOfListA;Spool
P
129

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.

Propend answered 1/6, 2009 at 9:4 Comment(5)
This is not even valid Java syntax.Koan
That is true, but it was more illustrative than factually correctPropend
I have the following method: createSingleString(List<? extends Object> objects) Inside this method I call String.valueOf(Object) to collapse the list into one string. It works great when input is List<Long>,List<Boolean>,etc. Thanks!Sonia
What if TestA is interface?Samite
Can you give a MCVE where this actually works? I get Cannot instantiate the type ArrayList<? extends TestA>.Burchette
R
53

With Java 8, you actually can

List<TestB> variable = collectionOfListA
    .stream()
    .map(e -> (TestB) e)
    .collect(Collectors.toList());
Rheum answered 10/2, 2015 at 10:8 Comment(3)
Is that actually casting the list? Because it reads more like series of operations to iterate over the whole list, casting each element, and then adding them to a newly instantiated list of the desired type. Put another way, couldn't you do exactly this with Java7- with a new List<TestB>, a loop and a cast?Salot
From the OP "and I would like to cast all the objects in that list" - so actually, this is one of the few answers that answers the question as stated, even if it's not the question people browsing here are after.Patency
In my case I need to do some filtering after casting so looping through the list is not my issue.Lightsome
P
50

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.

Pliske answered 1/6, 2009 at 3:29 Comment(1)
Also, using the material in this link can avoid the unchecked conversion, since the compiler can decipher the entire conversion.Circumbendibus
P
20

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.

Porscheporsena answered 1/6, 2009 at 3:30 Comment(0)
A
12

Since this is a widely referenced question, and the current answers mainly explain why it does not work (or propose hacky, dangerous solutions that I would never ever like to see in production code), I think it is appropriate to add another answer, showing the pitfalls, and a possible solution.


The reason why this does not work in general has already been pointed out in other answers: Whether or not the conversion is actually valid depends on the types of the objects that are contained in the original list. When there are objects in the list whose type is not of type TestB, but of a different subclass of TestA, then the cast is not valid.


Of course, the casts may be valid. You sometimes have information about the types that is not available for the compiler. In these cases, it is possible to cast the lists, but in general, it is not recommended:

One could either...

  • ... cast the whole list or
  • ... cast all elements of the list

The implications of the first approach (which corresponds to the currently accepted answer) are subtle. It might seem to work properly at the first glance. But if there are wrong types in the input list, then a ClassCastException will be thrown, maybe at a completely different location in the code, and it may be hard to debug this and to find out where the wrong element slipped into the list. The worst problem is that someone might even add the invalid elements after the list has been casted, making debugging even more difficult.

The problem of debugging these spurious ClassCastExceptions can be alleviated with the Collections#checkedCollection family of methods.


Filtering the list based on the type

A more type-safe way of converting from a List<Supertype> to a List<Subtype> is to actually filter the list, and create a new list that contains only elements that have certain type. There are some degrees of freedom for the implementation of such a method (e.g. regarding the treatment of null entries), but one possible implementation may look like this:

/**
 * 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;
}

This method can be used in order to filter arbitrary lists (not only with a given Subtype-Supertype relationship regarding the type parameters), as in this example:

// 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]
Arsenide answered 26/5, 2015 at 16:59 Comment(0)
D
9

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.

Dressing answered 1/6, 2009 at 4:16 Comment(2)
why was this down voted? It was useful to me, and I see no explanation for the down vote.Hellbender
Surely this post is not incorrect. But the person who asked the question finally needs a list of TestB. In that case this answer is misleading. You can add all the elements in List<TestB> to List<TestA> by calling List<TestA>.addAll(List<TestB>). But you can't use List<TestB>.addAll(List<TestA>);Potsherd
C
7

The best safe way is to implement an AbstractList and cast items in implementation. I created ListUtil helper class:

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

You can use cast method to blindly cast objects in list and convert method for safe casting. Example:

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
}
Cookbook answered 2/3, 2013 at 12:14 Comment(0)
A
5

The only way I know is by copying:

List<TestB> list = new ArrayList<TestB> (
    Arrays.asList (
        testAList.toArray(new TestB[0])
    )
);
Alisa answered 19/9, 2011 at 20:6 Comment(3)
Quite strange I think that this doesn't give a compiler warning.Trabzon
this is an oddity with arrays. Sigh, java arrays are so useless. But, I think it's certainly no worse, and much readable, than other answers. I don't see that anymore unsafe than a cast.Cauliflower
I’d do something like this. It’s a bit surprising, though, so I would also stick in a comment explaining why it works.Stinkhorn
E
5

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.

Electrotype answered 10/5, 2022 at 14:1 Comment(0)
A
4

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.

Aker answered 1/6, 2009 at 5:58 Comment(0)
K
2

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.

Kanishakanji answered 1/6, 2009 at 3:31 Comment(1)
I think he's talking about something like, you get 'a' as a method argument which is created as - TestA a = new TestB(), then you want to get the TestB object and then you need to cast it - TestB b = (TestB) a;Slovak
T
2

You can use the selectInstancesOf 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 selectInstancesOf not only downcasts the type, but will also filter if the collection contains mixed types.

Note: I am a committer for Eclipse Collections.

Telegraphic answered 28/4, 2018 at 18:33 Comment(0)
C
1

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.

Chimere answered 1/6, 2009 at 3:30 Comment(1)
"There is nothing to cast" and "You can't cast one to the other" is a bit of a contradiction. Integer j = 42; Integer i = (Integer) j; works fine, both are integers, they both have the same class, so "there is nothing to cast".Trabzon
D
1

The problem is that your method does NOT return a list of TestA if it contains a TestB, so what if it was correctly typed? Then this cast:

class TestA{};
class TestB extends TestA{};
List<? extends TestA> listA;
List<TestB> listB = (List<TestB>) listA;

works about as well as you could hope for (Eclipse warns you of an unchecked cast which is exactly what you are doing, so meh). So can you use this to solve your problem? Actually you can because of this:

List<TestA> badlist = null; // Actually contains TestBs, as specified
List<? extends TestA> talist = badlist;  // Umm, works
List<TextB> tblist = (List<TestB>)talist; // TADA!

Exactly what you asked for, right? or to be really exact:

List<TestB> tblist = (List<TestB>)(List<? extends TestA>) badlist;

seems to compile just fine for me.

Deprecate answered 22/6, 2017 at 18:9 Comment(0)
K
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);
}

This test shows how to cast List<Object> to List<MyClass>. But you need to take an attention to that objectList must contain instances of the same type as MyClass. And this example can be considered when List<T> is used. For this purpose get field Class<T> clazz in constructor and use it instead of MyClass.class.

UPDATED

Actually you can't cast from supertype to subtype in strong typed Java AFAIK. But you can introduce an interface that the supertype implements.

interface ITest {
}

class TestA implements ITest {
}

class TestB extends TestA {
}

public class Main {

  public static void main(String[] args) {
    List<ITest> testAList = Arrays.asList(new TestA(), new TestA());

    Class<ITest> clazz = ITest.class;
    List<ITest> testBList = testAList.stream()
        .map(clazz::cast)
        .collect(Collectors.toList());

    System.out.println(testBList.size());
    System.out.println(testAList);
  }
}

Or you can cast from subtype to supertype. Like this:

class TestA {
}

class TestB extends TestA {
}

public class Main {

  public static void main(String[] args) {
    var a = new TestA();
    var b = new TestB();
    System.out.println(((TestA) b));

    List<TestB> testBList = Arrays.asList(new TestB(), new TestB());

    Class<TestA> clazz = TestA.class;
    List<TestA> testAList = testBList.stream()
        .map(clazz::cast)
        .collect(Collectors.toList());

    System.out.println(testAList.size());
    System.out.println(testAList);
  }
}

FYI my original answer pointed on a possibility of using it with generics. In fact I took it from my Hibernate DAO code.

Kitti answered 27/3, 2018 at 7:57 Comment(1)
I'm sorry but this is very hackyProtamine
E
1

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.

Elbrus answered 18/3, 2020 at 9:3 Comment(0)
S
0

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.

Swiss answered 8/9, 2022 at 9:16 Comment(1)
I added an answer close to yours, but I believe that creating a list with null would not be ideal, so I filtered it before using isInstance on the output class and for the valid values I cast the mapInca
I
0

I had to do this implementation in a method of the company's system:

interface MyInterface{}
Class MyClass implements MyInterface{}

The method receives an interface list, that is, I already have an interface list instantiated

private get(List<MyInterface> interface) {
   List<MyClass> myClasses = interface.stream()
                 .filter(MyClass.class::isInstance)
                 .map(MyClass.class::cast)
                 .collect(Collectors.toList());
}
Inca answered 28/10, 2022 at 3:18 Comment(0)
L
-1

This should would work

List<TestA> testAList = new ArrayList<>();
List<TestB> testBList = new ArrayList<>()
testAList.addAll(new ArrayList<>(testBList));
Lighthearted answered 14/7, 2017 at 16:6 Comment(1)
This makes an unnecessary copy.Moses

© 2022 - 2024 — McMap. All rights reserved.