我正在阅读有效Java [第27项]中关于泛型的章节。
书中有这一段:
允许(尽管相对较少)类型参数受某个涉及该类型参数本身的表达式的限制。这就是所谓的递归类型绑定。
和此:
// Using a recursive type bound to express mutual comparability
public static <T extends Comparable<T>> T max(List<T> list) {...}
什么是递归类型绑定以及上面的代码如何帮助实现相互可比性?
答案 0 :(得分:23)
什么是递归类型绑定
这:<T extends Comparable<T>>
请注意,类型参数T
也是超级接口Comparable<T>
的签名的一部分。
以上代码如何帮助实现相互比较?
它确保您只能比较T
类型的对象。如果没有绑定类型,Comparable
会比较任何两个Object
。通过类型绑定,编译器可以确保只比较两个类型为T
的对象。
答案 1 :(得分:5)
Angelika Langer撰写的Java Generics FAQ中有一个条目,解释了此类声明的详细信息:http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106
答案 2 :(得分:0)
要了解递归类型范围的概念,让我们解决一个简单的问题。通过解决实际问题,这个概念更容易理解。我将在最后提供递归类型绑定的定义,因为在理解该概念之后,它会更有意义。
假设我们必须按照水果的大小对它们进行排序。有人告诉我们,我们只能比较相同类型的水果。例如,我们不能将苹果与橙子(双关语)进行比较。
因此,我们创建了一个简单的类型层次结构,如下所示,
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
}
}
解决方案
在此代码中,我们能够实现能够比较相同类型(即,苹果与苹果以及橙子与橙子)的目标。当我们将苹果与橙子进行比较时,会得到我们想要的错误。
问题
这里的问题是,用于实现compareTo()
和Apple
类的Orange
方法的代码是重复的。并将在我们从Fruit
扩展的所有类中进行更多重复,以在将来创建新的成果。在我们的示例中,重复代码的数量较少,但在现实世界中,每个类中的重复代码可以是数百行。
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);
}
}
解决方案
在这一步中,我们通过将compareTo()
方法的重复代码移到超类中来摆脱它。我们的扩展类Apple
和Orange
不再被通用代码污染。
问题
这里的问题是,我们现在能够比较不同的类型,将苹果与桔子进行比较不再给我们带来错误:
apple1.compareTo(orange1); // No error
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);
}
}
解决方案
为限制不同类型的比较,我们引入了类型参数T
。这样就无法将可比较的Fruit<Apple>
与可比较的Fruit<Orange>
进行比较。注意我们的Apple
和Orange
类;它们现在分别继承自Fruit<Apple>
和Fruit<Orange>
类型。现在,如果我们尝试比较不同类型,IDE将显示错误,这是我们期望的行为:
apple1.compareTo(orange1); // Error: different types
问题
但是在此步骤中,我们的Fruit
类未编译。 getSize()
的{{1}}方法对于编译器是未知的。这是因为我们的类型参数T
T
类没有任何界限。因此,Fruit
可以是任何类,不可能每个类都具有T
方法。因此,编译器正确地识别了getSize()
的{{1}}方法。
Fruit.java
getSize()
Apple.java
T
Orange.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.
}
}
最终解决方案
因此,我们告诉编译器我们的class Apple extends Fruit<Apple> {
public Apple(Integer size) {
super(size);
}
}
是class Orange extends Fruit<Orange> {
public Orange(Integer size) {
super(size);
}
}
的子类型。换句话说,我们指定上限T
。这样可以确保仅将Fruit
的子类型用作类型参数。现在,编译器知道可以在T extends Fruit<T>
类的子类型(Fruit
,getSize()
等)中找到Fruit
方法,因为Apple
也收到了包含Orange
方法的type(Comparable<T>
)。
这使我们摆脱了Fruit<T>
方法的重复代码,还使我们能够比较相同类型的水果,苹果与苹果以及橙子与橙子。
现在可以在问题中提供的getSize()
函数中使用compareTo()
方法。
在泛型中,当引用类型具有受引用类型本身限制的类型参数时,则认为该类型参数具有递归类型绑定。
在我们的示例中,泛型类型compareTo()
,max()
是我们的引用类型,其类型参数Fruit<T extends Fruit<T>>
受Fruit
自身的限制,因此,类型参数T
具有绑定的Fruit
递归类型。
递归类型是一种包含一个函数的递归类型,该函数将该类型本身用作某个参数或其返回值的类型。在我们的示例中,T
是递归类型的函数,该函数采用与参数相同的递归类型。
此模式有一个警告。编译器不会阻止我们创建带有其他子类型的类型参数的类:
Fruit<T>
请注意,在上面的compareTo(T other)
类中,我们错误地传递了class Orange extends Fruit<Orange> {...}
class Apple extends Fruit<Orange> {...} // No error
而不是Apple
本身作为类型参数。这导致Orange
方法采用Apple
而不是compareTo(T other)
。现在我们在比较不同类型时不再出错,并且突然之间无法将苹果与苹果进行比较:
Orange
因此,开发人员在扩展类时需要小心。
就是这样!希望有帮助。