What does "Recursive type bound" in Generics mean?
What is recursive type bound
This: <T extends Comparable<T>>
Note that the type parameter T
is also part of the signature of the super interface Comparable<T>
.
and how does the above piece of code help achieve mutual comparability?
It ensures that you can only compare objects of type T
. Without the type bound, Comparable
compares any two Object
s. With the type bound, the compiler can ensure that only two objects of type T
are compared.
There is an entry in the Java Generics FAQ written by Angelika Langer which explains the details of such declaration: http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106
To understand the concept of recursive type bounds, let's solve a simple problem. This concept is easier to understand by solving a real problem. I'll provide the definition of the recursive type bound at the end, because it makes more sense after understanding the concept.
Problem
Assume that we have to sort the fruits by their sizes. And we are told that we can only compare fruits of the same types. For example, we can't compare apples with oranges (pun intended).
So, we create a simple type hierarchy like following,
Fruit.java
interface Fruit {
Integer getSize();
}
Apple.java
class Apple implements Fruit, Comparable<Apple> {
private final Integer size;
public Apple(Integer size) {
this.size = size;
}
@Override public Integer getSize() {
return size;
}
@Override public int compareTo(Apple other) {
return size.compareTo(other.size);
}
}
Orange.java
class Orange implements Fruit, Comparable<Orange> {
private final Integer size;
public Orange(Integer size) {
this.size = size;
}
@Override public Integer getSize() {
return size;
}
@Override public int compareTo(Orange other) {
return size.compareTo(other.size);
}
}
Main.java
class Main {
public static void main(String[] args) {
Apple apple1 = new Apple(3);
Apple apple2 = new Apple(4);
apple1.compareTo(apple2);
Orange orange1 = new Orange(3);
Orange orange2 = new Orange(4);
orange1.compareTo(orange2);
apple1.compareTo(orange1); // Error: different types
}
}
Solution
In this code, we are able to achieve our objective of being able to compare the same types, that is, apples with apples and oranges with oranges. When we compare an apple with an orange we get an error which is what we want.
Problem
The problem here is that the code for implementing the compareTo()
method is duplicated for Apple
and Orange
class. And will be duplicated more in all the classes that we extend from the Fruit
, for creating new fruits in the future. The amount of repeated code in our example is less but in real world the repeated code can be of hundreds of lines in each class.
Moving Repeated Code to Common Class
Fruit.java
class Fruit implements Comparable<Fruit> {
private final Integer size;
public Fruit(Integer size) {
this.size = size;
}
public Integer getSize() {
return size;
}
@Override public int compareTo(Fruit other) {
return size.compareTo(other.getSize());
}
}
Apple.java
class Apple extends Fruit {
public Apple(Integer size) {
super(size);
}
}
Orange.java
class Orange extends Fruit {
public Orange(Integer size) {
super(size);
}
}
Solution
In this step, we get rid of the repeated code of compareTo()
method by moving it to a superclass. Our extended classes Apple
and Orange
are no longer polluted with common code.
Problem
Here the problem is that we are now able to compare different types, comparing apples to oranges no longer gives us an error:
apple1.compareTo(orange1); // No error
Introducing a Type Parameter
Fruit.java
class Fruit<T> implements Comparable<T> {
private final Integer size;
public Fruit(Integer size) {
this.size = size;
}
public Integer getSize() {
return size;
}
@Override public int compareTo(T other) {
return size.compareTo(other.getSize()); // Error: getSize() not available.
}
}
Apple.java
class Apple extends Fruit<Apple> {
public Apple(Integer size) {
super(size);
}
}
Orange.java
class Orange extends Fruit<Orange> {
public Orange(Integer size) {
super(size);
}
}
Solution
To restrict comparison of different types, we introduce a type parameter T
. So that the comparable Fruit<Apple>
cannot be compared to comparable Fruit<Orange>
. Note our Apple
and Orange
classes; they now inherit from the types Fruit<Apple>
and Fruit<Orange>
respectively. Now if we try to compare different types, the IDE shows an error, our desired behaviour:
apple1.compareTo(orange1); // Error: different types
Problem
But in this step, our Fruit
class doesn't compile. The getSize()
method of T
is unknown to the compiler. This is because the type parameter T
of our
Fruit
class doesn't have any bound. So, the T
could be any class, it is not possible that every class would have a getSize()
method. So the compiler is right in not recognizing the getSize()
method of T
.
Introducing a Recursive Type Bound
Fruit.java
class Fruit<T extends Fruit<T>> implements Comparable<T> {
private final Integer size;
public Fruit(Integer size) {
this.size = size;
}
public Integer getSize() {
return size;
}
@Override public int compareTo(T other) {
return size.compareTo(other.getSize()); // Now getSize() is available.
}
}
Apple.java
class Apple extends Fruit<Apple> {
public Apple(Integer size) {
super(size);
}
}
Orange.java
class Orange extends Fruit<Orange> {
public Orange(Integer size) {
super(size);
}
}
Final Solution
So, we tell the compiler that our T
is a subtype of Fruit
. In other words, we specify the upper bound T extends Fruit<T>
. This makes sure that only subtypes of Fruit
are allowed as type arguments. Now the compiler knows that the getSize()
method can be found in the subtype of Fruit
class (Apple
, Orange
etc.) because the Comparable<T>
also receives our type(Fruit<T>
) that contains the getSize()
method.
This allows us to get rid of the repeated code of compareTo()
method and also allows us to compare the fruits of the same types, apples with apples and oranges with oranges.
Now the compareTo()
method can be used inside the max()
function given in the question.
Definition of a Recursive Type Bound
In generics, when a reference type has a type parameter that is bounded by the reference type itself, then that type parameter is said to have a recursive type bound.
In our example, the generic type Fruit<T extends Fruit<T>>
, Fruit
is our reference type, its type parameter T
is bounded by the Fruit
itself, so, the type parameter T
has a recursive type bound Fruit<T>
.
A recursive type is one that includes a function that uses that type itself as a type for some argument or its return value. In our example, compareTo(T other)
is the function of the recursive type that takes the same recursive type as an argument.
Caveat
There is a caveat in this pattern. The compiler doesn’t prevent us from creating a class with a type argument of another subtype:
class Orange extends Fruit<Orange> {...}
class Apple extends Fruit<Orange> {...} // No error
Note in the Apple
class above, by mistake we passed Orange
instead of the Apple
itself as a type argument. This results in the compareTo(T other)
method to take Orange
instead of Apple
. Now we no longer get error while comparing different types and suddenly not able to compare apples with apples:
apple1.compareTo(apple2); // Error
apple1.compareTo(orange1); // No error
So, the developer needs to be careful while extending the classes.
That's it! Hope that helps.