Given a simple class …
public class Parent {
private final String value;
public Parent(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
… and a subclass of it, …
public class Child extends Parent {
public Child(String value) {
super(value);
}
}
… the following program compiles and executes without errors or warnings:
public class Main {
public static void main(String[] args) {
List<? extends Parent> items = getItems();
for (Parent p : items) {
System.out.println(p.toString());
}
}
private static List<? extends Parent> getItems() {
List<Parent> il = new ArrayList<Parent>();
il.add(new Parent("Hello"));
il.add(new Child("World"));
return il;
}
}
But what about the return type of getItems()
? It seems semantically wrong to me, since the result of the function does contain a Parent
object which is not a subclass of Parent
what I would expect from that method signature. Given this behaviour, I would have expected a simple List<Parent>
as return type. However, since the compiler doesn’t complain, do I miss something? Is there any benefit from this implementation, aside from an (unnecessary) “write protection” for the result list? Or is this java style to write-protect result lists (not in this example, but, for example, to expose an object’s field list without needing to create a copy)? Or can this simply be considered “wrong” (in terms of teaching)? If so, why does neither the compiler nor even Eclipse complain about it, not even showing a hint?
List<A>
is invariant. List<? extends A>
is covariant, and List<? super A>
is contravariant.
The covariant type can't be modified without a cast (which makes sense, because mutable collections are inherently not covariant).
If the collection doesn't need to be modified, then there is no reason not to use List<? extends A>
to gain the appropriate subtype relationships (e.g. List<? extends Integer>
is a subtype of List<? extends Number>
).
I wouldn't suggest looking at this as "write protection" since the cast to circumvent it is trivial. Look at it as simply using the most general type possible. If you're writing a method that accepts a List<A>
argument, and that method never needs to modify the list, then List<? extends A>
is the more general and thus more appropriate type.
As a return type, using covariance is also useful. Suppose you're implementing a method whose return type is List<Number>
, and you have a List<Integer>
you want to return. You're kinda found as a stranger in the Alps: Either copy it or do an ugly cast. But if your types were correct, and the interface called for a List<? extends Number>
, then you could just return the thing you have.
You already mentioned the "write protection" as a benefit, which, depending on the usage scenario, may be useful or not.
Another benefit is some flexibility, in the sense that you can change the implementation of the method and write something along the lines of
List<Child> il = new ArrayList<Child>();
il.add(new Child("Hello"));
il.add(new GrandChild("World")); // assume there is such a thing
List<? extends Parent> result = il;
return result;
which does change the type of the contents of your list, but it keeps the interface (read:public method signature) intact. This is crucial if you write a framework, for example.
The ? extends X
and ? super Y
syntax comes in handy when you start working with specialized List
s in sub-classes and you need to ensure that no invalid object is mixed in. With regular ArrayList
s you can always risk an unchecked cast and circumvent the restriction, but maybe this example illustrates the way you can use the parametrizations:
import java.util.AbstractList;
import java.util.List;
/**
* http://stackoverflow.com/q/25617284/1266906
*/
public class ListTest {
public static void main(String[] args) {
ParentList parents = new ParentList(new Parent("one"), new Child("two"));
List<? extends Parent> parentsA = parents; // Read-Only, items assignable to Parent
List<? super Parent> parentsB = parents; // Write-Only, input of type Parent
List<Parent> parentC = parents; // Valid as A and B were valid
List<? super Child> parentD = parents; // Valid as you can store a Child
// List<? extends Child> parentE = parents; // Invalid as not all contained values are Child
ChildList children = new ChildList(new Child("one"), new Child("two"));
List<? extends Parent> childrenA = children; // Read-Only, items assignable to Parent
// List<? super Parent> childrenB = children; // Invalid as ChildList can not store a Parent
List<? super Child> childrenC = children; // Write-Only, input of type Child
// List<Parent> childrenD = children; // Invalid as B was invalid
List<Child> childrenE = children;
}
public static class ChildList extends AbstractList<Child> {
private Child[] values;
public ChildList(Child... values) {
this.values = values;
}
@Override
public Child get(int index) {
return values[index];
}
@Override
public Child set(int index, Child element) {
Child oldValue = values[index];
values[index] = element;
return oldValue;
}
@Override
public int size() {
return values.length;
}
}
public static class ParentList extends AbstractList<Parent> {
private Parent[] values;
public ParentList(Parent... values) {
this.values = values;
}
@Override
public Parent get(int index) {
return values[index];
}
@Override
public Parent set(int index, Parent element) {
Parent oldValue = values[index];
values[index] = element;
return oldValue;
}
@Override
public int size() {
return values.length;
}
}
public static class Parent {
private final String value;
public Parent(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
public static class Child extends Parent {
public Child(String value) {
super(value);
}
}
}
? extends X
either as 'X
or any type extending X
' or 'something assignable to a variable of type X
'? super X
either as 'X
or any type X
extends' or 'something I can assign a variable of type X
to'.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With