もっとちゃんとJavaのこと理解したいなと思って、Effective Javaを読んだ。
オブジェクトの生成と消滅
コンストラクタの代わりにstaticファクトリメソッドを検討する
- staticファクトリメソッドのメリット
- staticファクトリメソッドのデメリット
- publicあるいはprotectedのコンストラクタを持たないクラスのサブクラスを作れない制約がある
- プログラマがstaticファクトリメソッドを見つけるのが難しい
- 共通する命名規約を遵守することで、軽減できる
- from:単一パラメータを受け取り、当の肩を持つ対応するインスタンスを返す型変換メソッド
- of:複数のパラメータを受け取り、それらを含んだ当の型のインスタンスを返す集約メソッド
- valueOf:fromやofの代わりとなる、冗長な名前のメソッド
- instance あるいは getInstance:パラメータがあればそのパラメータで表されているインスタンスを返すが、必ずしも同じ値を持つとは限らない
- create あるいは newInstance:呼び出しごとにメソッドは新たなインスタンスを返す
- getType:ファクトリメソッドが対象のクラスとは異なるクラスにある場合に使われる。Typeはファクトリメソッドから返されるオブジェクトの型を示している。(getInstanceと類似)
- newType:ファクトリメソッドが対象のクラスとは異なるクラスにある場合に使われる。Typeはファクトリメソッドから返されるオブジェクトの型を示している。(newInstanceと類似)
- type:getTypeやnewTypeの代わりとなる、簡潔な名前のメソッド。
- 共通する命名規約を遵守することで、軽減できる
多くのコンストラクタパラメータに直面したときはビルダーを検討する
テレスコーピング・コンストラクタ・パターンは、多くのパラメータがある場合、クライアントのコードを書くのが困難になり、可読性が落ちる。JavaBeansパターン(setter)は、生成過程の途中で不整合な状態にあるかもしれない。クラスを不変にする可能性を排除してしまう。 テレスコーピング・コンストラクタ・パターンの安全性とJavaBeansパターンの可読性を組み合わせたのがBuilderパターン。Builderパターンは、コードを書くのも読むのも容易になる。 Builderパターンの欠点は、「最初にビルダーを生成しないといけない」、「記述が長くなってしまう」。将来的にパラメータが増えそうなときは、最初からビルダーで始めるのがたいていよい。 ビルダーによるクライアントのコードは、テレスコーピング・コンストラクタ・パターンよりは読みやすく、JavaBeansバターンよりは安全。
privateコンストラクタかenum型でシングルトン特性を強制する
インターフェースを実装していない限り、シングルトンをモック実装で置き換えることが不可能なため、クラスをシングルトンにすると、クライアントのテストを困難にする。
- シングルトンを実装するための方法
- public finalのフィールド
- staticファクトリメソッド
- 単一要素を持つenumを宣言
シングルトンを保証し続けるには、すべてのインスタンスフィールドをtransientと宣言して、readResolveメソッドを提供しなければならない。そうしなければ、シリアライズされたインスタンスをデシリアライズするごとに、新たなインスタンスが生成されてしまう。
資源を直接結び付けるよりも依存性注入を選ぶ
多くのクラスが1つ以上の下層の資源に依存している。 クラスの複数のインスタンスをサポートして、それぞれのインスタンスでクライアントが望む資源を使えることが必要で、新しいインスタンスを生成するときにコンストラクタに資源を渡すことが単純な解決策になる。 依存性注入は柔軟性とテスト可能性を大幅に向上するが、普通に数千の依存を含むような大きなプロジェクトを撮り散らかす可能性があるが、依存性注入フレームワークを使えば取り除ける。
不必要なオブジェクトの生成を避ける
機能的に同じオブジェクトが必要な度に新たに生成するよりは、一つのオブジェクトを再利用する方が適切。オブジェクトが不変であれば、常に再利用できる。
使われなくなったオブジェクト参照を取り除く
ガベージコレクションを持つ言語は、オブジェクトを使い終えたときにそれが自動的に回収されることで、プログラマとしての仕事は楽になる。 スタックが大きくなってその後小さくなると、スタックから取り出されたオブジェクトはガベージコレクトされない。スタックはそれらのオブジェクトに対する使われなくなった参照を保持しているため。 クラスが独自のメモリを管理しているときは、プログラマはメモリリークに対して注意を払うべき。要素が解放されるときには、その要素に含まれたすべてのオブジェクト参照にnullを設定すべき。
ファイナライズとクリーナーを避ける
ファイナライザは予想不可能で、たいていは危険で、一般的には必要ない。
- ファイナライザとクリーナーの欠点
- 即座に実行される保証がない。オブジェクトが到達不可能となってから実行されるまでの時間は、任意の長さ。
セーフティネットとして、あるいは重要でないネイティブピアの資源の解放のためを除いて、クリーナー(またはファイナライザ)は使わない方がよい。
try-finallyよりもtry-with-resourcesを選ぶ
Javaライブラリは、closeメソッドを呼び出して手作業でクローズしなければならない多くの資源を含んでいる。例として、InputStream、OutputStream、java.sql.Connectionが含まれる。 クローズしなければならない資源を扱うときは、いつでもtry-with-resourcesを使うのがよい。その結果、コードは短くて明瞭で、生成される例外は有用なものになる。
すべてのオブジェクトに共通のメソッド
Objectは具象クラスであるにもかかわらず、主に拡張されるために設計されている。 そのfinalでないすべてのメソッド(equals, hashCode, toString, clone, finalize)は、オーバーライドされるように設計されているおり、明示的な一般契約を持っている。
equalsをオーバーライドするときは一般契約を使う
equalsメソッドのオーバーライドによる問題を防ぐ1番簡単な方法は、オーバーライドしないこと。その場合、個々のインスタンスは自分自身とだけ等しくなる。
次の条件のどれかが当てはまるなら、オーバライドしないのが正しいやり方。
- クラスの個々のインスタンスが本質的に一意である
- クラスが「論理的等価性」の検査を提供する必要がない
- スーパークラスがすでにequalsをオーバーライドしており、スーパークラスの振る舞いがこのクラスに対して適切である
- クラスがprivateあるいはパッケージプライベートであり、そのequalsメソッドが呼び出されないことが確かである
オーバーライドするのが適切なときは、クラスが単なるオブジェクトの同一性とは異なる論理等価性という概念を持っていて、かつスーパークラスがequalsメソッドをオーバーライドしていないとき。一般には、値クラスのとき。
equalsメソッドのオーバーライド時に厳守すべき5要件
- 反射性
- オブジェクトがそれ自身と等しくなければならない
- 対称性
- いかなる二つのオブジェクトでも、それらが等しいかどうかについて合意しなければならない
- 推移性
- 一つ目のオブジェクトが二つ目のオブジェクト等しく、かつ、二つ目のオブジェクトが三つ目のオブジェクトと等しければ、最初のオブジェクトと三つ目のオブジェクトは等しくなければならない
- 整合性
- 二つのオブジェクトが等しければ、どちらか片方あるいは両方が変更されない限り、いつまでも常に等しくあり続けなければならない。
- 非null性
- すべてのオブジェクトはnullと等しくあってはならない
高品質のequalsメソッドを作成するレシピ
- 引数が自分自身のオブジェクトへの参照であるか検査するために==演算子を使う
- 引数が正しい型であるか検査するためにinstanceof演算子を使う
- 引数を正しい型にキャストする
- クラスの「意味のある」フィールドのそれぞれについて、引数のオブジェクトのフィールドが、このオブジェクトの対応するフィールドと一致するか検査する
equalsをオーバーライドするときは、常にhashCodeをオーバーライドする
equalsをオーバーライドしているすべてのクラスで、hashCodeをオーバーライドしなければならない。そうしないクラスは、Object.hashCodeの一般契約を破ることになり、HashMap、HashSetなどのコレクションで用いられると適切に機能しない。
toStringを常にオーバーライドする
Object#toStringは、クラス名、「@」、ハッシュコードの符号なし16進数表現から構成されており、一般的にクラスのユーザが見たい内容ではない。実用的な場合、toStringメソッドはオブジェクトに含まれる興味があるすべての情報を含むべき。
cloneを注意してオーバーライドする
メソッドを含んでいないCloneableはObjectのprotectedのcloneメソッドの実装の振る舞いを決定する。Cloneableを実装しているクラスは、適切に機能するpublicのcloneメソッドを提供することが期待されている。
Comparableの実装を検討する
compareToメソッドはObjectでは宣言されていない。正確に言えば、compareToメソッドはComparableインターフェースの唯一のメソッド。Comparableを実装することで、インスタンスが自然な順序を持っていることをクラスは示している。 アルファベット順、数値順、年代順などの明らかに自然な順序を持つ値クラスを書くなら、Comparableインターフェースを実装すべき。compareToメソッドの実装内でフィールドの値を比較するときは、<演算子と>演算子を使わないほうがよい。代わりに、ボクシングされた基本データクラスのstaticのcompareメソッドか、Comparatorインターフェースのコンパレータ構築メソッドを利用する。
クラスとインターフェース
クラスとメンバーへのアクセス可能性を最小限にする
うまく設計されたコンポーネントと下手に設計されたコンポーネントを区別している唯一の最も重要な要素は、内部データと実装の詳細を他のコンポーネントからどの程度隠蔽しているかである。うまく設計されたコンポーネントは、その実装のすべての詳細を隠蔽し、実装とAPIをはっきりと分離している。 情報隠蔽(カプセル化)は多くの理由で重要。コンポーネントを並行して開発できるため、情報隠蔽はシステム開発のスピードを向上させる。各クラスやメンバーをできる限りアクセスできないようにすべき。
publicクラスでは、publicフィールドではなく、アクセサーメソッドを使う
publicクラスの可変なフィールドを公開すべきではない。しかし、可変であろうと不変であろうと、パッケージプライベートのクラスかprivateのネストしたクラスであればフィールドを後悔することが望ましい場合がある。
可変性を最小限にする
不変クラスは、そのインスタンスが変更できないという単なるクラス。不変クラスは、設計・実装、使用が可変クラスよりも容易。不変クラスは誤りにくく安全。
クラスを不変にする5つの規則
- オブジェクトの状態を変更するためのメソッドを提供しない
- クラスを拡張できないようにする
- すべてのフィールドをfinalにする
- すべてのフィールドをprivateにする
- 可変コンポーネントに対する独占的アクセスを保証する
不変クラスの唯一の実質的欠点は、個々の異なる値に対して別々のオブジェクトを必要とする。
継承よりもコンポジションを選ぶ
継承はコードの再利用するための強力な方法だが、常に再利用のための最善の道具とはならない。不適切に使われると、継承はもろいソフトウェアを作り出してしまう。 サブクラスとスーパークラスの実装が同じでプログラマの管理下にある場合、パッケージ内の継承を使うのは安全。拡張のために設計されて、かつ拡張のために文書化されているクラスを拡張する場合にも、継承を使うのは安全。 しかし、パッケージをまたがって、普通の具象クラスから継承することは危険。 メソッド呼び出しとは異なり、継承はカプセル化を破る。(= サブクラスは適切に機能するために、スーパークラスの実装の詳細に依存する)
メソッドをオーバーライドすることで発生してしまう問題については、既存のクラスを拡張する代わりに、新たなクラスに既存のクラスのインスタンスを参照するprivateのフィールドを持たせることで解決できる。既存のクラスが新たなクラスの構成要素になるため、この設計はコンポジションと呼ばれる。
コンポジションではなく継承を使うと決める前に、「拡張しようと考えているクラスは、そのAPIに何らかの血管を持っていないか。もし、欠陥があれば、自分のクラスにその血管を伝播させていることに不安はないか。」を自問する必要がある。
継承のために設計および文書化する、でなければ継承を禁止する
クラスはメソッドのオーバーライドの影響を正確に文書化しなければならない。(= クラスはオーバーライド可能なメソッドの自己利用を文書化しなければならない) 継承のためにクラスを設計するのは大変な仕事である。クラスの自己利用をすべて文書化しなければならないし、一旦文書化したら、クラスが存在する限りそれを守らなければならない。サブクラスの必要性があるとわかっていない限り、クラスをfinalと宣言するかアクセスできるコンストラクタがないようにすることで、継承を禁止するのがおそらくよい。
抽象クラスよりもインターフェースを選ぶ
Javaは、複数の実装を許す型を定義するためにインターフェースと抽象クラスの二つの仕組みを提供している。この二つの相違は、抽象クラスで定義された型を実装するには、クラスはその抽象クラスのサブクラスでなければならないこと。 新たなインターフェースを実装するように既存のクラスを変更するのは容易。インターフェースはミックスインを定義するには理想的。インターフェースは、回想を持たない型フレームワークの構築を可能にしている。
将来のためにインターフェースを設計する
主にラムダを活用するために、多くの新たなデフォルトメソッドが、Java8の中核のコレクションインターフェースに追加された。 考えられるすべての実装のすべての不変式を維持するデフォルトメソッドを書くことは必ずしも可能ではない。デフォルトメソッドの存在は、インターフェースをエラーや警告なしでコンパイルできるかもしれないが、実行時に失敗することがある。 インターフェースがリリースされた後に、インターフェースの欠陥によっては修正が可能かもしれないが、そのことに期待すべきではない。
型を定義するためだけにインターフェースを使う
クラスがインターフェースが実装することで、そのインスタンスでクライアントは何ができるのかについてクラスは述べるべき。他の目的のためにインターフェースを定義するのは不適切。 この検査に合格しないインターフェースの種類の一つは、いわゆる定数インターフェース。定数インターフェースパターンは、インターフェースの下手な使い方である。これを避けるために、enum型やインスタンス化不可能なユーティリティクラスで定数を提供するのがよい。
タグ付きクラスよりもクラス階層を選ぶ
タグ付きクラスは、冗長で、誤りやすく、非効率なので、適切であることはほとんどない。明示的なタグフィールドを持つクラスを書きたくなったら、そのタグを取り除いてクラス階層で置き換えられないかを考えるのがよい。
非staticのメンバークラスよりもstaticのメンバークラスを選ぶ
ネストしたクラスは、他のクラス内に定義されたクラス。ネストしたクラスには、staticメンバークラス、非staticのメンバークラス、無名クラス、ローカルクラスの4種類がある。staticのメンバークラス以外は、内部クラスとして知られている。
ネストしたクラスの用途は次の通りである。
- staticメンバークラス
- たまたまあるクラス内で宣言され、そのエンクロージングクラスのメンバーのすべてに、たとえそのメンバーがprivateと宣言されていてもアクセスできる通常のクラス。
- 使い方は、その外部クラスと一緒に使うと有用なpublicのヘルパークラスとしてである。
- staticと非staticのメンバークラスの構文的な唯一の相違点は、staticのメンバークラスはその宣言にstatic修飾子があること。
- 非staticのメンバークラスの個々のインスタンスは、それを含むクラスのエンクロージングインスタンスと暗黙に関連づけられている。
- エンクロージングインスタンスなしで、非staticのメンバークラスのインスタンスを生成することは不可能。
- 非staticのメンバークラス
- private staticのメンバークラス
- 使い方は、エンクロージングクラスが表すオブジェクトの構成要素を表すこと。
- 無名クラス
- 無名クラスは名前を持たない。そのエンクロージングクラスのメンバーではない。
- 無名クラスが宣言された箇所以外で、無名クラスをインスタンス化できない。instanceof検査やクラスの名前を指定する必要がある処理はできない。
- 無名クラスよりもラムダが好ましい。
- 使い方は、staticファクトリメソッド内の実装において。
- ローカルクラス
- ローカルクラスは、4種類のネストしたクラスの中で、おそらく最も使われてはいけない。
要約すると、4種類の異なるネストしたクラスがあり、それが異なる用途を持っている。 ネストしたクラスが、一つのメソッドの外からも見える必要があったり、メソッド内に問題なく入れるのに長すぎるならば、メンバークラスを使う。 メンバークラスの個々のインスタンスが、エンクロージングインスタンスへの参照が必要ならば、非staticにする。そうでなければstaticにする。 クラスがメソッド内に属しているべきであり、一箇所からのみインスタンスを生成する必要があり、そのクラスを特徴付ける型がすでに存在していれば、無名クラスにする。そうでなければ、ローカルクラスにする。
ソースファイルを単一のトップレベルのクラスに限定する
Javaコンパイラは、単一のソースファイルに複数のトップレベルのクラスを定義することを許すが、そうすることに何も利点はなく、重大なリスクがある。そのリスクは、一つのクラスに対して複数の定義の提供が可能になること。 トップレベルのクラスを、クラス名と同じ名前の別々のソースファイルへ分けることが、問題の解決である。
ジェネリックス
原型を使わない
一つ以上の型パラメータを宣言に持つクラスやインターフェースは、ジェネリッククラスやジェネリックインターフェース。 個々のジェネリック型は、クラス名やインターフェース名の後にアングルブラケットで囲んで、そのジェネリック型の仮型パラメータに対応する実型パラメータのリストからなるパラメータ化された型の集合を定義する。
無検査警告を取り除く
ジェネリクスを用いてプログラミングする際には、コンパイラの警告を多く目にします。無検査キャスト警告、無検査メソッド呼び出し警告、パラメータ化された可変引数型警告、無検査変換警告である。取り除けるすべての無検査警告を取り除くのがよい。 SuppressWarningアノテーションを、できる限り最小のスコープに対して使うのがよい。
配列よりもリストを選ぶ
配列は2つの重要な点で、ジェネリック型と異なっている。
- 配列は共変(covariant)でジェネリックは不変(invariant)
- SubがSuperのサブタイプならば、配列型 Sub が Super のサブタイプである
- List は List のサブタイプでもなければスーパータイプでもない
- 配列は具象化されている
ジェネリック型を使う
ジェネリック型は、クライアントのコードでキャストが必要である型より、安全で使いやすい。
ジェネリックメソッドを使う
クラスをジェネリック化できるように、メソッドもジェネリック化できる。入力パラメータと戻り値をキャストすることをクライアントに要求するメソッドより、安全で使いやすい。 メソッドを型安全にするには、引数と戻り値に対する要素型を表す型パラメータを宣言する。
APIの柔軟性向上のために境界ワイルドカードを使う
最大の柔軟性のためには、プロデューサがコンシューマを表す入力パラメータに対してワイルドカード型を使うのがよい。 広く使われるタイプのライプラリを書くなら、ワイルドカード型を適切に使うことは、必須だと考えるべき。それは、プロデューサ-extends、コンシューマ-super(PECS)。そして、すべての比較可能なものとコンパレータは、コンシューマであることを忘れてない。
ジェネリックと可変長引数を注意して組み合わせる
可変長引数の目的は、クライアントがメソッドへ可変長の引数を渡せるようにすることだが、それは漏出抽象化。 ヒープ汚染は、パラメータ化された型の変数が、その型ではないオブジェクトを参照している場合に発生する。 SafeVarargsアノテーションは、メソッド作成者がそのメソッドが型安全であることを約束することである。この約束とは引き替えに、コンパイラは呼び出しが安全ではないかもしれないメソッドのユーザに警告しなくなる。
次の場合にジェネリック可変長引数メソッドは安全。
- 可変長パラメータ配列に何も保存していなくて、かつ、
- 信頼できないコードに対してその配列(あるいは複製を)参照できるようにしていない。
ジェネリック(あるいはパラメータ化された)可変長パラメータを持つメソッドを書くなら、最初にそのメソッドが型安全であるようにするべき。そして、使いやすいように@SafeVarargeアノテーションをつける。
型安全な異種コンテナを検討する
コレクションAPIで示されているジェネリックスの一般的な使い方は、コンテナごとに固定数の型パラメータに制限している。コンテナでなくキーに対して型パラメータを指定することでこの制約をさけられる。 このような型安全異種コンテナに対するキーとしてClassオブジェクトを使える。このように使われるClassオブジェクトは、型トークンと呼ばれる。
enumとアノテーション
int定数の代わりにenumを使う
列挙型(enumerated type)は、一年の四季、太陽系の惑星、トランプのスーツなどの固定数の定数からその値が成り立つ型。 int enumパターン、String enumパターンの欠点を避けて、多くの恩恵をもたらす代替がenum型。 Javaのenum型は、public static final フィールドを通して、各列挙定数に対して一つのインスタンを公開しているクラス。enum型はアクセス可能なコンストラクタをっ持っていないので、事実上finalである。 定数値は、int enumパターンとは異なり、クライアントの中にコンパイルされない。toStringメソッドを呼び出すことで、enumを表示可能な文字列に変換できる。 int enumパターンの欠点を修正することに加えて、enum型には任意のメソッドやフィールドを追加でき、任意のインターフェースも実装できる。 機能が豊富なenum型を書くのは容易。enum定数にデータを関連付けるために、インスタンスフィールドを宣言して、データを受け取るコンストラクタを書いて、そのフィールドにデータを保存する。
序数(ordinal)の代わりにインスタンスフィールドを使う
すべてのenumはordinalメソッドを持っており、enum型内の各enum定数の数値で表されている位置を返す 序数からenumに関連づけられる値を使う代わりに、インスタンスフィールドに値を保存するのがよい。 ordinalは、EnumSetやEnumMapなどの汎用的なenumに基づくデータ構造により使われるように設計されており、そのようなデータ構造を書いていないのであれば、ordinalメソッドを使わないのが最善。
ビットフィールドの代わりにEnumSetを使う
列挙型の要素が集合で使われるなら、書く定数に異なる2つの要素を割り当てて、int enumパターンを使うのが従来の方法。ビットフィールドはint enum定数の短所だけではなく、さらに多くの短所を持っている。 EnumSetを使うのが良い。EnumSetクラスはビットフィールドの簡潔性とパフォーマンスを、enum型の多くの利点全てと組み合わせてくれる。
序数インデックスの代わりにEnumMapを使う
EnumMapとして知られるenumをキーとして使うように設計された後続なMapの実装がある。 EnumMapのコンストラクタは、キー型のClassオブジェクトを受け取る。これは境界型トークンであり、実行時のジェネリック型情報を提供している。 配列をインデックスするために序数を使うことが適切であることは滅多にない。代わりにEnumMapを使う。
拡張可能なenumをインターフェースで模倣する
拡張可能なenum型を書くことはできないが、基本のenum型に伴うインターフェースを書いて、そのインターフェースをその基本のenum型に実装させることで模倣できる。
命名パターンよりもアノテーションを選ぶ
何らかのプログラム要素がツールやフレームワークによる特別な処理を要求していることを示すために、命名パターンが使われるのが普通だった。アノテーションを利用できる場合、命名パターンを使うのは論外。
常にOverrideアノテーションを使う
一般的なプログラマにとって、それらの中で最も重要なのは@Overrideです。このアノテーションはメソッド宣言にだけ使えて、アノテーションがつけられたメソッド宣言がスーパータイプの宣言をオーバーライドしていることを示す。 スーパークラスの宣言をオーバーライドしているすべtのメソッド宣言に対して@Overrideアノテーションを使うべき。 抽象クラスやインターフェースでは、スーパークラスのメソッドやスーパーインターフェースのメソッドをオーバーライドしているすべてのメソッドにアノテーションをつける価値はある。コンパイラは多くの誤りから皆さんを保護できる。
型を定義するためにマーカーインターフェースを使う
マーカーインターフェースはメソッド宣言を含んでいないインターフェースであり、そのインターフェースを実装しているクラスが何らかの特性を持っていることを単に指定する。 マーカーインターフェースとマーカーアノテーションはどちらも用途がある。関連づけられた新たなメソッドを持たない型を定義したいなら、マーカーアノテーションであるべき。クラスとインターフェース以外のプログラム要素をマークしたい、あるいは、アノテーション型をステに多用しているフレームワーク内にマーカーを適合させたいなら、マーカーアノテーションが正しい選択。
ラムダとストリーム
関数オブジェクトを容易に作成できるように、Java8で関数型インターフェース、ラムダ、メソッド参照が追加された。データ要素のシーケンス処理するライブラリを提供するために、一緒にストリームAPIも追加された。
無名クラスよりもラムダを選ぶ
Java8では、単一の抽象メソッドを持つインターフェースは特別であり、特別に扱うに値するという概念を形式化した。 関数型インターフェースではない型のインスタンスを作成する必要があるときだけ、関数型オブジェクトとして無名クラスを使う。
ラムダよりもメソッド参照を選ぶ
メソッド参照は、たいていラムダよりも簡潔な代替を提供する。メソッド参照の方が短く明瞭である箇所では、メソッド参照を使うのがよい。そうではない箇所では、ラムダを使うのがよい。
標準の関数型インターフェースを使う
java.util.Functionには43個のインターフェスがあるが、6個の基本てインターフェースを覚えてさえいれば、残りのインターフェースは導き出せる。 Operatorインターフェースは、その結果の型が引数の型と同じ関数を表す。 Predicateインターフェースは、引数を受け取りbooleanを返す関数を表す。 Functionインターフェースは、その引数の型と戻り値の型が異なる関数を表す。 Supplierインターフェースは、引数を取らず値を返す関数を表す。 Consumerインターフェースは、引数を受け取り何も返さない関数を表す。\
標準の関数型インターフェースのほとんどは、基本データ型に対するサポートを提供するためだけに存在している。基本データ型の関数型インターフェースの代わりにボクシングされた基本データでもって、基本の関数型インターフェースを使わないようにするのがよい。\
ストリームを注意して使う
Stream APIは、大量操作の逐次処理や並列処理を行いやすくするためにJava 8で追加された。このAPIは二つの主要な抽象化を提供している。データ要素の有限あるいは無限なシーケンスを表すストリームと、データ要素に対する複数ステージの計算を表すストリームパイプラインである。 ストリームパイプラインは、ソースのストリーム、それに続く0個以上の中間操作、その後に一つの終端操作から構成される。 ストリームパイプラインは遅延して評価される。つまり、評価は終端操作が呼び出されるまで開始されないし、終端操作を完了されるために必要のないデータ要素は計算されない。 ストリームの乱用はプログラムの理解や保守を難しくする。ラムダのパラメータは明示的な型がないため、注意深く命名することはストリームパイプラインの可読性にとっては重要。
ストリームで副作用のない関数を選ぶ
ストリームのパラダイムの最も重要な部分は、計算を変換のシーケンスとして構築すること。中間操作と終端操作の両方のストリーム操作に渡される関数オブジェクトには副作用がないべき。 処理のすべてを終端のforEach操作で行い、外部の状態を更新するラムダを使うことは問題。ストリームが行った計算結果を表示する以外の処理を行っているforEach操作は「コード中の悪臭」である。 forEach操作は、最も力を持たない終端操作の一つだし、最もストリーム向きではない。forEach操作は、ストリームの計算結果を報告するためだけに使い、計算を行うために使うべきではない。
戻り値型としてStreamよりもCollectionを選ぶ
要素のシーケンスを返すメソッドを書く場合、ユーザによってはストリーム処理したいかもしれないし、ループで処理したいかもしれない。コレクションを返されるなら、返すのがよい。コレクションを返せないなら、StreamかIterableのどちらか自然と思われる方を返す。 将来のJavaのリリースで、Streamインターフェースの宣言がIterableを拡張するように修正されたら、ストリーム処理とループの両方が可能なのでStreamを返すべき。
ストリームを並列化するときは注意を払う
正しい速い並行プログラムを書くことは困難。安全違反と活性違反は並行プログラムではよくあることであり、並列なストリームパイプラインも例外ではない。 一般に、並列化によるパフォーマンスの向上は、ArrayList、HashMap、HashSet、ConcurrentHashMapの各インスタンス、配列、intの範囲(range)そしてlongの範囲で最も得られる。これらのデータ構造で共通なのは、すべてが望ましい大きさのサブレンジへと正確に低いコストで分割できること。 並列化に対する最善の終端操作はリダクションであり、パイプラインからの要素のすべてがStreamのreduceメソッドの一つ、もしくはmin、max、count、sumといった事前に用意されているリダクションを使ってまとめられる。 短絡操作であるanyMatch、allMatch、noneMatchも、並列化に適している。可変リダクションとして知られるStreamのcollectメソッドによって行われる操作は、並列化に対する優れた候補ではない。コレクションをまとめるオーバーヘッドは高くつくため。 ストリームの並列化は活性エラーを含む貧弱なパフォーマンスをもたらす可能性があるだけはなく、不正な結果や予期できない振る舞い(安全性エラー)になる可能性がある。 計算の正しさを維持し、パフォーマンスを向上させると信じるに値する十分な理由がない限り、ストリームパイプラインの並列化は試みないのがよい。
メソッド
パラメータと戻り値をどのように扱うか、メソッドのシグニチャをどのように設計するか、そしてメソッドをどのように文書化するか。
パラメータの正当性を検査する
メソッドとコンストラクタのパラメータとして渡される値が持つ何らかの制約は、明確に文書化すべきであり、メソッド本体の初めで検査して制約を強制すべき。そうしないと、エラーが検出される可能性が低くなり、エラーが発見されても原因の特定が困難になる。パラメータの正当性を検査しないと、エラーアトミック性を破る。 public と protectedのメソッドに対しては、パラメータ値に関する制約が守られていない場合にスローされる例外をJavadocの @throws タグを使って文書化すべき。 publicでないメソッドは、アサーション(assertion)を用いてパラメータを検査できる。アサーションは条件が成り立たなければAssertionErrorをスローする。 正当性検査が高くつくかもしれないもしくは現実的ではなく、かつ計算の処理の中で正当性検査が暗黙に行われる場合、計算を行う前にメソッドのパラメータを検査すべきという規則の例外である。
必要な場合、防御的にコピーする
Javaの使用を楽しくさせていることの一つは、Javaが型安全な言語だということ。 クラスのクライアントはクラスの不変式を破壊するために徹底した努力をする、と想定して防御的プログラミングをしなければならない。コンストラクタへの個々の変更可能なパラメータを防御的にコピーすることが重要。 クラスがクライアントから得たり、クライアントへ返したりする可変な要素を持っているならば、そのクラスはそれらの要素を防御的にコピーしなければならない。もし、コピーのコストが高く、かつ要素を不適切に変更しないということでクライアントを信頼できるならば、影響を受ける要素を変更しないことがクライアントの責任であることをドキュメンテーションに示すことで、防御コピーの代わりとしてもよい。
メソッドのシグニチャを注意深く設計する
- メソッド名を注意深く選ぶ
- 名前は常に標準命名規規約に従うべき。
- 理解可能で、同じパッケージ内の他の名前と矛盾のない名前を選ぶ
- 存在する広範囲のコンセンサス(合意)と矛盾がない名前を選ぶ
- 便利なメソッドを提供しすぎない
- 個々のメソッドは「自分の役割を果たす」べき
- 多くのメソッドを持つインターフェースは、ユーザ及び実装者の人生を複雑にする
- 長いパラメータのリストは避ける
- 4個以下を目標にする
- 同一の型のパラメータが何個も続くのは有害
- 対策として、「1. メソッドを複数分割して、各メソッドはパラメータのサブセットだけを必要とするようにする」、「2. パラメータの集まりを保持するヘルパークラスを作成する」、「3. Builderパターンをオブジェクト生成からメソッドの呼び出しに適用する」などがある。
- パラメータ型に関しては、クラスよりもインターフェースを選ぶ
オーバーロードを注意して使う
オーバーロードされたどのメソッドが呼び出されるかの選択はコンパイル時に行われる。オーバーロードされたメソッドの選択は静的であり、オーバーライドされたメソッドの選択は動的。困惑させるようなオーバーロードの使用は避けるべき。 安全で保守的な方針は、同じパラメータ数の二つのオーバーロードされたメソッドを提供しないこと。オーバーロードする代わりにメソッドには異なる名前をつける。 同じ引数位置で異なる関数型インターフェースを受け取るようにメソッドをオーバーロードしてはいけない。
可変長引数を注意して使う
可変長引数メソッドは、指定された型の0個以上の引数を受け付ける。最も深刻な問題は、クラアイントが引数なしでこのメソッドを呼び出した場合、コンパイル時でなく実行時に失敗すること。
nullではなく、空コレクションか空配列を返す
空コレクションや空配列の代わりに、nullを返さない方がよい。nullを返すことでAPIの使用が困難となり、誤りやすくなる。そして、パフォーマンスの利点もない。
オプショナルを注意して返す
Java 8 で追加された、Optionalクラスは、単一のnullでないT参照を保持するか、もしくは何も保持していない不変なコンテナを表す。Java 9では、Optionalはstream()メソッドを持つように修正された。 コレクション、ストリーム、マップ、配列、オプショナルを含むコンテナ型をオプショナルで包むべきではない。結果を返せないかもしれなくて、かつ何も結果が返されなければクライアントが特別な処理をせざるを得ないなら、Optionalを返すメソッドを宣言すべき。 戻り値以外でオプショナルを使うのはまれであるべき。
すべての公開API要素に対してドキュメントコメントを書く
APIを使えるようにするなら、それは文書化されなければならない。\
- APIを適切に文書化するには、すべての公開されているクラス、インターフェース、コンストラクタ、メソッド、フィールドの宣言の前にドキュメントコメントを書かなければならない。
- メソッドに関するドキュメントコメントは、メソッドとそのクライアント間の契約を簡潔に記述すべき
- ドキュメントコメントは、メソッドのすべての事前条件と事後条件を列挙すべき(事前条件とはクライアントがメソッドを呼び出すために成立していなければならない事柄で、事後条件とは呼び出しが正常に完了した後に成立していなければならない事柄)
- すべての副作用も文書化すべき
- enum型を文書化する際には、型とすべてのpublicメソッドだけではなく定数も文書化するのがよい
- アノテーションを文書化する際には、その型自身だけではなく、すべてのメンバーも文書化するのがよい
プログラミング一般
ローカル変数、制御構造、ライブラリ、データ型、二つの言語外機能(リフレクション、ネイティブメソッド)について書く。
ローカル変数のスコープを最小限にする
ローカル変数のスコープを最小限にすることで、コードの可読性と保守性が向上し、誤りの可能性が減る。 ローカル変数のスコープを最小限にする最も強力な技法は、ローカル変数が初めて使われたときに宣言すること。ほとんどすべてのローカル変数宣言は、初期化子を含むべき。
従来のforループよりもfor-eachループを選ぶ
for-eachループ(拡張for文)は、コードの散らかっている部分を取り除き、イテレータ変数とインデックス変数を隠蔽することでエラーの機会を排除する。
for-eachループが使えない三つの状況
- 破壊的フィルタリング
- 選択された要素を取り除きながらコレクションを操作する必要が場合、明示的なイテレータを使う必要がある
- 変換
- リストや配列を操作してその要素の値の一部、あるいは全部を置換する必要がある場合、要素の値を置換するためにリストイテレータや配列インデックスが必要
- 並列イテレーション
- 複数のコレクションを並列に操作する必要がある場合、イテレータやインデックス変数に対する明示的な制御が必要
ライブラリを知り、ライブラリを使う
標準ライブラリを使うことで、それを書いた専門家の知識と、それをあなたよりも前に使った人々の経験を利用できる。主要なリリースごとに多くの機能がライブラリに追加されており、それらの追加を知っておくことで得られるものがある。
正確な答えが必要ならば、floatとdoubleを避ける
float 型と double 型は、主に科学計算と工学計算のために設計されている。それらは2進浮動小数点算術を行う。正確な結果を提供しないし、正確な結果が必要な場合には使うべきではない。 BigDecimalを使うことの短所は、基本データの算術型を使うよりは不便で、遅いこと。BigDecimalを使うことで、丸めを制御できるという利点も加わり、丸めを必要とする操作を行う場合に8種類の丸めモードから選択できる。
ボクシングされた基本データよりも基本データ型を選ぶ
Javaは、int、booleanといった基本データ型(primitive type)と、StringやListといった参照型(reference type)の二つから構成される型システムを持っている。すべての基本データ型は、ボクシングされた基本データと呼ばれる対応する参照型を持っている。
基本データ型とボクシングされた基本データ間の三つの主な違い
- 基本データ型は値だけ持つが、ボクシングされた基本データのインスタンスは値とは別のアイデンティティを持っている
- 基本データ型は機能する値だけを持つが、個々のボクシングされた基本データは、対応する基本データ型の機能をすべての値に加えて、nullという機能しない値を持っている
- 基本データ型は、ボクシングされた基本データよりは一般に時間と空間に関してより効率的
自動ボクシングは、ボクシングされた基本データを使うことの冗長性を減らすが、危険性は減らさない
他の型が適切な場所では、文字列を避ける
- 文字列は、他の値型に対する代替としては貧弱
- 文字列は、列挙型に対する代替としては貧弱
- enumの方が文字列よりもはるかに優れた列挙型定数となる
- 文字列は、集合型に対する代替としては貧弱
- 文字列は、ケイパビリティに対する代替としては貧弱
文字列のパフォーマンスに用心する
n個の文字列を結合するのに、文字列結合演算子を繰り返し使うと、nに関して二次の時間を必要とする。それは、文字列が不変であるという事実の不幸な結果。 許容できるパフォーマンスを達成するには、Stringの代わりにStringBuilderを使うのがよい
インターフェースでオブジェクトを参照する
適切なインターフェース型が存在するならば、パラメータ、戻り値、変数、およびフィールドはすべてインターフェース型を使って宣言されるべき。 もし、型としてインターフェースを使う癖を身につけたら、プログラムはかなり柔軟になる。 適切なインターフェースがなければ、クラスが改装中で必要な機能を提供している最も上位のクラスを使うのがよい。
リフレクションよりもインターフェースを選ぶ
コア・リフレクション機構であるjava.lang.reflectは、任意のクラスに対するプログラミングによるアクセスを提供している。 リフレクションは、コンパイルされた時点で存在さえしない他のクラスを使えるようにするが、これには次の代価が伴う。
- 例外の検査も含めて、コンパイル時の型検査の恩恵をすべて失う
- リフクレションによるアクセスを行うコードは、ぎこちなく冗長
- パフォーマンスが悪くなる
- 通常のメソッド呼び出しよりもかなり遅い
大変限られた形式だけでリフレクションを使うことで、リフレクションのコストをほとんど負うことなく、リフレクションの利点を多く得られる。
ネイティブメソッドを注意して使う
CやC++などのネイティブのプログラミング言語で書かれたメソッドであるネイティブメソッドの呼び出しを、Javaアプリケーションから可能にするのが、Java Native Interface(JNI)である。 ネイティブメソッドは3つの主要な用途があった。
- レジストリやファイルロックなどのプラットフォーム固有の機構へのアクセスを提供
- 既存のネイティブコードのライブラリへのアクセスを提供
- パフォーマンス改善のために、アプリケーションのパフォーマンスが重要な部分をネイティブ言語で書くのに使われた
パフォーマンス改善のためにネイティブメソッドを使うことは勧められない。 ネイティブ言語は安全ではないため、ネイティブメソッドを使っているアプリケーションはメモリ破壊エラーの影響を受けるようになる短所がある。 ネイティブコードを利用する機会はほぼないが、もし利用する機会がある場合はネイティブコードの中のたった一つのバグで、アプリケーション全体がだめになってしまうため注意深く利用する必要がある。
注意して最適化する
時期尚早の最適化は、よくなるよりは外になりやすい。
- 速いプログラムよりも優れたプログラムを書くように努めるべき
- 優れたプログラムは、情報隠蔽の原則を具体化している
- パフォーマンスを制限するような設計上の決定を避けるように努めるべき
- API設計の決定によるパフォーマンスの結果を考慮すべき
- 一般的に優れたAPI設計は、よいパフォーマンスと矛盾していない
一般的に受け入れられている命名規約を守る
Javaプラットフォームの命名規約は、大雑把にいうと活字的と文法的の2種類に分類される。
活字的命名規約は、パッケージ、クラス、インターフェース、メソッド、フィールド、型変数を扱っている。 パッケージ名とモジュール名は、ピリオドで区切られた要素を持ち、階層的であるべき。要素は小文字のアルファベットとまれに数字から構成されるべき。 一般的に8文字以下であるべき。意味を持った省略形は推奨されていおり、utilitiesよりはutilなど。 メソッド名とフィールド名は、クラス名とインターフェース名と同じ活字的規約に従うが、メソッド名やフィールド名の最初の文字は小文字にすべき。 型パラメータ名はたいてい1文字。任意の型に対するT、コレクションの要素型に対するE、マップのキーと値に対するKとV、そして、例外に対するX。関数の戻り値型はたいていR。一連の任意の型に対しては、T、U、VやT1、T2、T3が使える。
文法的命名規約は、活字的規約よりも柔軟で議論の的となる。 enum型も含めてクラスは、一般的に単数名詞あるいは名詞句で命名される。インスタンス化できないユーティリティクラスはたいていCollectorsやCollectionsといったように複数名詞で命名される。 インスタンスとクラスは同じように命名される。あるいは、インターフェースはableやibleで終わる形容詞で命名される。 アノテーション型はどの品詞ということはなく、名詞、動詞、前置詞、形容詞すべてが使われる。 何らかの処理を行うメソッドは、一般に動詞あるいは(目的語を含む)動詞句で命名される。boolean値を返すメソッドは、たいてい単語isで始まり、まれにhasではじまる。その後に形容詞として機能する、名詞、名詞句、あるいは単語か句が続く。 オブジェクトの型を変換し、別の型の無関係なオブジェクトを返すメソッドは、たいていtoTypeと呼ばれる。toString、toArrayなど。 レシーバーオブジェクトの型とは異なる型を持つビューを返すメソッドは、たいていasTypeと呼ばれる。asListなど。 メソッドが呼び出されたオブジェクトと同じ値を持つ基本データを返すメソッドは、たいていtypeValueと呼ばれる。intValueなど staticファクトリメソッドに対する共通の名前は、from、of、valueOf、instance、getInstance、newInstance、getType、newTypeなど。\
例外
最適に使われた場合、例外はプログラムの可読性、信頼性、保守性を改善する。
例外的状態にだけ例外を使う
「JVMは配列へのアクセスのすべてを検査するので、通常のループ終了検査は冗長であり避けるべきだ」という推論には三つの誤りがある。
- 例外は例外的状況で使われるように設計されており、JVM実装者にとって、明示的な検査と同じくらいに例外を速くする動機はほとんどない
- try-catchブロック内にコードを書くことは、書かれない場合に最新のJVM実装が行うある種の最適化を排除する
- 配列をループする標準イディオムは、冗長な検査をするとは限らない。最新のJVM実装は、その検査を最適化して取り除く
例外は、例外的条件に対して使うべきであり、通常の制御フローに例外を使うことをクライアントに強制してはいけない。うまく設計されたAPIは、通常の制御フローに例外を使うことをクライアントに強制してはいけない。
回復可能な状態にはチェックされる例外を、プログラミングエラーには実行時例外を使う
Javaは、3種類のスローできる例外を提供している。チェックされる例外、実行時例外、エラーである。
チェックされる例外を使うと、チェックされない例外を使うかを決める基本的な規則は、「呼び出し元が適切に回復できるような状況に対してはチェックされる例外を使うべき」ということ。 チェックされない例外には、実行時例外とエラーの2種類がある。それらはキャッチされる必要はなく、一般的にキャッチされるべきでない例外。プログラムがチェックされない例外やエラーをスローしたならば、それは一般的に回復が不可能で、実行の継続が役立つのではなくむしろ害になるような場合。 プログラミングエラーを示すには、実行時例外を使う。実行時例外のほとんどは、事前条件違反を示している。 実装するすべてのチェックされない例外は、RuntimeExceptionをサブクラス化すべき。
チェックされる例外を不必要に使うのをさける
控えめに使われると、チェックされる例外はプログラムの信頼性を向上できるが、過剰に使われると、APIを使うのが苦痛になる。 呼び出し元が失敗から回復できないなら、チェックされない例外をスローする。もし、回復が可能であり、呼び出し元に例外的状態の処理を強制したいなら、最初にオプショナルを返すことを検討するのがよい。 失敗の場合にオプショナルが十分な情報をて今日しない場合にだけ、チェックされる例外をスローすべき。
標準的な例外を使う
標準的な例外を再利用することは、「APIを学んで使うのが簡単にできる」、「プログラムが見慣れない例外でごちゃごちゃしないため、APIを使うプログラムが読みやすい」、「例外クラスが少ないことは、小さなメモリ量とクラスのロードに費やされる時間が少ない」などの利点がある。 Exception、RuntimeException、Throwable、Errorを直接再利用するのはよくない。これらの例外がスローされたのか、他の例外がスローされたのか区別できない。
抽象概念に適した例外をスローする
メソッドが行っている処理と明らかに関係のない例外を、そのメソッドがスローした場合には混乱が生じる。この問題を避けるためには、上位レイヤは下位レベルの例外をキャッチして、上位レイヤの中で、上位レベルの抽象概念の観点から説明可能な例外をスローすべき(例外翻訳と呼ばれる)。 上位レベルの例外のコンストラクタは、スーパークラスの連鎖可能なコンストラクタへその原因を渡すことで、最終的にはThrowableといったThrowableの連鎖可能なコンストラクタの一つに渡される。 例外翻訳は、下位レイヤから何も考えないで例外を伝播させるよりは優れているが、乱用すべきではない。連鎖は、失敗を分析するための根本原因を捕捉する一方で、適切な上位レベルの例外のスローを可能にしている。
各メソッドがスローするすべての例外を文書化する
常にチェックされる例外を個々に宣言し、Javadocの@throwsタグを使って各例外がスローされる条件を正確に文書化するのがよい。メソッドがスローする可能性のあるチェックされない例外が適切に文書化された一覧は、そのメソッドが首尾よく実行されるための事前条件を効果的に記述する。
詳細メッセージエラーに記録情報を含める
例外の詳細メッセージは、後の分析のためにエラーを記録しておくべき。エラーを記録する際には、例外の詳細メッセージは、その例外の原因となったすべてのパラメータとフィールドの値を含むべき。 詳細メッセージにパスワードや暗号鍵といったものを含めるべきではない。 例外の詳細メッセージと、エンドユーザに対してわかりやすくなければならないユーザレベルのエラーメッセージを混同すべきではない。
エラーアトミック性に努める
失敗したメソッド呼び出しは、オブジェクトをそのメソッド呼び出しの前の状態にしておくべき。このような性質をもつメソッドは、エラーアトミックであると呼ばれる。 エラーアトミック性を達成する方法は次の通り。
- 不変オブジェクトを設計する
- 失敗するかもしれない部分が、オブジェクトを変更する部分よりも前に行われるように、計算を順序づけする
- オブジェクトの一時的コピーに対して操作を行い、操作が完了したらオブジェクトの内容を一時的コピーの内容で置き換える
例外を無視しない
空のcatchブロックでは、例外の目的(「例外的状態の処理を強制する」こと)が達成されない。例外を無視することを選択したら、catchブロックは無視するのが適切である理由を説明しているコメントを含むべきであり、変数もignoredと命名すべき。 チェックされない例外を少なくとも外側に単に伝播させるだけでも、プログラムを速やかにエラーにさせて、そのエラーをデバッグするのに役立つ情報を残してくれる。
並行性
スレッドは複数の処理を並行して行うことを可能にしている。多くのことがうまくいかない可能性があり、失敗を再現するのが困難な場合があるため、並行プログラミングはシングルスレッドプログラミングよりも難しい。
共有された可変データへのアクセスを同期する
複数のスレッドが可変データを共有する場合、そのデータを読み書きするスレッドは同期を行わなければならない。同期なしでは、あるスレッドでの変更が他のスレッドから見えることは保証されない。
過剰な同期は避ける
過剰な同期はパフォーマンス低下、デッドロック、あるいは予想外の振る舞いをもたらす可能性ある。活性エラーと安全性エラーを避けるためには、同期されたメソッドやブロック内で制御をクライアントに譲ってはいけない。
スレッドよりもエグゼキュータ、タスク、ストリームを選ぶ
エグゼキュータサービスを用いて多くのことができる。エグゼキュータサービスは、特定のタスクの完了を待つことができたり、タスクの集まりの中のどれかのタスクやすべてのタスクの完了を待つことができたり、エグゼキュータサービスの完了を待つことができたり、タスクが完了するごとに一つずつタスクの結果を取り出したり、特定の時刻や周期的にタスクを実行するようにスケジューリングできたりと、さまざまなことができる。
waitとnotifyよりも並行処理ユーティリティを選ぶ
waitとnotifyを使う困難さを考えると、代わりに高いレベルの並行処理ユーティリティを使うべき。 java.util.concurrent内の高いレベルのユーティリティは、三つのカテゴリー(エグゼキュータフレームワーク、コンカレントコレクション、シンクロナイザ)に分類される。 コンカレントコレクションは、標準コレクションインターフェースの高パフォーマンスな並行実装。コンカレントコレクションから並行な活動を排除することは不可能。コンカレントコレクションをロックすると、プログラムを遅くするだけ。 シンクロナイザは、スレッド同士が互いに待つことを可能にするオブジェクトであり、スレッドがその活動を調整できるようにする。
シリアライズ
オブジェクトをバイトストリームとして符号化し(シリアライズ)、バイトストリームの富豪からオブジェクトを再構築する(デシリアライズ)Javaのフレームワークであるオブジェクトのシリアライズを扱う。
Javaのシリアライズよりも代替手段を選ぶ
シリアライズに関わる根本的な問題は、その攻撃対象領域が広すぎて保護できないのt、常に広がっていること。攻撃対象領域には、Javaプラットフォームライブラリ内、Apache Commons Collections といったサードパーティのライブラリ内、アプリケーション自信内のクラスが含まれる。 シリアライズは危険であり、避けるべき。1からシステムを設計しているなら、代わりにJSONやprotobufといったクロスプラットフォームの構造化データ表現を使うのがよい。
Serializableを細心の注意を払って実装する
クラスのインスタンスをシリアライズ可能にするのは、クラスの宣言にimplements Serializableを追加するだけの単純なもの。
Serializableを実装することの主なコストは、一旦リリースされるとクラスの実装を変更する柔軟性を低下させること。 Serializableを実装する際の2つ目のコストは、バグやセキュリティホールの可能性を増大させること。通常、オブジェクトはコンストラクタを使って生成される。シリアライズは、オブジェクトの生成に関しては、言語外の仕組み。 Serializableを実装する際の3つ目のコストは、新しいバージョンのクラスのリリースに関連したテストの負荷を増大させること。 Serializableインターフェースを実装することは、軽く考えて決めることではない。 継承のために設計されたクラスはSerializableをめったに実装すべきではないし、インターフェースもSerializableをめったに拡張すべきではない。