Is there an advantage to list <? extends MyObject>?

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 its subclass, ...

 public class Child extends Parent { public Child(String value) { super(value); } } 

... the following program compiles and runs 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 getItems() ? It seems semantically wrong to me, since the result of the function contains a Parent object, which is not a subclass of Parent which I would expect from this method signature. Given this behavior, I would expect a simple List<Parent> as the return type. However, since the compiler does not complain, am I missing something? Are there any benefits from this implementation other than the (unnecessary) "write protection" for the list of results? Or is it a java style for write protection result lists (not in this example, but, for example, to open a list of object fields without creating a copy)? Or can it just be considered "wrong" (in terms of training)? If so, then why neither the compiler, nor even Eclipse complain about it, without even giving a hint?

+5
source share
4 answers

List<A> is invariant. List<? extends A> List<? extends A> covariant, and List<? super A> List<? super A> is contravariant.

The covariant type cannot be changed without casting (which makes sense because volatile collections are inherently not covariant).

If the collection does not need to be changed, then there is no reason not to use List<? extends A> List<? extends A> to get the corresponding subtypes (for example, List<? extends Integer> is a subtype of List<? extends Number> ).

I would not consider this as "write protection", since a shot to get around it is trivial. Look at this just using the most common type. If you write a method that takes a List<A> argument, and this method never needs to change the list, then List<? extends A> List<? extends A> is a more general and therefore more appropriate type.

It is useful to use covariance as the return type. Suppose you implement a method whose return type is List<Number> , and you have a List<Integer> that you want to return. You are somehow familiar in the Alps: either copy it, or make an ugly throw. But if your types were correct and the interface called List<? extends Number> List<? extends Number> , then you could just return what you have.

+4
source

You have already mentioned “write protection” as an advantage that, depending on the usage scenario, may or may not be useful.

Another advantage is some flexibility in the sense that you can change the implementation of a method and write something line by line

  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 changes the content type of your list, but it preserves the integrity of the interface (read: public method signature). This is important if you are writing a framework, for example.

+3
source

The syntax ? extends X ? extends X and ? super Y ? super Y comes in handy when you start working with specialized List in subclasses, and you need to make sure that an invalid object is invalid. With a regular ArrayList you can always risk uncontrolled selection and circumvention, but perhaps this example illustrates how to use parameterization:

 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); } } } 

Last name note:

  • read it ? extends X ? extends X either as " X , or any type extending X " or "something assigned to a variable of type X "
  • read it ? super X ? super X either like " X , or any type of X extends" or "something that I can assign to a variable of type X to."
+2
source

when do you say List<? extends Parent> List<? extends Parent> , this means any object of type Parent Type or any of its children. I agree that this is kind of misleading, but the way it works.

One potential benefit that I see from this template is the use of the reflection API in the main function.

Suppose you have a function return List<Parent> , and in your main() , if you do

 private static List<Parent> getItems() { ... } public static void main(String[] args) { List<? extends Parent> items = getItems(); for (Parent p : items) { System.out.println(p.getClass().getSimpleName()); } } 

give you

 Parent Parent 

as if you

 private static List<? extends Parent> getItems() { ... } 

The main thing will print

 Parent Child 
0
source

Source: https://habr.com/ru/post/1201612/


All Articles