Effective Java 第3版を読んだので、本書で紹介されていた全90項目のプラクティスを簡潔にまとめる。
https://www.maruzen-publishing.co.jp/item/?book_no=303408
本記事は第9章「プログラミング一般」の項目について記載する。
第9章 プログラミング一般
項目57 ローカル変数のスコープを最小限にする
ローカル変数のスコープを最小限にすることで、コードの可読性と保守性が向上する。
ローカル変数が最初に使われる箇所で宣言する
ローカル変数をブロックの先頭で宣言した場合、ブロックの最後までのコード量が多くなるためスコープが広がってしまう。
また、処理の内容が読み取りにくくなる恐れもある。
そのため、ローカル変数を使用する直前で宣言するべきである。
ローカル変数宣言時に初期化子を含ませる
基本的に初期化する情報が得られるまで宣言するべきではない。
ただし、try-catch文においてはtryブロック内で初期化されなければならないので、tryブロックの直前で宣言する必要がある。
whileループよりもforループを選ぶ
forとfor-eachでは、ループ変数を宣言でき、ループ変数のスコープを最小限にすることができる。
そのため、ループ変数をスコープ外で利用しない場合にはforループを選択するべきである。
メソッドを小さくして焦点をはっきりさせる
メソッド自体が小さくなるのに伴い、スコープも小さくなる。
メソッドの責務次第ではあるが、スコープを小さくするためにメソッドの分割を検討するべきである。
項目58 従来のforループよりもfor-eachループを選ぶ
よく見る、配列をイテレートする際のforループは以下のような記載となっている。
for (int i = 0; i < a.length(); i++ ) {
// 処理
}
従来のfor文の場合、イテレータ変数とインデックス変数が必要であるためコードの可読性は悪くなる。
また、処理が複雑になるのに伴い、エラーが起こる可能性が大きくなってしまっている。
for-eachループ(拡張for文)を使うことで、これらの問題を解決することができる。
また、パフォーマンスの点でも特段ペナルティは存在しない。
for (Element e : elements) {
// 処理
}
項目59 ライブラリを知り、ライブラリを使う
汎用的な処理の実装をするときに、既に提供されている標準ライブラリを導入して利用することを検討するべきである。
時間を無駄にせずに処理を実現できる
ライブラリを導入することで期待した動作が得られるのであれば、時間の削減が期待できる。
パフォーマンスが改善されることがある
日々の開発コミュニティによるライブラリ開発によってパフォーマンスが改善されることがある。
新たな機能が追加される
日々の開発コミュニティによるライブラリ開発によって新たな機能が追加されることがある。
コードの可読性と保守性が向上する
有名なライブラリであれば、第三者が開発コードを見たときに処理の内容を受け入れやすくなる。
項目60 正確な答えが必要ならば、floatとdoubleを避ける
floatやdoubleは、科学計算や工学計算のために設計されているものであり、2進浮動小数点算術を行う。
そのため、計算結果は正確なものとはならない。
小数点を用いて正確に計算する場合は、BigDecimal型を使うべきである。
BigDecimal型は、丸めを制御できる利点に加え、丸めの方式を選択できる。
小数点の考慮が不要な場合は、intやlong型を使うべきである。
項目61 ボクシングされた基本データよりも基本データ型を選ぶ
ボクシングされた基本データ(参照型)ではなく、基本的にはプリミティブ型を選んだほうが良い。
プリミティブ型のほうが、型安全性とパフォーマンスの面でメリットがある。
public class No61 {
static Integer i;
public static void main(String[] args) {
if (i == 1) { // 変数iがアンボクシングされるとNullPointerExceptionがスローされる。
// 処理
}
}
}
参照型はnullを取りうるので、意図しない参照時にNullPointerExceptionがスローされる。
また、プリミティブ型にアンボクシング処理が行われることで、パフォーマンスが低下する。
参照型を使うべき場面は、空の状態をnullで表現したいときかコレクションの要素として利用するときである。
int i = null; // NG
Integer i = null; // OK
List<int> intList; // NG
List<Integer> integerList; // OK
項目62 他の型が適切な場所では、文字列を避ける
数値や真偽値などString以外で表現できるものについては適切な型を利用するべき。
不適切に使われた場合、Stringは扱いにくく柔軟性に乏しくなる恐れがある。
項目63 文字列結合のパフォーマンスに用心する
文字列結合演算子(+)は、数個程度の文字列を結合するには手軽で便利である。
しかし、多くの文字列を結合する場合にはパフォーマンスが悪く実行時間が長くなる。
なぜなら、文字列型は不変であるため、結合するごとにインスタンスが再生成されるからである。
パフォーマンスが問題になる場合は、Stringの代わりにStringBuilderのappendメソッドを使うことを検討すべきである。
項目64 インタフェースでオブジェクトを参照する
オブジェクトを参照する際は、クラスよりもインタフェースを使うべきである。
Set<String> stringSet = new LinkedHashSet<>(); // OK
LinkedHashSet<String> stringSet = new LinkedHashSet(); // NG
インタフェースを使った場合、クラス実装が柔軟に行えるメリットがある。
たとえば、上記のコードの最初の宣言をHashSetクラスを使って以下のように書き換えることも容易に行える。
Set<String> stringSet = new HashSet<>(); // OK
項目65 リフレクションよりもインタフェースを選ぶ
リフレクションとは、任意のクラスに対するアクセスを動的に実行することを提供している。
コンパイル時点で、存在しないクラスの利用が可能となる。
ただし、いくつかのデメリットも存在する。
- コンパイル時の型検査が行えない。 そのため、プログラムは実行時に失敗する。
- リフレクションを使ったコードは冗長である。 可読性も保守性も悪い。
- パフォーマンスが悪くなる。 通常のメソッドの呼び出しに比べて非常に遅い。
このようなデメリットから、最近ではリフレクションを利用するアプリケーションは少なくなっている。
ただし、限られた形式でリフレクションを使う場合には、リフレクションのデメリットをほとんど出さずにメリット部分を享受できる。
リフレクションはインタンスを生成するだけに利用して、インタフェースやスーパークラスを通してインスタンスにアクセスすれば良い。
項目66 ネイティブメソッドを注意して使う
CやC++などのネイティブのプログラミング言語で提供されているネイティブメソッドは基本的に使うべきではない。
なぜなら、既に成熟されたJava上ではレガシーな存在であり、安全ではない。また、パフォーマンスを低下させる可能性がある。
項目67 注意して最適化する
速いプログラムよりも優れたプログラムを書く努力をするべきである。
優れたプログラムは、基本的にパフォーマンスが良いものとなる。
そのために、設計段階でパフォーマンスについて留意しておくことが大切である。
後になってパフォーマンスの向上を目的とした改修をすることが非常に困難だからだ。
項目68 一般的に受け入れられている命名規則を守る
命名規約には2種類に分類され、「活字的命名規約と」「文法的命名規約」がある。
これらを明確に使うことで可読性の良いコードとなる。
活字的命名規約
パッケージ名とモジュール名は、ピリオドで区切られた要素を持ち、階層的とする。
組織外で使われるパッケージ名にはインターネットドメイン名を採用して一意となるようにする。
また、パッケージ名の残りの要素は、一般的に8文字以下とするべきである。そのため、意味を持った省略形を使うことも推奨されている。メソッド名とフィールド名は、最初の文字を小文字にする。
定数フィールドは、すべて大文字を使い、アンダースコアで区切る。
ローカル変数名は、メンバー名と同様に先頭の文字を小文字にする。
省略形を使うことが許容されているが、処理が明確になるように注意深く命名すること。型パラメータは大文字の一文字で表現する。
文法的命名規約
処理を行うメソッド名には、動詞や動詞句をつける。
boolean値を返すメソッド名には、先頭にisをつける。
まれに、hasを付けたほうが理解しやすい場合もある。要素を返却するメソッド名には、名刺、名詞句、あるいは先頭にgetをつける。
オブジェクトの型を変換して、別の型のオブジェクトを返却するメソッドには、先頭にtoをつける。
toList()、toString()、toArray()など。