Search code examples
javalanguage-design

Why does Java have lower bounds in generics?


I'm trying to design my own programming language, and am thinking about generics. I've been doing Java for quite a while now and know about the extends and super generic bounds.

I'm reading this post and trying to understand the need for the lower bounds.

In my language, I am planning to do generics the same way as a regular field, if you say List<MyObject>, you can store either a MyObject, or any subtype of MyObject. Makes sense right?

On the post, they have the following class hierarchy:

class Person implements Comparable<Person> {
  ...
}

class Student extends Person {
  ...
}

They then have a sort method:

public static <T extends Comparable<T>> void sort(List<T> list) {
  ...
}

What I think, is that you should be able to send a List<Student> to this method. As a Student extends Person, the compare method would be handled by it's superclass, Person.

The reason for the error message is that the compiler infers the type parameter of the sort method as T:=Student and that class Student is not Comparable<Student> . It is Comparable<Person> , but that does not meet the requirements imposed by the bound of the type parameter of method sort. It is required that T (i.e. Student ) is Comparable<T> (i.e. Comparable<Student> ), which in fact it is not.

The above doesn't make any sense to me...you should be able to do student.compare(person), so why doesn't this work?

Maybe it's saying that Student should implement it's own comparable method so that Student has a say in the comparison? You don't need to do anything special, just override Person's method. You won't be able to guarantee you are comparing to another Student, but that can be checked with instanceof.

Is there something I'm missing here?

And after all this thinking, I'm now wondering what the purpose of extends is. From my understanding, in a List<MyType>, you can only put a MyType in, not any of it's subclasses. As mentioned above, this doesn't make any sense to me and you should be able to put any subclass in the list like a field.

I should probably make this clear, it's not "why doesn't it work in Java", but "why doesn't it work in generics theory". I just tagged java because that is where I'm making my comparisons.


Solution

  • First: The method declaration

    public static <T extends Comparable<T>> void sort(List<T> list)
    

    does not make much sense for me. I thing it should be

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

    Then it would be possible to write sort(listOfStudents). Now I will explain the advantage of upper and lower bounded wildcards:


    Polymorphism of type parameters is not transferred to it's generic type

    This mean a list of students (List<Student>) is not a list of persons (List<Person>). A instruction like

    List<Person> list = new List<Student>();
    

    would fail in Java. There is a simple reason: list.add(new Person()); would be illegal for a list of students but not for a list of persons.

    Upper Bounded Wildcards

    But maybe you have a function which doesn't care whether the objects are subclasses or not. For example: You could have a method like this:

    void printAll(List<Person> list)
    

    They just print some data about all persons to stdout. If you have a list of students (List<Student> listOfStudents) you could write:

    List<Person> listOfPersons = new ArrayList<>();
    for (final Student student : listOfStudents) {
        listOfPersons.add(student);
    }
    printAll(listOfPersons);
    

    But you may see that it isn't a very nice solution. Another solution would be to use upper bounded wildcards for printAll:

    void printAll(List<? extends Person> list)
    

    You can write something like Person person = list.get(0) in printAll. But you cannot write print.add(new Person()) because list could be a list of students or something else.

    Lower Bounded Wildcards

    Now the same in the other direction: Lets say you have a function which generates some students and put them in a list. Something like this:

    void generateStudents(List<Student> list) {
        for (int i = 0; i < 10; ++i) {
            list.add(new Student());
        }
    }
    

    Now you have a list of persons (List<Person> listOfPersons) and want to generate students in this list. You could write

    List<Student> listOfStudents = new ArrayList<>();
    generateStudents(listOfStudents);
    for (Student student : listOfStudents) {
        listOfPersons.add(student);
    }
    

    You may see again, that it is not a very nice solution. You could also change the declaration of generateStudents to

    void generateStudents(List<? super Student> list)
    

    Now, you can just write generateStudents(listOfPersons);.