effective java 輪読会 項目49-52
TRANSCRIPT
Effective Java 輪読会 第7回(項目49~52)
2014/2/19
開発部 野口
項目49 ボクシングされた基本データより基本データ型を選ぶ
基本データ型とボクシングされた基本データ型の 3 つの違い
基本データ型は値だけを持つが、ボクシングされた基本データ型のインスタンスは値とは別のアイデンティティを持っている
基本データ型は完全に機能する値だけを持っているが、ボクシングされた基本データ型はnull という機能しない値も持っている
基本データ型のほうがボクシングされた基本データ型よりも時間・空間においてより効率的
不完全なコンパレータ<基本データ型は値だけを持つが、ボクシングされた基本データ型のインスタンスは値とは別のアイデンティティを持っている>
first < second では自動アンボクシングされ、うまくいく
first == second では Integer インスタンスの同一性比較を行う 間違い!
Comparator<Integer> naturalOrder = new
Comparator<Integer>() {
public int compare(Integer first, Integer second) {
return first < second ? -1 : (first == second ? 0 : 1);
}
};
不完全なコンパレータの修正案
Comparator<Integer> naturalOrder = new
Comparator<Integer>() {
public int compare(Integer first, Integer second) {
int f = first; // 自動アンボクシングint s = second; // 自動アンボクシングreturn f < s ? -1 : (f == s ? 0 : 1); // アンボクシングなし
}
};
Integer と int との比較<基本データ型は完全に機能する値だけを持っているが、ボクシングされた基本データ型は null という機能しない値も持っている>
Unbelievable は表示……されません
かわりに、(i == 42) で NullPointerException がスローされます
単一操作内で基本データ型とボクシングされた基本データ型を混在させた場合には、ほとんどの場合にボクシングされた基本データ型は自動アンボクシングされる
この場合、i を Integer ではなく int で宣言すれば解決
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
if (i == 42)
System.out.println("Unbelievable");
}
}
恐ろしく遅いプログラム<基本データ型のほうがボクシングされた基本データ型よりも時間・空間においてより効率的>
public static void main(String[] args) {
long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += Long.valueOf(1); // ここでオートボクシングが行われる!
}
System.out.println(sum);
}
ボクシングされた基本データ型を使用すべきとき
コレクション内の要素、キー、値として
より一般的には、パラメータ化された型の型パラメータとして
☓Collection<int>
○ Collection<Integer>
リフレクションによってメソッドを呼び出すとき
参照: 項目53
まとめ
選択できる場合には、ボクシングされた基本データではなく、基本データ型を使用する
ボクシングされた基本データを使用する場合、注意する == では同一性比較が行われること
ボクシングされた基本データとアンボクシングされた基本データとの計算では、アンボクシングが行われること アンボクシングの際に、NullPointerException をスローする可能性がある
基本データ値をボクシングする際、コストを要する不必要なオブジェクト生成を行う可能性があること
項目50
他の型が適切な場所では、文字列を避ける
文字列は、他の値型に対する代替としては貧弱
外部から入ってきたデータはたいてい文字列だが、より適切な値に変換してから扱うべき
数値なら int、float、BigInteger 等
真偽値なら、boolean
等々
もし適切な型がなければ、適切な値型を書くべき
文字列は、列挙型に対する代替としては貧弱
enum の方がはるかに優れている
参照: 項目30
文字列は、集合型に対する代替としては貧弱
よりは、こうしよう
String compoundKey = className + "#" + i.next();
private static class CompoundKey {
private String className;
private String methodName;
...
}
文字列は、capability に対する代替としては貧弱
ところで、capability って?
capability って?(1)
capability【名詞】
1【不可算名詞】 [具体的には【可算名詞】]
a〔…の〕能力,才能,手腕,〔…すること〕のできること[能力,才能] 〔for,in,of〕.用例: evaluate the capability of a person 人の才能を評価する.
2【不可算名詞】a(物のもつ)〔…する〕特性,性能 〔for〕.用例the capability of gases for compression ガスの圧縮する性質.
...
capability って?(2)
Effective Java の例では、「偽造できないキー」のことを capability と呼んでいるようです(pp.218)
「時折、文字列はある機能へのアクセスを与えるために使用されます」(pp.217)
ThreadLocal の例でいうと、「スレッドローカルな変数にアクセスするためのキー」なので、(情報にアクセスする)「能力」の訳語が割と近そうです
でも「能力」じゃやっぱり意味不明なので、capability と呼びます
(capability の説明に用いられている)ThreadLocal って?
個々のスレッドが固有の値を持つための変数
現在の Java では、以下のようなかたちでライブラリとして提供されている
public final class ThreadLocal<T> {
public ThreadLocal();
public void set(T value);
public T get();
}
capability として、String の不適切な使用
public class ThreadLocal {
private ThreadLocal() {} // インスタンス化不可能
// 名前付き変数に対するカレントスレッドの値を設定する。public static void set(String key, Object value);
// 名前付き変数に対するカレントスレッドの値を返す。public static Object get(String key);
}
「capability として、String の不適切な使用」の問題点
キーが共有されたグローバルな名前空間を表している
クライアントが提供した文字列キーが一意でなければならない
2 つの独立したクライアントが同じ名前を使ってしまったら、意図せず変数を共有してしまう
意図して同じ名前を使うこともできてしまう(セキュリティの問題)
本物の capability
public class ThreadLocal {
private ThreadLocal() {} // インスタンス化不可能
public static class Key { // capability
Key();
}
// 一意の偽造できないキーを生成するpublic static Key getKey() {
return new Key();
}
public static void set(Key key, Object value);
public static Object get(Key key);
}
ThreadLocal に、もはや Key は不要
set / get をインスタンスメソッドにする
ThreadLocal のインスタンス自体が capability
public class ThreadLocal {
public ThreadLocal();
public void set(Object value);
public Object get();
}
ThreadLocal を型安全にする
これが(概ね)java.lang.ThreadlLocal が提供している API
public final class ThreadLocal<T> {
public ThreadLocal();
public void set(T value);
public T get();
}
まとめ
より良いデータ型が存在する、あるいは書くことができる場合に、何も考えずに文字列でオブジェクトを表現したりしない
間違って文字列を使いがちなのは、基本データ型、enum、集合型(それから、capability)
項目51
文字列結合のパフォーマンスに用心する
文字列結合のパフォーマンス
文字列結合演算子(+)は、便利
だが、n 個の文字列結合は、O(n^2) となる
なぜなら、文字列は immutable だから
文字列結合の不適切な使用
public String statement() {
String result = "";
for (int i = 0; i < numItems(); i++)
result += lineForItem(i); // String 結合return result;
}
String の代わりにStringBuilder を使用する
public String statement() {
StringBuilder b = new StringBuilder(numItems() *
LINE_WIDTH);
for (int i = 0; i < numItems(); i++)
b.append(lineForItem(i));
return b.toString();
}
まとめ
パフォーマンスが重要でない場合以外には、数個以上の文字列を結合するのに文字列結合演算子を使用しない
代わりに、StringBuilder の append メソッドを使用する
あるいは、文字配列を使用するか、文字列を結合する代わりに 1 つずつ処理する
あえてそうすべき時はほとんどなさそうですが……。
項目52
インタフェースでオブジェクトを参照する
オブジェクトを参照するには、クラスよりもインタフェースを使用すべき 適切なインタフェース型が存在するならば、パラメータ、戻り値、変数、フィールドはすべてインタフェース型を使用して宣言されるべき
オブジェクトのクラスを参照する必要がある唯一の場合は、そのオブジェクトを生成するとき
List インタフェース /
Vector クラスでの例
// 良い - 型としてインタフェースを使用// subscribers は同期されている必要があるList<Subscriber> subscribers = new Vector<Subscriber>();
// 悪い - 型としてクラスを使用Vector<Subscriber> subscribers = new Vector<Subscriber>();
型としてインタフェースを使用するメリット
List<Subscriber> subscribers = new ArrayList<Subscriber>();
型としてインタフェースを使用する注意点
コードが、インタフェースの一般契約で要求されていない(が、実装クラスは提供している)機能に依存しているなら、実装クラスを変更するとき、新たな実装がその機能を提供しているかどうかが重要となる
例)「Vector が同期されている」ことに依存するコードを書いていれば、それを ArrayList に置き換えることはできない
そのようなときは、変数を宣言する場所に、その要件をドキュメント化すること!
インタフェースを用いることで型の表現力の一部を捨ててしまっているので、(それを補う)これは重要だと思います
インタフェースを使用する利点にあずかった一例
java.lang.ThreadLocal
フィールドに Map を持つ
1.3 リリースでは、HashMap を用いていた
1.4 リリースでは、IdentityHashMap に変更することで、より速くなった
適切なインタフェースが存在しない場合は、クラスでオブジェクトを参照する String や BigInteger などの値クラスより一般的には、具象クラスが関連付けされたインタフェースを持たないとき例)Random
クラスに基づくフレームワークに属する場合例)java.util.TimerTask
インタフェースは実装しているが、そのインタフェースにない特別なメソッドをクラスが提供している場合例)PriorityQueue
まとめ
ある与えられたオブジェクトが適切なインタフェースを持っているかどうかを調べる
インタフェースを持っていれば、オブジェクトを参照するためにインタフェースを使用して、プログラムをより柔軟にする
インタフェースを持っていなければ、クラス階層中で必要な機能を提供している最も上位のクラスを使用する