upcast from List<subclass> to List<superclass> via List<?> - java

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.

No, this isn't generally type-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.
If the list implementation you're using is unmodifiable, then it won't actually cause problems - but I'd still personally avoid it where possible.

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.

Related

Java generics: what is the difference between <?> and skipping it?

Maybe it is silly question: Let say I have
abstract class A<T> {
List<Wrapper<T>> doStuff()
}
And I have class B extends A<String> and class C extends A<Integer>
Now I want to have:
List<A> aces = list with instances of B and C;
List<Wrapper> wrapperedItems = flattened list of lists returned from doStuff() on all items in aces
At this point I don't care what type is within Wrapper.
And my question is: shall I use somewhere <?> or can I skip it? What is the difference?
If you use List<Wrapper<?>>, you will be able to get objects out of the list, but you will not be able to add new items. The wildcard essentially correcponds to a type that is different from any other type, including other wildcards.
If you use List<Wrapper>, you will be able to both get list items and add new ones, but the onus is now up to you to guard for improper type casts.

Why generic type is not applicable for argument extends super class for both?

Here is the problem that I have been being tried to find the solution.
We have two class definitions. One of two extends other one.
class T{}
class TT extends T{}
The requirement is that there should be a list keeps object extends T
List<? extends T> list = new ArrayList<>();
But the problem occures when I try to put a TT object ( barely seems it is a subclass of T )
into the list.
list.add(new TT());
Compilation Error Message
The method add(capture#2-of ? extends Cell) in the type List is not applicable for the arguments (Cell)
You can create a List<T> list = new ArrayList<T>(); directly, this can allow all subtypes of T into the list. This is actually little difficult to understand. when you declare it as
List<? extends T> list = ...
It means that it can allow any unknown subtypes of T into the list. But, from that declaration we cannot ensure which is the exact sub-type of T. so, we can only add null into it
List<? extends T> indicates that anything can comes out of it can be cast to T, so the true list could be any of the following:
List<T>
List<T2>
List<TT>
etc
You can see that even a new T cannot safely be added to such a collection because it could be a List<T2> which T cannot be put into. As such, such List cannot have non null entries added to them.
In this case you may simply want List<T>
So why would you ever use this?!
This contravariance can be useful for method parameters or returns, in which a collection will be read, rather than added to. A use for this could be to create a method that accepts any collection that holds items that are T, or extend T.
public static void processList(Collection<? extends Vector3d> list){
for(Vector3d vector:list){
//do something
}
}
This method could accept any collection of objects that extends Vector3d, so ArrayList<MyExtendedVector3d> would be acceptable.
Equally a method could return such a collection. An example of a use case is described in Returning a Collection<ChildType> from a method that specifies that it returns Collection<ParentType>.
The requirement is that there should be a list keeps object extends T
If you just want a List where you can store objects of any class that extend from T, then just create a List like this:
List<T> list = new ArrayList<T>();
The way you've created a list currently, will not allow you to add anything except null to it.
There are boundary rules defined for Java Generics when using WildCards
**extends Wildcard Boundary**
List means a List of objects that are instances of the class T, or subclasses of T (e.g. TT). This means a Read is fine , but insertion would fail as you dont know whether the class is Typed to T
**super Wildcard Boundary**
When you know that the list is typed to either T, or a superclass of T, it is safe to insert instances of T or subclasses of T (e.g.TT ) into the list.
In your example , you should use "super"
An addition to the other answers posted here, I would simply add that I only use wild cards for method parameters and return types. They're intended for method signatures, not implementations. When I put a wildcard into a variable declaration, I always get into trouble.

Why is casting from List<A> to List<B> not recommended?

I learned that if I have two classes:
public class A {}
public class B extends A {}
I can cast from a List<A> to List<B> by doing this:
List<B> list = (List<B>)(List<?>) collectionOfListA;
This would generate a warning, but it works. However, I read that this is not recommended. Is it true in that case?
If I know it's returning me a List<B>, why making this cast is such a bad practice?
Please note that I want to know why, in this case, this is a bad practice, not why there is a warning. And "it's a bad practice because it generates a warning" is not a good answer.
An apple is a fruit, but a list of apples is-not a list of fruit.
If it was, you could put a banana in a list of apples
Casting the list directly is bad because it subverts the Java type system. The cast will always succeed, but the program may fail later with a ClassCastException when you go to retrieve items, at which point the source of the error may be unclear.
In general you want your program to fail as close to the source of the error as possible. If you cast the list, it may be passed around for a while before someone actually tries to access elements and by the time it throws a ClassCastException it might be really hard to track it back to that cast (at the very least hard-er than if the program failed at the cast).
From your comments it seems like you're sure that everything in listA is actually a B in which case I would recommend:
List<B> listB = new ArrayList(listA.size());
for (A a : listA) {
if (a == null || a instanceof B) {
listB.add((B) a);
} else {
//Either ignore or throw exception
}
}
For this to make sense, you have to understand that List<A> and List<B> are two different types, not hierarchically related, even if A and B are hierarchically related. Even if B was a subclass of A, casting a List<B> to a List<A> is bad because it could allow you to add to that list instances of A (but not B) and the compiler would happily do that, even though it wouldn't agree with the actual (runtime) type of the collection.
I didn't know that you can circumvent this in Java, but if you can it's only because of type erasure: because Java creates one class such as List<Object> and, for different "implementations" of that generic class, it merely adds casts in your code before compiling it. This is contrast to (say) C#, which actually uses the List<T> only as a "compiled" template (called "generic definition"), to create the concrete types on demand.
Generics work bit differently, so list< B > is not of type list< A > though B is a type of A.

Collection<Sub> casted as SuperCollection<Super>

In java one can cast instance of a class B into an instance of class A provided that B extends A.
Can this be done also for type-parametrized classes? For instance, for B extending A, I'd like to do the following:
List<A> l = new ArrayList<B>();
I think this should be legal, but the compiler doesn't agree with me on this point, so I tricked it with the following hack:
List<A> l = (List<A>)(List) new ArrayList<B>();
…but I think that I'll be hit by a velociraptor. Is there an elegant way of doing this?
I think this should be legal, but the compiler doesn't agree with me on this point
Generally, when humans and compilers disagree, it is safe to take the compiler's side. Instead of tricking the compiler, you should first understand why it does not allow the conversion.
Consider this: you make a List<B>, like this:
List<B> listB = new ArrayList<B>();
Everyone is happy so far. Now let's do what you think is correct:
// This does not compile, but let's pretend that it does
List<A> listA = (List<A>)listB;
At this point, we have listA that is entirely ours! Let's play with it: let's say we have another class C extending A. Can we do this?
listA.add(new C()); // Why not? The compiler should allow it!
But wait, do you see what we have done? Our listB contains a C now! This is why the compiler does not allow the conversion; this is also why your code is not safe in cases when you attempt to add anything to the list.
To work around this issue, you can use wildcards: List<? extends A>. However, this will limit your lists to read-only access. If you must allow writing (and you know that you are going to set only the right stuff) use the non-generic List instead.
List<? extends A> l = new ArrayList<B>();
You should read about generics in java here
Two ways to do it.......
- This way of handling the Collections are done, cause Array is checked during compile and as well as runtime, but Collections are checked only during Compile time.
- So there should NOT be an accidental addition of another type of object in to another type of Collection, ie Dog Object into a Cat Collection.
1.
public <T extends A> void go(ArrayList<T> aList){
}
2.
public void go(ArrayList<? extends A> aList){
}
In a non generic context, if B "is a" A, then B can be substituted for A, but A cannot be substituted for B.
With generics, List and List are both "is a" Collection<E>; that is, List is not a subclass of List, and therefore, you cannot substitute List for List.
The specific content on generics you're looking for can be found here, copied below for your convenience.
Given two concrete types A and B (for example, Number and Integer), MyClass has no relationship to MyClass, regardless of whether or not A and B are related. The common parent of MyClass and MyClass is Object.

Why is List<Number> not a sub-type of List<Object>?

public void wahey(List<Object> list) {}
wahey(new LinkedList<Number>());
The call to the method will not type-check. I can't even cast the parameter as follows:
wahey((List<Object>) new LinkedList<Number>());
From my research, I have gathered that the reason for not allowing this is type-safety. If we were allowed to do the above, then we could have the following:
List<Double> ld;
wahey(ld);
Inside the method wahey, we could add some Strings to the input list (as the parameter maintains a List<Object> reference). Now, after the method call, ld refers to a list with a type List<Double>, but the actual list contains some String objects!
This seems different to the normal way Java works without generics. For instance:
Object o;
Double d;
String s;
o = s;
d = (Double) o;
What we are doing here is essentially the same thing, except this will pass compile-time checks and only fail at run-time. The version with Lists won't compile.
This leads me to believe this is purely a design decision with regards to the type restrictions on generics. I was hoping to get some comments on this decision?
What you are doing in the "without generics" example is a cast, which makes it clear that you are doing something type-unsafe. The equivalent with generics would be:
Object o;
List<Double> d;
String s;
o = s;
d.add((Double) o);
Which behaves the same way (compiles, but fails at runtime). The reason for not allowing the behavior you're asking about is because it would allow implicit type-unsafe actions, which are much harder to notice in code. For example:
public void Foo(List<Object> list, Object obj) {
list.add(obj);
}
This looks perfectly fine and type-safe until you call it like this:
List<Double> list_d;
String s;
Foo(list_d, s);
Which also looks type-safe, because you as the caller don't necessarily know what Foo is going to do with its parameters.
So in that case you have two seemingly type-safe bits of code, which together end up being type-unsafe. That's bad, because it's hidden and therefore hard to avoid and harder to debug.
Consider if it was...
List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException
You would be able to add anything of the parent type to the list, which may not be what it was formerly declared as, which as the above example demonstrates, causes all sorts of problems. Thus, it is not allowed.
They aren't subtypes of each other due how generics work. What you want is to declare your function like this:
public void wahey(List<?> list) {}
Then it will accept a List of anything that extends Object. You can also do:
public void wahey(List<? extends Number> list) {}
This will let you take in Lists of something that's a subclass of Number.
I'd recommend you pick up a copy of "Java Generics and Collections" by Maurice Naftalin & Philip Wadler.
There are essentially two dimensions of abstraction here, the list abstraction and the abstraction of its contents. It's perfectly fine to vary along the list abstraction - to say, for instance, that it's a LinkedList or an ArrayList - but it's not fine to further restrict the contents, to say: This (list which holds objects) is a (linked list which holds only numbers). Because any reference that knows it as a (list which holds objects) understands, by the contract of its type, that it can hold any object.
This is quite different from what you have done in the non-generics example code, where you've said: treat this String as if it were a Double. You are instead trying to say: treat this (list which holds only numbers) as a (list which holds anything). And it doesn't, and the compiler can detect it, so it doesn't let you get away with it.
"What we are doing here is essentially
the same thing, except this will pass
compile-time checks and only fail at
run-time. The version with Lists won't
compile."
What you're observing makes perfect sense when you consider that the main purpose of Java generics is to get type incompatibilities to fail at compile time instead of run time.
From java.sun.com
Generics provides a way for you to
communicate the type of a collection
to the compiler, so that it can be
checked. Once the compiler knows the
element type of the collection, the
compiler can check that you have used
the collection consistently and can
insert the correct casts on values
being taken out of the collection.
In Java, List<S> is not a subtype of List<T> when S is a subtype of T. This rule provides type safety.
Let's say we allow a List<String> to be a subtype of List<Object>. Consider the following example:
public void foo(List<Object> objects) {
objects.add(new Integer(42));
}
List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);
So, forcing this provides type safety but it also has a drawback, it's less flexible. Consider the following example:
class MyCollection<T> {
public void addAll(Collection<T> otherCollection) {
...
}
}
Here you have a collection of T's, you want to add all items from another collection. You can't call this method with a Collection<S> for an S subtype of T. Ideally, this is ok because you are only adding elements into your collection, you are not modifying the parameter collection.
To fix this, Java provides what they call "wildcards". Wildcards are a way of providing covariance/contravariance. Now consider the following using wildcards:
class MyCollection<T> {
// Now we allow all types S that are a subtype of T
public void addAll(Collection<? extends T> otherCollection) {
...
otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T)
}
}
Now, with wildcards we allow covariance in the type T and we block operations that are not type safe (for example adding an item into the collection). This way we get flexibility and type safety.

Categories