泛型中的“递归类型绑定”是什么意思?

2022-09-01 10:29:02

我正在阅读关于有效Java的泛型的章节[Item 27]。

书中有这样一段话:

允许类型参数由涉及该类型参数本身的某个表达式限定,尽管相对较少。这就是所谓的递归类型绑定。

和这个:

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

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


答案 1

什么是递归类型绑定

这:<T extends Comparable<T>>

请注意,type 参数也是超级接口 的签名的一部分。TComparable<T>

以及上述代码如何帮助实现相互可比性?

它确保您只能比较 类型的对象。不带类型限制,比较任意两个 s。使用类型绑定,编译器可以确保只比较两个类型的对象。TComparableObjectT


答案 2

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


问题

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

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

水果.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()AppleOrangeFruit


将重复代码移动到公共类

水果.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()AppleOrange

问题

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

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);
    }
}

溶液

为了限制不同类型的比较,我们引入了一个类型参数。因此,可比性无法与可比性进行比较。注意我们的和类;它们现在分别继承自类型 和。现在,如果我们尝试比较不同类型的,IDE将显示一个错误,这是我们期望的行为:TFruit<Apple>Fruit<Orange>AppleOrangeFruit<Apple>Fruit<Orange>

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

问题

但在此步骤中,我们的类不会编译。编译器不知道 的方法。这是因为我们类的类型参数没有任何边界。因此,可以是任何类,不可能每个类都有一个方法。因此,编译器在不识别 的方法方面是正确的。FruitgetSize()TTFruitTgetSize()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);
    }
}

最终解决方案

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

这使我们能够摆脱方法的重复代码,并且还使我们能够比较相同类型的水果,苹果与苹果,橙子与橙子。compareTo()

现在,该方法可以在问题中给出的函数中使用。compareTo()max()


递归类型绑定的定义

在泛型中,当引用类型具有由引用类型本身限定的类型参数时,则该类型参数称为具有递归类型绑定。

在我们的示例中,泛型类型是我们的引用类型,其类型参数受自身的约束,因此,类型参数具有递归类型绑定。Fruit<T extends Fruit<T>>FruitTFruitTFruit<T>

递归类型是包含一个函数的类型,该函数将该类型本身用作某个参数或其返回值的类型。在我们的示例中,是采用相同递归类型作为参数的递归类型的函数。compareTo(T other)


警告

这种模式中有一个警告。编译器不会阻止我们使用另一个子类型的类型参数创建类:

class Orange extends Fruit<Orange> {...}
class Apple extends Fruit<Orange> {...}    // No error

请注意,在上面的类中,我们错误地将自身作为类型参数传递而不是本身。这将导致采用的方法代替 。现在,我们在比较不同类型的错误时不再遇到错误,并且突然无法将苹果与苹果进行比较:AppleOrangeApplecompareTo(T other)OrangeApple

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

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


就是这样!希望有所帮助。


推荐