3

I have a Storage class:

class Storage<E> {
    void add(E e) {
        // add element
    };

    void addAll(Iterable<? extends E> src) {
        for (E e : src)
            add(e);
    }
}

There are two classes where class Child extends Parent:

Parent

class Parent implements Comparable<Parent> {

    @Override
    public int compareTo(Parent o) {
        return 0; // some comparison logic
    }
}

Child

class Child extends Parent {

}

Driver class:

import java.util.Arrays;
import java.util.List;

public class GenericTest {

    public static void main(String[] args) {
        /******** CASE 1 *********/
        Storage<Parent> ds = new Storage<Parent>();
        ds.add(new Parent());
        
        ds.addAll(Arrays.asList(new Parent()));
        
        // Type params are invariant.
        // But List<Child> is possible due to bounded wildcard type on addAll
        ds.addAll(Arrays.asList(new Child())); // Makes sense
        
        /******** CASE 2 *********/
        List<Child> t = Arrays.asList();
        max(t);
        
    }
    
    static <T extends Comparable<T>> T max(List<T> list) {
        return null; // Return null so code can compile
    }
}

Because Storage is a generic class, the operations on its method makes sense; I get how case 1 is working.

In case 2, with above signature of max in GenericTest, I get the compilation error:

The method max(List<T>) in the type GenericTest is not applicable for the arguments (List<Child>)

I understand that Comparable<Child> is not a sub-type of Comparable<Parent> (Typed params are invariant).

So, I updated signature to

// update Comparable<T> to Comparable<? super T>
static <T extends Comparable<? super T>> T max(List<T> list)

Now, the code compiles and inferred signature is

<Child> Child GenericTest.max(List<Child> list)
This makes sense.

When I update the signature to

// update List<T> to List<? extends T>
static <T extends Comparable<T>> T max(List<? extends T> list)

the code compiles and inferred signature is

<Parent> Parent GenericTest.max(List<? extends Parent> list)

I couldn't understand how updating List<T> to List<? extends T> made the compiler infer the type Parent.

I mean, for a generic method (on which there is direct invocation using type Child), how did ? extends T helped compiler refer the parent class of Child? Note that this signature has Comparable<T> (and not Comparable<? super T).

Isn't extends about a type which is T itself and its sub-classes?

Edit:
Java 8 comes with updated type-inference rules. The below is sufficient for type-inference in Java 8 and later, but not for Java 7 and below:

static <T extends Comparable<T>> T max(List<? extends T> list)

It gives compilation error:

Bound mismatch: The generic method max(List<? extends T>) of type GenericTest is not applicable for the arguments (List<Child>). The inferred type Child is not a valid substitute for the bounded parameter <T extends Comparable<T>>

The signaure

static <T extends Comparable<? super T>> void max(List<T> list) 

is sufficient for Java 7, though.

adarsh
  • 1,393
  • 1
  • 8
  • 16
  • 2
    downvoters are not required to explain themselves. It wasn't me btw, and I can't see why this would be downvoted, but you aren't entitled to an explanation and the downvoter is unlikely to see your comment. – Software Engineer Jun 05 '21 at 13:08

2 Answers2

1

To make things clearer, the class Child:

  • extends Parent
  • implements Comparable<Parent>
1) static <T extends Comparable<T>> T max(List<T> list)
  • T is Child
  • fails because Child is not a Comparable<Child>
2) static <T extends Comparable<? super T>> T max(List<T> list)
  • T is Child
  • ? is Parent
  • it works because Child implements Comparable<Parent super Child>
3) static <T extends Comparable<T>> T max(List<? extends T> list)
  • T is Parent
  • ? is Child
  • it works because Child extends Parent implements Comparable<Parent>

Here in case 3), finding a valid T class can be seen as: "find the first super class of Child that implements a Comparable of itself".
As in case 1), it cannot be Child because it is not a Comparable<Child>. The first (and only) super class of Child that implements Comparable of itself is Parent.

I couldn't understand how updating List<T> to List<? extends T> made the compiler infer the type Parent.

  • List<T> forces T to be Child
  • List<? extends T> forces ? to be Child, not T
Tigger
  • 439
  • 2
  • 6
1

To me it fails with both JDK 8 and JDK 7, so I don't see the inconsistency which you describe between those JDks

JDK 8 compilation

enter image description here

JDK 7 compilation

enter image description here

As to why it fails it is called PECS (Producer Extends Consumer Super)

The following answer has helped me a lot to understand it.

If you say <? extends SomeType>, then you wanna describe a ‘box’ that is the same size or smaller than the ‘SomeType’ box.

Taking in consideration the aforementioned, when you described max method as

 static <T extends Comparable<T>> T max(List<? extends T> list)

With your declaration List<? extends T> list you need a container of the same or smaller than T. Which could be Child as you use it.

List<Child> t = Arrays.asList();

so this should have compiled.

However the return type is of <T extends Comparable<T>> T which then is translated into Child extends Comparable<Child>.

Java however has a limitation that prevents you from implementing the same generic interface (in your case Comparable) with different type parameters.

That is why the error says

required: java.util.List<? extends T>   
found: java.util.List<Child>   reason: inferred type does not
conform to declared bound(s)
inferred: Child
bound(s): java.lang.Comparable<Child>

enter image description here

Solution

You should have declared the return type with <T extends Comparable<? super T>>

Contra-variance: ? super T ( the family of all types that are supertypes of T) - a wildcard with a lower bound. T is the lower-most class in the inheritance hierarchy. from a previous answer on PECS

In that case it would have compiled as it would have set the bound to Comparable<Parent>.

Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47
  • *To me it fails with both JDK 8 and JDK 7, * That's weird. I rechecked and saw this issue. The type-inference did get [updated](https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html) in Java 8. Thanks for the link, that's helpful. – adarsh Jun 05 '21 at 14:39