6

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
lannyf
  • 9,865
  • 12
  • 70
  • 152

2 Answers2

3

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 
Valentin Ruano
  • 2,726
  • 19
  • 29
  • @Izruo meant to write `ChildClassA` thanks for spotting it. – Valentin Ruano May 23 '18 at 19:49
  • thanks! I updated the question that here the List is meant to be read only, no adding/deleting on it. If it is read only is there any other concerns? – lannyf May 23 '18 at 20:34
  • @lannyf in that case the solution is trivial, don't cast it further `List extends IData>` is good enough. What is stopping you from using wild char typed list variable? – Valentin Ruano May 23 '18 at 20:38
  • you're right. It is just the part holding the `List extends IData> aList` is in a center place and it may be assigned a `List` or `List` at runtime. And it will be used for calling other library code which takes only List. that why need to find a way to assign List extends T> to List. again the List will be read only. – lannyf May 23 '18 at 20:46
  • @lannyf it seems that the library restriction on accepting `List` only should be part of your question. I think the most straight forward solution is just to copy the list unless there is a performance issue. I wouldn't worry about memory or CPU unless is a real issue (avoid premature optimization). If you would insist in avoiding the copy and since the library is not meant to alter the list you code provide it a safe proxy to the actual list using `Collections.unmodifiableList(aList)`... actually the latter is the best option as it turns out it will return a `List`!!! – Valentin Ruano May 23 '18 at 21:05
  • @ValentinRuano Hehe, almost simultaneously with me. – Izruo May 23 '18 at 21:10
  • the Collections.unmodifiableList(aList) is best for this case, thx! – lannyf May 23 '18 at 22:39
3

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);
Izruo
  • 2,246
  • 1
  • 11
  • 23
  • @lzruo, thanks for explaining. the use case here is a little bit different (I will update the question), the output List is kinda of immutable, and the user of it will only iterate and get the IData and figure out what the real data it is. So the here the List extends IData> is just a reference to a instance of List (or List, so cast the List extends IData> to List does not change anything, more important is the output of this cast List is read only. – lannyf May 23 '18 at 20:27
  • @lannyf I took me a while, but now I got a pretty handy solution. – Izruo May 23 '18 at 21:09