Comparable インタフェースを実装するということは、メソッドの呼出しに関する一般契約に従ってcompareTo()メソッドを実装するということである。TreeSet や TreeMap のようなライブラリクラスは、Comparableオブジェクトを受け取り、そのcompareTo()メソッドを使ってオブジェクトをソートする。しかし、compareTo()メソッドを期待とは異なる方法で実装するクラスは予期せぬ結果をもたらすだろう。
Java SE 6 API 仕様 [API 2006] には compareTo() メソッドの一般契約について以下のように記されている。
- 実装では、すべての x と y に対して sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) が保証されなければならない。これは、y.compareTo(x) が例外を投げる場合は x.compareTo(y) も例外を投げることを意味する。
- 実装では、順序関係が推移的であることも保証しなければならない。つまり(x.compareTo(y)>0 && y.compareTo(z)>0) は x.compareTo(z)>0 を意味する。
- さらに、すべての z に対して x.compareTo(y) == 0 が sgn(x.compareTo(y) == sgn(x.compareTo(z)) を意味することも保証しなければならない。
- 必須というわけではないが、(x.compareTo(y)==0) == (x.equals(y)) であることが強く推奨される。一般に、Comparable インタフェースを実装しているクラスで、この条件に違反クラスはすべて、明確にこの事実を示す必要がある。「注:このクラスは equalsと一貫性のない自然順序付けを持つ」などと明示することが推奨される。
前述の説明では、sgn(expression) という表記は数学関数 signum 関数を表し、expression の値(負の数、ゼロ、正の数)に応じて、-1、0、1 のどれかを返す。
compareTo() メソッドの実装は前述の最初の3つの条件に決して違反してはならない。また、可能な限り第4の条件にも準拠すべきである。
違反コード (じゃんけん)
以下のプログラムはcompareTo()メソッドを使ってゲームの勝者を判別するじゃんけん(rock-paper-scissors)ゲームを実装している。
class GameEntry implements Comparable { public enum Roshambo {ROCK, PAPER, SCISSORS} private Roshambo value; public GameEntry(Roshambo value) { this.value = value; } public int compareTo(Object that) { if (!(that instanceof Roshambo)) { throw new ClassCastException(); } GameEntry t = (GameEntry) that; return (value == t.value) ? 0 : (value == Roshambo.ROCK && t.value == Roshambo.PAPER) ? -1 : (value == Roshambo.PAPER && t.value == Roshambo.SCISSORS) ? -1 : (value == Roshambo.SCISSORS && t.value == Roshambo.ROCK) ? -1 : 1; } }
しかし、このゲームは、一般契約で要求されている推移性に違反している。石はハサミに勝ち、ハサミは紙に勝つが、石は紙に勝たない。
適合コード (じゃんけん)
以下の適合コードはComparableインタフェースを使わずに同じゲームを実装している。
class GameEntry { public enum Roshambo {ROCK, PAPER, SCISSORS} private Roshambo value; public GameEntry(Roshambo value) { this.value = value; } public int beats(Object that) { if (!(that instanceof Roshambo)) { throw new ClassCastException(); } GameEntry t = (GameEntry) that; return (value == t.value) ? 0 : (value == Roshambo.ROCK && t.value == Roshambo.PAPER) ? -1 : (value == Roshambo.PAPER && t.value == Roshambo.SCISSORS) ? -1 : (value == Roshambo.SCISSORS && t.value == Roshambo.ROCK) ? -1 : 1; } }
リスク評価
compareToメソッドを実装する場合にその一般契約に違反すると予期せぬ結果が得られ、その結果、誤った比較を行ったり、情報漏えいを引き起こしたりする恐れがある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
MET10-J | 中 | 低 | 中 | P4 | L3 |
自動検出
このルールの違反を自動検出することは一般に不可能である。
関連ガイドライン
CERT C++ Secure Coding Standard | ARR40-CPP. Use a valid ordering rule |
MITRE CWE | CWE-573. Improper following of specification by caller |
参考文献
[API 2006] | Method compareTo() |
[JLS 2005] |
翻訳元
これは以下のページを翻訳したものです。
MET10-J. Follow the general contract when implementing the compareTo() method (revision 97)