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:
- A class has method
compareTo(Fruit)
doesn't mean it implementsComparable<Fruit>
. Only the opposite is correct. - In java API, method requiring comparison is based on
Comparable
interface, instead of checking if the class hascompareTo
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 methodcompareTo
only when we implementsComparable
. - 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.