Passing List<Subclass> to method expecting List<SuperClass> - java

I have a method that is expecting a List<SuperClass> as argument:
public void myMethod(List<SuperClass> list) {}
I want to call that method with a List<Subclass> something like:
List<SubClass> subList = new ArrayList<>();
// ...
myMethod(subList); // Got an argument mismatch error on this line.
Shouldn't I be able to do this when SubClass extends SuperClass?

No, generics don't work like that. What you could do is define your method as MyMethod(List<? extends SuperClass> list) (by convention it should be named myMethod(...) btw).
The problem with List<SuperClass> vs. List<SubClass> is that you could add new elements to such lists whereas the compiler wouldn't allow you to add something to a List<? extends SuperClass> - and this has a reason:
Consider the following:
class A {}
class B extends A {}
class C extends A {}
If you now have a List<A> you could add instances of A, B and C. However, if you pass a List<B> to a method as a List<? extends A> parameter, the compiler doesn't know whether it is allowed to add instances of A or C to that list (it wouldn't be allowed, but in case you'd pass a List<A> it would be). Thus the compiler restricts you not to do so.
Defining a parameter as List<A> tells the compiler that is is ok to put instances of all three classes to that list. Now if you would be allowed to pass a List<B> as such a parameter you could end up with a List<B> that contains instances of A and/or C. And this is clearly not what you want and could result in runtime bugs that should be prevented at compile time already - by using generics. That's why your approach doesn't work.

Worth noting, you can also create the list of your superClass from a list of subClass as such:
myMethod(new ArrayList<SuperClass>(list));

Related

How may I add `MyClass` to `List<? extends MyClass>` [duplicate]

This question already has answers here:
What is PECS (Producer Extends Consumer Super)?
(16 answers)
Method in the type Map<String,capture#1-of ? extends Object> is not applicable
(1 answer)
Closed 4 years ago.
I have a Java Class that contains subitems of Objects that extend MyClass.
class MyClass {
List<? extends MyClass> items;
[...]
For some reason I can't insert MyClass items to this list. I don't Understand why, and how I may bypass this issue. For example:
void foo(){
items = new LinkedList<MyClass>(); // This is OK
items.add(new MyClass()); // Not allowed... why?
}
The compiler says "The method add(capture#1-of ? extends MyClass) in the type List is not applicable for the arguments (MyClass)"
I really don't understand how to bypass this issue, and why the compiler should accept only a type which necessarely extends MyClass.
Note: why am I in the need to use this approach? Because I need to allow extension of MyClass to have list of subitems.
List<? extends MyClass> items means the type parameter is unknown type which is assignable to MyClass.
For example, it could be a List of MySubClass:
public MySubClass extends MyClass{}
List<? extends MyClass> items = new ArrayList<MySubClass>();
Now consider you have MyAnotherSubClass which extends from MyClass too:
public MyAnotherSubClass extends MyClass{}
MyClass item = new MyAnotherSubClass(); // refer it using MyClass
Obviously, List<MySubClass> should not be allowed to contain MyAnotherSubClass:
items.add(item); // compile error
The declaration
List<? extends MyClass> items;
says that items is a List whose type parameter is not exactly known, but is either MyClass or a subclass.
Re-read that, carefully. It explains why it is not type-safe to add anything to such a List: its type parameter is unknown. If it happens to be MySubClass1, then adding a MyClass or a MySubClass2 is incorrect. If it happens to be MySubClass2, then adding a MySubClass1 is incorrect. There is no type at all that can safely be added.
If you want a List to which you can add objects of type MyClass and also objects of any subclass of MyClass, then you probably are looking simply for List<MyClass>.
Technically, a List<? super MyClass> would also serve that specific purpose, but you would have the opposite problem with that: it would not be type safe to assume the list elements to be any type more specific than Object.
Using extends you can only get from the collection. You cannot put into it. You can do that using super.
So, in your case, if you use - List<? super MyClass> items; you will not get any compilation/runtime error.
Though super allows to both get and put, the return type during getting is ? super T.

Type casting in Java parametrized lists

Setup
I have two classes:
class A {
}
class B extends A {
}
And a List of type B:
List<B> listOfB = new ArrayList<>();
Part 1: Objective
What I'm effectively trying to achieve is to convert the type of listOfB from B to A. Following the Java tutorial, I'm assuming the correct way to do this is something along the lines of:
List<? extends A> listOfB = new ArrayList<>();
listOfB.add(new B()); // <--- Error here
List<A> listOfA = (List<A>) listOfB;
However this does not allow me to add objects of class A or B to listOfB. The error I get is:
add (capture <? extends A>) in List cannot be applied to (B)
Thus I'm currently resorting to the following method (which works perfectly fine):
List<A> listOfA = new ArrayList<A>(listOfB);
Part 2: Efficiency
Apparently there are not-so-good-ways of doing this (link) and the recommended way in the Java tutorials (link).
So whats the correct way to achieve this with List<? extends A>?
And I'd like to know the performance loss/gain of above methods. Does the constructor iterate thought listOfB to do type conversion? If so, does this effect the performance as the list becomes larger (compared to direct type conversion in Part 1)?
When you write List <? extends A> the meaning is that some unknown type elements which extend A resides on the list and only elements of that type. You cannot make any assumptions about the concrete type of the elements, thus you cannot add a B to your list. You wont be able to add anything since the compiler does not know the concrete type.
When you define your List <B>, since Lists are covariants in java, it means that even if B extends A, List <B> doesnt extend List <A>! That makes sense: if you could do that, than look at this example:
String extends Object right?
Then you could do:
List <String> a = new List <>();
a.add ("bla");
List <Object> asObj = a;
asObj.add (1);
And than if you try to iterate over a you think you get a list of strings but youll get a ClassCastException!
So, the question is why not defining your list as List of As?
Casting doesnt involve constructor iteration. The compiler just replaces the type erasures with the concrete types before runtime.
The second one is the way to go, ArrayList can just do an array copy of the internal data.
The reason why adding a B to List<? extends A> doesn't work, is that ? could be a subtype of B or a sibling. So it is unsafe to add a B to it.
i.e.
class C extends A {}
...
List<? extends A> listOfC = new ArrayList<C>(); // perfectly valid
listOfC.add(new B()); // uh oh, adding B to list of C!...

If you're trying to have list with various child classes of a single parent class, how would you do it?

Should you use
List<ParentClass> foo = new ArrayList<ParentClass>
foo.add(ChildClassObject)
or
List<? extends ParentClass> bar - new ArrayList<ParentClass>
bar.add(ChildClassObject)
Also, could someone explain more to me the latter parameterization of List?
Use the first approach. The second doesn't allow you to add any element. This is because the second approach uses an unknown type that extends ParentClass and cannot assure that the elements to be stored are from this type.
More info:
Java Generic with ArrayList <? extends A> add element
List<? extends ParentClass> is a list of some specific subtype of ParentClass. Since, it's not know what exact specific subtype it is, the compiler won't let you add any object to it.
For starters, it's easy to mistake it with any subtype of ParentClass and wonder why they can't add the instances of the subtypes.
For instance, suppose you have a following class hierarchy -
class P { }
class A extends P { }
class B extends P { }
Now if you declare a a list as follows -
List<? extends P> l = ...;
it means l is a list of some specific subtype of P, which could be either A or B (or even P itself), but the compiler doesn't know exactly which one. So, you can't add anything to it, because the chance is that you could be adding an instance of B to a list of As creating some heap pollution.
You would declare such a list when you are only interested in reading the elements of list as P's instances.

<T extends AnInterface> vs <? extends AnInterface>

I am a little confused with something.
I have a class where its not a collection, but it does refer to generic objects:
public class XClass<E extends AnInterface>{
E instanceobject;
public void add(E toAdd){}
}
public interface AnInterface{}
public class A implements AnInterface{}
public class B implements AnInterface{}
I believe I read somewhere that <? extends AnInterface> is to be used (when declaring an instance of XClass) if you want multiple subtype-types in the generic object at the same time, whereas <T extends AnInterface> would only allow you to have a single type of subtype in the generic class at once?
However, I can just use:
XClass<AnInterface> xc = new XClass<AnInterface>();
A a = new A();
B b = new B();
xc.add(a);
xc.add(b);
and this way I can pass in multiple subtypes of Supertype to the generic class......
I am not seeing the purpose of using "?" and is there anything wrong with using the Interface as the generic parameter?
The reason why you can add objects of both type A and B is due to the fact that you parametized your XClass with the interface, so there is nothing wrong with adding two different classes that implement that interface.
If, on the other hand, you had defined XClass as:
XClass<A> xc = new XClass<A>();
then the expression xc.add(b); would give a compilation error, since all the objects added must have the same type as was declared, in this case, A.
If you declare you xc as, for instance:
XClass<? extends AnInterface> xc = new XClass<AnInterface>();
Then it's not legal anymore to add a or b, since the only thing we know is that xc is of some unknown but fixed subtype of AnInterface, and there is no way to know if that unknown type is A or B or anything else.
But let's say you're writing a method to accept a XClass type that you can iterate over the elements that were added before. Your only restriction (for the sake of the example), is that the items extend AnInterface, you don't care what the actual type is.
You can declare this method like:
public static void dummyMethod(XClass<? extends AnInterface> dummy){
//do stuff here, all the elements extend (implement in this case), AnInterface, go wild.
}
And now you can pass into this method anything like XClass<A>, XClass<B> or XClass<AnInterface>, and it will all be valid.
Keep in mind that you can't add to the object you pass, for the same reason above. We don't know what the unknown type is!
public static void dummyMethod(XClass<? extends AnInterface> dummy){
//do stuff here, all the elements extend (implement in this case), AnInterface, go wild.
dummy.add(new A()); //you can't do this, we have no idea what type ? stand for in this case
}
You can use E if you want to have an instance of XClass to use only one subclass of AnInterface and no other Classes implementing AnInterface that do not extend / implement E.
For example given
public class ClassOne implements AnInterface {} and public class ClassTwo implements AnInterface {}
If you were to use
public class XClass<E extends AnInterface> and <ClassOne>XClass xc = new <ClassOne>XClass() then you can only use an object of ClassOne in your add method not one of ClassTwo. Using ? would allow you to pass in any class implementing AnInterface, either ClassOne or ClassTwo.
Using Identifier E means "For this object I want to use type E and any subclasses", using ? means "I want to use any type that matches the the expression"
In your example you need type erasure in the method "add", so you should't use wildcards in your class.
Wildcards are only to be used when you do not need type erasure (i.e. you don't care about the type as long as it is a subclass of..) and also when you will need to subtype the generics itself.
The wildcard simply means that it will be some class that meets that criteria. So ? extends AnInterface means it will be one (and only one) class that extends AnInterface.
So it could be:
XClass<Impl1>
XClass<Impl2>
etc...
However, at runtime, you don't know what that class will be. For this reason calling methods which take the actual type as a parameter is inherently unsafe, since it's impossible for the compiler to know if the parameter is appropriate for the actual instantiated instance.
Take lists as an example. Something might be declared like this:
List<? extends Number> list = new ArrayList<Integer>();
What would happen if you try to do either of these:
list.add(new Double(0));
list.add((Number) new Long(1L));
It would not compile, because the generic parameter type is unknown at compile time. So the compiler can't tell if Double or Number would be appropriate to pass to the actual instance (in this case ArrayList<Integer>). This is when you get the infamous capture-of compile error.
This, however is permissible, since you know for certain at compile time that the list can take any instance of Number (which includes subclasses).
List<Number> list = new ArrayList<Number>();
list.add(new Double(0));
list.add((Number) new Long(1L));

Why A->B doesn't make List<A>->List<B>? Wouldn't that remove need for wildcards?

Disclaimer: I'm not a professional developer, and I'm not intending to become one. Reading book about Java, as I wanted to try Android programming, no previous Java experience whatsoever.
I'm reading this book - and I rather like it. I've read part of chapter about generic classes, got to the point where they mention wildcards, and got confused.
If B extends A:
List<B> is not a subtype of List<A> (as I understand it they're exactly the same)
List<? extends B> is a subtype of List<? extends A>
The latter allows for writing functions that accept arguments that are of generic type - for example List<? extends A>. Such function would accept an argument of either List<B> or List<A>.
Now, for my question:
Wouldn't it be simpler to implement generics in a manner similar to C++ (in a "template" flavour)? This would make List<B> and List<A> two separate types, that would be related in expected way. This would also allow to simply state in a function that you expect an argument to be of type List<A>, which would allow List<B> to fit there just fine.
I'm guessing there was more than "we hate C++, let's make things different" behind this :) It's also quite possible that I don't know something yet, that makes wildcards a fantastic and useful tool. What's your take on this?
Edit: if you're mentioning List<X> in your answer, remember to use backticks, to avoid <X> being interpreted as HTML tag.
There's a simple reason.
Suppose you have a variable of type List<A>. Suppose List<B> was indeed a subtype of List<A>.
That means that when this would be legal:
List<A> a_list;
a_list = new List<B>(); //allowed when List<B> is subtype of list<A>
a_list.add(new A()); // WOAH!
Where I say WOAH, the following happens: You add an item of type A to a_list. Since a_list was declared as List<A>, this should be legal. But wait: a_list is pointing to something of type List<B>.
So now we add something of type A to a list that should store only items of type B, and this is clearly not what we want, since A is not a subclass of B!
If List<B> was a subtype of List<A> the following code would be legal, as java does remove most of the Generic magic at compile time. (If that was a good decision or not is a different topic).
public void addNewAToList(List<A> list) {
list.add(new A());
}
public static void main(String[] args) {
List<B> listB = new LinkedList<B>();
addNewAToList(listB); // <--- compiler error
for (B b : listB) { // <--- otherwise: there is an A in the list.
System.out.println(b);
}
}
The problem is that if you have class C extends A and are given a List<A>, you can put a C in because a C is an A. But if Java allowed you to just give the method a List<B>, then you're putting a C in a list of Bs. And a C is not a B, so there'll be an error down the line. Java's wildcard solution doesn't let you add anything but null to a List<? extends A>, saving you from this mistake.
Well, Comeau c++ online compiler fails on this:
template<typename T> class List {
};
class A {
};
class B: public A {
};
void function(List<A> listA) {
}
int main(int argc, char** argv) {
List<A> a;
List<B> b;
function(a);
function(b);
}
giving this error:
"ComeauTest.c", line 18: error: no suitable user-defined conversion from "List<B>" to
"List<A>" exists
function(b);
So C++ and Java are similar when dealing with these kinds of types.
Static typing dictates that a subtype must support all operations of its supertype.
List<Fruit> supports inserting Fruit objects.
List<Banana> does not -- you cannot insert arbitrary fruits into a List<Banana>, only bananas.
Hence, List<Banana> is not a subtype of List<Fruit>.

Categories

Resources