Home > database >  Using Comparable interface to prohibit comparison between inherited types
Using Comparable interface to prohibit comparison between inherited types

Time:04-18

I am going through the "Java Generics and Collections" book by Naftalin. I want to check my understanding regarding a point that they make in Chapter 3.

They explain about how the Comparable interface allows us to control what types we can compare with one another. In the below code example that they provide, they say that it is not possible to compare Apple with Orange. To be precise, this is what they say:

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

But as you can see, on the last line of the main method, we are able to do so, because the compareTo from the base class ends up being called. So I presume the solution to this would be to duplicate the implementation of the compareTo method by moving it to each of the Apple and Orange classes? And removing it from the base Fruit class? Or is there a better way to do this?

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

CodePudding user response:

  1. A class has method compareTo(Fruit) doesn't mean it implements Comparable<Fruit>. Only the opposite is correct.
  2. In java API, method requiring comparison is based on Comparable interface, instead of checking if the class has compareTo method.

Since Fruit does not implements Comparable<Fruit>(it is a design problem if it implements), what the book say is correct. You can see Collections.max can't be called with List<Fruit>.

IMO, what make us confuse is the method name compareTo in Fruit.

  • The name is ambiguous to Comparable method,
    So by convention, we should name a method compareTo only when we implements Comparable.
  • Wrong method may be called due to static binding
    // Apple#compareTo is called
    System.out.println(apple1.compareTo(apple2));
    Fruit fruitWhichIsApple2 = apple2;
    // Fruit#compareTo is called
    System.out.println(apple1.compareTo(fruitWhichIsApple2));
    

Although it is marked as protected(which avoid usage from other package class), it is much clear if we rename it to something else.

  • Related