【问题标题】:What does "Recursive type bound" in Generics mean?泛型中的“递归类型绑定”是什么意思?
【发布时间】:2011-09-12 09:45:33
【问题描述】:

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

书中有这么一段:

虽然比较少见,但允许类型参数受涉及该类型参数本身的某个表达式的限制。这就是所谓的递归类型绑定。

还有这个:

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

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

【问题讨论】:

    标签: java generics effective-java


    【解决方案1】:

    什么是递归类型绑定

    这个:&lt;T extends Comparable&lt;T&gt;&gt;

    注意类型参数T也是超级接口Comparable&lt;T&gt;签名的一部分。

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

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

    【讨论】:

      【解决方案2】:

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


      问题

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

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

      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() 方法的代码对于AppleOrange 类是重复的。并且将在我们从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&lt;Apple&gt; 不能与可比较的Fruit&lt;Orange&gt; 比较。注意我们的AppleOrange 类;它们现在分别继承自 Fruit&lt;Apple&gt;Fruit&lt;Orange&gt; 类型。现在,如果我们尝试比较不同的类型,IDE 会显示错误,这是我们想要的行为:

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

      问题

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


      引入递归类型绑定

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

      最终解决方案

      所以,我们告诉编译器我们的TFruit 的子类型。换句话说,我们指定上限T extends Fruit&lt;T&gt;。这确保只允许 Fruit 的子类型作为类型参数。现在编译器知道getSize() 方法可以在Fruit 类的子类型(AppleOrange 等)中找到,因为Comparable&lt;T&gt; 也接收我们的包含@ 的类型(Fruit&lt;T&gt;) 987654369@方法。

      这让我们摆脱了compareTo()方法的重复代码,也让我们可以比较相同类型的水果,苹果和苹果,橙子和橙子。

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


      递归类型绑定的定义

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

      在我们的例子中,泛型类型Fruit&lt;T extends Fruit&lt;T&gt;&gt;,Fruit是我们的引用类型,它的类型参数T是由Fruit本身限定的,所以,类型参数T有一个递归类型限定Fruit&lt;T&gt;.

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


      警告

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

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

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

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

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


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

      【讨论】:

      • 这是一个很好的答案!感谢您花时间以这种方式分解它。
      • 感谢您的详细解答。我有个问题。如果递归类型的整个动机是为T 建立一个上限,那么在“引入类型参数”部分,为什么我们不能只写public class Fruit &lt;T&gt; implements Comparable&lt;T extends Fruit&gt;?当我尝试它时,我的 IDE 显示“Unexpected Bound”。这还不足以告诉编译器T 是 Fruit 的子类吗?
      • @rgbk21,我们无法更改已经存在的类或接口的类型,例如 Comparable,因为它在 Java 库中被声明为 Comparable&lt;T&gt; 而不是 Comparable&lt;T extends SomeClass&gt;。这是我们必须履行的合同。您在Fruit 上指定的任何界限都会自动传递给Comparable&lt;T&gt;。例如,如果您写Fruit&lt;T extends Fruit&lt;T&gt;&gt;,那么Comparable 将在后台自动接收Fruit&lt;T&gt;。另请注意,recursive 类型绑定的目标是建立该类型本身的绑定。希望能消除您的疑虑。
      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-10
        • 2012-04-19
        • 2019-04-02
        • 1970-01-01
        • 1970-01-01
        • 2021-04-07
        相关资源
        最近更新 更多