Java魂1章
ということで1章、Javaの復習のおかしな部分の紹介。長文注意。
まずは、1.1.1 ポインタに関する不変的な問題にて。
実際には、JavaはC++で言うところの参照を使用しています。
1.1.1 ポインタに関する不変的な問題より
・・・しょっぱなからこれ。Javaの参照は、C++の参照とは全くの別物だ。C++の参照は、エイリアスであり、Javaの参照はC/C++のポインタに近い。これは、以下のコードを実行すると良く分かると思う。
// test.cpp #include <iostream> int a = 10; // リファレンス版 void func(int& r) { r = a; } // ポインタ版 void func(int* p) { p = &a; } int main() { int i = 0; func(i); std::cout << "func(int&)後:" << i << std::endl; // 10 i = 0; func(&i); std::cout << "func(int*)後:" << i << std::endl; // 0 }
public class Test { private static Integer a = 10; private static void method(Integer i) { i = a; } public static void main(String[] args) { Integer i = 0; method(i); System.out.println("method(Integer)後:" + i); // 0 } }
この結果から、Javaの参照はC++の参照とは違うということが分かる。Javaの参照は、どちらかというとC/C++のポインタに近い性質を持つ*1。また、「参照を渡している」ともあるのだが、これははっきりと間違いである。ただしくは、「参照の値を渡している」である。
次は、1.2.3.1 forループの基本。
public static int[] extractXCoords2(final List points) { int[] results = new int[points.size()]; Point element = null; Iterator iter = points.iterator(); for (int idx = 0; iter.hasNext(); idx++) { element = (Point)iter.next(); results[idx] = element.x; } return results; }1.2.3.1 forループの基本より
このコードを「最初の例よりかなり簡潔です。」と言っている。最初のコードはwhileを使っているのだが、最初のほうが可読性はいいと思う*2。それに、下のコードのほうがよほど簡潔だと思う。可読性もいいし。
public static int[] extractXCoords2(final List points) { int[] results = new int[points.size()]; Object[] tmp = points.toArray(); for (int i = 0; i < results.length; i++) { Point element = (Point) tmp[i]; results[i] = element.x; } return results; }
また、以下のコードを、
これはforループで行えることを示す素晴らしい実例です。
1.2.3.1 forループの基本より
と紹介している。
for (System.setProperty("user.sanity", "minimal"); exit == false; System.out.println(System.currentTimeMillis())) { // 何らかのコードを実行する idx++; if (idx == 10) { exit = true; } }1.2.3.1 forループの基本より
こんなコードを自信満々に紹介しないでよ*3・・・ 文法的には確かに正しいが、こんな自分勝手で他人に分かりにくいコードは絶対書くべきではないと思う。
この後、break、continue、ラベル、assertについてのつまらない説明が入る。で、その次が「1.2.6 コンストラクタの連結」。これもまたつまらない。要は、thisで自分のクラスのコンストラクタが呼べますよ、って話。ちなみに、この本の元の本の題名は、「Hardcore Java」である。
さて、この後の「1.3.1 好ましい制限」でも、素晴らしい暴論を吐いてくれる。
ヘルパメソッドをprotectedにすると、ユーザがそのクラスを拡張できます。また、protectedにすると、ユーザはそのメソッドを直接使用できないので、そのメソッドに対するセキュリティも保持できます。その反面、そのクラスを拡張したクラスは、ヘルパメソッドにアクセスできます。しかし、通常は、そのクラスを拡張する人はそのヘルパメソッドの動作を知っていることが前提となります。たとえ知らなかったとしても、最悪でもその派生クラスを破壊することになるだけです。
〜略〜
最後にまとめると、属性にはprivate、公開するメソッドにはpublic、ヘルパメソッドにはprotectedを使用したほうが賢明です。これにより、カプセル化を破壊することなくクラスに最大限の再利用性をもたらすことができます。
1.3.1 好ましい制限より
・・・本気で言っているのだろうか。ヘルパメソッドのアクセス制限を常にprotectedにするのは非常に危険である。そのメソッドをオーバーライドしてpublicにされる可能性もあるし、なによりLSPが破られる可能性が出来てしまう。
public class Helper { private int i = 10; public final int get() { return i; } public final void set(int i) { this.i = i; } protected void reset() { i = 10; } public int div10(int a) { reset(); return a / i; } private static void hack1() { Helper h = new Helper() { protected void reset() { set(0); // ! } }; // 以下は実行時エラー System.out.println(h.div10(10)); } private static void hack2() { class HackedHelper extends Helper { public void reset() { // ! set(0); } } HackedHelper h = new HackedHelper(); // HackedHelperを使うとヘルパメソッドがどこからでも呼べる h.reset(); } public static void main(String[] args) { hack1(); hack2(); } }
少々作為的な例だが、ヘルパメソッドを何でもかんでもprotectedにすればいいというのは完全に間違いだと思う*4。この例では示していないが、ヘルパメソッドをオーバーライドすると上位のクラス*5の前提に依存したクライアントコードを破壊してしまうこともある。
1.4.1にはこんな記述もある。
JavaのSystemストリームには、コンソールに対する読み書きの機能があります。
1.4.1 Systemストリームより
Systemストリーム?聞いたことがない。これは自分が無知なのかもしれないが、Googleに聞いてみても、MPEG関連か、この本の目次しか引っかからない。
一章、Javaの復習だけでこれである。著者様自身が復習すべきですね。2章以降もめちゃくちゃです。対象読者が中上級者でよかったですよ。中上級者なら、間違いを探しながら楽しく読めますし*6。ただ、そのために3600円が払えるかというと・・・あぁ、お金返して欲しいなw