Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to assign List<? extends BaseClass> to List<BaseClass>

having

class BaseClass implements IData ();
class ChildClassA() extends BaseClass;
class ChildClassB() extends BaseClass;

since cannot do

List<BaseClass> aList = new ArrayList<ChildClassA>()

so there is a

List<? extends IData> aList 
for pointint to either 
    ArrayList<ChildClassA>(),  
or 
    ArrayList<ChildClassB>()

the aList is built by other routing at runtime, and that part of code has a function to take a List<IData> from the the aList

the question is if the List<? extends IData> aList is point to ArrayList<ChildClassA>() or ArrayList<ChildClassB>(),

can it do ListData<IData> outputList = (List<IData>) aList? something like below:

(seems it is working, but not sure if there is better way to assign the generics array other than casting.)

Edit: the output of the List<IData> outputList is for read only use (immutable), no insert/delete on it, it will only iterate the IData to react on what the IData really is.

List<? extends IData> aList =  new ArrayList<ChildClassA>();
ListData<IData> outputList = (List<IData>)aList

List<? extends IData> aList =  new ArrayList<ChildClassB>();
ListData<IData> outputList = (List<IData>)aList
like image 967
lannyf Avatar asked May 23 '18 19:05

lannyf


2 Answers

tl;dr Use Collections#unmodifiableList:

List<IData> outputList = Collections.unmodifiableList(aList);

For more information on this topic, you might want to get familiar with the PECS principle.


It's not possible, because the two types are incompatible.

A List<BaseClass> is just what it is declared, a list of BaseClass objects. More precisely, it makes two guarantees:

  • objects retrieved from it are assignable to BaseClass
  • every object that is assignable to BaseClass can be added to it (and no other)

A List<? extends BaseClass> is a more loose declaration. Precisely, it simply does not make the second guarantee. However, not only the guarantee is gone, but it is now impossible to add items to it, since the exact generic type of the list is undefined. It might even change for the same list declaration (not the same list object) at runtime.

As a consequence, a List<? extends BaseClass> is not assignable to a List<BaseClass>, since the latter makes a guarantee the first is unable to fulfill.


Practically speaking, consider the following method:

public List<BaseClass> makeList() {
    // TODO implement method
    return null;
}

If someone implements this method, returning a List<? extends BaseClass>, a client using this method would be unable to add items to it, although its declaration indicates otherwise.

Because of that, such an assignment results in a compilation error.

To fix the example problem the loose declaration can be added to the method:

public List<? extends BaseClass> makeList() {
    // TODO implement method
    return null;
}

This will signal every client, that the list returned from this method is not meant for adding items to it.


Now let's get back to your use case. In my opinion the most appropriate fix is to the rephrase the function that

take[s] a List from the the aList.

As it seems it is currently declared as

public void useList(List<BaseClass> list);

but since it does not add items to the list, it should be declared as

public void useList(List<? extends BaseClass> list);

However, if that method is part of a currently unchangeable API, you can still do:

List<? extends BaseClass> list;
....
List<BaseClass> tmp = Collections.unmodifiableList(list);
useList(tmp);
like image 107
Izruo Avatar answered Nov 02 '22 13:11

Izruo


No, it is unsafe.

After that cast it would be legit to add to the list that is supposed to contain only ChildClassA typed elements, element of the other child type ChildClassB type and vice-versa.

We can simply your code a bit to make it more obvious why this should not be allowed:

List<ChildClassA> aList =  new ArrayList<ChildClassA>();
aList.add(a1);
aList.add(a2);
//...
List<IData> iDataList = (List<IData>) aList;
iDataList.add(b1);
iDataList.add(b2);
//...
for (ChildClassA a : aList) {
   // a some point a is going to be assigned b1 or b2 and they results in cast
   // exception.
}

Note that iDataList makes reference to the very same list object as aList. If that cast was allowed then you would be able to add elements to aList that are not ChildClassA instances.

The best solution is on the details.

If the problem is that a third-party library requires a List<IData> typed reference and as long as it is only for reading you can use a unmodifiable proxy as returned by Collections.unmodifiableList:

import java.util.Collections;
//...
final List<ChildClassA> aList = new ArrayList<>();
//... add stuff to aList
final List<IData> readOnlyIDataList = Collections.unmodifiableList(aList); 
//... read only access operations readOnlyIDataList 
like image 3
Valentin Ruano Avatar answered Nov 02 '22 15:11

Valentin Ruano