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

时间:2011-09-12 09:45:33

标签: java generics effective-java

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

书中有这一段:

  

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

和此:

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

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

3 个答案:

答案 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()方法的重复代码移到超类中来摆脱它。我们的扩展类AppleOrange不再被通用代码污染。

问题

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

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>进行比较。注意我们的AppleOrange类;它们现在分别继承自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>类的子类型(FruitgetSize()等)中找到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

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


就是这样!希望有帮助。