SSブログ

クラスが適切にパラメータ化されていれば、ClassCastException がスローされます [項目12]

項目12 「Comparableの実装を検討する」のp.63から
この契約の数学的性質を取り去ってはいけません。equals の契約(項目8)と同様に、compareTo契約は、見かけほど複雑ではありません。1 つのクラス内では、どのような適切な順序関係であっても、compareTo 契約を満足します。equals と異なり、クラスをまたがっては、compareTo は機能する必要はありません。つまり、比較されようとしている2 つのオブジェクトが別々のクラスを参照しているのであれば、ClassCastException をスローすることが許されています。たいていは、ClassCastException をスローすることが、まさにcompareTo が行うべきことです。そして、クラスが適切にパラメータ化されていれば、ClassCastException がスローされます。契約は、クラス間の比較を排除してはいませんが、リリース1.6 では、Java プラットフォームライブラリーのどのクラスも、クラス間の比較をサポートしていません。
ここで話題となっているのは、java.lang.Comparableインタフェースです。その定義は、次の通りです。
package java.lang;

public interface Comparable<T> {
    int compareTo(T o);
}
これを実装するFooクラスを作成してみると次のようになります。
class Foo implements Comparable<Foo> {
    public int compareTo(Foo o) {
        // ここに比較のコード
        return 0; // とりあえずコンパイルできるようにするため
    }
}
そもそも、このcompareToメソッドには、nullかFoo型のインスタンスしか渡せませんので、別のクラスのインスタンスが渡されて、ClassCastExceptionがスローされることはありません。

では、「クラスが適切にパラメータ化されていれば、ClassCastException がスローされます。」とは何を意味しているのでしょうか。それを理解するには、バージョン1.5から導入されたジェネリックスがどのようにして実現されているかを理解していなければなりません。

Comparableはジェネリック型ですが、コンパイルされた場合には、その型変数Tは、何も境界が指定されていないので、Objectとしてコンパイルされます(イレイジャ)。つまり、compareToメソッドの引数の型は、Objectとなります。その結果、Comparableを実装するというのは、JVMから見れば次のインタフェースを実装していることが求められます。
package java.lang;

public interface Comparable {
    int compareTo(Object o);
}
しかし、Fooクラスで定義したcompareToメソッドは、compareTo(Object o)ではなく、compareTo(Foo o)です。したがって、JVMから見るとComparableインタフェースを実装していることになりません。でも、Javaの言語仕様としては、compareTo(Foo o)をソースコードに定義することで、ジェネリック型であるComparableを実装したことになります。この矛盾した状況を解決するために、コンパイラはFooクラスをコンパイルするとcompareTo(Object o)というメソッドを挿入します。

実際にコンパイルして、どのようなメソッドが存在するかをjavapコマンドで表示すると次のようになります。
Yoshiki-no-MacBook-Air:ej2e yoshiki$ javac Foo.java
Yoshiki-no-MacBook-Air:ej2e yoshiki$ javap Foo
Compiled from "Foo.java"
class Foo implements java.lang.Comparable<Foo> {
  Foo();
  public int compareTo(Foo);
  public int compareTo(java.lang.Object);
}
ソースコードには存在しないメソッドが追加されています。このようなメソッドをブリッジメソッドと呼びます。JVMから見るとこの追加されたメソッドがあるためComparableインタフェースを実装していることになります。では、そのメソッドの中で何を行っているかというと、次のような処理になっています。
public int compareTo(Object o) {
    return compareTo((Foo) o);
}
つまり、Foo型へキャストして、それからソースコード上に書かれたcompareTo(Foo o)メソッドを呼び出しています。このcompareTo(Object o)メソッドは、コレクションをソートする場合などに呼び出されます。

つまり、「クラスが適切にパラメータ化されていれば、ClassCastException がスローされます。」なのです。

ブリッジメソッドやイレイジャについては、拙著『Java 2 Standard Edition 5.0 拡張された言語仕様について』を参照してみてください。
nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

※ブログオーナーが承認したコメントのみ表示されます。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。