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

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!


Solution 1:

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:

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:

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:

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.