Checker CompareTo   as of Julia version 2.6.0 (built on 6 Sep 2018)

belongs to group Basic

Identify incorrect definitions of compareTo()


A class C that implements the raw interface java.lang.Comparable must implement the compareTo(Object) method. If C is made to implement the non-raw interface java.lang.Comparable<C>, then it must implement the compareTo(C) method instead.

Moreover, it is good practice to make compareTo() consistent with equals(): if the comparison of two objects yields 0, then they should be equal. The validity of this implication is in general undecidable. There are, however, frequent situations when, typically, this implication does not hold. An example is when equals() is inherited from java.lang.Object.

This checker verifies that compareTo(Object) is defined for classes implementing the raw java.lang.Comparable interface, instead of the (possibly more logical) method compareTo(C). Moreover, it verifies the consistency of compareTo() wrt equals().

Inconsistent definitions of compareTo()/equals() induce unexpected behaviors when objects are put inside most sorted collection classes of the standard Java library.

Action: use the non-raw interface java.lang.Comparable<C>; make equals() consistent with compareTo(). In most cases, this just amounts to providing the missing definition of equals().

Examples


Consider the following program:

public abstract class CompareTo implements Comparable {
  private static int nextId;
  private final int id = nextId++;

  public final int compareTo(CompareTo other) {
    return id - other.id;
  }
}

This checker issues the following warning:

CompareTo.java:6: [CompareTo: CompareToForNonObjectWarning] Did you want to define compareTo(Object) here?

since the wrong compareTo signature is used. In this example, the programmer should make CompareTo implement Comparable<CompareTo> or change the signature of the method into compareTo(Object) and check the type of the actual parameter inside the method's body.

Consider now the following program:

public abstract class CompareTo implements Comparable<CompareTo> {
  private static int nextId;
  private final int id = nextId++;

  public final int compareTo(CompareTo other) {
    return id - other.id;
  }
}

class A extends CompareTo {
  private final String name;

  public A(String name) {
    this.name = name;
  }
}

This checker issues the following warning:

CompareTo.java:1: [CompareTo: CompareToWithDefaultEqualsWarning] compareTo(Object) seems inconsistent with equals(Object)

since there might be non-equal instances of class A whose comparison yields 0. In this example, the programmer should, for instance, modify class CompareTo as follows:

public abstract class CompareTo implements Comparable<CompareTo> {
  private static int nextId;
  private final int id = nextId++;

  @Override
  public final int compareTo(CompareTo other) {
    return id - other.id;
  }

  @Override
  public boolean equals(Object other) {
    return other instanceof CompareTo && ((CompareTo) other).id == id;
  }
}

Consider now the following program:

public final class CompareToVsEquals3 implements Comparable<CompareToVsEquals3> {
  private int f;
	
  public CompareToVsEquals3(int f) {
    this.f = f;
  }

  @Override
  public int compareTo(CompareToVsEquals3 o) {
    if (f > 0)
      return -1;
    else
      return 0;
  }

  @Override
  public String toString() {
    return String.valueOf(f);
  }
}

This checker issues the following warning:

CompareToVsEquals3.java:10: [CompareTo: AsymmetricalCompareToWarning] compareTo does not seem symmetrical: o1.compareTo(o2) != o2.compareTo(o1)
CompareToVsEquals3.java:10: [CompareTo: CompareToWithDefaultEqualsWarning] compareTo seems inconsistent with the inherited Object.equals

since this implementation of compareTo() is not symmetrical and might return 0 also when the inherited equals() method returns false. The programmer should correct this class for instance in the subsequent way:

public final class CompareToVsEquals3 implements Comparable<CompareToVsEquals3> {
  private int f;
	
  public CompareToVsEquals3(int f) {
    this.f = f;
  }

  @Override
  public int compareTo(CompareToVsEquals3 o) {
    return f - o.f;
  }

  @Override
  public boolean equals(Object other) {
    return other instanceof CompareToVsEquals3 && ((CompareToVsEquals3) other).f == f;
  }

  @Override
  public int hashCode() {
    return f;
  }

  @Override
  public String toString() {
    return String.valueOf(f);
  }
}

As another example, it might be the case that compareTo() and equals() are not consistent, as in the following class:

public final class CompareToVsEquals9 implements Comparable<CompareToVsEquals9> {
  private int f;
	
  public CompareToVsEquals9(int f) {
    this.f = f;
  }

  @Override
  public int compareTo(CompareToVsEquals9 o) {
    if (this == o || this.equals(o))
      return 1;
    else
      return -1;
  }

  @Override
  public boolean equals(Object other) {
    return other instanceof CompareToVsEquals9 && ((CompareToVsEquals9) other).f == f;
  }

  @Override
  public String toString() {
    return String.valueOf(f);
  }
}

This checker issues the following warning:

CompareToVsEquals9.java:10: [CompareTo: CompareToInconsistentWithEqualsWarning] compareTo does not seem to return 0 if and only if equals returns true

since when this.equals(o) we have this.compareTo(o) == 1, rather than this.compareTo(o) == 0, as required by the contract of these two methods. The same solution as for CompareToVsEquals3 above applies to this case.