java.lang.Comparable インタフェースを実装するクラスのインスタンスは、順序性を持った比較を行えることを意味します。
public interface Comparable<T> {
int compareTo(T o);
}
Comparable
オブジェクトのコレクションは、標準ライブラリとして用意されているユーティリティクラスを使ってソートできるようになります。
List
要素として格納した場合に Collections.sort
でソートできるArrays.sort
でソートできるまた、Comparable
オブジェクトを前提としているソート済みコレクションの要素として扱うことができるようになります。
アルファベットや数値のように順序性を持っているとみなせる値クラスを定義する場合は、Comparable
インタフェースを実装すべきです。
Comparable#compareTo(T)
は下記のように実装します。
ClassCastException
を返す(適切にパラメータ化すればコンパイラが導入するブリッジメソッドによって自動的にこの実装が行われるので通常は意識する必要はない)下記は Comparable
インタフェースの実装例です。
double
型の浮動小数点数フィールドを比較する場合は、Double.compare
を使用していることに注意してください。
public class CompositeNumber implements Comparable<CompositeNumber> {
private int val1;
private double val2;
public CompositeNumber(int val1, double val2) {
this.val1 = val1;
this.val2 = val2;
}
@Override
public int compareTo(CompositeNumber o) {
if (val1 < o.val1) return -1;
if (val1 > o.val1) return 1;
return Double.compare(val2, o.val2);
}
}
Comparable#compareTo(T)
メソッドの契約である、「負の値を返す」「正の値を返す」という部分に注目してみてください。
これはつまり、負の値として -1
、正の値として 1
といった固定の値を返す必要はないということを示しています。
この特性に気付いた人は、整数フィールドの比較は下記のように減算を利用して効率化すべきだと思うかもしれません。
ただ、この実装には Java 特有の問題が含まれています。
@Override
public int compareTo(CompositeNumber o) {
if (val1 != o.val1) return val1 - o.val1; // オーバフローの可能性
return Double.compare(val2, o.val2);
}
上記の val1 - o.val1
という減算処理では、オーバフローの可能性に留意する必要があります。
Java の int 型の減算処理では、論理的な計算結果が Integer.MAX_VALUE
(2^31 - 1) を上回ってしまうとオーバフローが発生し、負の値が返されてしまいます。
下記のコードを実行してみれば分かりますが、正の値から負の値を引いた結果が負の値になってしまうケースがあります。
int a = 2000000000;
int b = -2000000000;
System.out.println(a - b); // -294967296 !?
そのため、若干効率的ではなさそうに見えても、整数フィールドの比較には <
や >
を用いた比較を行っておく方が安全です。
java.util.Date
などの compareTo
の実装も、下記のように比較演算子を使用して比較するようになっています。
public int compareTo(Date anotherDate) {
long thisTime = getMillisOf(this);
long anotherTime = getMillisOf(anotherDate);
return (thisTime<anotherTime ? -1 : (thisTime==anotherTime ? 0 : 1));
}
Comparable
インタフェースの契約として、推移的であること というものがあります。
(x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0.
また、対称性があることも求められています。
x.compareTo(y) の符号 != y.compareTo(x) の符号
Comparable
を実装したクラスを継承して、compareTo
メソッドをオーバライドしてしまうと、スーパークラスのインスタンスと、サブクラスのインスタンスを比較する際に、すべての契約を満たすことができないケースが出てきてしまいます(複雑なので詳細は省略)。
つまり、Comparable を実装している値クラスは継承してはいけない、という結論に達します。
値クラスを拡張する場合は、新しいクラスで、拡張したい値クラスのオブジェクトを保持する形(委譲)で拡張するようにしましょう。
新しいクラスで Comparable
を実装する場合は、保持している値クラスの compareTo
を利用して実装すれば OK です。