views:

65

answers:

2

I have a class A and a class B extends A

In another class C I have a field

private List<B> listB;

Now, for some unusual reason, I have to implement this method in C

public List<A> getList();

I tried to do so by forcing an upcast of listB field to List<A> via a List<?> cast:

public List<A> getList(){
    return (List<A>)(List<?>)listB;
}

Clients should do

List<A> list = getList();
for(A a:list){
    //do something with a
}

I did some test and it seems work correctly, but honestly I am not sure of the all possible implications.

Is this solution correct? And Is it the best solution?

Thanks for your answers.

+7  A: 

No, this isn't safe. The client shouldn't be able to do

List<A> list = getList();

because otherwise they could write

list.add(new C()); // Where C extends A

Then the original code which knows about the list as a List<B> will have problems when it tries to use it, assuming that every element is compatible with B.

You could either wrap the original list to make it read-only, effectively - or make getList return a List<? extends A>, which means that clients won't be able to add items to it anyway.

Jon Skeet
The latter solution seems best here. If an interface specified that it returned `List<? extends A>` then the concrete class could actually refine that to `List<B>`. Alternatively, C could have a type parameter `class C<T extends A>` with `List<T> getList()`.
Mark Peters
By the way, wrapping it is as easy as `return Collections.<A>unmodifiableList(listB);` (I'm not sure whether the explicit type will be needed in your case). The library was smartly built to make this easy on you.
Mark Peters
Even if there wasn't the problem of the element types, allowing modification of an internal collection like this is probably not what you want. I think copying is probably better than wrapping as unmodifiable, so that when the real collection is modified client code still holding a reference is not broken.
Tom Hawtin - tackline
+1  A: 

The problem with this is that clients can, unwittingly, insert A objects in what is actually a list of more specific B objects only:

c.getList().add(new A());

This will cause all kinds of breakage when your code tries to take an object from the list assuming that it's a B, but it isn't.

If your only goal is to let the client iterate over the list, it is better to hand out an Iterable<A> instead:

public Iterable<A> getAs() { return this.theListOfAs; }

Through this Iterable, one can only inspect and remove elements, but not add them.

If you want to disable removal as well, wrap the List's Iterable in your own implementation, throwing UnsupportedOperationException when remove() is called.

Thomas