effective java 輪読会 項目45-48
TRANSCRIPT
Effective Java 輪読会Item 45-48
開発部 陳映融 2014/2/19
第8章 プログラミング一般 項目45 ローカル変数のスコープを最小限にする
項目46 従来の for ループより for-each ループを選ぶ
項目47 ライブラリーを知り、ライブラリーを使う
項目48 正確な答えが必要ならば、float と double を避ける
項目49 ボクシングされた基本データより基本データ型を選ぶ
項目50 他の型が適切な場所では、文字列を避ける
項目51 文字列結合のパフォーマンスに用心する
項目52 インタフェースでオブジェクトを参照する
項目53 リフレクションよりインタフェースを選ぶ
項目54 ネイティブメソッドを注意して使用する
項目55 注意して最適化する
項目56 一般的に受け入れられている命名規約を守る
2
Item 45ローカル変数のスコープを最小限にする
ローカル変数のスコープ
4
復習:ローカル変数のスコープは宣言のあと、ブロックが終わるまで
ローカル変数のスコープを最小化することで、コードの可読性と保守性が向上し、誤りの可能性が減る
本質的には項目13 「クラスとメンバーへのアクセスを最小限にする」と類似
void scopeSample() {... // 未宣言int var = 0; // ここからが var のスコープ{
...var--; // 内部ブロックもスコープに含まれる
}var--; // 所在ブロックが終わるまで有効
}
ローカル変数は初めて使うときに宣言
5
ローカル変数を早めに宣言する場合
処理を理解しようとする読み手の注意を逸らしてしまう
たくさんの変数を(一時的に)覚えることで、大事な部分への注意が散らしてしまう
変数使用された時に型と初期値を思え出せないかも
// ブロックの先頭に宣言するのは C 言語のスタイル、やらないことvoid declarationInCStyle() {
int i = 0, j = 0, k = 0; // ループ制御用String s1, s2, ... // 文字列変数いろいろdouble d1, d2, ... // 数値変数いろいろ... // 変数宣言たくさん...覚えきれんわ!{
...x = y; // えっと、こいつらの初期値と型は...何だっけ?
}}
ローカル変数宣言と初期化子
6
ほとんどローカル変数宣言は初期化子を含んでいるべき
初期化子を含むことで初期化漏れ防止
変数を合理的に初期化する情報を揃えてから変数を宣言(&初期化)
初期化に必要な情報が足りない場合、宣言を先送りすべき
void declarationWithoutInit() {int var; // まだ使わないけど先に宣言しておこう... // いろいろ書いたら数ページになって(そこまで書いたらメソッドを分けたほうがいいけ
ど)var++; // えっと、初期化した...よね?...
}
void declarationWithInitInfo() {Variable tooEarly; // 初期化の情報が足りていない ⇒ 早すぎる... // いろいろ情報収集Info info = collectInfo(args); // ここに来てやっと情報が揃ったVariable properTime = new Variable(info); // 使用直前に宣言&初期化 ⇒ 適切なタイミングuseVariable(properTime);...
}
ローカル変数宣言と初期化子
7
前述規則(ローカル変数宣言は初期化子を含んでいるべき)の例外: try-catch 文
チェックされる例外を投げるメソッドの戻り値で変数を初期化する場合
Java 1.7 の AutoCloseable の導入で Closeable も規則適用可能に
try-with-resources:
static void tryCatchSample() {BufferedReader br = null;try {
br = new BufferedReader(new FileReader(“filePath”)); // 初期化で例外を投げるかもSystem.out.println(br.readLine()); // ここにも例外を投げる可能性がある
} catch (IOException ex) {...
} finally {if (br != null) br.close(); // try ブロックの外での使用、宣言はブロック外でなければ
}}
static void tryWithResourcesSample() throws IOException {try (BufferedReader br =
new BufferedReader(new FileReader(“filePath”))) { // br のスコープは try ブロック内System.out.println(br.readLine());
}}
ループでの変数スコープ最小化
8
for と for-each はループ変数を宣言できる
変数スコープはループ本体と for 予約語の後の括弧内のコードに限定
ループごとの冗長な計算を同じ結果が返される場合、ループ変数で回避
// コレクションをイテレートする好ましいイデオムfor (Element e : c) {
doSomething(e);}
// リリース 1.5 より前のジェネリックがない場合、いまも使えるけど...for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomething((Element) i.next()); }
// 初期化時に一回だけ処理for (int i = 0, n = expensiveComputation(); i < n; i++) {
doSomething(i); }
ループでの変数スコープ最小化
9
while の場合、だいたいブロックの外で宣言される変数を使用する
コピー&ペーストや要素変数名の再利用でバグを織り込みやすい
for ループ使用する場合はコンパイル時エラーで検出可能
Iterator<Element> i = c1.iterator();while (i.hasNext()) { doSomething((Element) i.next()); }
// バグがあるけど問題なくコンパイルできるIterator<Element> i2 = c2.iterator();while (i.hasNext()) { doSomething((Element) i.next()); } // NoSuchElementException!
for (Iterator<Element> i = c1.iterator(); i.hasNext(); ) {doSomething((Element) i.next());
}
// コンパイル時エラー - シンボル i は解決できないfor (Iterator<Element> i2 = c2.iterator(); i.hasNext(); ) {
doSomething((Element) i2.next());}
メソッドを小さくする
10
メソッドを小さくして、処理をはっきりさせる
一つのメソッドに複数の処理があって、
一つの処理用のローカル変数が別の処理のコードのスコープ内にある場合
⇒ メソッドを分ける(一つの処理に対して一つのメソッド)
まとめ
11
ローカル変数のスコープを最小化することで
コード可読性と保守性が向上
誤りの可能性が減る
ローカル変数のスコープを最小限にする技法
ローカル変数が初めて使用される時に宣言する
ローカル変数は初期化子を含んで宣言する
ループの場合、 while ループより for ループを選ぶ
ループ終了後にループ変数の内容が必要ない場合に限る
メソッドを小さくして焦点をはっきりさせる
Item 46従来の for ループより for-each ループを選ぶ
イテレーション用の変数隠蔽
13
リリース 1.5 より前のイテレーション
コレクション、ジェネリックがない場合
配列の場合
イテレータやインデックス変数を隠蔽してエラーの機会を排除
配列上限計算は一度だけなので、パフォーマンス上のペナルティはない
// イテレータ変数を宣言以外の部分で間違えてると、コンパイル時に検出できないかもfor (Iterator i = c.iterator(); i.hasNext(); ) {
doSomething((Element) i.next()); }
// インデックス変数を宣言以外の部分で間違えてると、コンパイル時に検出できないかもfor (int i = 0; i < a.length; i++) {
doSomething(a[i]); }
// コレクションと配列をイテレートする好ましいイデオムfor (Element e : c) {
doSomething(e);}
ネストしたイテレーション
14
for ループでのイテレータ変数の操作で使用ミス例
next メソッドの過剰な呼び出し
⇒ 外側要素を変数で保持するように修正...
for-each ループで実装したほうが簡潔
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); )for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
decks.add (new Card(i.next(), j.next()); // i が底をついたら NoSuchElementException
// コレクションと配列をイテレートする望ましいイデオムfor (Suit suit : suits)
for (Rank rank : ranks)decks.add (new Card(suit, rank));
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) {Suit suit = i.next();for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
decks.add (new Card(suit, j.next()); }
Iterable インタフェース
15
Iterable インタフェースを実装することで for-each 使用可能に
要素のグループを実装する際、 Collection を実装しなくても、Iterable を実装するように
public interface Iterable<E> {Iterator<E> iterator();
}
まとめ
16
明瞭性とバグ予防に関して、 for-each ループは for ループより優れた長所提供し、パフォーマンス上のペナルティはない
for-each ループを使用できない3つの状況
フィルタリング:コレクションをイテレートして選択された要素だけ削除する
⇒ イテレータ使用して remove メソッドを呼び出す
変換:リストや配列をイテレートして一部か全部の要素の値を置換する
⇒ リストイテレータか配列インデックス変数が必要
並列イテレーション:複数のコレクションを並列にイテレートする
⇒ イテレータやインデックス変数に対して明示的な制御が必要
Item 47ライブラリーを知り、ライブラリーを使う
標準ライブラリーを使おう
18
整数の乱数を生成メソッド例の教訓
本当にいい random メソッドは、いろいろ知らないと作れないので、
標準ライブラリーではすでにやってくれたから、それを使いましょう!
標準ライブラリーを利用する利点
専門家の知識と、以前それを利用した人々の経験を利用することになる
時間を無駄にする必要がなく、目的の課題に集中できる
自分では何もしなくても、時間と共にパフォーマンスが改善されたりする
自分のコードを主流に置くことになる
標準ライブラリーの知るべき機能
19
主要リリースごとに機能が追加されて、それを知ることは得るものがある
標準ライブラリーの知るべき機能
java.lang
java.util (リリース 1.2 にコレクションフレームワーク追加)
java.util.concurrent (リリース 1.5 にコンカレンシーユティリティ追加)
java.io (ある程度)
ちなみに、リリース 1.7 で追加された機能(の一部)
文字列による switch
ダイアモンド演算子
例外の機能拡張(リソース付き、安全な再スロー、マルチキャッチ)
まとめ
20
無駄な努力をしないこと
かなり共通だと思えることをする前に、まず標準ライブラリーから使えるものを探す
一般的には、ライブラリーのコードは自分で書いたものより優れていて、時間とともに改善されていく可能性がある
Item 48正確な答えが必要ならば、float と double を避ける
float 型と double 型は近似計算
22
科学計算と工学計算のために設計されている
2進数浮動小数点算数
広い範囲の大きさに対して正確な近似を行う
正確な結果を提供しない
⇒ float 型と double 型は、特に金銭計算に適しない
正確な答えを得るには
23
金銭計算など、正確な答えが必要な場合は、 BigDecimal か int か long を使用する
BigDecimal 型を使用
正確な値は出してくれるけど
基本データの算数型を使用するより不便(演算子は使えない)
遅い
int/long 型を使用
金額に応じて使い分ける
小数点がどこかを自分で把握しておく
まとめ
24
正確な答えを必要とする計算は float や double を使用しない
代わりに BigDecimal か、 int や long を使用する
BigDecimal を使用する状況
システムに小数点を把握させたい
基本データ型を使用しない不便さとコストを気にしない
丸めを完全に制御したい
数が大きい(18桁を超える)
int や long を使用する状況
パフォーマンスが重要
小数点の把握が気にならない
数が大きすぎない(9桁を超えないならint、18桁を超えないならlong)