Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

<? extends Class> and <? super Class> in Java - why it works this way?

Tags:

java

generics

Yet another novice, trying to comprehend Java Generics. I've observed all topics, I found, but I still have huge questions. Could you please explain me the following things:

  1. <? extends SomeClass> means, that ? is "any type", and extends SomeClass means, that this any type can be only a subclass of SomeClass. OK, I write two elementary classes:
abstract class Person {     private String name;     public Person(String name) {         this.name = name;     } }  class Student extends Person {     public Student(String name) {         super(name);     } } 

Class Student will be ? in our example. ? extends Person, to be precise. Then I'm trying to add new student to ArrayList, that, as I understand from written above, applies all classes, that are subclasses of Person:

Student clarissa = new Student("Clarissa Starling"); List<? extends Person> list = new ArrayList<>(); list.add(clarissa); //Does not compile 

Eclipse says:

"The method add(capture#3-of ? extends Person) in the type List is not applicable for the arguments (Student)"

How can class Student be not applicable, when we declared List, paramethrized by <? extends Person>, and Student exactly extends class Person?

Nevertheless, the following code:

List<? super Person> list = new ArrayList<>(); list.add(clarissa);  

compiles and works well (list.get(0), passed to println method, shows me the correct result of toString invocation). As I understand, List<? super Person> means, that I can pass to this list any type, that is super type for our Person class (in our case it is Object class only). But we see, that, contrary to logic, we can easy add subclass Student to our List<? super Person>!

OK, put aside our emotions, and let's see, what can happen with Clarissa Starling in our collection. Let's take our class Student, and add a couple of methods to it:

class Student extends Person {     private int grant;     public Student(String name) {         super(name);     }      public void setGrant(int grant) {         this.grant = grant;     }      public int getGrant() {         return this.grant;     }  } 

Then we pass an object, instantiated from this renewed class (our object "clarissa", for example), to List<? extends Person>. Doing this, we mean, that we can store subclass in the collection of its superclasses. Maybe, I don't understand some fundamenthal ideas, but at this stage I don't see any difference between adding subclass to the collection of its superclasses and the assigning of the reference to object "clarissa" to variable, typed Person. We have the same reducing of invokable methods, when we want to treat one of them, using our superclass variable. So, why List<? extends SomeClass> does not work the same way, wherein List<? super SomeClass> works conversely?

  1. I don't understand the fundamenthal difference between <T> (or <E>, or any other letter from appropriate part of JLS) and <?>. Both <T> and <?> are typeholders, so why we have two "keywords" (this symbols are NOT keywords, I just used this word for emphasizing the heavy meaning of both symbols in Java language) for the same purpose?
like image 988
Victor Zhdanov Avatar asked Feb 27 '16 11:02

Victor Zhdanov


People also ask

What does <? Extends mean in Java?

The extends keyword extends a class (indicates that a class is inherited from another class). In Java, it is possible to inherit attributes and methods from one class to another. We group the "inheritance concept" into two categories: subclass (child) - the class that inherits from another class.

What does <? Super t mean in Java?

super T denotes an unknown type that is a supertype of T (or T itself; remember that the supertype relation is reflexive). It is the dual of the bounded wildcards we've been using, where we use ? extends T to denote an unknown type that is a subtype of T .

What is the difference between List <? Super T and List <? Extends T?

extends Number> represents a list of Number or its sub-types such as Integer and Double. Lower Bounded Wildcards: List<? super Integer> represents a list of Integer or its super-types Number and Object.

How does super () work in Java?

The super keyword in Java is a reference variable that is used to refer parent class objects. The super() in Java is a reference variable that is used to refer parent class constructors. super can be used to call parent class' variables and methods. super() can be used to call parent class' constructors only.


2 Answers

The way I look at it is this - the placeholder T stands in for a definite type and in places where we need to know the actual type we need to be able to work it out. In contrast the wildcard ? means any type and I will never need to know what that type is. You can use the extends and super bounds to limit that wildcard in some way but there's no way to get the actual type.

So, if I have a List<? extends MySuper> then all I know about it is that every object in it implements the MySuper interface, and all the objects in that list are of the same type. I don't know what that type is, only that it's some subtype of MySuper. That means I can get objects out of that list so long as I only need to use the MySuper interface. What I can't do is to put objects into the list because I don't know what the type is - the compiler won't allow it because even if I happen to have an object of the right type, it can't be sure at compile time. So, the collection is, in a sense a read-only collection.

The logic works the other way when you have List<? super MySuper>. Here we're saying the collection is of a definite type which is a supertype of MySuper. This means that you can always add a MySuper object to it. What you can't do, because you don't know the actual type, is retrieve objects from it. So you've now got a kind of write-only collection.

Where you use a bounded wildcard versus the 'standard' generic type parameter is where the value of the differences start to become apparent. Let's say I have 3 classes Person, Student and Teacher, with Person being the base that Student and Teacher extend. In an API you may write a method that takes a collection of Person and does something to every item in the collection. That's fine, but you really only care that the collection is of some type that is compatible with the Person interface - it should work with List<Student> and List<Teacher> equally well. If you define the method like this

public void myMethod(List<Person> people) {     for (Person p: people) {         p.doThing();     } } 

then it can't take List<Student> or List<Teacher>. So, instead, you would define it to take List<? extends Person>...

public void myMethod(List<? extends Person> people){     for (Person p: people) {         p.doThing();     } } 

You can do that because myMethod never needs to add to the list. And now you find that List<Student> and List<Teacher> can both be passed into the method.

Now, let's say that you've got another method which wants to add Students to a list. If the method parameter takes a List<Student> then it can't take a List<People> even though that should be fine. So, you implement it as taking a List<? super Student> e.g.

public void listPopulatingMethod(List<? extends Student> source, List<? super Student> sink) {     for (Student s: source) {         sink.add(s);     } } 

This is the heart of PECS, which you can read about in much greater detail elsewhere... What is PECS (Producer Extends Consumer Super)? http://www.javacodegeeks.com/2011/04/java-generics-quick-tutorial.html

like image 55
sisyphus Avatar answered Oct 14 '22 16:10

sisyphus


List<? super Person> list = new ArrayList<>(); list.add(clarissa); // clarissa is an instance of Student class 

The reason why you can do things above, Suppose there is a Person class, Student class extends Person. You can think List means that in this List all elements is of class Person or superclass of Person, so when you add Person class instance or subclass of Person class, the implicit casting will happen. e.g. Person person = new Student();

public static void main(String[] args) {         ArrayList<? super Person> people = new ArrayList<>();         people.add(new Person());         people.add(new Object()); // this will not compile } 

But if you add Object class instance into the list, explicit casting or down casting is required and the compiler does not know whether say (Person) Object can be successful.

like image 27
Drake Avatar answered Oct 14 '22 14:10

Drake