28

我正在阅读 Effective Java [Item 27] 中的泛型一章。

书中有这样一段话:

尽管相对很少见,但允许类型参数受一些涉及该类型参数本身的表达式的限制。这就是所谓的递归类型绑定。

还有这个:

// Using a recursive type bound to express mutual comparability
public static <T extends Comparable<T>> T max(List<T> list) {...}

什么是递归类型绑定,上面的代码如何帮助实现相互可比性?

4

3 回答 3

31

什么是递归类型绑定

这:<T extends Comparable<T>>

请注意,类型参数T也是超级接口签名的一部分Comparable<T>

上面这段代码如何帮助实现相互可比性?

它确保您只能比较 type 的对象T。没有类型限制,Comparable比较任意两个Objects。通过类型绑定,编译器可以确保只T比较两个类型的对象。

于 2011-09-12T09:53:03.093 回答
15

为了理解递归类型边界的概念,让我们解决一个简单的问题。通过解决实际问题,这个概念更容易理解。我将在最后提供递归类型绑定的定义,因为在理解了这个概念之后它更有意义。


问题

假设我们必须按大小对水果进行分类。而且我们被告知我们只能比较相同类型的水果。例如,我们不能比较苹果和橙子(双关语)。

因此,我们创建了一个简单的类型层次结构,如下所示,

水果.java

interface Fruit {
    Integer getSize();
}

苹果.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);
    }
}

橙色.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);
    }
}

主.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()方法的代码为AppleOrange类重复。并且将在我们从 扩展的所有类中复制更多,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());
    }
}

苹果.java

class Apple extends Fruit {
    public Apple(Integer size) {
        super(size);
    }
}

橙色.java

class Orange extends Fruit {
    public Orange(Integer size) {
        super(size);
    }
}

解决方案

在这一步中,我们compareTo()通过将其移至超类来消除方法的重复代码。我们的扩展类Apple不再Orange被通用代码污染。

问题

这里的问题是我们现在能够比较不同的类型,比较苹果和橙子不再给我们一个错误:

apple1.compareTo(orange1);    // No error

引入类型参数

水果.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.
    }
}

苹果.java

class Apple extends Fruit<Apple> {
    public Apple(Integer size) {
        super(size);
    }
}

橙色.java

class Orange extends Fruit<Orange> {
    public Orange(Integer size) {
        super(size);
    }
}

解决方案

为了限制不同类型的比较,我们引入了类型参数T。以至于可比性Fruit<Apple>不能与可比性相提并论Fruit<Orange>。注意我们的AppleOrange类;Fruit<Apple>它们现在分别从类型和继承Fruit<Orange>。现在,如果我们尝试比较不同的类型,IDE 会显示错误,这是我们想要的行为:

apple1.compareTo(orange1);  // Error: different types

问题

但是在这一步中,我们的Fruit类没有编译。编译器不知道的getSize()方法。T这是因为T我们 Fruit类的类型参数没有任何限制。所以,T可以是任何类,不可能每个类都有一个getSize()方法。所以编译器没有识别getSize().T


引入递归类型绑定

水果.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.
    }
}

苹果.java

class Apple extends Fruit<Apple> {
    public Apple(Integer size) {
        super(size);
    }
}

橙色.java

class Orange extends Fruit<Orange> {
    public Orange(Integer size) {
        super(size);
    }
}

最终解决方案

因此,我们告诉编译器 ourTFruit. 换句话说,我们指定了上限T extends Fruit<T>。这确保只Fruit允许子类型作为类型参数。现在编译器知道该getSize()方法可以在Fruit类 (AppleOrange) 的子类型中找到,因为它Comparable<T>也接收Fruit<T>到包含该getSize()方法的 type( )。

这让我们摆脱了重复的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传递。这导致采用的方法而不是. 现在我们在比较不同类型时不再出错,并且突然无法将苹果与苹果进行比较:OrangeApplecompareTo(T other)OrangeApple

apple1.compareTo(apple2);     // Error
apple1.compareTo(orange1);    // No error

因此,开发人员在扩展类时需要小心。


就是这样!希望有帮助。

于 2020-11-18T21:31:19.343 回答
8

Angelika Langer 编写的 Java 泛型常见问题解答中有一个条目解释了此类声明的详细信息:http ://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106

于 2011-09-12T10:11:12.550 回答