使用 Comparable 接口禁止继承类型之间的比较

Using Comparable interface to prohibit comparison between inherited types

我正在阅读 Naftalin 的“Java Generics and Collections”一书。我想检查一下我对他们在第 3 章中提出的观点的理解。

他们解释了 Comparable 接口如何让我们控制可以相互比较的类型。在他们提供的下面的代码示例中,他们说无法将 AppleOrange 进行比较。准确地说,他们是这样说的:

Since Apple implements Comparable<Apple>, it is clear that you can compare apples with apples, but not with oranges.

但是如您所见,在 main 方法的最后一行,我们能够这样做,因为来自基础 class 的 compareTo 最终被调用.所以我认为解决这个问题的方法是通过将 compareTo 方法移动到每个 AppleOrange class是吗?并将其从基础 Fruit class 中移除?或者有更好的方法吗?

abstract class Fruit {
    protected String name;
    protected int size;

  protected Fruit(String name, int size) {
    this.name = checkNotNull(name);
    this.size = size;
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof Fruit) {
      Fruit that = (Fruit) o;
      return this.name.equals(that.name) && this.size == that.size;
    }
    return false;
  }

  @Override
  public int hashCode() {
    return name.hashCode() * 29 + size;
  }

  protected int compareTo(@Nullable Fruit that) {
    if (this.size > that.size) {
      return 1;
    } else if (this.size < that.size) {
      return -1;
    } else return 0;
  }
  }
}

class Apple extends Fruit implements Comparable<Apple> {
  public Apple(int size) {
    super("Apple", size);
  }

  @Override
  public int compareTo(Apple apple) {
    return super.compareTo(apple);
  }
}

class Orange extends Fruit implements Comparable<Orange> {
  protected Orange(int size) {
    super("Orange", size);
  }

  @Override
  public int compareTo(Orange orange) {
    return super.compareTo(orange);
  }
}

class TestFruit {
  public static void main(String[] args) {
    Apple apple1 = new Apple(1);
    Apple apple2 = new Apple(2);
    Orange orange1 = new Orange(1);
    Orange orange2 = new Orange(2);

    List<Apple> apples = List.of(apple1, apple2);
    List<Orange> oranges = List.of(orange1, orange2);

    // Both of these work as expected
    System.out.println(Collections.max(apples)); // Apple{name=Apple, size=2}
    System.out.println(Collections.max(oranges)); // Orange{name=Orange, size=2}

    // But now, as expected, when you try to create a mixed list, it will no longer work
    List<Fruit> mixed = List.of(apple1, orange2);
    // Compile error on the below line
    System.out.println(Collections.max(mixed));

    // But this also works now, because compareTo from the Fruit class
    // is being used here.
    System.out.println(apple1.compareTo(orange1)); // -1
  }
}
  1. A class 有方法 compareTo(Fruit) 并不意味着它实现了 Comparable<Fruit>。只有相反才是正确的。
  2. 在javaAPI中,需要比较的方法是基于Comparable接口,而不是检查class是否有compareTo方法。

由于Fruit没有实现Comparable<Fruit>(实现了就是设计问题),书上说的是对的。你可以看到 Collections.max cannot be called with List<Fruit>.

IMO,让我们感到困惑的是 Fruit.

中的方法名称 compareTo
  • 名称与 Comparable 方法不明确,
    所以按照惯例,我们应该只在实现 Comparable.
  • 时命名一个方法 compareTo
  • 静态绑定可能调用了错误的方法
    // Apple#compareTo is called
    System.out.println(apple1.compareTo(apple2));
    Fruit fruitWhichIsApple2 = apple2;
    // Fruit#compareTo is called
    System.out.println(apple1.compareTo(fruitWhichIsApple2));
    

虽然它被标记为protected(这避免了其他包class的使用),但如果我们将它重命名为其他名称就更清楚了。