Most efficient way to cast List<SubClass> to List<BaseClass>
JavaInheritanceCastingJava Problem Overview
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!
Java Solutions
Solution 1 - Java
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.
Solution 2 - Java
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.)
Solution 3 - Java
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
Solution 4 - Java
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.
Solution 5 - Java
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);
Solution 6 - Java
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 ArrayList
s 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.)
Solution 7 - Java
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());
Solution 8 - Java
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 D
s, which is memory corruption: attempting to iterate all the D
s 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.
Solution 9 - Java
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..
Solution 10 - Java
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;
}
}