LoginSignup
8
6

Java Goldの黒本に対する批判

Last updated at Posted at 2024-03-15

初めに

この記事は志賀澄人氏の著書「徹底攻略Java SE 11 Gold問題集」に対する批判記事です。
2022年6月11日 第1版第2刷発行のものを引用しています。
この記事を書いた私自身は別に卓越したプログラマでもなんでもないので、誤りがあれば訂正をお願いします。

2024/4/4追記
黒本の内容の粗探しをしているわけではないのですが、自分が当初想像していた以上に誤りが多いためこの記事も今後頻繁に更新するかもしれません。
黒本のサンプルコードをすべて写経までして勉強してプログラミングの知識をつけてしまいました。今自分の中にある知識も何が正しくて何が間違っているのかわからない状態です。他の書籍やネットの情報を見て、そういえば黒本に書いてあった内容と食い違うなと思ったときには何が正しいのか調べてここに書いていきます。

2024/4/7追記
出版社に問い合わせてから3週間がたちましたが、まだ回答には時間がかかるようです。申し訳ないとは思っているのですが、最初の問い合わせの後にも複数の誤りを見つけたため、それに関して再度問い合わせました。回答に時間がかかるということは私の批判は少なくとも的外れではないのでしょう。
Builderパターンに関しては単独で問い合わせていたため先に回答が来たのですが、著者からの回答は要領を得ないものでした。回答から察するに、著者はおそらくデザインパターンを理解していません。
黒本は現場経験一年程度のエンジニアである私が見てもJavaの理解を誤っていると考えられる箇所がたくさんあります。より経験豊富なエンジニアが見れば誤りだらけなのかもしれません。
ですが、何より問題なのは黒本がJavaの参考書として世間に受け入れられていることでしょう。何年もの間誰も誤りを指摘してこなかったことが不思議で、残念で仕方がありません。日本のこの業界のレベルが低いことを端的に表していると思います。

2024/4/13追記
ガベージコレクションについて誤りがあると指摘していましたが、私の指摘も不十分でした。すぐリライトします。ガベージコレクションに関して著者が二重に間違えていたためです。黒本は第12章の第4問の正解をBとしていますがおそらく正解はCです。また、第12章の内容はすべて誤りかもしれません。
また、この記事もだいぶ見づらくなってきたので趣旨は変えずにリライトする予定です。タイトルも変更するかもしれません。

2024/4/14追記
黒本が試験内容をすべてカバーしていないことに気づいて、もうどうでもよくなりました。うすうす気づいていましたが、著者は試験対策本を書くにあたって試験内容を確認していません。試験に出る内容を書かず試験に出ない内容を書く試験対策本というスタンスならそれを突き通せばいいんじゃないでしょうか。本を執筆するにあたってろくに調査もせずに自分の主張だけは書いて結果として誤った内容になっているのにそれにだれも気づけないなんて話があるんですね。出版社は著者を問い詰めたほうがいいと思います。あと、黒本の内容を訂正したところで私には何の得にもなりませんし、もう記事の更新もやめます。今私が欲しいのは黒本で得た知識を頭から完全に消去する機械です。

第1章

staticで修飾されたネストしたクラス

黒本の29ページには

クラス内に定義したクラスのことを総称して「ネストしたクラス」と呼びますが、正確にはネストしたクラスにはいくつかの種類があり、それぞれ次のような名称が異なります。

と書かれており、「ネストしたクラス」として以下の4つの種類が紹介されています。

  • インナークラス
  • staticインナークラス
  • ローカルクラス
  • 匿名クラス

これ以降、なんの断りもなく「staticインナークラス」という用語が黒本を通して使用されていますが、staticで修飾されたクラスはインナークラスではありません。
以下の通りオラクルの文書で明言されています。

An inner class is a nested class that is not explicitly or implicitly declared static.

Inner Classes and Enclosing Instances

もし黒本だけで学習していたらstaticインナークラスの存在に疑いの目を向けることはありませんでした。黒本の誤りに気づけたのは、黒本とは別に「スッキリわかるJava入門 実践編」という本を買っていたためです。
その本には以下の通り書かれていました。

厳密には、static付きのものはJava言語仕様上、インナークラスの範囲には含まれないことになっています。

驚くべきことにこの二冊は同じ出版社から出ています。「スッキリ」がJavaの言語仕様に準拠した記載を心掛けている一方、黒本の記載は正確さに欠けます。これに気づいたため黒本の記載は誤記の範疇を超えて虚偽であると感じ、黒本自体に批判的な目を向けるようになりました。

2024/4/3追記
やはりEffective Javaでも「staticのメンバークラス」という呼称が用いられ、「staticのメンバークラス以外は、内部クラス(inner class)として知られています。」と書かれています。
そんな名前なんてどうでもいいじゃんと思う人もいるかもしれませんが、これは著者が公式ドキュメントやEffective Javaを読んでないか、読んだうえで読者をだまそうとしているかの証明です(後者だとは思いたくありません)。
おそらくEffective Javaを読んでいないからインナークラスに対して自由な発想が書けるのだと思います。

staticで修飾されたネストしたクラスのメンバへのアクセス

第1章の第4問です。問題は以下の通りです。

次のプログラムをコンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)

public class Outer {
    private int num = 2;
    public void hoge() {
        Inner test = new Inner();
        test.data = 100;
        test.execute();
    }
    private static class Inner {
        private int data;
        void execute() {
            System.out.println(num * data);
        }
    }
}

そして解答の選択肢の一部が以下です。

A. 5行目でコンパイルエラーが発生する

このコードを読んだとき、直感的に5行目でコンパイルエラーが発生すると思いませんか。Innerクラスのprivateフィールドであるdataを直接呼び出し、100を代入することは一見して不可能です。
ですが、Javaの仕様上dataに100を代入することは可能です。
しかし黒本にはそれが可能である理由が一切記載されておらず、投げやりだと感じます。

2024/4/3追記
なお、Effective Javaは114ページで以下のように解説しています。

staticのメンバークラスは、(中略)たまたまあるクラス内で宣言され、そのエンクロージングクラスのメンバーすべてに、たとえそのメンバーがprivateと宣言されていてもアクセスできる通常のクラスと考えるのがよいです。

ローカルクラス

39ページでローカルクラスについて以下のように解説しています。

ローカルクラスの使い方の特徴は、メソッドごとに実装内容を変えられることです。

この記述は意味不明です。
ローカルクラスの第一の特徴はメソッド外からのアクセスを禁止できることではありませんか。

クロージャ

42ページでクロージャについて以下のように解説しています。

Java以外のプログラミング言語には、ローカルクラスのインスタンスからローカル変数にアクセスするときに、自由にローカル変数の値を変更できる「クロージャー」と呼ばれる仕組みが用意されているものがあります。

クロージャを「自由にローカル変数の値を変更できる」仕組みと説明するのは適切でなく、さらにJavaにもラムダ式が導入されたJava 8以降から既にクロージャは存在します。

匿名クラス

43ページで匿名クラスについて以下のように解説しています。

前問ではメソッド内に定義するローカルクラスについて解説しましたが、クラスの定義を特定のメソッド内だけで使用し、ほかのメソッドと共有しないのであれば、匿名クラスを使用するほうがよいでしょう。

匿名クラスの認識について根本から誤りがあるように思います。
まず、匿名クラスはローカルクラスの代替ではありません。匿名クラスはJavaにラムダ式が導入される以前に処理を引数として渡すために用いられたものです。
しかし、例として掲載されているコードにはそのようなものは一切なく、あろうことか生成したインスタンスを変数に格納して使用しています。
匿名クラスはローカルクラスの代替であるという認識があるからか、45ページには次のような解説があります。

匿名クラスには、コンストラクタは定義できません。コンストラクタは、クラス名と同じ名前のメソッドでなければいけませんが、匿名クラスには名前がないためです。匿名クラスを初期化したい場合は、コンストラクタの代わりに初期化子「{}」を使用します。

匿名クラスにコンストラクタが定義できないのは、本当に匿名クラスに名前がないためでしょうか。仮にJavaのコンストラクタがPythonで__init__と定義されるようにinit()という名前で定義されていたとします。そうすればコンストラクタは実装が可能ですが、どのような用途があるでしょうか。
匿名クラスのメリットはコードの記述の省略にあります。定義と同時にインスタンスを生成するため、メソッドの引数に記述することができるようになります。(そして、今やその役割はラムダ式にとって代わられました)。
例として掲載されているコードのように匿名クラスのインスタンスを変数に格納して利用したところで何のメリットもありません。
匿名クラスは定義したその場で使い捨てるため、コンストラクタで初期値を設定してインスタンスごとにふるまいを変えるようなクラスらしい使い方をするのは誤りでしょう。
また、変数に格納して利用するなら、より自由度の高いローカルクラスを使えばいいのです。
解説の後半では匿名クラスは初期化子で初期化すると記載されていますが、例として掲載されているコードでは以下のような奇妙なことを行っています。初期化する例は掲載されていません。

List<String> list = new ArrayList<String>() {
    {
        super.add("A");
        super.add("B");
        super.add("C");
    }
}

2024/4/3追記
なお、Effective Javaは195ページで以下のように解説しています。

関数オブジェクトを作成する主な手段は無名クラス(anonymous class)でした。

無名クラスは、関数オブジェクトが必要な古典的なオブジェクト指向デザインパターン、とりわけStrategyパターンに対しては適切でした。

もちろん、Effective Javaは無名クラスよりもラムダ式を選ぶことを推奨しています。

第2章

関数型インタフェース

関数型インタフェースの使用例として、それぞれに以下のようなコードが掲載されています。以下は81ページに掲載されているPredicateの例です。

Predicate<String> p1 = x -> x.isEmpty();
System.out.println(p1.test(""));

これを使用例として記載するのはいかがかと思います。実際にこのような使い方はしないからです。
私が昔いた現場に自称Javaができる人がいたのですが、実際にこのようなコードを書いており非常に厄介でした。

2024/4/8追記
ページ数を誤っていたので修正しました。

Supplier(2024/4/5追記)

70ページでファクトリクラスを解説した上で、71ページでSupplierを利用すればファクトリクラスは不要になると解説しています。そこに例として書かれているコードをクラス図としてまとめ上げると以下のようになります。

image.png

TestクラスはBクラスやCクラスのインスタンスを利用しますが、ファクトリクラスがあることでBクラスやCクラスには依存しなくて済み、これがファクトリクラスの利点だと説明しています。なお、SampleクラスはTestクラスのexecuteメソッドを実行するためのエントリポイントです。この時点でexecuteメソッドに引数はありません。
黒本ではここからSupplierを利用してファクトリクラスを不要にしていくそうです。まず、TestクラスのexecuteメソッドをSupplier型の引数を受け取るように変更します。

public void execute(Supplier<A> supplier)

そして、Testクラスのexecuteメソッドを呼び出す際に、Bのインスタンスを生成するラムダ式を引数として渡します。その結果、「ファクトリクラスを使わなくなった分だけ、コードが簡潔にな」るそうです。
では、簡潔になったクラスの依存関係はどうなったでしょうか。もちろん、以下のようになります。

image.png

ファクトリクラスで生成していたインスタンスをSampleクラスが生成するようになったため、SampleクラスがAインタフェース、Bクラス、Cクラスに依存するようになりました。
そのうえで73ページで以下の通り解説しています。

ここでは、GoF(Gang of Four)が記したデザインパターンから「Strategyパターン」を取り上げ、ラムダ式と関数型インタフェースを使ったコードを紹介しています。本書では、Strategyパターンそのものの説明は割愛しますが、Webサイトなどを参考に学習することをお勧めします。

何を解説したいのかわかりませんが、指摘するならば以下でしょうか。

  • そもそもファクトリクラスって何だろうか。黒本独自の用語ではないか。
  • サンプルコードではSupplierを適用する前と後でクラスの依存関係が変化しているが、なぜ言及しないのか。
  • Supplierを使用する意味があるのか。直接インスタンスを渡せばいいのではないか。
  • これがStrategyパターンだというなら最初からStrategyパターンの説明をすればよかったのではないか。

Builderパターン(2024/3/30追記)

76ページでGoFのBuilderパターンとしてEffective JavaのBuilderパターンを紹介しています。名前は同じですが両者は全くの別物です。GoFのBuilderパターンはオブジェクトの生成に関するデザインパターンですが、Effective JavaのBuilderパターンはコンストラクタの引数を生成するためのJava特有のデザインパターンです。
Effective JavaのBuilderパターンは引数の省略ができないJavaの言語仕様の欠陥を補うためのパターンであり、引数を省略できるC#のようなほかの言語では用いません。
GoFの著書を参照していればこのような勘違いはありえませんが、おそらく参照していないのでしょう。黒本には参考文献のリストがありません。参考文献も明示しないような書籍の情報を今まで信じていた自分が馬鹿で悔しいです。

2024/4/3追記
GoFのBuilderパターンとEffective JavaのBuilderパターンを全くの別物としていましたが、Effective Javaを読む限りEffective JavaのBilderパターンはGoFのBuilderパターンを軸にしているようです。
ただし、ビルダーのセッターメソッドがビルダー自身を返すよう、すなわち、セッターメソッドの呼び出しを連鎖できるようにデザインすることはGoFのBuilderパターンには関係ありません。Effective Javaに関してもこの呼び出しの連鎖に関しては流れるようなAPIという呼称をつけています。
黒本では流れるようなAPIのことをGoFのBuilderパターンであると説明しているので、誤りであることに変わりはありません。
GoFのBuilderパターンとして紹介するのであればBuilderクラスのほかに少なくともDirectorクラスは必要かなと思います。
また、黒本で紹介されているBuiderパターンではオプションパラメータをデフォルト値に初期化することが行われていないので、Effective JavaのBuilderパターンを完全になぞっているわけでもありません(これに関してはnameフィールドがnullであることを初期値とするなら問題はありませんが、それはそれでnullに対して無頓着かなと思います)。

2024/4/4追記
黒本の77ページにはConsumer型を活用したBuilderパターンの例が記載されていますが、これもおかしいです。今まで自分も騙されていたので他の初学者も騙されているかと思います。

import java.util.function.Consumer;

public class Item {
    private int id;
    private String name;
    private int price;

    private Item(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.price = builder.price;
    }

    @Override
    public String toString() {
        return "Item [id=" + id + ", name=" + name + ", price=" + price + "]";
    }
    
    public static class Builder {
        private int id;
        public String name;
        public int price;

        public Builder(int id) {
            this.id = id;
        }
        public Builder with(Consumer<Builder> consumer) {
            consumer.accept(this);
            return this;
        }
        public Item build() {
            return new Item(this);
        }
    }
}

解説には「ビルダーのフィールドをpublicとして公開し、Consumerを受け取るwithメソッドを提供してい」ると書かれています。
以下のように使用するようです。

public class Sample {
    public static void main(String[] args) {
        Item item = new Item.Builder(100)
                .with(b -> {
                    b.name = "orange";
                    b.price = 120;
                })
                .build();
        System.out.println(item);

まずビルダーのフィールドをpublicとして公開することが誤りですが、それを正しいとしたとしてもwithメソッドを使用する意味がありません。フィールドをpublicとして公開したのであれば、以下のようにフィールドに直接アクセスして値を書き換えればよいのです。

public class Sample {
    public static void main(String[] args) {
        Item.Builder b = new Item.Builder(100);
        b.name = "orange";
        b.price = 120;
        Item item = b.build();
        System.out.println(item);
    }
}

BuilderパターンにConsumerを導入する目的は「setterメソッドを増やすことなく、簡潔にコードを記述」することと解説されていますが、フィールドをpublicにして公開したならばConsumerでwithメソッドを定義する必要もありません。私の示したビルダーの利用法であればwithメソッドを定義する必要もなく、はるかに「簡潔にコードを記述」できます。
そもそもフィールドをpublicとして公開することが誤りなのですが。

Predicate

黒本では関数型インタフェースのPredicateの意味を「断定する」と説明していますが、正しくは「述語」です。
オラクルのAPIドキュメントにもはっきりと「述語」と書かれています(が著者はドキュメントを読んでいないのでしょうか)。

2024/4/7追記
勘違いされるといやだなと思ったので追記しますが、この指摘は翻訳を間違えているから訂正してほしいというものではありません。
実はこの記事を書く少し前に、Java Silverの黒本でPredicateが「断定する」と解説されている件で問い合わせを行っていました。Silverではサイトの正誤表に記載される運びとなったのですが、Goldでは今もなお正誤表には記載されていません。
それはおいておいて、プログラムの世界では普通Predicateは「述語」を意味します。C#にもPredicateは存在しますが、やはりドキュメントには述語と書かれています。C#に詳しい方に黒本ではPredicateが「断定する」という意味で説明されていると話したところ、Predicateは「述語」であると断言していました(そもそもPredicateに「断定する」という意味があることを知りませんでした)。
この指摘の意味するところは、著者にはプログラミングの知識がなく、自分なりの解釈を本に書く癖があるということです。

合成述語(2024/4/9追記)

82ページから合成述語について解説していますが、誤りが多いです。
まず論理演算の組み合わせによる条件判定の例を記載しています。

import Person.Gender;

public class OldSample {
    public static void main(String[] args) {
    Person sample = new Person(Gender.MALE, 19);

    if (sample.getGender().equals(Gender.MALE)
        || sample.getGender().equals(Gender.FEMALE)
            && sample.getAge() >= 20) {
        System.out.println("OK");
        return;
    }
    System.out.println("NG");

これはPersonインスタンスが男性、もしくは女性かつ成人である場合にOKと出力します。
これを「Predicateインタフェースを使って改善したのが次のコード例」だそうです。

import java.util.function.Predicate;
import Person.Gender;

public class NewSample {
    public static void main(String[] args) {
        Predicate<Person> isMale =
                p -> p.getGender().equals(Gender.MALE);
        // 注
        // ここのenumはGender.FEMALEの誤りと思われるが原著のまま記載
        Predicate<Person> isFemale =
                p -> p.getGender().equals(Gender.MALE);
        Predicate<Person> isAdult =
                p -> p.getAge() >= 20;

        Person sample = new Person(Gender.MALE, 19);
        if (isMale.or(isFemale.and(isAdult)).test(sample)) {
            System.out.println("OK");
            return;
        }
        System.out.println("NG");
    }
}

そして84ページで以下のように解説しています。

この方法は、コードの改善方法である「リファクタリング」のうち「説明用変数の導入」と呼ばれるものです。論理演算子がいくつも組み合わさると理解しづらくなりますが、説明用変数の導入を取り入れ、1つひとつの条件を表現する変数で論理演算を記述することにより、条件式が明確でわかりやすくなります。

Predicateは変数ではなく関数ですし、説明用変数ならばfinalで宣言すべきですし、論理演算子はandメソッドとorメソッドに変わっただけでいくつも組み合わさってることには変わりがありませんし、条件式は一見しただけでは条件の詳細がわからなくなり明確どころか曖昧になっています。改善後のコードではisAdultが成人であるときtrueを返すのは予想できますが、成人年齢が何歳なのかはPredicateの中身を見ないとわからなくなりました。

なお、場合にはよりますが私がこのコードを「リファクタリング」するとすればこうします。大概の人もこうするでしょう。

import Person.Gender;

public class OldSample {
    public static void main(String[] args) {
    Person sample = new Person(Gender.MALE, 19);

    if (sample.getGender().equals(Gender.FEMALE)
        && sample.getAge() < 20) {
        System.out.println("NG");
        return;
    }
    System.out.println("OK");

第4章

Optional

170ページでOptionalについて以下のように解説しています。

Java SE 8で導入されたjava.util.Optionalは、メソッドの処理結果を扱うためのクラスで、処理結果の正常・異常にかかわらず同じ型で扱うことができます。このクラスを利用することにより、例外処理に関するコードを減らすことができ、その結果、コードの可読性を上げることができます。

誤りです。
まず、設計の原則として処理結果が異常である場合は戻り値を返してはいけません。例外をスローするべきです。(こんなこともわかってない人の書いた本を4000円で買ったと思うと腹が立ちます。もっと安い良書がたくさんあります。「スッキリ」は3000円だったよ)。
そして、Optionalの目的も例外処理に関するコードを減らすことではありません。
また、コードの可読性にはさほど影響しません。
Optionalは間違いなくnull安全を高めるために導入されたものです。

2024/4/3追記
Effective Javaではオプショナルは「値を返さないかもしれないメソッドを書くための三つ目の方法」と書かれています。従来の一つ目の方法が例外をスローすること、二つ目の方法がnullを返すことです。
なんだ、例外処理の代替案なのだと思うかもしれませんが、Effective Javaは値を返さない場合に戻り値を返す方法を提供したと言っているのであり、黒本の解説にある「処理結果の正常・異常にかかわらない同じ型」を提供したわけではないのです。
例をあげましょう。あなたはDBの電話番号を取得するメソッドの実装を依頼されました。そしてあなたは戻り値をOptional<String>にしたとします。あなたは黒本を読み、処理結果に異常がある場合に空のOptionalを返せば例外処理に関するコードを減らせると「知っていた」からです。電話番号を読み込んだときはその番号がOptionalにラップされて返り、読み込めなかったときは空のOptionalが返ります。そしてあなたは返却されたOptionalに対してさまざまな例外処理を考えます。しかしここで気づきます。空のOptionalではなぜ電話番号が取得できなかったのかがわからないのです。DBに電話番号以外の値が入っていたのか、DBにロックがかかっているのか、DBに接続できないのか、それともDBがそもそもないのか...。しかし、あなたはまだ黒本を信じています。そしてOptionalが空だった場合にorElseThrowメソッドを使えば例外が投げられることも知っています。そこであなたはOptionalが空だった場合はorElseThrowメソッドで例外を発生させることにしました。めでたし、めでたし。
後日、本番と同等のDBを使用してテストをしたところ、あなたが実装したメソッドで例外が発生しました。あなたは慌てて自分の実装を修正しようとしますが、Optionalで「例外処理を減らした」ため、どこでどのような例外が発生したのかわかりません。あくる日DBを見てようやく気付きます。そもそも電話番号は必須項目ではなかったのです。電話番号はDBに文字列として格納されており、カラムがNULLの場合はJava側でもnullとして取得されました。さらに運良く(悪く)あなたはofメソッドではなくofNullableメソッドを使用していました。これはあなたが新人でネットの記事を参考にしたと仮定すれば不自然な選択ではありません。
あなたはOptionalについて誤解しました。「値を返さないかもしれないメソッドを書くための三つ目の方法」を捻じ曲げて「例外処理に関するコードを減ら」そうとしました。あなたは知らず知らずのうちに、例外が発生した異常なときカラムがNULLで電話番号を返せない正常なときの両方で空のOptionalを返却しました。そしてそれをすべて一つの例外とみなして例外処理を書いてしまいました。

findAny(2024/4/5追記)

200~201ページでfindFirstとfindAnyの違いについて以下のように解説しています。

どちらも基本的には、ストリーム内の最初の要素を持ったOptionalのインスタンスへの参照を戻します。そのため、次の2つのコードは共に、「A」をコンソールに表示します。

String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array);
Optional<String> result = stream.findAny();
result.ifPresent(System.out::println);
String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array);
Optional<String> result = stream.findFirst();
result.ifPresent(System.out::println);

2つのメソッドの違いは、並列処理時に現れます。findAnyメソッドは、常に同じ要素を返す保証はありません。そのため、並列処理をしたときは、並列で処理されているうちのどれかが戻り値として戻されることになります。たとえば、次のコードを実行すると、1~5の値がランダムに表示されます。なお、筆者の環境では、3が圧倒的に多く表示されました(後略)。

直接そうは書かれていませんが、この書き方だとfindAnyメソッドは並列処理時findFirstメソッドと同じくストリーム内の最初の要素を返すように思えます。
一方JavaのAPI仕様書ではfindAnyについて以下のように説明しています。

この操作の動作は明らかに非決定論的です。ストリームの任意の要素を自由に選択できます。これは、並列処理でのパフォーマンスを最大化できるようにするためです。デメリットは、同じソースに対して呼び出すたびに、違う結果が返される可能性がある点です。(安定した結果が必要な場合は、かわりにfindFirst()を使用してください。)

JavaのAPI仕様書ではfindAnyが並列処理時には安定した結果を返すとは書かれておらず、安定した結果が必要な場合はfindFirstを使用しろと書かれています。この書きぶりからして、findAnyは並列処理時であってもストリーム内の最初の要素を返すとは限らないのではないかと思います。
もしかすると、本当にfindAnyは並列処理時であれば必ずストリーム内の最初の要素を返すのかもしれません。ただ、公式の説明と食い違うことを記載するならば根拠が欲しいのです。その根拠がもし「筆者の環境では」何度試しても同じ挙動だったというものでなければいいのですが。
公式の見解とは違う主張をしているので試験対策本としても致命的かなと思います。

第5章

java.io.File

242ページからjava.io.Fileの解説がだらだら続き、いくつかメソッドが紹介されています。紹介されているメソッドには以下のようなものがあります。

  • createNewFile
  • mkdir
  • isDirectory
  • listFiles
  • list

クラスやメソッドの命名自体がアンチパターンなので、試験に出ないなら覚えたくありません。オラクル自身がjava.nio.file.Pathとjava.nio.file.Filesを作っている以上、java.io.Fileのメソッドを試験に出すとは思えません。試験に出ないし役にも立たないことを覚えさせようとしないでほしかったです。
なお、命名がアンチパターンであると私が主張する理由は以下の通りです。

  • Fileクラスは実際はパスを扱っているため、Pathという名前が適切。
  • createNewFileメソッドはcreateの意味自体が「新しくものを作る」という意味なのでcreateFileという名前のほうが適切。(本当に英語圏の人が作ったのかと思う)。nioではcreateFileとなっている。
  • mkdirメソッドは意味もなく単語を省略するよりcreateDirectoryという名前のほうが適切。makeでは意味が定まらないのでより具体的なcreateのほうが適切。こちらもnioではcreateDirectoryとなっている。
  • isDirectoryメソッドはfile.isDirectoryがtrueを返す場合があり気持ち悪い。
    listFilesメソッドとlistメソッドは第5章の第2問の選択肢として登場する。問題は以下の通りだが、わかりづらい命名を覚える義理はないと思う。

java.io.Fileクラスのメソッドで、ディレクトリ内の一覧をFileとして取得するためのものを選びなさい。(1つ選択)

2024/4/7追記
Java Goldの試験内容を改めて以下のページで確認しました。I/Oの試験内容は以下ですべてです。

  • I/Oストリームを使用してコンソールおよびファイル・データに対するデータの読取りと書込みを行う
  • I/Oストリームを使用してファイルの読取りと書込みを行う
  • シリアライゼーションを使用してオブジェクトの読取りと書込みを行う
  • Pathインタフェースを使用してファイルおよびディレクトリ・パスを操作する
  • Filesクラスを使用してファイルまたはディレクトリのチェック、削除、コピー、移動を行う
  • ファイルに対してストリームAPIを使用する

私が覚えたくないと言ったメソッドはすべてjava.io.Fileのもので、I/Oストリームとも関係しないため、本当に試験に出ないのではないでしょうか。勘弁してほしいです。

第6章

Statement(2024/4/10追記)

黒本の310ページからStatementについて解説しています。311ページには「試験対策」というコラムで以下のように解説しています。

Statementはパラメータを受け取らないSQL文を実行するためのインタフェース、PreparedStatementはパラメータを受け取るSQL文を実行するためのインタフェースです。

一方、Java GoldのJDBCによるデータベース・アプリケーションの試験内容は以下ですべてです。

  • JDBCのURLおよびDriverManagerを使用してデータベースに接続する
  • PreparedStatementを使用してCRUD操作を実行する
  • PreparedStatementおよびCallableStatement APIを使用してデータベース操作を実行する

つまりStatementは出ないので試験対策として覚える必要はありません。これだけなら些細な問題ですが、第10問とその解説にStatementが登場し、解説の内容が異常です。問題は以下の通りです。

次のコードをコンパイル、実行したときの結果として、正しいものを選びなさい。(1つ選択)

var sql = "delete from item where id = ?";
try (var ps = con.prepareStatement(sql)){
    ps.setInt(1, 1);
    ps.executeUpdate("update item set name='test' where id = ?");
}

そして解答の選択肢の一部が以下です。

C. コンパイルエラーが発生する。
D. 実行時に例外がスローされる。

このコードは実行時に例外がスローされるのですが、316ページでは以下のように解説しています。

設問のコードではPreparedStatementのexecuteUpdateメソッドにSQLを渡していますが、コンパイルエラーにはなりません。これは、executeUpdateメソッドがPreparedStatementの継承元であるStatementインタフェースで定義されているメソッドだからです。(中略)StatementのexecuteUpdateメソッドは、実行するタイミングでSQL文を引数として受け取ります。PreparedStatementは、このメソッドを継承によって引き継いでいるため、PreparedStatementのexecuteUpdateメソッドの引数にSQL文を渡してもコンパイルエラーにはなりません。

問題のコードに登場するexecuteUpdateメソッドはPreparedStatementのメソッドではなくStatementのメソッドです。解説の前半では「PreparedStatementのexecuteUpdateメソッド」と解説し、中盤で「StatementのexecuteUpdateメソッド」を継承していると解説し、後半でまた「PreparedStatementのexecuteUpdateメソッド」と解説しており、結局どちらのメソッドであるのかは著者もわかっていないのではないでしょうか。
正しく解説するならば、StatementにはString型の引数を受け取るexecuteUpdateメソッドが定義されており、PreparedStatementはそのexecuteUpdateメソッドを継承しているが、PreparedStatementには独自に引数を受け取らないexecuteUpdateメソッドが定義されているため、実質的にexecuteUpdateメソッドがオーバーロードされた状態で定義されている、といったところでしょうか。
これはPreparedStatementがリスコフの置換原則に違反しているともとれます。明らかに誤ったコードを書いているのにコンパイルエラーにならないのはJavaの欠陥とも言えるでしょう。そして、オラクルが自身の言語の欠陥を問題として出題するとは思えません(そもそもStatementは試験内容ではないので絶対出題されないでしょうが)。なので、この問題は著者の創作だと思います。

ResultSet(2024/4/10追記)

黒本の319ページでResultSetについて以下のように解説しています。

DBMSのリソースを解放するため、PreparedStatementインタフェースやResultSetインタフェースは使い終わったら明示的に閉じなければいけません。PreparedStatementのcloseメソッドを明示的に呼び出すか、try-with-resourcesにより自動的にcloseが実行されると、ResultSetオブジェクトも同時に閉じます。

一文目ではResultSetは明示的に閉じなければならないと解説していますが、二文目ではResultSetは自動的に閉じると解説しています。

第7章

ワイルドカード

ジェネリクスでワイルドカードを指定した際の解説についてです。
全体を通して「メソッドの引数にはnullリテラルしか渡せない」という解説がなされるのですが、nullなら渡してもよいととらえられる書きぶりのせいでワイルドカードの本質を理解するのに余計時間がかかりました。引数は受け取れないに等しいと一言書いてほしいです。(が筆者も本質を分かっていないのかもしれませんね)。

2024/4/8追記
これも追記をしておきます。
黒本の353ページで上限境界ワイルドカードについて以下のように解説しています。

上限境界ワイルドカードを使うことで、非境界ワイルドカードの特徴である「メソッドの戻り値はObject型になる」を「任意の型にする」ことができるようになります。

黒本は一応正しい説明をしています。ただし、354ページで以下のようにも解説しています。

型が不定なフィールドに値を代入することはできません。唯一代入できる値はnullリテラルのみです。nullリテラルであれば、たとえどの型なのかわからなくても、ClassCastExceptionは発生し得ないからです。

太字の箇所は私が意図的に太字にしたわけではなく、黒本で実際に太字になっています。上限境界ワイルドカードの解説において太字になっている箇所は「上限境界ワイルドカード」「extends」「nullリテラルのみ」そして正答の選択肢を表す「A」の4か所で、筆者が上限境界ワイルドカードの本質を見誤っている(か読者に伝える気がない)んだなと思います。
正直なところ筆者が何を考えて書いたのかがわからないため批判が難しいです。nullのフィールドにnullを代入できることを強調し、根拠として「例外が発生しない」ことを挙げられても私には理解できないです。

Deque(2024/4/10追記)

これは多少意地の悪い指摘です。第7章の第12問です。問題は以下の通りです。

java.util.Dequeインタフェースに関する説明として、正しいものを選びなさい。(1つ選択)

そして解答の選択肢の一部が以下です。

A. 要素を管理するオブジェクトを数珠つなぎにして管理する
C. 両端から要素を挿入・削除できるデータ構造を定義する
D. 最初に格納した要素を最後に取り出すことのできる仕組みを提供する

著者はCを正解として解説していますが、AとDを不正解とする解説が短すぎて投げやりだなと感じます。362ページで両端キューについてArrayDequeを用いたサンプルコードを記載して解説したうえで、363ページで以下のように解説しています。

選択肢Aのように要素を数珠つなぎにして管理するのは、Listインタフェースを実現したLinkedListクラスの特徴です。(中略)選択肢Dは、Stackクラスの特徴です。よって、これらの選択肢は誤りです。

まず、選択肢AのLinkedListクラスはListインタフェースを実装すると同時にDequeインタフェースも実装しています。選択肢AはDequeインタフェースの説明としては誤りかもしれませんが、Dequeインタフェースを実装したLinkedList、すなわち両端キューのデータ構造の説明としては必ずしも誤りではありません。
そして、選択肢DのStackクラスは、その代替としてDequeインタフェースを使用するよう公式ドキュメントに記載されています。スタックにできることは両端キューにもできるため、選択肢Aと同様に両端キューの説明としては誤りではありません。

不正解の選択肢はもちろんDequeインタフェースの説明としては不適切なのですが、両端キューの説明としては必ずしも誤りではありません。

第9章

try-with-resources(2024/4/11追記)

黒本の414ページでリソースにnullを渡した場合について以下のように解説しています。

次のようなコードも、参照先がない(null)と宣言した変数をtryで変更しようとしているため、「実質的finalではない」としてコンパイルエラーとなります(選択肢B)。
なお、自動的に閉じる対象の変数がnullでも、事前にnullチェックを行ってからcloseメソッドを呼び出すため、NullPointerExceptionが発生することはありません。

次のようなコードとは以下のコードです。

public class SampleUsing {
    public static void main(String[] args) throws Exception {
        SampleResource resource = null;
        try(resource) {
            // do something
        }
    }
}

黒本はコンパイルエラーになると解説していますが、このコードはコンパイルエラーにはならず、実行もでき、正常終了します。そもそもこの解説自体が矛盾塊です。コンパイルエラーになったコードが変数のnullチェックを行いnullの場合もcloseメソッドが呼び出されると書いてあるのですから。
もちろん、以上のコードをIDEに記述すると、resourceが常にnullである旨の警告は表示されますが、これはコンパイルエラーではありません。もし著者がこの種の警告とコンパイルエラーを混同しているとしたら黒本の半分ほどの問題と解説に疑念が生じます。「コンパイルエラーが発生する」という選択肢を含む問題が半数を占めるためです。

第10章

getとgetProperty(2024/4/5追記)

黒本の443ページでPropertiesオブジェクトから値を取り出す方法について以下のように解説しています。

Propertiesオブジェクトからキーを指定して値を取り出すには、getメソッドやgetPropertyメソッドを使います。

黒本が言うgetメソッドとはPropertiesクラスが継承しているHashtableクラスのメソッドのことなのですが、普通に考えればPropertiesオブジェクトから値を取り出すのにHashtableクラスのgetメソッドは使いません。
黒本自体も446ページでgetメソッドはObject型の戻り値を戻すため、戻り値をString型にダウンキャストする必要があると言及しています(そもそもObject型をダウンキャストすること自体変だと思いますが)。
なお、443ページの解説は第10章の第7問の解説です。問題は以下の通りです。

次のコードを確認してください。

Properties prop = new Propreties();
prop.load(new FileReader("sample.properties"));

変数propを使い、値の一覧を表示するコードとして、正しいものを選びなさい。(2つ選択)

そして正解の選択肢の1つがgetメソッドを使用した以下のものです。

Set keys = prop.keySet();
for (Object key : keys) {
    System.out.println(prop.get(key));
}

このような問題が本当に試験に出るんでしょうか。黒本に載っている問題はもちろん著者による再現だろうと思っていましたが(事実一定の再現度はあると思いますが)、これは創作ではないかなと感じます。オラクルとしてもプロパティファイルの値の取得にはPropertiesクラスを使用してほしいはずなので、こんな問題は出さないでしょう。
黒本に記載されている問題や解説のコードは全体的にコンパイルが通ればOKであるという認識のもと書かれているように見えます。動くもののコードとしては汚く、実際の現場ではレビューを通らないだろうなというものが数多く見受けられます。これは試験対策本だから汚いコードをあえて載せているんだというスタンスならば、本書冒頭で「正確な記述につとめ」たとは書かないでほしいです(私は黒本に載っているコードは「正確」なのだと勘違いていましたよ)。

第12章

セキュアコーディング(2024/4/6追記)

黒本の498ページでセキュアコーディングについて以下のように解説しています。

カーネギーメロン大学のSoftware Engineering Institute(SEI)では、セキュリティに配慮したコーディング標準「SEI CERT Oracle Cording Standard for Java」を公開しています。その中でもセキュアコーディング(セキュアプログラミング/防衛的プログラミング)で最も重要なトップ10を「Top 10 Secure Coding Pracitces」としてまとめています。以下の1~10が、その概要です。

そして「SEI CERT Oracle Cording Standard for Java」へのリンクが脚注に記載されています。以下のリンクです。

ですが、このリンクが不適切だと思います。
トップページは明らかにこちらです。

また、「Top 10 Secure Coding Pracitces」が掲載されているページを見ればわかるのですが、これは「SEI CERT Oracle Cording Standard for Java」の一部ではありません。「SEI CERT Oracle Cording Standard」の一記事であり、Javaに特化した原則ではありません。
また、この解説の後に「Top 10 Secure Coding Pracitces」の概要が紹介されていると思っていたのですが、黒本に載っている概要が原文よりも長いです。すべてに目を通したわけではないのですが、概要を紹介すると称して筆者特有の意見が混ざっているものがあります。
「2.コンパイラからの警告に注意を払う」を例にあげます。原文は以下です。

Compile code using the highest warning level available for your compiler and eliminate warnings by modifying the code. Use static and dynamic analysis tools to detect and eliminate additional security flaws.

原文をできるだけ忠実に翻訳すると以下のようになるでしょう。

コンパイラで利用可能な限り最高の警告レベルでコードをコンパイルし、コードを修正して警告を排除してください。さらに、静的分析ツールと動的分析ツールを用いてさらなるセキュリティ上の欠陥を検出して排除してください。

一方黒本が紹介している概要は以下です。

コンパイラが発生させる警告を無視せず、コードを修正することで警告が発生しないようにコーディングすることです。コンパイラのほかに、セキュリティ問題を発見し、排除するための分析ツールを併用するのもよいでしょう。これらのツールやコンパイラによっては、警告レベルを制御できるものもありますが、できるだけ高い警告レベルを採用することで、セキュリティのレベルが担保されます。また、分析ツールを利用することで、プログラマーの学習効果も働き、セキュリティに対する意識が向上するメリットもあります。

読者に正しい情報を伝えようとする意思がまったく見て取れず不快です。

整数オーバーフロー攻撃(2024/4/6編集)

記事が見づらくなってしまうため、この項目は追記ではなく編集をしました。
過去に何を書いていたか気になる方は編集履歴をご覧ください。

503ページで整数オーバーフロー攻撃への対策として、次の3つの方法が挙げられています。

  1. 事前条件テスト
  2. アップキャスト
  3. BigIntegerクラスを使う

そのうち、アップキャストの解説に不備があります。
この解説はおそらく以下のページを引用していると思われます。

しかし、黒本は先のページのすべてを引用せず、一部だけを引用しています。
黒本では503ページでアップキャストについて以下のように解説をしています。

アップキャストとは、入力された値を1つ大きなプリミティブ整数型に型変換し、その型で演算を行う方法です。演算途中の値が元の整数型で表現できる範囲に収まっているかを検査し、範囲外の値であれば例外をスローすればよいのです。

一方で先のページでは以下のように解説しています。

入力値を一つ大きなプリミティブ整数型にキャストし、その型で演算を行う。計算途中の値が元の整数型で表現できる範囲に収まっているかを検査し、範囲外の値であれば例外 ArithmeticException をスローする。範囲検査は各演算の後で逐一行わなくてはならないことに注意。より複雑な式で各演算結果の範囲検査を行わないと、アップキャストした型において整数オーバーフローが発生するかもしれない。

黒本では先のページで解説されている「範囲検査は各演算の後で逐一行」うという記載が抜けています。また、先のページを読めばここでいう「演算」は加算・減算・乗算・除算の場合を前提としているということがわかるのですが、黒本には記載されていません。
黒本の解説だとアップキャストは累乗などのあらゆる「演算」にも効果があるように勘違いしてしまいます。
また、黒本全体にいえることなのですが、筆者の意見と第三者の意見は区別できるように書き、第三者の意見を引用した場合は引用元として文献への導線を正しく記載するべきです。
この解説は先のページを引用しているとは書かれていないのですが、引用していなければこれほど文章は似通ったものにはなりませんし、何より「アップキャスト」という単語を整数オーバーフローへの対策方法として使用している記事はネット上を探してもこのページくらいです。普通「アップキャスト」といえばサブクラスからスーパークラスへの型変換を意味します。

ガベージコレクション

第12章の第4問です。問題は以下の通りです。

機密情報の扱いに関する説明として、誤っているものを選びなさい。(1つ選択)

そして解答の選択肢に以下のようなものがあります。

C. 機密情報を扱ったインスタンスはガベージコレクションを待たずに、速やかに削除しなければならない

誤っている選択肢はほかにあり、この記述は正しいということになるのですが、第13章の第11問の解説には以下のように書かれています。

インスタンスを削除するのはガベージコレクタの役割

Javaでは「インスタンスを削除するのはガベージコレクタの役割」であると書いておきながら、インスタンスは「ガベージコレクションを待たずに」削除するべきだと書いています。どちらかが誤りです。

2024/4/6追記
505ページでガベージコレクションについて以下のように解説しています。

機密情報を扱ったインスタンスはできるだけ速やかにメモリから削除するよう設計しましょう。ガベージコレクションがあるからといって、放置してはいけません。ガベージコレクションが実行されるまでの間に、攻撃者によって機密情報が漏洩する可能性があるからです。利用し終わった機密情報を持つインスタンスは、明示的にその状態をクリアする機能を持たせることで、フィールドの値をクリアし終わってから、ガベージコレクションにインスタンスの処理がされるよう設計しましょう。

もう誤りを指摘するのがめんどくさいです。ガベージコレクションをわかっていない人が書いた解説だと思います。

2024/4/7追記
この解説ですが、逆から読んでいくとある程度意味が通ることに気づきました。逆から読むと以下のようになります。

利用し終わった機密情報を持つインスタンスは、明示的にその状態をクリアする機能を持たせることで、フィールドの値をクリアし終わってから、ガベージコレクションにインスタンスの処理がされるよう設計しましょう。ガベージコレクションが実行されるまでの間に、攻撃者によって機密情報が漏洩する可能性があるからです。ガベージコレクションがあるからといって、放置してはいけません。機密情報を扱ったインスタンスはできるだけ速やかにメモリから削除するよう設計しましょう。

こうすれば最後の一文以外は意味が通ります。

第13章

SQLインジェクション(2024/4/13追記)

第13章の第14問の問題と解答と解説が誤りです。
問題は以下の通りです。

以下に示すプログラムの「/* insert code here */」に記述するコードとして、適切なのはAとBのどちらか。その理由と共に正しい記述を選択肢より選びなさい。(1つ選択)
A: "SELECT * FROM TABLE WHERE NAME =" + name
B: "SELECT * FROM TABLE WHERE NAME =" + stmt.enquoteIdentifier(name, true);

黒本が正解としている選択肢は以下の通りです。

C. B:呼び出し側のコードが提供する値を引用符で閉じるとSQLインジェクションが防止される

プログラミングができる方ならこの時点で問題が滅茶苦茶なのはわかると思いますが、そのうえで588ページで以下のように解説しています。

AのSQL文では、変数nameの値に「'a' or 'a' = 'a'」を与えると、nameが「a」もしくは「aがaである」ことが条件となり、常に条件が一致することになります。このようなSQL文は、SQLインジェクションの原因になり得るため推奨されません。(中略)
java.sql.StatementインタフェースのenquoteIdentifierメソッドを使うことで、SQLに与える値を引用符で囲むことができます。そうすることで、SQLインジェクションを防ぐ一定の効果があります。SQL文のリテラルは、文字列や日付は引用符で囲む必要がありますが、数値を囲む必要はありません。

私が今からする主張をまとめると以下のようになります。

  • このような問題は試験には出ません。
  • AのSQLは構文エラーを起こす可能性があり、BのSQLは構文エラーを起こします。
  • enquoteIdentifierの使い方が正しくありません。
  • 著者のいう「引用符」が具体的に何の記号かはっきりしません。
  • 値を引用符で閉じてもSQLインジェクションを防ぐ効果はありません。
  • そもそもSQLインジェクションを防ぐならStatementを使うべきではありません。
  • 黒本はJava Goldの試験対策本であって技術書ではないはずだが、誤情報を載せた技術書の側面はある。

まず、StatementはJava Goldの試験内容に含まれていません。よって、Statement及びenquoteIdentifierメソッドが絡む問題は出題されません。試験内容には「データ整合性ガイドラインの実装(インジェクションおよびインクルードと入力検証)」が含まれているためSQLインジェクションが出題される可能性はありますが、出題されるとしたら以下のページの「Guideline 3-2 / INJECT-2: Avoid dynamic SQL」の内容でしょう。

次に、文字列連結でSQLを組み立てる例としてAは適切でなく、Bは誤りです。私はStatementを用いて文字列連結でSQLを組み立てることは推奨しませんが、もしコードを書くとすれば以下のようになります。ただしSQLインジェクションに対して脆弱性があります

"SELECT * FROM TABLE WHERE NAME =" + stmt.enquoteLiteral(name);

StatementインタフェースのenquoteLiteralメソッドは引数に受け取った文字列をシングルクォーテーションでくくって返します。一方enquoteIdentifierメソッドは引数に受け取った文字列をダブルクォーテーションでくくって返します。前者はSQLの文字列型のリテラルを作成するために使用し、後者はSQLの識別子、つまりテーブル名やカラム名を作成するために使用します。SQLの構文では、テーブル名やカラム名にスペースなどの特殊記号が含まれる場合にダブルクォーテーションでくくる必要があるためです。なお、そもそも識別子に特殊記号を用いることは推奨しません。
Aのコードはnameに格納されている文字列がシングルクォーテーションでくくられている前提であれば正しいSQL文が組み立てられるため誤りではありませんが適切でもありません。Bは文字列型のリテラルを作成するために識別子を作成するメソッドを使用しているため論外です。
黒本ではBのコードを正解としているため、著者は認識を誤っているでしょう。そのことは著者の解説における「引用符」がシングルクォーテーションとダブルクォーテーションを混同していることにも表れています。
著者は値を引用符で囲むことでSQLインジェクションを防ぐ一定の効果があるとしていますが、この解説の引用符もシングルクォーテーションのことなのかダブルクォーテーションのことなのかわかりません。ダブルクオーテーションだとしたら正常な入力値を受け取っても構文エラーとなり論外なので、仮にシングルクォーテーションだとして黒本のいう通り「'a' or 'a' = 'a'」という文字列を囲んでみましょう。以下のようになります。

SELECT * FROM TABLE WHERE NAME = ''a' or 'a' = 'a''

シンタックスハイライトが水色の部分が文字列、白の部分が識別子とみなされ、当然構文エラーを起こします。不正な入力に対して構文エラーを起こすため、安全とは言えません。また、シングルクォーテーションでくくられることはSQLインジェクションを試みる攻撃者も知っています。なので、攻撃者が実際に使用する入力値は以下のようなものです。

a' or 'a' = 'a

今度はこれを入力してみましょう。

SELECT * FROM TABLE WHERE NAME = 'a' or 'a' = 'a'

SQLインジェクションが成功しました。入力値を引用符で囲んでもSQLインジェクションを防ぐ効果がないのは明らかです。
では、どのようにすればSQLインジェクションを防ぐことができるでしょうか。以下のページが参考になるでしょう。

1つめのページは、このサイトの別ページを著者が黒本に参考文献として記載しているので当然読んでると思っていましたが著者は読んでないんだなと思いました。2つめのページはIPAのページです。IPAがSQLインジェクションへの対策として一番に記載しているのは、SQL文の組み立てはプレースホルダで実装することです。平たく言えばPreparedStatementを使用することで、SQLインジェクションへの対策となると書いています。Java Goldの試験内容にStatementが含まれず、PreparedStatementが含まれるのもこれが理由でしょう。不便でありセキュリティリスクの伴うStatementよりも便利で安全性の高いPreparedStatementを使ってほしいからです。先ほど掲載したページの「Guideline 3-2 / INJECT-2: Avoid dynamic SQL」にも以下のように書いてあります。

use java.sql.PreparedStatement or java.sql.CallableStatement instead of java.sql.Statement.

なお余談ですが、時間がある方はIPAが二番目に記載しているSQLインジェクションへの対策を見てください。以下のように解説されています。

SQL文の組み立てを文字列連結により行う場合は、SQL文中で可変となる値をリテラル(定数)の形で埋め込みます。値を文字列型として埋め込む場合は、値をシングルクォートで囲んで記述しますが、その際に文字列リテラル内で特別な意味を持つ記号文字をエスケープ処理します(たとえば、「'」→「''」、「\」→「\\」等)。

このようにエスケープ処理を行うことでもSQLインジェクションへの対策となります。そしてこれは可能性の話ですが、著者はエスケープ処理の意味を理解しておらず、文字列内のすべてのシングルクォーテーションを重ねて書くことをシングルクォーテーションをダブルクォーテーションに変更すると勘違いしたのではないでしょうか。すなわち、「''」と「"」を勘違いしたのです。
最後に、この本はJava Goldの試験対策本であって、プログラミングの技術書ではありません(と出版社からもその旨の回答をいただきました)。しかし、7ページでは「実際の現場で必要となる技術についても丁寧に解説してい」ると書き、試験には出題されないStatementを取り上げ、逆に出題されるであろうPreparedStatementがSQLインジェクションに有効であることはどこにも記載せず、Javaとは関係のないSQLインジェクションの具体的な方法を記載しておきながら、SQLインジェクションの対策として解説されている内容はすべて誤りなうえ、この解説に限らず引用や参考文献の提示はあいまいで、著者自身ちゃんと文献に目を通しているのかどうかもわからず、4000円もします。

全体を通して

サンプルコードの命名に関して(2024/4/9追記)

黒本のサンプルコードの命名がアンチパターンだったとしても試験には合格できるでしょうが、命名がアンチパターンなせいでサンプルコードの理解に時間がかかるようになっています。初学者だったころの私は黒本のサンプルコードを読んでJavaはこれほどにも難しいのかと思っていましたが、これは明らかにサンプルコードの命名が悪いせいです。黒本の命名は参考にしないほうがいいと思います。
サンプルコードに出てくる命名のなかで顕著なのがsampletestexecuteの3つです。testは特段何かを評価しているわけではありません。あとはABCといった命名もよく出てきます。サンプルコードにおいて名前に特に意味を持たせないならメタ構文変数を使うべきでしょう。サンプルコードを読む側からすれば、testだと評価する、executeだと実行するという意味を受け取ってしまいますし、アルファベットの命名も順序に意味があるのではないかと考えてしまいます。ではメタ構文変数をまったく使用していないのかといえばそうでもなくて、59ページには以下のようなコードが記載されています。

public enum Test {
    A("hoge"), B("hege"), C("fuge");
    Test(String value) {
        System.out.println(value);
    }
}

(もっとコードに具体性を持たせればよいのではないかというのはおいておいて、)hogeは有名なメタ構文変数なのであと二つもメタ構文変数だろうと推測は立てられますが、hegefugeは聞いたことがありません。著者の意図するところがわからないため批判も難しいですが、fugapiyoのほうが一般的で無難で誤解を生む可能性も低いのは確かですし、なぜ校正の段階で誰もこれを指摘できなかったのかと疑問に思います。

中には具体性を持ったサンプルコードもありますが、命名は中途半端です。例えば30ページで、

このコード例では、name(名前)とprice(値段)という2つのフィールドを持ったItemクラスを定義しています。

と説明していますが、ここまでしてなぜItemクラスをProductクラスといった名前にしないのかなと思います。Itemクラスはsortメソッドの解説のために用意されたもので、後のページでItem型のリストが登場します。リストの名前は当然itemsなのですが、リストに入れる「要素」だからItemクラスと命名したように見えます。あまり命名に関心がないのでしょう。

また、並列処理の解説では全編にわたってスレッドプールのインスタンスを格納する変数にexecという名前を付けています。これはインスタンスを格納する型がExecutorServiceだからでしょうが、普通はpoolと命名するでしょう。プログラミングの知識が多少ある人間なら少なくともexecという命名は避けると思います。公式ドキュメントもpoolと命名しています。

最後に

本来、誤記とか誤植とかは出版社に連絡して訂正を促すべきなのですが、黒本は誤記、誤植の範疇を超えてます。(Silverにも誤記があり、そちらは出版社に連絡しました)。4000円で嘘をばらまかないでほしいですし、お金も返してほしいというのが本音です。
黒本を理解しても、Java Goldに合格しても、仕事で通用するコードが書ける証明にはなりません。実際にJava Goldの試験を受けて合格した身なので言いますが、Java Goldに合格するよりも設計の技術書を読みながら実際に動くコードを個人で書くほうが実力が付くと思いました。

2024/4/11追記
最後に余計な事を書いてちょっと誤解を生んでしまったのですが、最初から黒本で得た知識が仕事に直結するとは思っていません。これはあくまで資格試験の対策本です。ただ、Java Goldが「設計者の意図を正しく理解して独力で機能実装が行える」ことを証明するための資格である(と黒本の裏表紙にも書いてあります)ことを否定したかったのです。ネットには黒本のおかげでJava Goldに合格した人が大勢いますが、誰一人として黒本の誤りに気付いていません。Java Goldはこの記事で言及している程度の誤りを素通りしてしまう人でも合格できます。そのような人たちが「独力で機能実装が行える」とは思えません。だから私は「黒本を理解しても、Java Goldに合格しても、仕事で通用するコードが書ける証明にはな」らないと書いています。
また、黒本は7ページで、本書を「読み終わったあとは試験合格以上の実力がつくでしょう」と言及していますが、それに対して私は試験に合格したうえで「設計の技術書を読みながら実際に動くコードを個人で書くほうが実力が付く」と思いました。

余談

これからJavaを勉強する人には黒本だけで勉強して昔の自分のようにはなってほしくないので、いくつか併用して学習するのにおすすめの書籍を紹介しておきます。私は読んだことはありませんが、黒本を使用せず紫本を使用する手も良いと思います。

スッキリわかるJava入門 第4版 実践編

私は第3版を愛読していてボロボロになるまで読んだのですが、新しい版が出たので買いなおそうかなと思っているほどおすすめです。Javaだけでなく他の言語の理解にも役に立ちます。(そもそも後半はJavaとは関係のないことの解説をしていると著者も明言していますし、)この本はJavaの説明を通して「プログラミングとは何か」を伝えようとしているため、その知識がC#やPythonを書く際にも役に立ちました。前述しましたが黒本と同じ出版社から出ています。

Effective Java 第3版

Javaを書く上での必読書と言われています。私は黒本を買ってお金がなくなったのでこの本は図書館で少し閲覧しただけですが、黒本より正確で丁寧です。Java Goldの取得が目的でないならこの本を買ってもいいと思います。著者は黒本の498ページで言及されているカーネギーメロン大学の教授です。

追記

出版社に問い合わせしました。

2024/3/30 Builderパターンの項目を追記しました。
2024/4/3 Effective Javaからの引用に基づいて追記しました。
2024/4/4 Builderパターンの項目を追記しました。初めにを追記しました。
2024/4/5 findAnyの項目を追記しました。getとgetPropertyの項目を追記しました。整数オーバーフロー攻撃の項目を追記しました。Supplierの項目を追記しました。
2024/4/6 セキュアコーディングの項目を追記しました。整数オーバーフロー攻撃の項目を編集しました。ガベージコレクションの項目を追記しました。
2024/4/7 ガベージコレクションの項目を追記しました。Predicateの項目を追記しました。java.io.Fileの項目を追記しました。初めにを追記しました。
2024/4/8 ワイルドカードの項目を追記しました。関数型インタフェースの項目を追記しました。
2024/4/9 合成述語の項目を追記しました。サンプルコードの命名に関しての項目を追記しました。余談を追記しました。
2024/4/10 Statementの項目を追記しました。ResultSetの項目を追記しました。Dequeの項目を追記しました。
2024/4/11 try-with-resourcesの項目を追記しました。最後にを追記しました。
2024/4/13 SQLインジェクションの項目を追記しました。初めにを追記しました。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6