来月、ついに長い沈黙を破ってEffective Javaの第3版が発売される:Effective Java (3rd Edition)
学生のときに第2版で読書会とかやってたタイプの人間なので感慨深い。
それに先駆けて、一部で評判の良かったJava8対応の入門書『Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで』を積読リストから引っ張り出して目を通してみた。サポートページはGitHubにある。
コレクションの基本からStream、デザインパターンまで、最低限知っておくべきであろう内容がコンパクトにまとまっていて、『本格入門』の名に恥じない充実の一冊だったと思う。講義で習うような、文法レベルの入門の次の(次の)一冊にちょうど良さそう1。
特に「著者はこうしている」「現場ではこういう場面で使う」という実際的な記述が多々あったのが個人的には高ポイント。
強いて言えば、ライブラリや周辺ツールに関しては『使い方』の話が中心になっていたけど、ここでもっと「現場ではこういうプロジェクトでこんな使い方をしています」的な言及があれば嬉しかったかもしれない。
あと、スレッドセーフに関しては、その重要性や難しさ、使い所を十分に書ききれていない感が強かった。もちろん入門書なのでそこまでゴリゴリの記述は期待していないけど、コードサンプルも説明不足感があり、やや物足りない。
そんな感想を抱きつつ、以下、(教科書的な内容が中心だけど)いくつかポインタとしてメモを共有。
追記 (2017/12/03)
https://twitter.com/muraken720/status/936431112527626240
とのことです!
2. 基本的な書き方を身につける
- 変数名の前後に
_
をつけるのはもはや不要- IDEで書くのが当たり前なので、フィールドとローカル変数の名前が一緒でも区別がつく
- フレームワークによっては末尾の
_
によって変数のバインドに失敗するものもある
- 変数は名詞、メソッドは動詞で命名
- booleanの変数名に
isXxx
とつけるのは違う- 変数名は
xxx
で、問い合わせるメソッドがisXxx
と命名されるべき
- 変数名は
- booleanの変数名に
3. 型を極める
- autoboxing, unboxing
- 原則としてautoboxing, unboxingは使用せず、明示的に変換
- ファイル操作、DBアクセス、HTTPリクエストなどの結果得られる値を保持する場合はラッパークラスを使う
- 数値計算はプリミティブ型を使う
- 記述量の削減(型の変換を明記しないこと)が効果的な場合に限り、autoboxing, unboxingを利用
- 原則としてautoboxing, unboxingは使用せず、明示的に変換
- インタフェース
- 絶対publicになるが、著者は省略せずに
public interface
と書く
- 絶対publicになるが、著者は省略せずに
4. 配列とコレクションを極める
List
ArrayList
- forなどをつかった全体的な繰り返し処理向き
LinkedList
- 配列をiterateしつつ、途中で要素の追加/削除を行うとき
CopyOnWriteArray
- 複数のスレッドから同時にアクセスしても正しく処理される、ArrayList の拡張
Map
ConcurrentHashMap
- 複数スレッドから同時にアクセスする場合
LinkedHashMap
- 要素の追加順序を保持したいとき
TreeMap
- キーの大小を意識するとき
HashMap
- 上記の3つのMapが適さない、その他の場合に使う
Set
- 内部的にはMapを持っていて、キーだけを使っているのがSet
- 値は
null
以外ならなんでもいいけど、通常はtrue
- 値は
- なので実装、使い分けはMapと同じ
HashSet
LinkedHashSet
TreeSet
- だだし
ConcurrentHashSet
は存在しないCollections.newSetFromMap()
でConcurrentHashMap
からセットが作れるので
5. ストリーム処理を使いこなす
- Stream APIは次の3操作からなる
- ストリームの作成 (collection, array -> stream)
- 中間操作(filterなど;stream -> stream)
- 終端操作(forEachによる出力など;stream -> collection, array, element-wise process, aggregate)
6. 例外を極める
- tryブロックの中の処理は最小限にする
- catchした例外をもみ消さない
throws Exception
はNG- 例外のトレンド
- ExceptionよりRuntimeException
- フレームワークの独自例外はだいたいRuntimeException
- プログラム開始部分に近い共通部分でまとめて例外処理をさせたいねらい
- ラムダ式の中で発生した例外の扱い
- ラムダ式内で発生した実行時例外はラムダ式の外側に例外が伝わり、それを捕捉できる
- ただし、処理する要素ひとつひとつが発した例外のすべてを受け取れるわけではないので、「処理する要素次第でコケることもある」というときはラムダ式の内側で例外を捕捉したほうがよい
- Java8から導入されたOptionalクラスによる、nullチェックからの解放&NPEの抑止
- ただ、Optionalは
isPresent()
による値の存在確認が必須になり、nullチェックと手間が変わらない説もある- nullチェック:開発者の注意次第
- Optional:オブジェクトを返す側で、値が存在する場合と存在しない場合、それぞれのコードを書くことを開発者に強制できる
- ただ、Optionalは
- ExceptionよりRuntimeException
7. 文字列操作を極める
- 文字列のsplitやreplaceにStringクラスのメソッドを使うか、正規表現オブジェクトを使うか
- 一度だけならStringクラスのメソッドをつかって簡潔にかけばいい
- 大量の文字列に対して何回もやるなら、事前に正規表現オブジェクトを生成して使いまわす
- 「デプロイしたら文字化けする」問題にはまらないために、(特にサーバサイドJavaの開発では)デフォルトエンコーディングを使わないよう意識することが大切
8. ファイル操作を極める
- ファイルを「簡潔なコードで」「安全に」「効率よく」扱うことが大切
9. 日付処理を極める
Data
orCalendar
を使う場合、両者の相互変換が必要DateFormat
クラスで文字列との相互変換もやりやすくできる- ただし、これではスレッドセーフではないので、使いまわしはNG
- i.e., 同一フォーマッターが同時に利用されると意図しない値が返る可能性がある
- formatterに渡す文字列を定数として保持しておいて、都度
DateFormat
クラスのインスタンスを生成すること
- ただし、これではスレッドセーフではないので、使いまわしはNG
- 使い分けが面倒で、しかも immutable じゃない → Java8からはどちらの長所も備えた Data and Time API が追加
- クラス
LocalDate
- 年、月、日LocalTime
- 時、分、秒LocalDateTime
- 文字列との相互変換は
DateTimeFormatter
クラスを利用DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now())
- これはスレッドセーフ
- クラス
- Java8 からは標準で和暦に対応したクラス
java.time.chrono.JapaneseDate
も用意されている
10. オブジェクト指向をたしなむ
- 値渡しと参照渡しの挙動の違いに注意
- 筆者のルール
- 原則として、引数オブジェクトの修正は避ける
- 戻り値がvoidなら、引数オブジェクトを修正してもよい
- 戻り値がvoid以外なら、引数オブジェクトの修正はNG
- MutableなクラスとImmutableなクラスを使い分けるべし
- 可視性(アクセス修飾子)を適切に設定してバグを減らす
- Good practices
- 原則、最も範囲が狭い可視性にする
- クラスのフィールドは private
- 外部からアクセスするメソッドのみ public
- 拡張性をあげるために protected にする
- 筆者は将来に向けた拡張性の向上(継承クラスでのoverrideの可能性)を優先して、private よりも protected を好んで利用する
- テスト容易性をあげるために protected にする
- 同一パッケージ内のテストクラスで、リフレクション無しで値の上書き、テストができる
- リフレクションは対象のフィールドを文字列で指定して操作するので、リファクタリングに弱く、できれば避けたい
- 原則、最も範囲が狭い可視性にする
- Good practices
- オブジェクトのライフサイクルに関するgood practices
- ライフサイクルを短くして事故を防ぐ
- インスタンス変数はローカル変数に置き換えられないか検討
- 具体的には『必要な値はその都度メソッドの引数として渡す』『数が多い場合は値を保持する専用のクラスにまとめて、そのインスタンスを引数にとる』など
- マルチスレッドの文脈では値のセットと操作の間のインターリーブが問題になるので、特に大切
- ライフサイクルを長くして性能をあげる
- ライフサイクルを短くすると、GCの発生回数が増加する
- 長くすれば、値の再利用によってGCの発生回数が抑えられる“場合がある”(逆もあり、ライフサイクルが長いとかえってGC回数が増えることも)
- インスタンス変数、クラス変数などのフィールドを持たないクラス(=ステートレスなクラス)は複数スレッドアクセスによる事故の心配もないので、ライフサイクルを長くしてもOK
- ライフサイクルを短くして事故を防ぐ
- 全メソッドが static なユーティリティクラスを作る場合
- 拡張性がなく、テスト時にモック化できなくて面倒なので、筆者はユーティリティメソッドは非staticでつくり、利用時にユーティリティクラスのインスタンスを明示的にstaticで生成する
11. スレッドセーフをたしなむ
- スレッドセーフにするポイント
- ステートレスにする(クラス変数、インスタンス変数を持たない)
- 渡したい変数たちをServiceクラスなどでラップしてわざわざ保持せず、都度引数として渡すようにすることで属性をできるだけ削除
- 処理の結果を使って何かをするとき(結果として得られるオブジェクトはimmutableに)
- コールバック
- Futureパターン
- 「メソッド単位」ではなく、必要最低限な「一連の処理」に対して
synchronized
- 「HashMapのputメソッドを呼ぶ一行だけ」とか「ConcurrentHashMapを使うだけ」とかは、単一のポイントは保護できても、一連の処理がatomicに実行されることは保証しない
- なので、 synchronizedメソッド
public synchronized void increment() { ... }
のように、処理の塊で同期化
- なので、 synchronizedメソッド
- synchronizedメソッドが同期してくれる範囲
- 同一クラス内のsynchronizedメソッドは同期し合う
- クラスが異なれば同期しない
- 同一ロックオブジェクトに対するsynchronizedのみが同期し合う
- synchronizedメソッドの実体は
synchronized(this) { ... }
であり、クラス自身をロックオブジェクトとした同期 this
以外を対象にした同期synchronized(xxx) { ... }
とは非同期
- synchronizedメソッドの実体は
- 同一クラスの同一メソッドでも、そのインスタンスが異なればロックはかからない
- 全く同じ処理でも、ロックオブジェクトのインスタンスが異なればロックはかからない
- 同一クラス内のsynchronizedメソッドは同期し合う
- ただし、synchronizedブロックの範囲を広く取りすぎると、そこはすべてシングルスレッドで実行され、各スレッドの待ち時間も増えてスループット低下
- 狭すぎず広すぎず、適切なロック範囲を設定すること
- 「HashMapのputメソッドを呼ぶ一行だけ」とか「ConcurrentHashMapを使うだけ」とかは、単一のポイントは保護できても、一連の処理がatomicに実行されることは保証しない
- ステートレスにする(クラス変数、インスタンス変数を持たない)
12. デザインパターンをたしなむ
GoFで特に有用だと思うものたち:
オブジェクトの「生成」に関するパターン
- AbstractFactory: 関連する一連のインスタンス群をまとめて生成
- 環境や条件によって異なる処理内容をユーザが意識しなくていいので、フレームワークを作成するときによく使う
- Builder: 複合化されたインスタンス生成処理を隠蔽
- Singleton: あるクラスについて、インスタンスが単一であることを保証
プログラムの「構造」に関するパターン
- Adapter: インタフェースに互換性のないクラス同士を組み合わせる
- フレームワーク内部の処理をどうしても別の方法で呼び出したい、というヤバいときに使える
- Composite: 再帰的な構造の取り扱いを容易にする
- ツリー構造のデータの表現には確実に利用できる
オブジェクトの「振る舞い」に関するパターン
- Command: 「命令」をインスタンスとして扱うことで、処理の組み合わせを容易にする
- あるオブジェクトのもつ値を季節やステータスのような外部的なパラメータに依った異なるパターンの組み合わせで変更したいとき
- Strategy: 戦略を簡単に切り替えられる仕組みを提供
- Commandパターンと同じシーンで使えるが、
- Commandは「命令」そのもののオブジェクトを処理対象にセット
- Strategyは「アルゴリズム」をオブジェクト化して、他の中間クラスがそれを利用することで対象に処理を実行
- Commandパターンと同じシーンで使えるが、
- Iterator: 保有するインスタンスの各要素に順番にアクセスする方法を提供
- Javaのfor-eachのためにコレクションAPI内で利用されている
- Observer: あるインスタンスの状態が変化した際に、そのインスタンス自身が状態の変化を通知
- 現場では「ユーザの操作によって値が変更されたときに、何らかの処理を呼び出して、別々の開発者が実装した表示処理とバックグラウンド処理を実行する」のような切り分けを行う際に役立つ
13. 周辺ツールで品質をあげる
- Maven
- Javadoc
- Checkstyle
- FindBugs
- JUnit
- Jenkins
- Checkstyle, FindBugs, JaCoCoのプラグインを入れてレポーティングに使う
14. ライブラリで効率をあげる
- Apache Commons
- Super CSV
- Jackson
- SLF4J + Logback
1. すでに世の中ではJava9が話題になり、Effective Java第3版もJava9対応とのことだけど、まぁそんなに急ぐこともあるまい。 ↩
シェアする
カテゴリ
あわせて読みたい
- 2017-11-04
- Over-/Under-samplingをして学習した2クラス分類器の予測確率を調整する式
- 2017-10-01
- Pythonのconcurrent.futuresを試す
- 2017-04-09
- なぜSparkか
最終更新日: 2022-01-18
書いた人: たくち
Takuya Kitazawa(たくち)です。長野県出身、カナダ・バンクーバー在住のソフトウェアエンジニア。これまでB2B/B2Cの各領域で、Web技術・データサイエンス・機械学習のプロダクト化および顧客への導入支援・コンサルティング、そして関連分野の啓蒙活動に携わってきました。現在は主に北米(カナダ)、アジア(日本)、アフリカ(マラウイ)の個人および企業を対象にフリーランスとして活動中。詳しい経歴はレジュメ を参照ください。いろいろなまちを走って、時に自然と戯れながら、その時間その場所の「日常」を生きています。ご意見・ご感想およびお仕事のご相談は [email protected] まで。
近況 一杯のコーヒーを贈る免責事項
- Amazonのアソシエイトとして、当サイトは amazon.co.jp 上の適格販売により収入を得ています。
- 当サイトおよび関連するメディア上での発言はすべて私個人の見解であり、所属する(あるいは過去に所属した)組織のいかなる見解を代表するものでもありません。
- 当サイトのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、個人ブログという性質上、誤情報や客観性を欠いた意見が入り込んでいることもございます。いかなる場合でも、当サイトおよびリンク先に掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。
- その他、記事の内容や掲載画像などに問題がございましたら、直接メールでご連絡ください。確認の後、対応させていただきます。