Java

Effective Java Third Edition 2版からの更新点 個人的メモ

注文していたEffective Java Third Editionがようやく届いたので、第二版からの変更点などをまとめました。
個人的メモかつ、第二版と変わっていないところは読み飛ばしているので、洩れ抜けあるのはご容赦ください。
第二版は日本語版を参照しているので、訳文との関係で不整合があるかもしれません。
間違い等ありましたらご指摘いただけると嬉しいです。

凡例

Effective Java Third Editionからの引用、またはその翻訳

  • 内容のサマリー

平文 - 個人の感想


Item 2

ビルダーに再帰型パラメーターを使用したものが追加されています。
親の抽象クラスPizzaが返すビルダーをBuilder<T extends Builder<T>>として、そのメソッドaddTopping()が返す型をTにすることで、流暢にサブクラスを設計できます。

Item 5: リソースをハードコードするよりも、依存性注入(Dependency injection)を使う

Staticユーティリティークラスやシングルトンは、動作をリソースによってパラメーター化できるクラスには不適当です

  • Factoryを渡すより、Supplier<T>を渡す。

Item 6

不必要なオブジェクトの生成の例が、Calendarを使っていたものからString.matches()に変更されています(Patternを都度生成しているのが問題)。

Item 8: ファイナライザーやクリーナーを避ける

Java 9で追加されたクリーナーが話題に上がっています。

クリーナーはファイナライザーほど危険ではないですが、それでも予測できず、遅く、一般的に不必要です

  • finalizer attack - ファイナライザーを利用して不正なオブジェクトをヒープに残し、メソッドを呼び出す (記述自体は2版にもあったが、より強調されている)

  • 代わりにAutoCloseableを実装すべき

Item 9: try-finallyよりもtry-with-resourcesを使う

  • try-finallyは間違いやすい(ブロック自身のミス、JDKの実装のミス)

Item 10

  • equals()の実装は難しい。AutoValueを使うか、IDEに生成させる

個人的にはAutoValueはあまり好きではないので、Lombokを使うかKotlinで書きます。

IDEs do not make careless mistakes, and humans do
IDEは不注意なミスを犯さないが、人間は犯す

Item 11

コード例がハッシュコードの生成に、Type.hashCode()(Short.hashCode()など)を使うように変更されています。
hashCodeの生成のドキュメンテーションについて、「しないほうがよい」から「するな」と表現が強くなりました(ドキュメント化すると、将来変更できなくなる)。

Item 12

  • staticユーティリティークラスや列挙体にtoString()はいらない

Item 13

  • 不変クラスはCloneableを提供してはならない(そもそもコピーせずに共有できる)

Item 14

  • Type.compare()を使う
  • Comparator.comparing...()を使う
  • 差分に基づくコンパレーター(オブジェクト間の値を引き算するやつ)は使用してはいけない(オーバーフロー)

Item 15

  • Java 9のモジュールが広く使われるようになるかはまだわからない。
  • モジュールはadvisory。無理して使う必要はない。

Item 19

  • @implSpecを使う

Item 20

  • デフォルトメソッドは役に立つが限界があるので、抽象クラスはいまだに役立つ。

Item 21: 後々のことを考えてインターフェースを設計する

考えうるすべての実装を変更せずにデフォルトメソッドを書くのは、いつも可能なわけではない
インターフェースを設計するときは、多大な注意を払って行わねばならないことは、いまだ最高に重要である

  • e.g. Apache Commons synchronizedCollection デフォルトメソッドは(オーバーライドされるまで)同期されない!
  • デフォルトメソッドがあるからと言って、気軽にインターフェースを設計してはいけない

旧項目21

削除、Item 42に吸収合併

Item 24

  • ラムダが追加されたので、関数オブジェクトやプロセスオブジェクトは不要になった

Item 25: ソースファイルには、一つのトップレベルクラスだけを書く

  • 誤って同名のソースを作ると、コンパイルできなくなる可能性がある。

Kotlinみたいな言語では複数のpublicクラスを書けますが、読みやすさを考えるとできる限り分けたほうが良い気がします。

Item 26: 原型を使用しない

「新たなコードで」がなくなりました(無理にリファクタしろというわけではないです)

Item 27:

ダイアモンド演算子の記述

Item 30

ダイアモンド演算子が追加されたため、新しいコレクションを作るとき、型パラメーターを省略するためのnewHashMapユーティリティーメソッドの記述が消えました。

generic singleton pattern

private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
    return (UnaryOperator<T>) IDENTITY_FN;
}

実戦で使う機会はあまりない気がします。

Item 32: 総称型と可変長引数を注意して組み合わせる

  • @SafeVarargsを常に使え、安全でないならやめろ

Item 34

StringからEnumへ変換

private static final Map<String, Operation> stringToEnum = Stream.of(values()).collect(toMap(Object::toString, e -> e));
public static Optional<Operation> fromString(String symbol) {
    return Optional.ofNufllable(stringToEnum.get(symbol));
}

個人的には例外を吐きたいです(Item 55で議論されています)。

PayrollDayが微妙にリファクタリングされていました。

Item 37

HerbをPlant、TypeをLifecycleにしたのはなぜなのか。

Item 39

JUnit -> JUnit 3
JUnit 4はアノテーションベースなので、まさにこの項目の助言に従っている。

Item 42: 匿名クラスの代わりにラムダを使用する

ラムダのパラメーターの型は、それによってプログラムがより綺麗にならない限り使用しません。
ラムダには名前とドキュメンテーションが欠けています。処理が自己説明的でないか、数行よりも大きいなら、ラムダを使ってはいけません。

  • 自分への参照が必要な場合、匿名クラスが必要
  • ラムダはシリアライズしてはいけない

Item 43: ラムダの代わりにメソッド参照を使う

  • ラムダを使ったほうが簡潔な場合、そうする(e.g. Function::identity vs x -> x)

Item 44: 標準の関数型インターフェースを使用する

サブクラスが「プリミティヴ・メソッド」をオーバーライドしてスーパークラスの動作をカスタマイズする「テンプレート・メソッド」パターンは、はるかに魅力的でなくなっています。現代的な代替方法は、同じ働きをする関数オブジェクトを受け取るstaticファクトリーやコンストラクターを提供することです。

  • 自分で独自の関数型インターフェースを作るよりも、標準のものを使用する
  • 独自の関数型インターフェースを定義するとき

    • 広く使われ、説明的な名前によってメリットがある場合
    • 強く関係する契約がある場合
    • 独自のデフォルトメソッドによるメリットがある場合
  • @FunctionalInterfaceを使う

クライアントにとって曖昧になる可能性がある場合、違う関数型インターフェースを同じ位置で引数に持つ異なったオーバーロードしているメソッドを作ってはいけません。

  • e.g. ExecutorService.submit()RunnableCallable<T>をとるので分かりづらい。

Item 45: ストリームを注意して使用する

just because you can doesn't mean you should

ストリームの過剰使用はプログラムを読みづらく、保守しづらくします

耳が痛いですね

ストリームパイプラインを使うコードでヘルパーメソッドを使うことは、イテレーターを使用するコードで使うよりも、さらに重要です。

  • charにStreamを使ってはいけない

メソッドの名前(primes)はストリームの要素を説明する複数形の名詞です。この命名規則はストリームパイプラインの可読性を向上させるため、ストリームを返すすべてのメソッドで強く推奨されます。

Item 46: ストリームにおいて、副作用のない関数を使う

  • Streamを使うには、APIだけでなく(関数型プログラミングの)パラダイムにも適応する必要がある。

forEach命令はストリームの処理結果を報告するためだけに使用されるべきで、処理を行うのに使用してはいけません。

forEach(e -> list.add(e))とかありがちですよね。

Item 47: 戻り値にはストリームよりもコレクションを使用する

もしあなたがシーケンスを返すパブリックAPIを書いているのなら、ストリームパイプラインを書きたい人だけでなくfor-each文を書きたい人のためにも書くべきです。

コレクションとして返すためだけに、大きなシーケンスをメモリーに保存してはいけません。

特殊な目的のコレクションを実装することを検討してください。

Item 48: ストリームを注意してパラレルにする

ソースがStream.iterateであるか、中間操作limitが使用されている場合、パイプラインをパラレルにすることは、パフォーマンスを向上させはしないでしょう。

  • 無差別にストリームをパラレル化してはいけない

ArrayList, HashMap, HashSet, ConcurrentHashMapのインスタンス、配列、intのレンジ、longのレンジのストリームを並列化するのが、最もパフォーマンスの向上が大きいでしょう。

  • ストリームをパラレル化するのはパフォーマンスを低下させるだけでなく、間違った結果や予想できない動作を起こす可能性がある

  • forEachforEachOrderedの違い

  • 大まかな目安: ストリームの要素数 x コードの行数 > 10万を超える場合、パフォーマンスが向上するかも
    書籍内のソース: When to use parallel streams

ストリームのパラレル化はパフォーマンスの最適化であることを覚えておくのは重要です。ほかの最適化同様、変更の前後でパフォーマンスを計測し、そうする必要があることを確かめなければなりません。

Item 49

  • Objects.requireNonNullを使う

Item 50

Dateは時代遅れであり、新規のコードではもはや使用しないでください

Item 55: 注意してオプショナルを返す

マップ、ストリーム、配列といったコンテナ―型、オプショナルは、オプショナルでラップしてはいけません。
結果を返さない場合があり、かつ、クライアントが特別な処理をする必要がある場合、Optional<T>を返すようメソッドを宣言します。

  • OptionalInt, OptionalLong, OptionalDoubleを使う。Optional<T>だとオートボクシングが発生してパフォーマンスが悪くなる。

  • コレクションや配列のキーや値、要素にオプショナルを使用しない

  • フィールドにオプショナルを保存するのは多くの場合「におう」コードだが、正当化される場合もある(オプショナルなフィールドがたくさんあり、getterから単にオプショナルを返せばいい場合など)

Item 56

  • @implSpecを使う
  • ピリオドに注意(文の終わりとみなされる)

Item 59

  • RandomよりもThreadLocalRandomを使う
  • Guavaは便利

旧項目73: スレッドグループを避ける

削除

Item 75

  • Java 9でIndexOutOfBoundsExceptionに、int indexを引数に持つコンストラクターが追加。

Item 80: スレッドよりエグゼキューターとタスク、ストリームを選ぶ

ストリームが候補に追加されています。

Item 85: Javaのシリアライズよりも、代替手段を使う

シリアライズの危険性がかなり強調されています。

シリアライゼーション脆弱性を避ける一番の方法は何もデシリアライズしないことです
新しく書かれるシステムで、Javaのシリアライゼーションを使用する理由はありません

  • 代替手段: JSON, Protobuf

次善の手段は、信頼されないデータを決してデシリアライズしないことです

  • どうしてもシリアライザーが必要な時は、java.io.ObjectInputFilterを使う

結論として、シリアライゼーションは危険であり、使用されるべきではありません。

感想

  • 新規部分に関しては、常識的なことばかりでとくに目新しい部分はなし
  • とはいっても常識的なことをもう一度学びなおすのは意義のあることかと
  • ストリームのやみくもな使用を戒めているのはさすが
  • シリアライズの危険性が強調されているのはとても良いこと
  • Googleの宣伝が増えている(ブロックさんは元Google社員)
  • 全体的にブラッシュアップされているので、まだ持っていない人は邦訳が出次第買うといいと思います。持っている人はあえて新しく買う必要はないかな、くらいの内容でした。

おまけ - ブロック氏のPC

AMD Opteron 170 Core i7-4770K
2GB RAM DDR3-1866 16GB RAM
Windows XP Windows 7 Professional SP1
JDK 1.6 Java HotSpot Azul Zulu 9.0.0.15