ElvisクラスのleaveTheBuilding()メソッド [項目3]
(こちらのブログ記事からの転記です)
『Effective Java 第2版』の項目3 「privateのコンストラクタかenum型でシングルトン特性を強制する」(17頁)の
Wikipediaの"Elvis has left the buidling"よれば、次のように説明されています。
このようなpun(駄洒落)は、英語のネイティブスピーカーではない私にとっては、翻訳者泣かせです。今回のような例は、特に翻訳する必要もないので問題ないのですが、『Java Puzzlers』を翻訳した時は、かなり苦労しました。
この本では、とにかくパズルのタイトルが駄洒落や語呂合わせだらけでした。最初は著者のJoshua Blochに都度問い合わせして、訳注を追加していました。しかし、あまりにも多いので、訳注では無理と判断しました。それで、日本語版に向けて特別に付録C「語呂合わせとポップカルチャー参照」という解説のための付録を書いてもらって、それを翻訳したものを日本語版に含めたのです。
ところで、この『Java Puzzlers』ですが、タイトルが良くないのかあまり売れていないです。一度、その点に関してJoshua Blochと話した時に、『Defective Java』の方が良かったかもと冗談を彼は言っていました。内容としては、Javaでプログラミングする人にとっては、『Effective Java 第2版』と同様に必読だと私は思っています。Javaでプログラミングする上で知っておくべきことを、パズル形式で教えてくれる内容です。発売からすでに3年以上経過していますが、内容は陳腐化していません。
【追記:2014年6月16日】
言語仕様の一部変更、コンパイラのバグ修正等で、『Java Puzzlers』の2、3個のパズルが適切ではなくなっています。残念ながら『Java Puzzlers』は、絶版となっています。
『Effective Java 第2版』の項目3 「privateのコンストラクタかenum型でシングルトン特性を強制する」(17頁)の
Elvis
クラスには、初版にはなかったleaveTheBuidling()
メソッドが追加されています。翻訳した時は、メソッドが追加されているなという程度で深く気にしなかったのですが、調べてみました。Wikipediaの"Elvis has left the buidling"よれば、次のように説明されています。
"Elvis has left the building!" is a phrase that was often used by public address announcers following Elvis Presley concerts to disperse audiences who lingered in hopes of an Elvis encore. Al Dvorin, a concert announcer who traveled with Elvis throughout the performer's career, made the phrase famous when his voice was captured on many recordings of Elvis' performances.Elvis Presleyのコンサートで、アンコールを期待して帰らない聴衆に対して使われた言葉のようです。
Elvis
クラスに追加されたleaveTheBuilding()
メソッドを見て、分かる人にはすぐに分かるのでしょうが・・・・このようなpun(駄洒落)は、英語のネイティブスピーカーではない私にとっては、翻訳者泣かせです。今回のような例は、特に翻訳する必要もないので問題ないのですが、『Java Puzzlers』を翻訳した時は、かなり苦労しました。
この本では、とにかくパズルのタイトルが駄洒落や語呂合わせだらけでした。最初は著者のJoshua Blochに都度問い合わせして、訳注を追加していました。しかし、あまりにも多いので、訳注では無理と判断しました。それで、日本語版に向けて特別に付録C「語呂合わせとポップカルチャー参照」という解説のための付録を書いてもらって、それを翻訳したものを日本語版に含めたのです。
ところで、この『Java Puzzlers』ですが、タイトルが良くないのかあまり売れていないです。一度、その点に関してJoshua Blochと話した時に、『Defective Java』の方が良かったかもと冗談を彼は言っていました。内容としては、Javaでプログラミングする人にとっては、『Effective Java 第2版』と同様に必読だと私は思っています。Javaでプログラミングする上で知っておくべきことを、パズル形式で教えてくれる内容です。発売からすでに3年以上経過していますが、内容は陳腐化していません。
【追記:2014年6月16日】
言語仕様の一部変更、コンパイラのバグ修正等で、『Java Puzzlers』の2、3個のパズルが適切ではなくなっています。残念ながら『Java Puzzlers』は、絶版となっています。
ハッシュコードの衝突の増加 [項目9]
(こちらのブログの記事からの転記です)
項目9「
EFFECTIVE JAVA 第2版 (The Java Series)
- 作者: Joshua Bloch
- 出版社/メーカー: 丸善出版
- 発売日: 2014/03/11
- メディア: 単行本(ソフトカバー)
項目9「
equals
をオーバーライドする時は、常にhashCode
をオーバーライドする」では、ハッシュコードの計算方法がp.47に示されています。そして、計算の最初のステップとして、次のようにのべられています。この17を使用することに関して、p.48には次のように述べられています。
- 何らかのゼロではない定数、たとえば、17を、
result
という名のint
変数に保存します。
ゼロでない初期値がステップ1で使用されていますので、ステップ2aで計算された結果ゼロとなるハッシュ値を持つ最初のフィールドから、ハッシュ値は影響を受けます。もし、ステップ1で初期値としてゼロが使用されたら、全般的なハッシュ値は、そのような最初のフィールドから影響を受けないことになり、衝突が増加することになります。値17は、任意の値です。この衝突が増加するということに関しては、ハッシュコードの計算対象となるフィールドが固定数であれば、値が0であっても衝突は増加しません。たとえば、次のような
Point
クラスにhashCode
メソッドを実装すると、計算対象のフィールドはx
とy
の2つです。この場合、初期値がゼロでもハッシュコード値の衝突が増えることはありません。では、次のようなクラスではどうでしょうか。public class Point { public final int x; public final int y; public Point(int x, int y) { this.x = x; this.y = y; } }
このpublic class IntArray { private int[] data; public IntArray(int[] data) { this.data = data.clone(); } .... }
IntArray
クラスは、int
の配列を保持することになります。したがって、ハッシュコードの計算対象は、配列の個々の要素となります。そして、その要素の個数はインスタンスごとに異なります。もし、ハッシュコードの初期値としてゼロを使用すると、簡単にハッシコードが衝突します。次の二つのインスタンス生成で作成されたインスタンスは、ハッシュコードの初期値としてゼロが使用されると、どちらもハッシュコード値がゼロとなり、衝突することになります。new IntArray(new int[] {0}); new IntArray(new int[] {0, 0});
クラスが適切にパラメータ化されていれば、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 拡張された言語仕様について』を参照してみてください。
このブログについて [このブログについて]
Effective Java 第2版 (The Java Series)
- 作者: Joshua Bloch
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2008/11/27
- メディア: 単行本(ソフトカバー)
2000年から以下の書籍を使用した「Java研修」を行ってきました。
Java研修では、『Effective Java』をきちんと理解して実践で役立てもらうための基礎知識を『プログラミング言語Java』で先に学習してもらっています。しかし、『Effective Java』の読者は、十分な基礎知識なしでいきなり『Effective Java』を読まれている人が多いと思います。
このブログでは、研修の受講生からの質問を中心として『Effective Java 第2版』の内容に関して理解を助けるための解説を書いていきます。
注意事項としては、以下の通りです。
- 必ずしも書籍の最初の項目から順番とは限りませんので、順不同となります。
- 必要に応じて『プログラミング言語Java第4版』の記述を引用することになります。
- 不定期に書いていきます。