Reputation: 3012
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 isComparable<Person>
, but that does not meet the requirements imposed by the bound of the type parameter of method sort. It is required thatT
(i.e.Student
) isComparable<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.
Upvotes: 2
Views: 1232
Reputation: 15723
I think your question would be stated better as: "What are valid use cases for using lower bound generics in JAVA"? I had the same question and it makes sense to use the upper bounds when you have a list and you want to use different types that are all subtypes or the supertype of a hierarchy of classes.
For example, you want methods to populate a list of numbers with int, double, and long. Using extends you can do this as they are all subtypes of Number.
On the other hand, if you want to restrict methods to only use Integer, then you want the more narrowly defined class so that only int is allowed, not float, double, etc.
From the java docs:
The abstract class Number is the superclass of platform classes representing numeric values that are convertible to the primitive types byte, double, float, int, long, and short.
The Integer class wraps a value of the primitive type int in an object. An object of type Integer contains a single field whose type is int.
A better example might be work you are doing at the JVM level, so you want to restrict your methods to use the VirtualMachineError to get specific information written to the logs, rather than using the Error class, from which it inherits, to limit the number of errors you write to the logs for something very refined, possibly relating to some debugging task.
These are obviously contrived examples, but the theme is the lower bounds can be used to restrict method type parameters.
Upvotes: 0
Reputation: 3296
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:
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.
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.
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);
.
Upvotes: 4
Reputation: 564
I think your confusion may be coming from the fact that while elements of List<Student>
can be compared to each other by virtue of the fact that class Student
subclasses Person
which implements Comparable<Person>
(class Student
therefore inherits compareTo(Person o)
, which can be called with an instance of Student
), you still cannot call the sort
method with a List<Student>
...
The problem is that when the Java compiler encounters the statement:
sort(studentList);
Where studentList
is an instance of parameterized type List<Student>
, it uses type inference to infer that the type argument to the sort
method T
is Student
, and Student
does not satisfy the upper bound: Student extends Comparable<Student>
. Therefore, the compiler will throw an error in this case, telling you that the inferred type does not conform to the constraints.
The article that you linked to shows you that the solution to this is to re-write the sort method as:
public static <T extends Comparable <? super T > > void sort(List<T> list)
This method signature loosens the constraint on the type parameter so that you can call the method with a List<Student>
.
I'm not too clear on the last part of your post:
in a
List<MyType>
, you can only put aMyType
in, not any of it's subclasses.
If you're referring to the elements of List<MyType>
, yes, you can put any element that is a subtype of MyType
in this list, say, MySubType
. If you're referring to a variable with List<MyType>
as its reference type, then no, you cannot put a reference to List<MySubType>
in a variable of type List<MyType>
. The reason for this is easy to see when you consider the following:
List<MyType> a;
List<MySubType> b = new ArrayList<>();
a = b; // compile-time-error, but assume OK for now
a.add(new MyType()); // Based on the type of a, this should be OK, but it's not because a is actually a reference to List<MySubType>.
Upvotes: 2
Reputation: 4695
I also think you should refer to Java Generics by Wadler and Naftalin, an excellent introduction to Java (5+) Type System.
When you ask "what is the purpose of extends
keyword?" based on your observations about collections of objects, the first thing you should remember is generic collections are tricky. I quote from the Wadler/Naftalin book (emphasis mine):
In Java, one type is a subtype of another if they are related by an extends or implements clause: Integer is a subtype of Number. Subtyping is transitive.
If A is a subtype of B, B is the supertype of A.
Liskov’s Substitution Principle tells us that wherever a value of one type is expected, one may provide a value of any subtype of that type: a variable of a given type may be assigned a value of any subtype of that type, and a method with a parameter of a given type may be invoked with an argument of any subtype of that type.
It’s because of violation of Liskov’s Substitution Principle (that would arise very rapidly in practice) that a List<Integer> is not a subtype of List<Number> although Integer is a subtype of Number. The other way round also does not work because List<Number> is NOT a subtype of List<Integer>.
This paragraph should help us understand why the keyword extends
is essential to support inheritance and polymorphism, and yet it (sort of) comes in the way of generic collections.
Upvotes: 1