Skip to the content.

Effective Java

Effective Java によせて

第1章 はじめに

第2章 オブジェクトの生成と消滅

コンストラクタの代わりに static ファクトリメソッドを検討する

多くのコンストラクタパラメータに直面したときはビルダーを検討する

private のコンストラクタか enum 型でシングルトン特性を強制する

これはggればでてくる

private のコンストラクタでインスタンス化不可能を強制する

はい

資源を直接結び付けるよりも依存性注入を選ぶ

クラスの振る舞いが他の資源に依存するなら、private static final な field として持ってしまうのではなくて、 外から注入するようにしようね、というやつ。拡張性とテスタビリティが段違い。 ※ よく使うのがあるなら factory method で提供しちゃえばいいってことかな。

不必要なオブジェクトの生成を避ける

new String("hogehoge") とかやるなよって話。 String.matches とかも、内部的に Pattern インスタンスを毎回生成してるらしいので、 たくさん呼ぶなら Pattern を cache した方が良い。

使われなくなったオブジェクト参照を取り除く

例えば stack の内部実装で配列を使っている場合、pop された object のところは null で上書きしておかないと参照が生き残ってて GC が回収してくんない。

ファイナライザとクリーナーを避ける

危ないし遅いから AutoClosable を代わりに使おうね。
ファイナライザ・クリーナーを使うのは、auto close のセーフティネットとしてならアリ。
あとネイティブピア(Java の object じゃないので GC に回収されない)の分を強制的に回収したいなら実装するしかない。

try-finally よりも try-with-resource を選ぶ

はい。

第3章 すべてのオブジェクトに共通のメソッド

equals をオーバーライドするときは一般契約に従う

はい。javadoc 読もうね!
しないときはどういうときかというと

equals をオーバーライドするときは、常に hashCode をオーバーライドする

はい。hashCode が重いなら cache してもいいよ!

toString を常にオーバーライドする

はい。

close を注意してオーバーライドする

なんかよくわからんが Closable を実装するクラス作りたいときないし別にいいか。

Comparable の実装を検討する

equals と同じで一般契約に従おうね!
オーバフローと浮動小数点誤差に気を付ける。

第4章 クラスとインターフェース

クラスとメンバーへのアクセス可能性を最小限にする

はい。

public のクラスでは、public のフィールドではなく、アクセッサーメソッドを使う

はい。

可変性を最小限にする

はい。
class を final にしなくても、constructor を全部 private にすることで実は同じような効果が得られる。

継承よりもコンポジションを選ぶ

コンポジション + 転送(いわゆる委譲)をやった方がいいときが多い。

継承のために設計および文書化する、でなければ継承を禁止する

継承によりカプセル化は簡単に破壊され得る。したがって、継承可能メソッドは implSpec アノテーションによって実装要件を明示し、継承クラスはそれを遵守しなければならない。それができないなら継承はすべきでない。その場合、class を final にするか、すべてのコンストラクタを private にしてファクトリメソッドを追加するかのどちらかをしておかなければならない。

抽象クラスよりもインターフェースを選ぶ

抽象クラスは三角形、インターフェースは一点
つまりピンポイントで実装したいときはインターフェースが勝る
基本的にはインターフェース、default 実装を与えたいなら abstract Interface を定義して、
template method pattern で実装する。
こうして作った抽象クラスの実装を骨格実装 (skeletal implementation) と呼ぶ。
所謂 mixin をやるのにはインターフェースが向いてる。

将来のためにインタフェースを設計する

default method によって、実行時にこける可能性がある。
ex) org.apache.commons.collections4.collection.SynchronizedCollection 同期処理をするが、 default の removeIf は同期なんて知ったこっちゃないので ConcurrentModificationException が起きる可能性がある。
なので、上記クラスは removeIf を override しなきゃいけない(のに、していない)。
教訓:default method を追加するのは、compilation error は起こさないかもしれないが、論理的に実装クラスを破壊する可能性がある。

型を定義するためだけにインタフェースを使う

定数インタフェース:アンチパターン
解決策

タグ付きクラスよりもクラス階層を選ぶ

タグ付きクラス:内部 enum を持ってるやつ。継承と何も変わらないので継承使おうね、という話。

非 static のメンバークラスよりも static のメンバークラスを選ぶ

enclosing instance: 内部クラスから見た、外部クラスのインスタンス
enclosing instance にアクセスする必要がある場合のみ、非 static のメンバークラスを選ぶ。
ex) Collection interface の実装は、イテレーションを内部クラスとして実装するために、非 static メンバークラスを一般に用いる。
ネストしたクラスが、一つのメソッドの外からも見える必要があったり、メソッド内に問題なく入れるのに長すぎるならば、メンバークラスを使う。
その際、メンバークラスの個々のインスタンスが enclosing instance への参照を持ちたいなら、非 static にする。それ以外は static にする。
クラスがメソッド内に属しているべきであって、インスタンス生成をする必要のある個所が一か所に特定できる場合、
既にそのクラスを特徴付けるクラスが宣言されているのであれば、無名クラスにする。
そうでなく、新たにクラスを宣言しなければならないのであれば、ローカルクラスにする。

ソースファイルを単一のトップレベルのクラスに限定する

単一のソースファイルに複数のトップレベルのクラスを入れない。
代わりに、 static の内部クラスとすべき。

第5章 ジェネリックス

原型を使わない

はい。

無検査警告を取り除く

はい。
どうしても取り除くことができず、警告を起こしているコードが型安全だと明確に示すことができるなら suppress warnings する。
suppress warnings の scope はなるべく狭く。method につけるのではなく local variable につける、とか。
で、なんで安全と言えるのかに関する comment を入れよう。

配列よりもリストを選ぶ

はい。

ジェネリック型を使う

new T[] ができない!:new Object[] として downcast するか、諦めて Object[] を持つことにして、取り出す度に T に downcast するかのどちらか。

ジェネリックメソッドを使う

generic singleton factory pattern: UnaryOperator#identity とか。返すのは常に単位射で同じ instance だけど、compiler 上は parameterized なものとして扱いたいので。
再帰型境界 (recursive type bound): Comparable を使うときによく E extends Comparable<E> とか書くけど、あれ。

API の柔軟性向上のために境界ワイルドカードを使う

はい。
型パラメータが method 宣言中に一度しか現れないならそれをワイルドカードで置き換えられるはず。

ジェネリックスと可変長引数を注意して組み合わせる

ヒープ汚染:パラメータ化された方の変数が、その型ではないオブジェクトを参照している場合に発生する。コンパイラが自動生成したキャストが失敗する可能性があり、ジェネリック型システムの根本的な保証を破るものである。
配列自体が危険だよって話じゃね? Integer[]Object[] に cast しちゃうと String とかぶち込めるようになっちゃう、的な。
ヒープ汚染は伝播させてはいけない = 可変長引数で受け取ったときにそれは配列として変数に保存されるが、その参照を決してその method の外側に公開してはいけない、ということ。

型安全な異種コンテナを検討する

クラスリテラル:String.class みたいなやつ。これを引数としてどこかに渡す場合、それは型情報を伝えるためであって、そうして渡されたクラスリテラルのことを型トークン (type token) と呼ぶ。
型安全異種コンテナ (typesafe heterogeneous container) :Map<Class<?>, Object> を field に持ってるやつ。 asSubclass とか cast とかの method を駆使すると実行時に検査をすることで、compilation error とか警告とかなくコーディングすることができるようになる。エラー発生も早めることができてまあまあうれしいね。

第6章 enum とアノテーション

int 定数の代わりに enum を使う

int enum pattern: アンチパターン。int じゃなくても String でもだめ。
enum ごとに分岐してやる処理 -> 定数固有メソッド実装 (constant-specific method implementation) で行ける場合もある。特に、分岐を enum class 内に定義している場合。
戦略 enum (strategy enum) : 例えば、曜日を定義するんだけど、週末と平日を分けたい場合、inner enum として WEEKDAY WEEKEND を持つ enum を宣言。これを曜日 enum の constructor に渡すようにする。

序数の代わりにインスタンスフィールドを使う

ordinal は使うなよ。はい。

ビットフィールドの代わりに EnumSet を使う

int enum パターンと似た話。Set<Enum> を引数に受け取る method を作ることで解決する。

序数インデックスの代わりに EnumMap を使う

ordinal 使うなよ。はい。EnumMap があるのでそれ使う。ネストすることもある。

拡張可能な enum をインタフェースで模倣する

はい。 コードの重複が出てきた場合は、ヘルパクラスか static なヘルパメソッドにカプセル化する。

命名パターンよりもアノテーションを選ぶ

テストメソッドは Test prefix をつけるとかするのではなくて @Test annotation をつけるようにデザインする。こういう annotation を marker annotation と呼ぶ。失敗すべきテストは、想定される例外 Class を引数に取る annotation を作る。
配列を保持する annotation もいいが、 @Repeatable を使用することもできる。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
  Class<? extends Throwable> value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
  ExceptionTest[] value();
}

常に Override アノテーションを使う

はい。

型を定義するためにマーカーインタフェースを使う

はい。Serializable とか。型を定義したくないなら marker annotation でいい。

第7章 ラムダとストリーム

無名クラスよりもラムダを選ぶ

はい。 関数名・documentation が欠けるので、計算が自明じゃなかったり長かったりするならラムダはやめとく。

ラムダよりもメソッド参照を選ぶ

はい。

標準の関数型インタフェースを使う

はい。実装者に強く求める契約があるならそれ専用のを作ってもいい。

ストリームを注意して使う

はい。前の値が必要なときは Pair とかに持たなきゃいけないけど、煩雑になりやすい。逆マッピングの利用とかも検討する。

ストリームで副作用のない関数を選ぶ

はい。

戻り値型として Stream よりも Collection を選ぶ

Collection として扱いたい開発者もいるかもしれないから。

ストリームを並列化するときは注意を払う

見境なく Stream を並列化しないこと。例えば、limit が挟まってる場合に並列処理をすると、その要素を計算すべきかどうかが独立に決定できないために、無駄な計算がたくさん行われる。Stream#iterate を使っている場合も、直前の要素まで iteration しないと次の要素計算できないから無駄計算がたくさん発生する。※ LinkedList の random access の話と似てるなあ。
可変リダクション (mutable reduction) はオーバヘッドが高くつくので、並列化しても恩恵が少ない。
あとは副作用がある場合、並列化によって壊れる可能性もある。※ それは実装者が悪くね?
並列化の中で乱数が欲しい場合、SplittableRandom を使うとかの工夫が必要な場合もある。

第8章 メソッド

パラメータの正当性を検査する

Object#requireNonNull とかによる assertion。assertion は method の入り口でやるべきだよね。はい。

必要な場合、防衛的にコピーする

コンストラクタでコピーしたり、getter で見せるものはコピーにしておいて、参照を切っておいたり。はい。最悪 javadoc で逃げてもいい。

メソッドのシグニチャを注意深く設計する

オーバーロードを注意して使う

可変長引数を注意して使う

必須なら (int first, int… remainings) みたいな signature にする。はい。

null ではなく、空コレクションか空配列を返す

はい。

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

non-null 意識。はい。
instance field に Optional は持つべきではない -> その field を省略した subclass を定義すべき場合がほとんど。でも組み合わせ爆発起きるならしょうがないね、ぐらいな感じ。

すべての公開 API 要素に対してドキュメントコメントを書く

class/interface/constructor/method/field に対して宣言の前に javadoc を書く。
書くべきもの

第9章 プログラミング一般

ローカル変数のスコープを最小限にする

method 短くしようね。はい。

従来の for ループよりも for-each ループを選ぶ

はい。以下の場合は使えない。

ライブラリを知り、ライブラリを使う

java.lang java.util java.io ぐらいはちゃんと知っとくべき。新しい feature は追っかけようね。

正確な答えが必要ならば、float と double を避ける

はい。

ボクシングされた基本データよりも基本データ型を選ぶ

基本データ型の方が

他の型が適切な場所では、文字列を避ける

はい。

文字列結合のパフォーマンスに用心する

はい。StringBuilder 使いな。

インタフェースでオブジェクトを参照する

はい。ArrayList じゃなくて List 使いな。field の型の話ね。

リフレクションよりもインタフェースを選ぶ

リフレクションしていいのは service provider framework を作るときぐらい。他の場合は interface 刺しといて実装は後から注入しようね、という OOP の本懐で解決しようぜ。
リフレクションすると

ネイティブメソッドを注意して使う

はい。言われんでも使わんけどな……

注意して最適化する

最適化するな。でも、最適化の余地を潰すような設計はするな。
プログラムを書くときは性能度外視。
システム設計、とりわけ API、通信プロトコル、永続データ形式を設計しているときはパフォーマンスについて真剣に考える。作り終わって計測して速ければそれで終わり。遅ければプロファイラで見てボトルネック特定して、採用されてるアルゴリズム見て改善してく。

一般に受け入れられている命名規則を守る

はい。

第10章 例外

例外的状態にだけ例外を使う

まあ、はい。
例えば Iteratornext を持っているがこれは NoSuchElementException を吐く可能性がある。一方で、そうなるかどうかを検査するための hasNext がある。つまり、後者を併せて使用することで、client は例外ハンドリングを強制されることなく API を利用できる。

回復可能な状態にはチェックされる例外を、プログラミングエラーには実行時例外を使う

迷ったら実行時例外で。どっちでもないな、と思うなら例外定義自体やめようね。チェックされる例外を投げる場合、回復処理を助けるための method を定義しようね。

チェックされる例外を不必要に使うのを避ける

チェックされる例外は基本的には Optional な戻り値で解決する。ただ、Optional だとなんで失敗したかについての情報が不足する場合があるので、そういうときは使ってもいいよ。
※ vavr の Try 使えば?という話はある気がする。

標準的な例外を使う

使いまわせるなら使いまわそう。ただし semantics に気を付けて使う。詳細を追加したいときは extends する。でも継承の乱用は悪。

抽象概念に適した例外をスローする

例外翻訳: 低レイヤで発生した例外を、高レイヤに対して別の例外に包んで投げなおすこと。乱用は避ける。実は assertion でいいんじゃないの?とか。

各メソッドがスローするすべての例外を文書化する

はい。javadoc 書こうね。多くの member method に共通してるようなものは class の javadoc に書いてもいい。

詳細メッセージにエラー記録情報を含める

はい。なんで例外出たの?がわかるようにしておこうね。traceability の話。逆に、security 的にヤバいものは含まないようにしようね。

エラーアトミック性に努める

エラーアトミック性: 例外が出たら rollback する。つまりその method が呼び出される前の状態に復元しておく。
これを達成できないなら、そう javadoc に書いておく。

例外を無視しない

握りつぶすなよーというやつ。try-catch を業務的な分岐に使うな、というやつね。
無視しても許容される場合として、既に必要な情報は読み取れたから中断する必要ないとか、そんな重要じゃない(例えば UI の色がちょっと変わるだけとか)とか、そういうのなら百歩譲っていいかな。でも握りつぶすのが正当であることは comment で示そうね。

第11章 並行性

共有された可変データへのアクセスを同期する

Thread#stop 使うな。
ある変数への参照を同期したい場合、その変数への書き込み method と読み出し method の両方を synchronized する必要がある。

過剰な同期は避ける

同期の最中に異質な method の呼び出しは避ける。
異質: client が override できるような、中身の知れないような method のこと。データを壊したり、デッドロックを起こしたり、そういう実装が client 次第で可能であるから、呼び出さない方が良い。

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

自分でスレッドを立てるよりも、エグゼキュータサービスにタスクを食わせる方がよかったりする。

wait と notify よりも並行処理ユーティリティを選ぶ

concurrent collection 使おうね。

スレッド安全性を文書化する

遅延初期化を注意して使う

スレッドスケジューラに依存しない

第12章 シリアライズ

Java のシリアライズよりも代替手段を選ぶ

JSON とか protobuf とかで代替する。やるとしてもホワイトリスト形式で、信頼できない出所のデータストリームを deserialize しないこと。

Serializable を細心の注意を払って実装する

なので基本的に Serializable やめた方がいいねって話。

カスタムシリアライズ形式の使用を検討する

防御的に readObject メソッドを書く

インスタンス制御に対しては readResolve よりも enum 型を選ぶ

シリアライズされたインスタンスの代わりに、シリアライズ・プロキシを検討する

シリアライズプロキシパターンというのがあるらしい。へー。

ホームへ戻る