Most efficient way to cast List<SubClass> to List<BaseClass>

JavaInheritanceCasting

Java 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 and would suggest you change whatever code needs the List to accept the List you have.

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

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

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRiley LarkView Question on Stackoverflow
Solution 1 - JavaericksonView Answer on Stackoverflow
Solution 2 - JavaPaŭlo EbermannView Answer on Stackoverflow
Solution 3 - Javahd42View Answer on Stackoverflow
Solution 4 - Javajohn16384View Answer on Stackoverflow
Solution 5 - JavasigirisettiView Answer on Stackoverflow
Solution 6 - JavaMike NakisView Answer on Stackoverflow
Solution 7 - JavadrordkView Answer on Stackoverflow
Solution 8 - JavaMike NakisView Answer on Stackoverflow
Solution 9 - JavaolivervbkView Answer on Stackoverflow
Solution 10 - JavakanaparthikiranView Answer on Stackoverflow