Type Casting List<ChildClass> to List<BaseClass>

Mukul Chandel :

I have a class structure like this :

public class BaseClass {

}

public class ChildClass extends BaseClass {

}

Lets say I have a method getList that returns List<BaseClass> :

public List<BaseClass> getList() {
     List<BaseClass> b = new ArrayList<>();
     return b;
}

List<BaseClass> b = getList();

I want to make the above method getList() generic so that it can either return an object of type List<BaseClass> or List<ChildClass> based on some conditions and assign the returned value to an object of type List<BaseClass> only. The reason for this is that I am dealing with some legacy code where objects are of type List<BaseClass> and I want to avoid refactoring them to something like List<? extends BaseClass> as this would block existing operations like add() on these list objects.

I understand that List<BaseClass> and List<ChildClass> are not covarient.

However, using the below techniques I am able to cast List<ChildClass> to List<BaseClass> and I am confused that how it is happening behind the scenes and whether it is right to do so or not.

Using explicit casting :

List<BaseClass> b = new ArrayList<>();
List<ChildClass> c = new ArrayList<>();
b = c; // gives compilation error
b = (List<BaseClass>) c; // gives compilation error
b = (List<BaseClass>)(List<?>) c; // this works

Using Generics :


public List<ChildClass> getChildClassList() {
   // code to fetch/generate list and return
}

public List<BaseClass> getBaseClassList() {
   // code to fetch/generate list and return
}

public static <T extends BaseClass> List<T> getList() {
    if(something) {
        List<ChildClass> c = getChildClassList();
        return (List<T>) c; // this cast is required here.
    } else {
        List<BaseClass> b = getBaseClassList();
        return (List<T>) b;
    }
}

List<BaseClass> = getList(); // This works perfectly fine but,
                             // I dont understand how casting is happening here.
                             // Also with cast to List<T> inside getList how is the final return type determined?

Using wildcards:


public List<ChildClass> getChildClassList() {
   // code to fetch/generate list and return
}

public List<BaseClass> getBaseClassList() {
   // code to fetch/generate list and return
}

public List<? extends BaseClass> getList() {
    if(something) {
        List<Child> c = getChildClassList();
        return c;
    } else {
        List<Base> b = getBaseClassList();
        return b;
    }
}

List<BaseClass> = getList(); // Gives compilation error saying incompatible types

// Casting this, however, works fine
List<BaseClass> = (List<BaseClass>) getList();

These feel a bit like a hack to me probably because Java does not support doing a direct cast. I am afraid there is a catch here which I might be missing.

Although, these work fine for my use case, is there any implication with this approach that I should worry about?

Is it safe to write such code?

Is it a bad way of writing code? If yes, then how this should be handled?

Andy Turner :

is there any implication with this approach that I should worry about?

With the generic case, yes: you can call List<ChildClass> list = getList();, and end up with a list full of things that aren't instances of ChildClass.

Fundamentally, you can't do anything that results in a different type being safely returned from the method. The best you can do is the wildcard case:

List<? extends BaseClass> list = getList();

because everything in that list will either be a BaseClass (of some flavour), or null.

You can then safely convert this to a List<BaseClass> by copying it:

List<BaseClass> list2 = new ArrayList<>(list);

Note that if you were using something like Guava's ImmutableList, this "copy" could be free:

ImmutableList<BaseClass> list2 = ImmutableList.copyOf(list);

Despite the name, copyOf returns the parameter, cast, if that is an instance of ImmutableList, because this is a safe covariant cast.


In terms of converting list to a ChildClass: there's not much you can do at compile time. You have to turn this into a runtime check:

// Throw an exception or something if this is false.
boolean allAreChildClass = list.stream().allMatch(e -> e == null || e instanceof ChildClass);

And then make a copy of the list, performing the casting.

List<ChildClass> list3 =
    list.stream().map(ChildClass.class::cast).collect(Collectors.toList());

Or, if you can be certain that list isn't going to be used anywhere else, you can simply cast:

List<ChildClass> list3 = (List<ChildClass>) (List<?>) list;

but you have to be really sure that list is either not used elsewhere, or otherwise not going to be modified by anything other than the casting code.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=310307&siteId=1