オブジェクトの大小を判定することを比較と言います。英語ではcompareという単語を当てています。基本データ型と違いオブジェクトはそのデータの持ち方も様々ですから大小を判定すると言っても何を元に判定すれば良いか一概には決められません。Javaではこの大小比較について自分で定義することができます。
等しいことを判定するときの誤解
まず初めに良くある誤解の例を見ます。基本データ型については二つの値が等しいかを判定するには==をつかえば良いのでした。しかしオブジェクト同士の比較での==の利用はオブジェクトの参照先(メモリ上のどこに記憶しているかの場所の情報)の比較のため内容が同じであるかの判定はしません。
以下のようなクラスを定義します。
class ClassA { int number; ClassA(int number){ this.number = number; } }
引数で取得したintデータを内部に持っているクラスです。このクラスを利用して以下のように二つのインスタンスを作り比較することにします。
ClassA a = new ClassA(1); ClassA b = new ClassA(1); System.out.println(a == b);
結果は以下の通りです
false
同じ値を引数に入れているのにもかかわらず「等しくない」と判断されるのはこの二つの変数が別々に作成され別々の場所に記憶されているからです。
以下のように参照先を同じにするとtrueになります。
ClassA a = new ClassA(1); ClassA b = a; System.out.println(a == b);
同じ場所に記憶した同じインスタンスを比較しているのですから当然といえば当然です。
Objectクラスにはequalsメソッドという比較用のメソッドがありますがこれもfalseになります。
ClassA a = new ClassA(1); ClassA b = new ClassA(1); System.out.println(a.equals(b));
Comparableの導入
Comparableというインタフェースがあります。これをクラスに実装することによってこのクラスは「比較できる」機能があることを担保できます。一般にインタフェースには-ableという可能性を示す形容詞化語尾をつけることがあります。通常のクラスを名詞とするとインタフェースは可能性を表す形容詞と捉えるのはいいアイディアだと思います。
Comparableを実装するにはこのインタフェースで宣言されているintを返すcompareToというメソッドを実装する必要があります。
このメソッドを実装するにあたり以下のことに注意します。(インスタンスAとインスタンスBはそれぞれA、Bと表記しています。
compareToの実装内容については以下の通りにします。
- A.compareTo(B)を実施したときAとBが等しい場合には0を戻す
- A.compareTo(B)を実施したときA < Bの場合は負の数字を返す
- A.compareTo(B)を実施したときA > Bの場合は正の数字を返す
この三点が守られて入れば以下の条件もクリアできるはずです。
- A.compareTo(B)が0の場合B.compareTo(A)も0であること
- A.compareTo(B)が負の数字の場合B.compareTo(A)は正の数字であること
- A.compareTo(B)が正の数字の場合B.compareTo(A)は負の数字であること
- A.compareTo(B)が0の場合A.compareTo(C)とB.compareTo(C)は同じ値であること
- A.compareTo(B)が正の数字でB.compareTo(C)が正の数字の場合A.compareTo(C)も正の数字となること
これで実装して見ます。クラスの内部にあるフィールド変数numberを比較するようにしています。int型の比較はint型のラップクラスであるIntegerクラスに実装があるのでこれを利用します。
またequalsメソッドも実装しています。継承元(extends指定がないクラスの継承元はデフォルトでObjectです)のObjectクラスにあるequalsメソッドをオーバーライドつまり上書きしています。引数のオブジェクトがClassAのインスタンスの場合はcompareToの結果が0であるかを確認しています。インスタンスでない場合は一律falseを返しています。
@Overrideというアノーテーションは書いておくとメソッドの上書きであることが保証されます。仮にメソッド名を間違えてしまうとコンパイル出来なくなる為上書きされていないことに気づくことが出来ます。
class ClassA implements Comparable { int number; ClassA(int number){ this.number = number; } @Override public int compareTo(ClassA o) { return Integer.compare(number, o.number); } @Override public boolean equals(Object o){ if(o instanceof ClassA){ return 0 == compareTo((ClassA)o); } return false; } }
さて再度以下のコードを実行して見ましょう。
ClassA a = new ClassA(1); ClassA b = new ClassA(1); System.out.println(a.equals(b));
今度はtrueが帰ってきます。これはClassAが比較方法を定義していて、同じになるインスタンス同士を比較しているからです。