150

I have a List<SubClass> that I want to treat as a List<BaseClass>. It seems like it shouldn't be a problem since casting a SubClass to a BaseClass is a snap, but my compiler complains that the cast is impossible.

So, what's the best way to get a reference to the same objects as a List<BaseClass>?

Right now I'm just making a new list and copying the old list:

List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)

But as I understand it that has to create an entirely new list. I'd like a reference to the original list, if possible!

Riley Lark
  • 20,324
  • 14
  • 78
  • 124
  • 3
    the answer you have here: http://stackoverflow.com/questions/662508/how-can-i-cast-a-list-using-generics-in-java – lukastymo Feb 22 '11 at 18:18

10 Answers10

192

The syntax for this sort of assignment uses a wildcard:

List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;

It's important to realize that a List<SubClass> is not interchangeable with a List<BaseClass>. Code that retains a reference to the List<SubClass> will expect every item in the list to be a SubClass. If another part of code referred to the list as a List<BaseClass>, the compiler will not complain when a BaseClass or AnotherSubClass is inserted. But this will cause a ClassCastException for the first piece of code, which assumes that everything in the list is a SubClass.

Generic collections do not behave the same as arrays in Java. Arrays are covariant; that is, it is allowed to do this:

SubClass[] subs = ...;
BaseClass[] bases = subs;

This is allowed, because the array "knows" the type of its elements. If someone attempts to store something that isn't an instance of SubClass in the array (via the bases reference), a runtime exception will be thrown.

Generic collections do not "know" their component type; this information is "erased" at compile time. Therefore, they can't raise a runtime exception when an invalid store occurs. Instead, a ClassCastException will be raised at some far distant, hard-to-associate point in code when a value is read from the collection. If you heed compiler warnings about type safety, you will avoid these type errors at runtime.

erickson
  • 257,800
  • 54
  • 385
  • 479
  • Should note that with Arrays, instead of ClassCastException when retrieving an object that is not of type SubClass (or derived), you will get an ArrayStoreException when inserting. – Axel Feb 22 '11 at 19:56
  • Thanks especially for the explanation of why the two lists cannot be considered the same. – Riley Lark Feb 22 '11 at 20:05
  • The Java type system pretends that arrays are covariant, but they aren't really substitutable, proven by the ArrayStoreException. – Paŭlo Ebermann Mar 08 '16 at 15:28
43

erickson already explained why you can't do this, but here some solutions:

If you only want to take elements out of your base list, in principle your receiving method should be declared as taking a List<? extends BaseClass>.

But if it isn't and you can't change it, you can wrap the list with Collections.unmodifiableList(...), which allows returning a List of a supertype of the argument's parameter. (It avoids the typesafety problem by throwing UnsupportedOperationException on insertion tries.)

Paŭlo Ebermann
  • 71,139
  • 18
  • 140
  • 206
14

As @erickson explained, if you really want a reference to the original list, make sure no code inserts anything to that list if you ever want to use it again under its original declaration. The simplest way to get it is to just cast it to a plain old ungeneric list:

List<BaseClass> baseList = (List)new ArrayList<SubClass>();

I would not recommend this if you don't know what happens to the List and would suggest you change whatever code needs the List to accept the List you have.

hd42
  • 1,719
  • 17
  • 29
4

I missed the answer where you just cast the original list, using a double cast. So here it is for completeness:

List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;

Nothing is copied, and the operation is fast. However, you are tricking the compiler here so you must make absolutely sure to not modify the list in such a way that the subList starts containing items of a different sub type. When dealing with immutable lists this is usually not an issue.

john16384
  • 7,284
  • 2
  • 25
  • 40
3

Below is a useful snippet that works. It constructs a new array list but JVM object creation over head is in-significant.

I saw other answers are un-necessarily complicated.

List<BaseClass> baselist = new ArrayList<>(sublist);
sigirisetti
  • 299
  • 2
  • 8
  • 1
    Thank you for this code snippet, which may provide some immediate help. A proper explanation [would greatly improve](//meta.stackexchange.com/q/114762) its educational value by showing *why* this is a good solution to the problem, and would make it more useful to future readers with similar, but not identical, questions. Please [edit] your answer to add explanation, and give an indication of what limitations and assumptions apply. In particular, how does this differ from the code in the question that makes a new list? – Toby Speight Jul 19 '17 at 12:04
  • The overhead is not insignifcant, as it also has to (shallow) copy every reference. As such it scales with the size of the list, so it is an O(n) operation. – john16384 Feb 22 '20 at 18:44
3

Back in 2017 I posted another answer to this question which is an okay answer, but yesterday I discovered a better way of doing this.

The best way I have found is as follows:

(Note: S = superclass, D = descendant)

List<S> supers = List.copyOf( descendants );

This has the following advantages:

  • It is a neat one-liner.
  • It does not require the ugly List<? extends S> construct.
  • 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 original List<D> is an ArrayList<D> then a copy of the ArrayList must be made. If you were to cast the ArrayList<D> as List<S>, you would be opening up the possibility of inadvertently adding a S into that List<S>, which would then cause your original ArrayList<D> to contain a S among the Ds, which is memory corruption: attempting to iterate all the Ds in the original ArrayList<D> would throw a ClassCastException.

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


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

Mike Nakis
  • 50,434
  • 8
  • 92
  • 124
1

What you are trying to do is very useful and I find that I need to do it very often in code that I write.

Most java programmers would not think twice before implementing getConvertedList() by allocating a new ArrayList<>(), populating it with all the elements from the original list, and returning it. I enjoy entertaining the thought that about 30% of all clock cycles consumed by java code running on millions of machines all over the planet is doing nothing but creating such useless copies of ArrayLists which are garbage-collected microseconds after their creation.

The solution to this problem is, of course, down-casting the collection. Here is how to do it:

static <T,U extends T> List<T> downCastList( List<U> list )
{
    @SuppressWarnings( "unchecked" )
    List<T> result = (List<T>)list;
    return result;
}

The intermediate result variable is necessary due to a perversion of the java language:

  • return (List<T>)list; would produce an "unchecked cast" warning;

  • in order to suppress the warning, you need a @SuppressWarnings( "unchecked" ) annotation, and good programming practices mandate that it must be placed in the smallest possible scope, which is the individual statement, not the method.

  • in java an annotation cannot be placed on just any line of code; it must be placed on some entity, like a class, a field, a method, etc.

  • luckily, one such annotatable entity is a local variable declaration.

  • therefore, we have to declare a new local variable to use the @SuppressWarnings annotation on it, and then return the value of that variable. (It should not matter anyway, because it should be optimized away by the JIT.)

Mike Nakis
  • 50,434
  • 8
  • 92
  • 124
1

How about casting all elements. It will create a new list, but will reference the original objects from the old list.

List<BaseClass> convertedList = listOfSubClass.stream().map(x -> (BaseClass)x).collect(Collectors.toList());
drordk
  • 351
  • 2
  • 8
-1

Something like this should work too:

public static <T> List<T> convertListWithExtendableClasses(
    final List< ? extends T> originalList,
    final Class<T> clazz )
{
    final List<T> newList = new ArrayList<>();
    for ( final T item : originalList )
    {
        newList.add( item );
    }// for
    return newList;
}

Don't really know why clazz is needed in Eclipse..

olivervbk
  • 282
  • 2
  • 11
-1

This is the complete working piece of code using Generics, to cast sub class list to super class.

Caller method that passes subclass type

List<SubClass> subClassParam = new ArrayList<>();    
getElementDefinitionStatuses(subClassParam);

Callee method that accepts any subtype of the base class

private static List<String> getElementDefinitionStatuses(List<? extends 
    BaseClass> baseClassVariableName) {
     return allElementStatuses;
    }
}
kanaparthikiran
  • 507
  • 12
  • 14