Effective Java 第二版
著者:Joshua Bloch ジョンシュア・ブロック (sun → google[Chief Java Architect])
訳 :柴田芳樹
Effective Java によせて
言語を使用するために習得しなければならない3つの事項
1. 文法 2. 語彙 3. 日々の事柄を話すための慣習的で効果的な方法
教室では文法、語彙のみ教わることが多い
慣習的な方法を習わずに必死に話を伝えようとするして、ネイティブに笑われる
プログラミング言語も全く同じ
本書は3番目の要求に取り組んでいる
- 全体読んでわかったこと
基本的に言いたいことは、エラーについてコンパイル時に気づくことが正しく、実行時に気づくことは良くない
パフォーマンスに言及している
他、英語翻訳の片言的な書き方が目立つ
第1章 はじめに
ライブラリ
- java.lang - java.util - java.util.concurrent - java.io
対象者
- Java使いこなしている人
そうでない人は入門書をお読みください
第2章 オブジェクトの生成と消滅
- どのようにオブジェクトを生成スべきか
- いつどのように生成を回避スべきか
- オブジェクトが適切なタイミングで消滅することをどのように保証するか
- オブジェクト消滅前に実行しなければならない後処理をどのように行うか
項目1 コントラクタの変わりにstaticファクトリーメソッドを検討する
Staticファクトリーメソッド
長所と短所が存在する
長所
メソッド名がつけられるので分かりやすい
コンストラクタのように同じシグニチャを持つものが2つ以上存在できないという制約はないし、ドキュメントがなくても分かりやすい コンストラクタはドキュメントがないと、中身を見ないと何をしているかわからない
メソッドが呼び出されるごとに新たなオブジェクトを生成する必要がない
不必要に重複したオブジェクトの生成を回避できる Flyweightパターンに似てる 何度呼び出されても同じオブジェクトを返すことができる インスタンス制御されている(incetance-controlled) equalsメソッドの代わりに==演算子が使用可能 →パフォーマンスが大幅に上がる Enum型はこの保証を提供している
戻り値の型の任意のサブタイプのオブジェクトでも返すことができる
インターフェースに基づくフレームワーク(interface-based-framework)項目18 Collections Framework コレクションのインターフェースの便利な実装が32個ある 概念的重みが軽くなっている クライアントはインターフェースで返されたオブジェクトを受け取る EnumSetはenum型の要素数に応じて返却するインスタンスが異なる これらはクライアントには見えない Java Database Connectivity API (JDBC) などのサービス・プロバイダフレームワークの基本となるもの サービスインターフェース:プロバイダが実装する プロバイダ登録API:クライアント サービスアクセスAPI:サービスのインスタンスをクライアントが取得するためのもの
パラメータ化された型のインスタンス生成の面倒さを低減する
new でもダイヤモンド構文でJava1.7から簡単に記述可能 あまりすごいメリットでもない
短所
publicあるいはprotectedのコンストラクタを持たないクラスのサブクラスを作れないこと
継承ではなくコンポジションを使おう
それらが容易に他のstaticメソッドと区別がつかないこと
項目2 数多くのコンストラクタパラメータに直面した時にはビルダーを検討する
staticファクトリメソッドもコンストラクタが持っている共通の制約
数多くのパラメータに対してうまく対応できない
これらの対処として、テレスコーピングコンストラクタパターンを利用していた
テレスコーピングパターン
パラメータの数が増えるとすぐに手に負えなくなる クライアントがパラメータの値の2つを逆にしてしまっても、コンパイラは何も警告しませんが誤った振る舞いをしてしまう
JavaBeansパターン
パラメータなしのオブジェクトを生成して、setメソッドでセットしていく 生成過程で不整合な状態にあるかもしれない バグを含んでいるコードから離れた場所で失敗するかも
Builderパターン
テレスコーピングコンストラクタの安全性とJavaBeansパターンの可読性を併せ持つ AdaやPythonにみたいな名前付きオプションパラメータをビルダーパターンは模倣しています パフォーマンスに影響がある可能性があるが、パラメータが4つ以上ある場面では大抵の場合メリットのほうが大きい 途中でBuilderに替えると使われなくなったコンストラクタやStaticメソッドはゴミになってしまうので、最初からビルダーで始めたほうがよい
項目3 privateのコンストラクタかenum型でシングルトン特性を強制する
シングルトンとは
一度しかインスタンスが作成されないクラス
*割愛*再確認
現状、シングルトンを実装する最善の方法は、単一要素のenum型を用いること
項目4 privateのコンストラクタでインスタンス化不可能を強制する
ユーティリティクラスを作成する際は、public static メソッドを定義のうえ、
コンストラクタをprivateにして、明示的にインスタンス化できないかつ継承できないことをコメントに記すべし
コンストラクタの中に、throw new AssertionError(); を定義すると、誤ってクラス内からコンストラクタが呼び出されたときの保険になる
項目5 不必要なオブジェクトの生成を避ける
String s = new String(“stringette”); // これは絶対にやってはいけない
呼び出しごとに新しいインスタンスが生成されてしまう。
不変な値であれば、クラス定数としてprivate static finalで定義しておき、
初期化を static{}でするべき
一度も呼び出されない場合は無駄な初期化になりえるが、遅延初期化を実装するのはもっと大変なので避けてもよい
自動ボクシング時にインスタンスが生成されるので気をつけて
項目39「防御的コピー」との対比
必要な場所で防御的コピーをしなければ、悪質なバグやセキュリティホールを生み出しかねない
不必要なオブジェクトを生成するのは、単にスタイルとパフォーマンスに影響するだけ
項目6 廃れたオブジェクト参照を取り除く
配列の廃れた参照に対する配慮
スタックが大きくなったあとに小さくなるト、大きくなった部分のオブジェクトがガベージコレクトの対象にならない
Queオブジェクトでよいよねー
項目7 ファイナライザを避ける
ファイナラーザは予測不可能で大抵の場合は必要ない
不安定な振る舞い
低いパフォーマンス
移植性の問題原因
実行される保証がない
ファイナライザを持つオブジェクトの生成と開放は430倍遅い
明示的に閉じる、inputStream,outputStreamオブジェクトもfinalizeメソッドを実装しており、GC対象になったときに呼び出されるが、あくまで保険的な意味合いなので、明示的にcloseするのが好ましい
ファイナライザを使用する場面
安全ネット(closeシワスレの保険)
ネイティブ資源の開放
結論
ファイナライザは使用しない
第3章 すべてのオブジェクトに共通のメソッド
項目8 equalsをオーバーライドする時は一般契約に従う
equalsメソッドを誤ってオーバーライドしては行けない
次の条件に当てはまるときはオーバーライドしないのが妥当
クラスの個々のインスタンスは、本質的に一意である
論理的等価性検査をクラスが提供するかどうか関心がない
スーパークラスがすでにequalsをオーバーライドしており、スーパークラスの振る舞いがこのクラスに対して適切である
AbstractSetやAbstractListが実装しており、SetやListは継承している
クラスがprivateあるいはパッケージプライベートであり、そのequalsメソッドが決して呼び出されないことが確かである
このような場合は、偶然呼び出された時を考慮して、 throw new AssertionError(); を実装するべき
- equalsメソッドは同値関係を実装する
- 反射的
- 対照的
- 推移的
- 整合的
咀嚼できていない
そもそもequalsメソッドを実装(オーバーライド)することが稀
いままでそのような処理を見たことがない
項目9 equalsをオーバーライドする時は、常にhashCodeをオーバーライドする
hashMapのキーにequalsメソッドを実装したクラスを設定する場合、new 対象オブジェクト名();で
設定すると、equalsメソッドではtrueを返却する場面で、hashCodeメソッドはことなる値を返却するためMapの値が取れない現象が発生するらしい
こちらも項目8同様あまり使用しないのではないか
equalsメソッドをオーバーライドするときに気をつけよう!
そもそもオブジェクトをキーにHashマップを作成する場面があまり想像できない
hash値を算出するにあたって「31」を使用するべきらしい
偶数だと乗算がオーバーフローしたときに情報が失われる?みたい
https://www.thekingsmuseum.info/entry/2015/08/28/000748
https://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/
項目10 toStringを常にオーバーライドする
デフォルトだと「クラス名@ハッシュコード」
一般的にユーザが見てもなんのメリットもない情報
返される文字列は「簡潔だが、人が読みやすくなっている有益な表現」であるべき!
実用的な場合には、toStringメソッドはオブジェクトに含まれる興味のある情報をすべて!!返すべき!
なお、返却する値の説明はJavadocコメントに記載すべき!
フィールド値をすべて出力したい際は、
commmons.langのToStringBuilder#reflectionToString(Object object)を使ってもよいのでは?
パフォーマンスが懸念…
どのくらい遅くてどんなデメリットがあるのか別途検証したい!
項目11 cloneを注意してオーバーライドする
cloneメソッドはObjectクラスで実装されている
Cloneableインターフェース自体は空である。
Cloneableインターフェースを実装しないと、cloneメソッド呼び出し時に
CloneNotSupportedExceptionがスローされる
Cloneableインターフェースを実装しているクラスは、適切に機能する、publicかprotectedのCloneメソッドを提供すべき
返却されるオブジェクトは自クラスにキャストすべき
x.clone () != x が true であり、
x.clone().getClass() == x.getClass() も true であるインスタンスを生成すること。
ただし、これらの項目は絶対的なものではなく、
x.clone().equals(x) は通常 true である。
ObjectクラスはJavaのNativeソースでCloneableの実装をしているみたい
http://stackoverflow.com/questions/12032292/is-it-possible-to-find-source-for-java-native-method
少し消化不良なので再確認
項目12 Comparableの実装を検討する
ソートしたい場合に検討
Comparableインターフェース compareToメソッドを実装する
自分自身が渡されたオブジェクトより小さければ負の値を返す
自分自身が渡されたオブジェクトより大きければ正の値を返す
自分自身が渡されたオブジェクトと等しければ 0 を返す
ので、こちらを順序だてて実装
ただ、実装クラスを拡張する場合は、継承ではなく移譲すること
補足
Comparatorインターフェースを実装することで並び替えを実現することもできる
違い
Comparableインターフェース:自クラスに実装
Comparatorインターフェース:他クラスに実装
Comparatorが比較処理を外だしできていい感じに思えるが、クラス作成するのが面倒。。
JavaSE8から、「Comparator.comparingメソッド」が使えるようになったので、キー項目を単純にソートするだけならラムダ式でスマートに実装可能
http://totech.hateblo.jp/entry/2017/01/10/123602
第4章 クラスとインタフェース
項目13 クラスとメンバーへのアクセス可能性を最小限にする
うまい設計と下手な設計の違い
どの程度内部データと実装の詳細を隠蔽しているか
情報隠蔽またはカプセル化と呼ばれる
ソフトウェアの再利用を促進
大規模システム構築の場合のリスク軽減
システムが成功しなくても、個々のモジュールとしては成功するかもしれません
各クラスやメンバーをできる限りアクセスできないようにするべき
メソッドをオーバーライドする際は、スーパークラスが実装しているメソッドより、広い範囲のアクセス修飾子を指定しないといけない
インターフェース内のすべてのメソッドは暗黙にpublic
インターフェースを実装するクラスは、オーバーライド時、絶対にpublicを指定しないと行けない
インスタンスフィールドは決して、publicにしては行けない
publicの可変フィールドを持つクラスはスレッドセーフではない!
スレッドセーフ関連について深く理解できていないので再確認
public static finalで可変オブジェクトを格納しているソースはありえない。しては行けない。
そのフィールドに入る値の不変性を保証できなくなる
配列をfinalで宣言しても変更できないのは配列のインスタンスであり、配列の要素を変更することは可能
リストやマップも通常finalで宣言しても変更可能
unmodifiableListを使わなければならない
https://www.sejuku.net/blog/20977
セキュリティホールになりうる例として紹介されている
項目14 publicのクラスでは、publicのフィールドではなく、アクセッサーメソッドを使う
セッターゲッタの話
publicなクラスの場合、フィールドをpublicで公開すると、
その内部的な表現を永久に変更できなくなる
クラスがパッケージプライベートかprivateのネストしたクラスならpublicにすることは本質的に問題ない
Javaライブラリーでいくつかのクラスはこの提言を無視している
Java.awtのPointクラスやDimensionクラス
フィールドが不変な場合、publicとして公開することは、害が少ないが、一般的にはゲッターとセッターを提供すべき
項目15 可変性を最小限にする
クラスを不変にするための5つの原則
1・オブジェクトの状態を変更するためのいかなるメソッドも提供しない
2.クラスが拡張できないことを保証する。
finalでクラスを定義
3.すべてのフィールドをfinalにする。
4.すべてのフィールドをprivateにする。
5.可変コンポーネントに対する独占的アクセスを保証する
クラスが可変オブジェクトを参照しているフィールドを持っている場合、クライアントが参照できないことを保証すること
この項目で言いたいことを掘り下げれてないのであとで見返す
項目16 継承よりコンポジションを選ぶ
継承はコードを再利用するための強力な方法
不適切に使用されると継承はもろいソフトウェアを作り出します
パッケージをまたがって普通の具象クラスから継承することは危険
ここでいう継承は実装継承を意味します
クラスがインターフェースを実装した場合や、
インターフェースが他のインターフェースを拡張した場合のインターフェース継承には適用しない
メソッド呼び出しと異なり、継承はカプセル化をやぶる
スーパークラスに変更があった場合、サブクラスは一緒に変更しなければ行けない可能性がある
HashSetを継承して、addメソッドが呼び出された回数をカウントするサブクラスを作成する
add時にカウントアップ
addAll時に、引数のサイズ分カウントアップしようとするが、HashSetクラスが内部的に
addAllメソッドの中でaddメソッドを呼び出しているので、2重カウントとなってしまう
このサブクラスはHashSetの実装に依存しており、脆いクラスになっていると言える
スーパークラスのメソッド追加も、サブクラスの実装メソッドと同じシグニチャで、戻り値が異なる場合、
サブクラスはコンパイルできなくなる
propertiesクラスの例
結論
継承はなるべく使わないようにしよう
経験則、Javaでバッチ処理を実装する際、ログを残すための設定、基本となる設定ファイルを読み込み値の保持をスーパークラスでやっているロジックをみたことがあるが、そういう場合は継承は効果的なの
かもしれない。(処理をがつがつ書いているスーパークラスじゃないから)
項目17 継承のために設計および文書化する、でなければ継承を禁止する
クラスはオーバーライド可能なメソッドの事故利用を文書化しないと行けない
自己利用(self-use)の文書化
継承は慎重に
カプセル化を破壊する可能性がある
咀嚼しきれてないので再度見直す
項目18 抽象クラスよりインタフェースを選ぶ
複数の実装を許す型を定義するために
抽象クラスとインターフェースを定義している
違いは、抽象クラスはいくつかの実装を含むことが許されていて、インターフェースは許されていないこと
抽象クラスで定義された型を実装するためには、クラスはその抽象クラスのサブクラスでなければならない
複数のインターフェースを混ぜ合わせて実装することをミックスインという
このミックスインはインターフェースのみ許されていて、抽象クラスでは多重継承が許されていないため実現できない
P92の組み合わせ爆弾について不明
なんで抽象クラスではなく、インターフェースじゃないとだめなのか、具体的なデメリットが確立できていないので再確認が必要
以下に調査を書いていたが結論でてない
https://www.thekingsmuseum.info/entry/2015/09/26/165815
項目19 型を定義するためだけにインタフェースを使用する
インターフェースを実装するということは、そのクラスのインスタンスは何ができるかについて述べているべき
定数インターフェースはこの限りでない。定数管理にインターフェースを使うのはよろしくない
Javaのライブラリーにはjava.io.ObjectStreamConstantsなどの定数インターフェースがあるが、真似すべきではない!
定数を提供する場合、
クラスやインターフェースと強く結びついているならば、IntegerやFloatなどのように定数を提供
定数が列挙型のメンバーとしてみなされるべきなら、Enum型で提供すべき
そうでなければインスタンス化不可能なユーティリティクラスで定数を提供すべき
項目20 タグ付クラスよりクラス階層を選ぶ
タグ付きクラスは冗長で誤りやすく、非効率
タグ付きクラス:インスタンスが2以上の特性を持っていて、その特性を示すためのタグフィールドを持ったクラスのこと
円と四角形を表現したいと思い、2種類のタグ属性をフィールドに保持したクラス
コンストラクタでオブジェクト生成時に、生成しようとしているオブジェクトはどちらのタグに当たるのかを判断して、
フィールドに保持する。
タグ付きクラスを使用する事によるデメリット
一方のタグでは使わない、不要なフィールドにより、メモリ領域を抱えることになる
処理にswitchを用いる事になり、タグが増加した場合の修正が大変
タグフィールドを持つクラスを書きたくなったら、そのタグを取り除いてクラス階層で置き換えられないかを考えてください
タグフィールドを持つクラスに出くわしたら、クラス階層になるようにリファクタリングできないか検討してください
腑に落ちた。タグ付きクラスは設計しないようにする。できるだけ継承関係(階層)で表現する
項目21 戦略を表現するために関数オブジェクトを使用する
関数ポインタの主な使用方法は、戦略パターンを実装すること
戦略を表すインターフェースと、個々の具象戦略に関してそのインターフェースを実装しているクラスを宣言
具象クラスが1度しか使用されない場合はそのクラスは一般には無名クラスを使用して宣言及びインスタンス化すべき
繰り返し使用される場合は、そのクラスは一般的にprivate staticのメンバークラスであり、戦略インターフェースの型を持つpublic static finalのフィールドを通して提供されます
インターフェースを実装したフィールドを持たないステートレスなクラスを定義することは有用
ステートレスなクラスは、シングルトンで設計すべき、メモリ節約
項目22 非staticのメンバークラスよりstaticのメンバークラスを選ぶ
ネストしたクラスは、他のクラス内に定義されたクラスのこと
ネストしたクラスは、そのエンクロージングクラスに対して仕えるためだけに存在すべき
ネストクラスとしては以下の4種類がある
staticのメンバークラス
非staticのメンバークラス(内部クラス)
無名クラス(内部クラス)
ローカルクラス(内部クラス)
非staticな内部クラスはエンクロージングクラスへの参照を持っているので、
参照する必要がなければ、staticなメンバークラスを定義するべき
第5章 ジェネリックス
項目23 新たなコードで原型を使用しない
- 原型の使用は実行時例外の可能性があるため、使用しないこと
- ジェネリクスが提供されるコードとの互換性を保つためだけに、原型の使用がサポートされている
- 原型のまま使用することは可能であるが、できる限りやめたほうが懸命
ジェネリクスの安全性と表現力をすべて失うことになる
結論 ジェネリクスの型を使用するとコンパイル時に誤りに気づくことができ、メリットのほうが多いため、 必ず型宣言すること。
項目24 無検査警告を取り除く
- 無検査キャスト警告
- 無検査メソッド呼び出し警告
- 無検査ジェネリック配列生成警告
- 無検査変換警告
- このような警告を放置しているソースを見るが、取り除くことが可能なすべての無検査警告は取り除いたほうがよい
もし、警告を起こしているコードが型安全だsと明確に示すことができれば、そのときに限って
- @SuppressWarnings(“unchecked”)アノテーションで警告を抑止してください
安全だとわかっているのに警告を放置するのもだめだし、安全じゃない警告も取り除くべき
- SuppressWarningアノテーションを使用する際はできる限り最小のスコープで使用するべき
広範囲につけることも可能だが、意図せぬ警告を抑止してしまう可能性があるため
■まとめ
- 無検査警告は重要、無視しないでください
- すべての無検査警告は実行時の ClassCastExceptionの可能性があることを表しています
- コードが安全だと明確に示せるのであれば、最小スコープで@SuppressWarningアノテーションで警告を抑止してください
- 警告を抑止するときは抑止した理由をコメントで残してください
項目25 配列よりリストを選ぶ
配列は2つの重要な点でジェネリック型と異なる
[1]. 配列は共変 SubがSuperのサブタイプである場合、配列型Sub[]がSuper[]のサブタイプだということ
実行時にわかるよりコンパイル時にエラーになることがわかったほうがよい
1 | // 実行時に失敗する |
[2]. 配列は具象化されている
ジェネリックはイレイジャ(erasure)で実装されているのでコンパイル時のみ型制約を強制し、
実行時には要素の型情報を廃棄(すなわちイレイズ)することを意味している
これらの基本的な相違点により、配列とジェネリックスはうまく調和しません。
ジェネリック配列の生成は、型安全ではないから許されていない
もし許されていると、他の正しいプログラム内にコンパイラが生成したキャストが、実行時にClassCastExceptionで失敗することになる
これは、ジェネリック型システムが提供している、基本的な保証を破ることになる
この2つを調和させることは難しいので、設計する際は配列ではなく、リストを使用する
項目26 ジェネリック型を使用する
ジェネリック型はクライアントのコードでキャストが必要である型より、安全で使いやすい
新たな型を設計する場合、クライアントのコードでキャストが不要であることを確認してください
たいていの場合、型はジェネリックにすることを意味している
時間が許せば、既存の型をジェネリック化してください
そうすることで、既存のクライアントヲ動作させたまま、それらの型の新たなユーザをもっと楽にしてくれます
※再確認項目
項目27 ジェネリックメソッドを使用する
※再確認項目
項目28 APIの柔軟性向上のために境界ワイルドカードを使用する
ジェネリック型は本来であれば、継承関係問わずすべて一致していないと行けない
PECS Producer Extends Consumer Super
APIの型を柔軟にしてくれる
※再確認項目
項目29 型安全な異種コンテナーを検討する
※再確認項目
第6章 enumとアノテーション
Java1.5より以下が追加された
- Enum型(あらたな種類のクラス)
- アノテーション型(新たな種類のインターフェース)
これらを活用する方法を紹介
項目30 int定数の代わりにenumを使用する
理解度:★★★★☆
int定数に対するenum型の利点は、否定できるものではなく、はるかに読みやすく、安全であり、強力
多くのEnum:明示的なコンストラクタやメンバを必要としない
他の多く:各定数にデータを関連付けたり、そのデータに依存して振る舞いが変わるメソッドを提供
自分自身の値によってswitchを実装するよりは、abstractメソッドを実装するように強制したほうがよい
複数のenum定数が共通の振る舞いヲ共有する場合は、abstractメソッドではなく、戦略enumパターンを検討するほうがよい
項目31 序数の代わりにインスタンスフィールドを使用する
理解度:★★★★★
ordinalメソッドが提供されているが、それはEnumSetやEnumMapなどの汎用のenumに基づくデータ構造によりしようされるように設計されているため、そのようなデータ構造を書いているのでなければ、ordinalメソッドは全く使用しないことが最善
フィールドに序数を関連付けたいときは、コンストラクタ引数を渡してインスタンスフィールドに保持すること!!
項目32 ビットフィールドの代わりにEnumSetを使用する
理解度:★★★★☆
ビットフィールドを用いた実装方法
1 | public class Text { |
定数の集合を引き回す必要がある場合にビットフィールドを使用することに固執している人もいる
デメリット
- int num定数のすべての短所を持っている(コンパイルされるとクラスに入り込む・名前空間がない)
- 数値として表示された場合は解釈が困難
- ビットフィールドすべてをイテレートする簡単な方法も存在しない
良い例)
1 | // EnumSet - a modern replacement for bit fields (Page 170) |
- まとめ
- 列挙型が集合の中で使用されるというだけで、それをビットフィールドで表現する理由はない
- 短所としては、不変なEnumSetが生成できないことですが、1.6移行のリリースで改善できるだろう
- EnumSetをCollections.unmodifiableSetで包むことで対応可能
- 考察
- 不変のEnumSetはJava8でも追加されていない
項目33 序数インデックスの代わりにEnumMapを使用する
理解度:★★☆☆☆
相転移マップを作成する際の例
固体、液体、気体 の状態遷移
- まとめ
- 配列インデックスのために序数を使用することが適切であることはめったにない
- 代わりにEnumMapを使用すること
- 表現使用している関係が多次元なら、EnumMap<…, EnumMap<…>>を使用してください
項目34 拡張可能なenumをインタフェースで模倣する
理解度:★★★☆☆
項目29の境界型トークンと境界ワイルドカード型らへんが理解できていないので再確認
- まとめ
- 拡張可能なEnum型を書くことはできませんが、基本のenum型に伴うインターフェースを書いて、そのインターフェースをその基本のenum型に実装させることで模倣できる
- そのインターフェースを実装している独自のenum型を実装させることができる
項目35 命名パターンよりアノテーションを選ぶ
理解度:★★★★☆
命名パターンとは
- testで始まるメソッドをテスト対象として抽出する、、、みたいなルールをつけておくこと
- これは簡単に破られる、実行時まで気づかない、下手すると実行時も気づかず、正常終了したと思いこむ
- testで始まるメソッドをテスト対象として抽出する、、、みたいなルールをつけておくこと
アノテーションを使えば、アノテーションが付与されているものを抽出することが可能
まとめ
- 今はアノテーションが提供されているので命名パターンを使うのは避けること
標準提供のアノテーション例
- Override
- Deprecated
- SuppressWarnings
項目36 常にOverrideアノテーションを使用する
理解度:★★★★★
常にOverrideすること
問題のあるプログラム
1 | package effectivejava.chapter6.item40; |
eqalsメソッドのシグニチャがObjectではないため、オーバーライドではなく、オーバーロードになってしまっている
このことにプログラマは気づけない
常に@Overrideをつけることでこれを回避できる
1 | package effectivejava.chapter6.item40; |
HashSetに格納しており、全てすべて異なるオブジェクトと判断されて、結果に誤りが生じているが、正しくequalsメソッドをオーバーライドすることで回避
例外
- 抽象クラスの抽象メソッドを実装するときは、実装していないことをコンパイラが警告してくれるので@overrideを記載しなくても大丈夫。記載して問題ない
まとめ
- スーパータイプの宣言をオーバーライドしているすべてのメソッド宣言にOverrideアノテーションを使用することでコンパイラは多くの誤りを指摘してくれる
- 具象クラスでは、抽象メソッド宣言をオーバーライドしているメソッドにアノテーションをつける必要はない
項目37 型を定義するためにマーカーインタフェースを使用する
理解度:★★★★☆
マーカーインターフェース
- メソッド宣言を1つももたいないインターフェース
- そのインターフェースを実装しているクラスがなにか特別な性質を持っていることを示す
- マークされたクラスのインスタンスが実装している型を定義することに意味を持っている
- 指摘:Serializableインターフェースを活用しなかった、ObjectOutputStream.write(Object)への指摘
マーカーアノテーション
- 型定義はしていない
- 何らかの特性を示すためのアノテーション
- アノテーション自体がメタデータを示すものなので、アノテーション全部がマーカーの役割を担っている
どちらを使うか決める方法
- クラスやインターフェース以外のプログラム要素に対してマーカーが適用されるのであれば「マーカーアノテーション」
- このマークを持つオブジェクトだけを受け付ける1個以上のメソッドを書きたいのであれば「マーカーインターフェース」 Serializableト同じ用法
- このマーカーの使用を特定のインターフェースの要素に永久に制限したい「マーカーインターフェースのサブインターフェースで使用」
まとめ
- マーカーインターフェースの使用を最初に検討
- 使用できないのであれば、マーカーアノテーションを使用する
第7章 メソッド
- この章について
- メソッド設計の側面について議論
- パラメータと戻り値をどの様に扱うか
- メソッドのシグニチャをどの様に設計するか
- メソッドをどの様に文書化するか
- 多くは、メソッドだけでなく、コンストラクタにも適用される
- 本章は利便性、頑強性、柔軟性に焦点を当てている
項目38 パラメータの正当性を検査する
理解度:★★★★☆
ほとんどのメソッド、コンストラクタはパラメータとして渡される値に関して、何らかの制約を持っている
Nullであっては行けない、負の値であっては行けないとか
このような制約は明確に文書化すべきであり、メソッド本体のはじめに検査することで制約を強制すべき
そうしないと、予想外の場所でエラーになり原因特定が困難になる
- パラメタの検査をメソッドがしないと発生する問題
- メソッドが処理の途中でわけのわからない例外で失敗する
- メソッドは正常に終了するが、何も言わずに誤った結果を計算する
- いくつかのオブジェクトを不正な状態にして、あとになって全くコードと関係のないところでエラーを引き起こす
publicのメソッドに対しては、パラメータ値に関する制約が守られていない場合にスローされる例外を、Javadocの@throwsタグを使用して文書化すべき(項目64)
アサート定義について
通常の正当性検査とは異なり、アサーションは条件が成り立たなければAssertionErrorをスローします。通常の正当性検査とは異なり、javaのインタプリタに-ea(あるいは、-enableassertions)を渡してアサーションを有効にしない限り、アサーションはなんの効果もなく基本的にコストは発生しません。
- まとめ
- メソッド、コンストラクタを書く場合は、その都度、そのパラメータにどのような制約が存在するのか考えるべき
- それらの制約を文書化すべきだし、メソッド本体のはじめで、明示的に検査することで制約を強制すべき
- このような習慣を身につけることが大事
- この地道な作業は、正当性検査に初めて引っかかったときに報われます
項目39 必要な場合には、防御的にコピーする
Java言語は安全な言語
CやC++などの安全で内言語に感染するバッファーオーバーラン、配列オーバーラン、でたらめなポインタ、他のメモリ破壊エラーなどに対して、Java言語には免疫がある
これはメモリをすべて1つの巨大な配列として扱っている言語では不可能です
たとえ安全であっても、自分の方で何かしらの努力をしなければ他のクラスから防御されない
「クラスのクライアントは、クラスの不変式を破壊するために徹底した努力をすると想定して防御的にプログラムしなければなりません。」=壊される想定で防御していかなければならない
あなたのプログラムを利用する、プログラマーの単純な間違いに対処するためにも、防御が必要
攻撃の可能性例)
1 | package effectivejava.chapter8.item50; |
コンストラクタにオブジェクトを渡して、コンストラクタ側でメンバー変数として保持する場合、コンストラクタ内で新たにnewして(防御的にコピーして)メンバー変数に保持しなければならない
これを利用した攻撃を、コンピュータセキュリティのコミッティではtime-of-check/time-of-use攻撃と呼ばれている(TOCTOU攻撃)
まとめ
- クラスがそのクライアントから得たり、クライアントへ返したりする可変の要素を持っているならば、そのクラスはそれらの要素を防御的にコピーしなければならない
- もしコピーのコストが非常に高く、要素を不適切に変更しないということをでクラスがクライアントを信頼できるならば、影響を受ける要素を変更しないことがクライアントの責任であるとドキュメンテーションに概要を示すことで、防御的コピーの代わりとしても良いです。
パフォーマンス懸念について調査
- ArrayListのデフォルトコンストラクタで生成されるインスタンスのデータ容量は10であり、大量データをAddすることが確定している場合は最初からサイズ指定してコンストラクタを使うべき
- 参考)「Javaでプログラムを書く際に意識しておきたいこと」https://blog.y-yuki.net/entry/2017/06/07/130000
- ArrayListのデフォルトコンストラクタで生成されるインスタンスのデータ容量は10であり、大量データをAddすることが確定している場合は最初からサイズ指定してコンストラクタを使うべき
項目40 メソッドのシグニチャを注意深く設計する
理解度:★★★☆☆
本項目はAPI設計のヒントを集めたもの
メソッド名を注意深く選ぶ
- 標準命名規約(項目56)に従うべき
- 目標:理解可能で、同じパッケージ内の他の名前と矛盾のない名前を選ぶこと
- 目標:存在する広範囲のコンセンサス(合致、合意)と矛盾がない名前を選ぶこと
- 疑問がある場合はガイダンスとして、JavaライブラリのAPIを参考にしてください、矛盾が沢山ありますが、大きさと範囲を考えると避けられない矛盾。
- かなりのコンセンサスがあることに注目してほしい
便利なメソッドを提供しすぎない
- 自分の役割を果たすべき
- 頻繁に使用される場合だけ、ある操作に対する速記(shorthand)を提供することを検討すること
長いパラメタのリストは避ける(4個以下を目標にする)
- ほとんどのプログラマは長いパラメータリストを覚えられない
- 同じ型のパラメータが何個も続くのは特に有害
- 長いパラメータを短くする方法
- 1.メソッドを分割して、各メソッドはパラメータのサブセットだけを必要とする
- 2.パラメータの集まりを保持するヘルパークラスを作成する(Staticのメンバークラス)(項目22)
- 3.ビルダーパターンをオブジェクト生成からメソッド呼び出しに適用すること
パラメータ型に関しては、クラスよりインターフェースを選ぶ(項目52)
- HashMapパラメタに取るメソッドを作成する理由はなくて、Map型で定義しておく
- そうすれば、Hashtable、HashMap、TreeMapどれでも渡すことが可能となる
- booleanパラメータより2つの要素を持つenum型を使用する。
1
2
3
4
5
6
7
8
9
10public enum TemperatureScale { FAHRENHEIT, CELSIUS }
Thermometer.newInstatnce(true);
// より
Thermometer.newInstance(TemperatureScale.CELSIUS);
// のほうが分かりやすい
// TemperatureScaleに KELVIN を追加することも可能
項目41 オーバーロードを注意して使用する
理解度:★★★☆☆
1 | // 不完全! このプログラムは何を表示するでしょうか |
- まとめ
- オーバーロードできるからと言ってオーバーロードスべきではない
- 一般的には、同じ数のパラメータを持つ複数のシグニチャでメソッドをオーバーロードすることは控えるべき
- 場合によっては、コンストラクタが関与すると従えない可能性もある..
- そうなっても、キャストによって同じ組み合わせを異なるオーバーロードされたメソッドにわたすことができる状況は避けるべき
- 避けられない場合は、オーバーロードされたメソッドが同じふるまいをすることを保証すべき
項目42 可変長引数を注意して使用する
理解度:★★★☆☆
- 可変長引数メソッド
- リリース1.5で言語に追加された
- 0個以上の引数を受け付ける
可変長引数のあたいは配列として初期化される
Java1.4の場合
- 基本型配列はArrays.toList(Object[] obj)メソッドの引数にとれない
- 渡すと、コンパイルエラーになる
Java1.5の場合
- コンパイルエラーにならない。しかし、、
- 配列はObject型のtoStringメソッドを継承しているので、呼び出すと、ハッシュ値が出力され、値が見れない
- 解決方法は Arrays.toString()メソッドを使用すること
まとめ
- 可変長引数メソッドは、可変長の引数を必要とするメソッドを定義する便利な方法だが乱用すべきではない
- 不適切に使用すると可変長引数メソッドは困惑する可能性がある
項目43 nullではなく、空配列か空コレクションを返す
メッソドの戻り値(オブジェクト)がnullかどうかを判定するロジックを見たことがありますか?
空(長さゼロ)配列や空コレクションの代わりに、nullを返すメソッドを使用する場合には必ず書かなければ行けない
クライアント側を書いているプログラマは戻り値nullを処理する特別なコードを書き忘れてしまう危険性がある
- 配列を生成するコストを回避できると主張されることもあるが、次の2点を理由に間違いだと言える
- 1.問題となっているメソッドがパフォーマンス上、本当の原因で有ることがプロファイリングにより示されない限り、ネックと言えない
- 2.項目を1つも返さない呼び出しすべてが、同一の長さゼロ配列を返すことが可能
- 長さゼロ配列は不変であり、不変オブジェクトは制限なく共有して問題ないため
1 | // コレクションから配列を返す正しい方法 |
1 | // コレクションのコピーを返す正しい方法 |
- まとめ
- 配列やコレクションを返すメソッドが、空配列や空コレクションの代わりに、nullを返すべき理由は決してありません。
項目44 すべての公開API要素に対してドキュメントコメントを書く
APIを適切に文書化するためには、すべての公開されているクラス、インターフェース、コンストラクタ、メソッド、フィールド宣言の前にドキュメントコメントを書かなければなりません。
メソッドに関するドキュメントコメントは、メソッドとそのクライアント間の契約を簡潔に記述すべき
- メソッドが何を行っているかを記載するべき
- メソッドのすべての事前条件、事後条件を列挙すべき
- 事前条件:@param (引数の説明) に記載する
- 事後条件:@throws に記載する
↑この部分、書籍の記載不備だと思われ、咀嚼できていない
メソッドはいかなる副作用も文書化すべき
- たとえば、メソッドがバックグラウンドのスレッドを開始するとしたら、明記すべき
ドキュメンテーションガイド
まとめ
- ドキュメンテーションコメントはAPIを標準化する最善で最も効率的な方法
- すべての公開APIでドキュメンテーションコメントは必須だとみなされるべき
- 標準に沿って記載されるべきで、メタタグはエスケープされるべき
第8章 プログラミング一般
項目45 ローカル変数のスコープを最小限にする
- ローカル変数のスコープを最小限にする最も強力な方法は、ローカル変数が使用される前に宣言されること
ローカル変数は、初期化子を含んでいるべき
ループ変数の内容が、ループが終了したあとに必要ないものであれば、whileループよりforループを選択スべき
forループであれば、2つのループで異なった変数名を使用する理由はありませんので、コピペによる誤りを犯すことはない(コンパイルエラーになるから)
まとめ
- ローカル変数のスコープを最小限にすること
- 変数使用前に宣言すること
- whileよりforループをうまく活用すること
- メソッドを切り分けてスコープを制御すること
- ローカル変数のスコープを最小限にすること
項目46 従来のforループよりfor-eachループを選ぶ
- for-eachループ=拡張for文のことを言っている
1 | // コレクションと配列をいてレートするための好ましいイデオム |
for文がネストする際はなおさら、拡張For文の恩恵を受けられる
for-eachループにより、コレクションと配列に対してイテレートできるだけでなく、Iterableインターフェースを実装した、いかなるオブジェクトに対してもイテレートできる
- 利用できる場面であるならば利用するべき
- 利用できない場面については以下の通り
- 1.フィルタリング:選択された要素を削除する必要がある場合
- 2.変換:リストや配列をイテレートして、その要素のいくつかを変換、置換する場合
- 3.並列イテレーション
これらのケースに該当する場合は、通常のfor文を使用して、最善の方法で実装しているという認識を持つこと
項目47 ライブラリーを知り、ライブラリーを使う
0からある上限値までの整数の乱数を生成したい時、自分で実装するとうまくいかないケースがある
1 | private static final Random rnd = new Random(); |
※欠陥の内容については、数学的な内容になるのでここ(今回の学習)では触れない
この機能を実現するためのライブラリーはもう既に提供されています。
1 | Random.nextInt(int); |
- 標準ライブラリを使用することで、それを書いた専門家の知識と、それをあなたよりも前に使用した人々の経験を利用することになる
- 自分の課題に少しばかりだけ関連している問題に対する場当たり的な解決策を書くことで、時間を無駄にする必要がない。アプリケーションに時間を費やすべき
- 自分では何もしなくても、時間とともにパフォーマンスが改善されたりする
- Java標準ライブラリの中で知っておくべきもの(自分の道具箱に入れておくべきもの)
- java.lang
- java.util(コレクションフレームワーク)
- java.io
- java.util.concurrent
まとめ
- 無駄な努力はしないこと!
- ライブラリーに存在していないか、実装前に確認すること!
- 一般的なライブラリーは、頭の片隅においておくこと!
個人的な心がけ
- あえてライブラリーを実装していない、車輪の再開発実装を見かけたら、ライブラリーを使うとすると…ということを意識しておく
項目48 正確な答えが必要ならば、floatとdoubleを避ける
float型とdouble型は、特に金銭計算には適していない
- 正確に、0.1を表現することが不可能だから
この問題を解くために正しい方法は、金銭計算には、BigDecimal、int、あるいはlongを使用すること
留意事項
- BigDecimalのデメリット
- 基本データの算術型を使用するよりは不便
- 処理が遅い
- BigDecimalのデメリット
まとめ
- 正確な答えを必要とする計算に、floatやdoubleを使用しないこと!
- BigDecimalの使用を検討する
- BigDecimalは、小数点を扱っているかいなかという状態の管理と、処理速度が遅いというデメリットを持っている
- 桁数が9桁を超えないのであればintを用いる
- 桁数が18桁を超えないのであればlongを用いる
- 桁数が18桁を超えるのであればBigDecimalを用いる
項目49 ボクシングされた基本データより基本データ型を選ぶ
java1.5から、自動ボクシングと自動アンボクシングの機能が追加された
これは基本データ型とボクシングされた基本データ型の違いを不明瞭にしたが、決して同じものと見てはいけない!
3つの違い
- 基本データ型は値だけを持つが、ボクシングされた基本データのインスタンスは値とは別のアイデンティティを持っている - 基本データ型は完全に機能する値だけを持っているが、ボクシングされた基本データ型は、機能する値+nullを持つ - 基本データ型は、ボクシングされた基本データ型より時間と空間に関して効率的
ボクシングされた基本データ型に対して == 演算子を適用するのは、殆どの場合誤り
- オブジェクトの同一性を確認してしまうため
まとめ
- 基本的に基本データ型を使用すること
- 自動ボクシングの機能に依存しないこと
- ボクシングされた基本データ型に対して==演算子を用いることはない
- ボクシングされた基本データ型とアンボクシングされた基本データを含む混合型の計算を行うと、アンボクシングが行われる
- アンボクシングの際にNullPointerExceptionをスローする可能性がある
項目50 他の型が適切な場所では、文字列を避ける
- 文字列は、他の値型に対する代替としては貧弱
- データが本質的に文字である場合にだけ文字列として扱うべき
- 「はい/いいえ」の問に対する答えならboolean型
- 数値なら、int float BigInteger
- 文字列は、列挙型に対する代替としては貧弱
- 文字列は、集合型に対する代替としては貧弱
- ある実態が複数の構成要素を持っているならば、その集合を示すクラスを定義することが懸命。たいていは private static のメンバークラスを書く
文字列は、ケイパビリティ(capability)に対する代替としては貧弱
- ThreadLocalに関する懸念点について咀嚼できていないので再確認する
まとめ
- 何も考えずに文字列ばかりを使用しないこと
- 基本データ型、enum、集合型が選べないか確認すること
所感
- たまにテーブルで数値、タイムスタンプで持っているのに、Java処理で文字列型として扱っているプログラムを見るが、そのようなプログラムは適切ではないと判断することができるのではないか
項目51 文字列結合のパフォーマンスに用心する
n個の文字列を結合するのに、文字列結合演算子を繰り返し使用すると、nに関して2次の時間を必要としていまいます
- この原因は、文字列が不変(immutable)であるという事実の不幸な結果
- 結合時に両方の内容がコピーされます
許容できるパフォーマンスを達成するためには、Stringの代わりにStringBuilderを使用する
- パフォーマンスの違いは50倍~85倍ほど違い
処理速度検証参考
- StringBufferは古い、StringBuilderを使用しましょうと記載があるが、スレッド処理をする場合はStringBuffer1択
上記の検証資料を見るとStringBufferもStringBuilderと速度の大差がない様に思う…
まとめ
- 文字列結合はStringBuilder、またはStringBuilderを用いる
- 繰り返さない、数の少ない結合に関しては+演算子を用いる場合もある
- 例えば、
LOG.info("処理回数:" + count + "回目")
とか局所的に使う場合
- 例えば、
項目52 インターフェースでオブジェクトを参照する
- 適切なインターフェース型が存在するのであれば、パラメータ、戻り値、変数、およびフィールドはすべてインターフェース型を使用して宣言されるべき
- 型として、インターフェースを使用する癖を身に着けたならば、あなたのプログラムはかなり柔軟になる
- 注意点
- もとの実装がインターフェースの一般契約で要求されていない何らかの特別な機能を提供していて、コードがその機能に依存しているならば、新たな実装が同じ機能を提供することが重要
適切なインターフェースが存在しない場合には、インターフェースではなく、クラスでオブジェクトを参照することは適切
- 値クラス
- 基本的なデータ型としてインターフェースではなくクラスであるフレームワークに属しているケース
- インターフェースは実装しているが、そのインターフェースにない特別なメソッドをクラスが提供しているケース
まとめ
- 適切なインターフェースが存在する場合はインターフェース型で実装
- 適切なインターフェースが存在しない場合は、クラス改装中で必要な機能を提供しているクラスで実装
項目53 リフレクションよりインタフェースを選ぶ
コアリフレクション機構:java.lang.reflect はロードされたクラスに関する情報へのプログラムによるアクセスを提供している
- Classインスタンスで表されているクラスの、コンストラクタ、メソッド、フィールドを表しているConstructorインスタンス、Methodインスタンス、Fieldインスタンスを取得可能
- さらにこれらを反射的に操作できる
リフレクションはコンパイルされた時点で存在さえしない他のクラスを使用することを可能にする
- コンパイル時の型検査の恩恵をすべて失います
- リフレクションによるアクセスを行うコードは、ぎこちなく、冗長
- パフォーマンスが悪くなる
リフレクションは設計時のみ使用されるべき
- 一般に、実行時に普通のアプリケーション内で、オブジェクトはリフレクションによりアクセスされるべきではない
まとめ
- ある種の洗練されたシステムプログラミング処理に必要とされる強力な機構ですが、多くの短所を持っている
- コンパイル時に知られていないクラスと一緒に動作しなければならないプログラムを書くのであれば、できる限り、オブジェクトのインスタンス化のためだけにリフレクションを使用する
所感
- リフレクションはログ出力時に使用したい
- 実運用では積極的な利用は避けるべきで、ツールとかで使う分であれば問題ない
- 引数にクラス名を渡して、インスタンス化したあとに、スーパークラスやインターフェースに型を当てて使用する分には問題ないことを理解
項目54 ネイティブメソッドを注意して使用する
- CやC++などのネイティブのプログラミング言語で書かれた特別なメソッドであるネイティブメソッドをJavaアプリケーションが呼び出すのを可能にするのがJava Native Interface(JNI)
JNIは以前パフォーマンスの改善等で使用されていたが、Java1.4以降のリリースあたりで、Javaで実装しチューニングしたクラスを提供し始めたため、現在あまり使われていない
リリース1.4
- java.util.prefs レジストリの機能
リリース1.6
- java.awt.SyustemTray デスクトップのシステムトレイ領域へのアクセスを提供
古いコードへのアクセスにネイティブメソッドを使用することは正当ですが、パフォーマンスの改善のためにネイティブメソッドを使用することは、全く勧められません。
- BigIntegerも最初はCで書かれた高速な多倍精度算術ライブラリーを使用して実装されていた歴史があるが、リリース1.3でJavaに置き換えられて注意深くチューニングされた
まとめ
- パフォマンスの改善のために使用しないこと
- 古いコードへのアクセスで利用する場合は、最小限に留めること
所感
- ネイティブコードをJavaで意図的に使用するコーディングの実装経験がないが、以下で紹介されているようにC++のクラス、メソッドを呼び出しできるんだな程度で理解した
- 実務でネイティブメソッドを使用する場面は今後もないだろう。留意しておく
項目55 注意して最適化する
まとめ
- 早いプログラムを書く努力をしてはいけない、良いプログラムを書く努力をすること
- API設計、通信プロトコル、永続データ形式を設計しているときは、パフォーマンスについて意識すること
- システム構築を終えたあと、パフォーマンス測定し、十分に早ければそれでよい、早くなければプロファイラの力を借りて問題の特定、最適化
所感
- ビジネスレベルでパフォーマンスを意識した最適化をしようと思いすぎないこと
- API設計や、汎用的に使用されるようなクラスを作成する際は、パフォーマンスを意識すること(繰り返し呼び出される必要がある)
- Javaプロファイラについて調査(jvisualvm)
- http://www.techscore.com/blog/2017/12/11/identifying_performance_bottlenecks_with_jvisualvm/
- Eclipse使用時、GUIでメソッドごとに処理時間を一覧できるため便利
項目56 一般的に受け入れられている命名規約を守る
命名規約
- 活字的規約
- 文法的規約
パッケージ名
- ピリオドで区切られた要素をもち、階層的であるべき
- 区切られた要素は小文字のアルファベット文字と稀に数字から構成されるべき
- 組織外で使用されるパッケージの名前は、トップレベルドメインを最初にした、その組織のインターネットドメイン名で始まるべき
- java,javaxで始まる名前をもつパッケージはこの規約の例外です
- パッケージを記述する1つ以上の要素から構成されるべき、要素は短く一般的に8文字以下であるべき
- 意味を持った省略形は推奨されている。例えば、utilitiesesよりはutilなど
クラス名、インターフェース名(enum型名、アノテーション型名)
- 1つかそれ以上の単語から構成されるべきであり、個々の単語の最初の文字は大文字(パスカルケース)
- メソッド名はキャメルケースにすべき
- 定数フィールドは、大文字+アンダースコア
まとめ
- 標準の命名規約を取り入れて学習することを意識する
- 長く維持されてきた観葉的な用法が別に定義されている場合、これらの規約に盲目的に従うべきではない
所感
- 命名が適切だと他者に伝わりやすい+自分でも見返しやすい
- コーディング時に意識する
- 参考)https://qiita.com/rkonno/items/1b30daf83854fecbb814
第9章 例外
最適に使用された場合、例外はプログラムの読みやすさ、信頼性、保守性を改善
不適切に使用された場合、逆効果となる。ここでは例外を効率的に使用するためのガイドラインを定義
項目57 例外的状態にだけ例外を使用する
- 最悪な例
1 | // 例外のひどい乱用。決して、行ってはならない!!! |
まとめ
- 例外は例外的状態で使用されるために設計されている
- 通常の制御の流れに対して例外を使用しないでください
所感
- この項目は明白。あるべきタイミングで例外処理を実装すること。例外処理実装のパターンみたいなものに言及されていないが気になる。。。
項目58 回復可能な状態にはチェックされる例外を、プログラミングエラーには実行時例外を使用する
3種類の例外
- チェックされる例外
- チェックされない例外(実行時例外)
- チェックされない例外(エラー)
- 各例外がいつ使用するのが適切かについてプログラマの間で混乱がある。
呼び出し側が適切に回復できるような状況に対してはチェックされる例外を使用すること
- プログラミングエラーを示す場合は実行時例外を使用してください。実行時例外の殆どは事前条件違反を示しています。
- 実装するすべてのチェックされない例外は、RuntimeExceptionをサブクラス化するべき
Exception,RuntimeException,Errorのサブクラスではない例外を定義することは可能だが、得られるものはないし、ユーザを混乱させるだけなのでやめたほうがよい
まとめ
- 回復可能な状態にはチェックされる例外を使用
- プログラミングエラーには実行時例外を使用
項目59 チェックされる例外を不必要に使用するのを避ける
まとめ
- プログラマがこれ以上どうしようもないのであればチェックしない例外が適切。常にチェックされる例外を実装すべきではない
- チェックされる例外呼び出しをbooleanで定義して呼び分けをする方法もある
- このリファクタリングが常に適切とは限らないが、リファクタリング前より見やすくなるのはわかると思う
- 項目57の状態検査メソッドと基本的に同じであり、同じ注意事項が課せられる
- オブジェクトが外部同期なしに並行してアクセスされたり、外部要因により状態遷移したりするのであれば、このリファクタリングは適切でない
所感
- booleanを使った処理の成功、失敗の振り分けは実装したことあるが、戻り値がほしい場合はチェック例外を書かざるを得ない印象
項目60 標準例外を使用する
ここでは一般的に再利用されている例外について議論する
既存の例外を再利用することのメリット
- APIを学んで使用するのが容易になる
- プログラマが既に熟知している確立された慣例と一致するから
- プログラムが見慣れない例外でごちゃごちゃしないので読みやすい
- 例外クラスが少ないと、より少ないメモリ量と、クラスロードに費やされる時間が少ない
- APIを学んで使用するのが容易になる
一般的な例外
- IllegalArgumentException
- パラメータ値が不適切
- IllegalStateException
- メソッド呼び出しに対してオブジェクト状態が不正
- NullPointerException
- パラメータ値が禁止されているnull
- ArrayIndexOutOfBoundsException
- インデックスパラメータ値が範囲外
- ConcurrentModificationException
- 禁止されているオブジェクトの並行した変更を検出
- UnsupportedOperationException
- オブジェクトがメソッドをサポートしていない
- IllegalArgumentException
まとめ
- 一般的な例外を、実装ケースに合わせて使用すること
所感
- できるなら、個別に例外クラスを実装するような文化を作らないようにする(個別に定義した例外クラスだらけのシステム(独自ライブラリ)を見たことがあるがよくない)
- 一般的な例外クラスを使いこなせる様にする
- この場合はこの例外を使用するのが最適だと判断できるようにする
項目61 抽象概念に適した例外をスローする
- 上位レイヤは下位レベルの例外をキャッチして、上位レイヤの中で上位レベルの抽象概念の観点から説明可能な例外をスローすべき
まとめ
- 下位レイヤからの例外を防いだり下位レイヤの例外から上位レイヤを隔離剃ることが実現不可能であり、下位レベルのメソッドがスローするどれかの例外が上位レイヤに対して不適切ならば、例外翻訳を使用すること。
所感
- 例外翻訳は、メソッドで投げられた例外をキャッチして、別の例外をnewして投げ返すこと
- 投げ返すに適切な例外があれば使用すること
- そもそも投げ返すパターンについて整理する必要があるなと感じた
項目62 各メソッドがスローするすべての例外を文書化する
- 例外がthrowされる条件をjavadocの@throwsタグに明記して、正確に文書化してください
まとめ
- 作成する各メソッドがスローする可能性のあるすべての例外を文書化、チェックされない例外に対しても。また、具象メソッドだけでなく抽象メソッドに対しても
- 個々のチェックされる例外に対してthrows説を提供すること。チェックされない例外はthorows説を提供しないこと
- メソッドがthrowする可能性のある例外を文書化するのを忘れると、他の人がクラスやインターフェースを効果的に利用したりするのが困難だったり、不可能になる
所感
- @thorwsタグに、少なくともチェックされる例外の説明を記載する努力をする
- どのような原因でその例外が発生するのかなど
- @thorwsタグに、少なくともチェックされる例外の説明を記載する努力をする
項目63 詳細メッセージにエラー記録情報を含める
エラーを記録するためには、例外の文字列表現は、その例外の原因となったすべてのパラメータとフィールドの値を含んでいるべき
所感
- どの様にエラー情報を提供するのかについて言及していないように思える
- 再確認したい項目
項目64 エラーアトミック性に努める
エラーアトミック:一般的にいえば、失敗したメソッド呼び出しは、オブジェクトをそのメソッド呼び出しの前の状態にしておくべき
達成するためのアプローチ
- 不変オブジェクトを設計する
- 可変オブジェクトに対しては
- 操作を行う前にパラメータの正当性を検査する。そうすることでパラメータの変更を行う前にエラーを投げることができる
1 | public Object poo() { |
このように、size0のリストに、サイズを確認せずに要素の取り出しを行うと例外がスローされ、sizeフィールドが不整合な負の状態になり、オブジェクトを扱えなくなる
まとめ
- アトミック性をまもることは必須ではない。コスト、煩雑さとを伴うのであれば避ける
- APIドキュメントに、アトミック性を保持できないことを明記するべき
- 残念ながら、既存のAPIドキュメントの多くはこの規則に従っていない
所感
- そもそもアトミック性を保持しなければならないようなクラスを実装する機会があまりなかった(変化するフィールド(加算/減算/ステータス定義の変化)を持つクラスを使い回すシチュエーションがないような気がする。あっても既存のライブラリクラス。Dateとか)
- バッチ処理等でサービス/ビジネスクラス内に処理回数をインクリメントする変数を定義した場合、変数を加算する前に対象の処理が正常終了しているかどうかを確かめるとか…
項目65 例外を無視しない
- 例外を無視するのはだめ!
- 火災警報器を無視して警報を切ってしまうのと同じ。誰も火災があるかどうかを知ることができない
1 | try { |
- まとめ
- 例外を無視するべきではない
- 例外を無視しても最低限コメントで説明をするべき
第10章 並行性
- スレッドは同一プログラム内で複数の処理を並行して行うことを可能にしている
- 今日では当たり前になった、マルチコアプロセッサの恩恵を受けるためにも並行処理は必須
- 本章では明瞭で、正確で、きちんと文書化された並行プログラムを書くのに役立つ助言を含んでいます。
項目66 共有された可変データへのアクセスを同期する
- 壊れた同期
1 | package effectivejava.chapter11.item78.brokenstopthread; |
1秒後に、メインスレッドがサブスレッドを終了させると思うかもしれませんが、実際そうならない
- 問題
- メインスレッドが行ったsropRequestedの値の変更をサブスレッドが検知できないため
- 以下、修正されたクラス
1 | package effectivejava.chapter11.item78.fixedstopthread1; |
期待通りに1秒でクラスが終了します。
読み込み操作と、書き込み動作の両方が同期されていなければ行けない。同期はなんの効果もない
- 以下のような書き方もできる
1 | package effectivejava.chapter11.item78.fixedstopthread2; |
だが、volatile修飾子では、加算処理などの同期がうまく取れないので、
場合により、AtomicLongを使用すること
1 | // 不完全、動機が必要! |
- まとめ
- 複数のスレッドが可変データを共有する場合には、そのデータを読み書きするスレッドは同期を行わなければなりません。
- スレッド間通信だけが必要で、相互排他が必要なければ、volatile修飾子は同期として受け入れられる形式
- 正しく使用するのは難しい
項目67 過剰な同期は避ける
項目66では不十分な同期の危険性を示唆したが、本項目はその逆
- 活性エラーと安全性エラーを回避するためには、同期されたメソッドやブロック内で決して制御をクライアントに譲ってはいけません。