クラスが適切にパラメータ化されていれば、ClassCastException がスローされます [項目12]
項目12 「Comparableの実装を検討する」のp.63から
では、「クラスが適切にパラメータ化されていれば、ClassCastException がスローされます。」とは何を意味しているのでしょうか。それを理解するには、バージョン1.5から導入されたジェネリックスがどのようにして実現されているかを理解していなければなりません。
Comparableはジェネリック型ですが、コンパイルされた場合には、その型変数Tは、何も境界が指定されていないので、Objectとしてコンパイルされます(イレイジャ)。つまり、compareToメソッドの引数の型は、Objectとなります。その結果、Comparableを実装するというのは、JVMから見れば次のインタフェースを実装していることが求められます。
実際にコンパイルして、どのようなメソッドが存在するかをjavapコマンドで表示すると次のようになります。
つまり、「クラスが適切にパラメータ化されていれば、ClassCastException がスローされます。」なのです。
ブリッジメソッドやイレイジャについては、拙著『Java 2 Standard Edition 5.0 拡張された言語仕様について』を参照してみてください。
この契約の数学的性質を取り去ってはいけません。equals の契約(項目8)と同様に、compareTo契約は、見かけほど複雑ではありません。1 つのクラス内では、どのような適切な順序関係であっても、compareTo 契約を満足します。equals と異なり、クラスをまたがっては、compareTo は機能する必要はありません。つまり、比較されようとしている2 つのオブジェクトが別々のクラスを参照しているのであれば、ClassCastException をスローすることが許されています。たいていは、ClassCastException をスローすることが、まさにcompareTo が行うべきことです。そして、クラスが適切にパラメータ化されていれば、ClassCastException がスローされます。契約は、クラス間の比較を排除してはいませんが、リリース1.6 では、Java プラットフォームライブラリーのどのクラスも、クラス間の比較をサポートしていません。ここで話題となっているのは、java.lang.Comparableインタフェースです。その定義は、次の通りです。
これを実装するFooクラスを作成してみると次のようになります。package java.lang; public interface Comparable<T> { int compareTo(T o); }
そもそも、このcompareToメソッドには、nullかFoo型のインスタンスしか渡せませんので、別のクラスのインスタンスが渡されて、ClassCastExceptionがスローされることはありません。class Foo implements Comparable<Foo> { public int compareTo(Foo o) { // ここに比較のコード return 0; // とりあえずコンパイルできるようにするため } }
では、「クラスが適切にパラメータ化されていれば、ClassCastException がスローされます。」とは何を意味しているのでしょうか。それを理解するには、バージョン1.5から導入されたジェネリックスがどのようにして実現されているかを理解していなければなりません。
Comparableはジェネリック型ですが、コンパイルされた場合には、その型変数Tは、何も境界が指定されていないので、Objectとしてコンパイルされます(イレイジャ)。つまり、compareToメソッドの引数の型は、Objectとなります。その結果、Comparableを実装するというのは、JVMから見れば次のインタフェースを実装していることが求められます。
しかし、Fooクラスで定義したcompareToメソッドは、compareTo(Object o)ではなく、compareTo(Foo o)です。したがって、JVMから見るとComparableインタフェースを実装していることになりません。でも、Javaの言語仕様としては、compareTo(Foo o)をソースコードに定義することで、ジェネリック型であるComparableを実装したことになります。この矛盾した状況を解決するために、コンパイラはFooクラスをコンパイルするとcompareTo(Object o)というメソッドを挿入します。package java.lang; public interface Comparable { int compareTo(Object o); }
実際にコンパイルして、どのようなメソッドが存在するかをjavapコマンドで表示すると次のようになります。
ソースコードには存在しないメソッドが追加されています。このようなメソッドをブリッジメソッドと呼びます。JVMから見るとこの追加されたメソッドがあるためComparableインタフェースを実装していることになります。では、そのメソッドの中で何を行っているかというと、次のような処理になっています。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); }
つまり、Foo型へキャストして、それからソースコード上に書かれたcompareTo(Foo o)メソッドを呼び出しています。このcompareTo(Object o)メソッドは、コレクションをソートする場合などに呼び出されます。public int compareTo(Object o) { return compareTo((Foo) o); }
つまり、「クラスが適切にパラメータ化されていれば、ClassCastException がスローされます。」なのです。
ブリッジメソッドやイレイジャについては、拙著『Java 2 Standard Edition 5.0 拡張された言語仕様について』を参照してみてください。
2014-01-04 17:05
nice!(0)
コメント(0)
コメント 0