Effective Java Second Edition
社内勉強会資料
第4章 Classes and Interfaces
大槌 剛彦 (@ohtsuchi)
項目13 クラスとメンバーへのアクセス可能性を最小限にする
• 情報隠蔽(information hiding) or カプセル化(encapsulation) – 他のモジュールから内部の実装を隠蔽
– 実装とAPIを分離
– APIを通して他のモジュールと通信
– 利点
• モジュールの並行開発→ 開発スピードのUP
• メンテナンスの負担緩和
• アクセス可能性(accessibility)を決定するアクセス制御 (access control) – 宣言された場所 と、
– アクセス修飾子(private, protected, and public) で決まる
– 目安
• できるだけアクセスできないように
項目13 クラスとメンバーへのアクセス可能性を最小限にする
• トップレベル(ネストしていない)クラス・インタフェース – 2種類のアクセスレベル
• package-private
• public – 可能ならば package-private にする
– public : 互換性を維持するために永久にサポートする義務。APIの一部。
– package-private top-level class を1つのクラスだけが使用している場合
• その使用している1つのクラスの private nested class にする事を検討
• メンバー(field, method, nested class, nested interface) – 4種類のアクセスレベル
• private
• package-private
• protected
• Public – できるだけ全てのメンバーをprivateにする
項目13 クラスとメンバーへのアクセス可能性を最小限にする
• Serializable を実装している場合 – private と package-private のメンバーであっても公開API の中に leak してしまう(項目74 )
• 古いバージョンでシリアライズ
• → 内部表現を変更
• → 新しいバージョンでデシリアライズ
• → プログラム失敗
• メソッドをオーバライド – サブクラスでアクセスレベルを低くできない
• インタフェースにあるメソッド – 全てpublic
• テストを容易にするために – private → package-private まで緩和するのは受け入れられる
項目13 クラスとメンバーへのアクセス可能性を最小限にする
• インスタンスフィールド:public にすべきではない → 項目14 – 以下の事を放棄することになる
• そのフィールドに保存できる値を制限
• そのフィールドに関係する不変式(invariant)を強制
• そのフィールドが変更された時に何らかの補助処理を行う
– 例外
• public static final の定数 は公開してよい(定数)
– 基本データ型
– 不変オブジェクト(項目15)への参照
– finalの可変オブジェクトの参照は?
» 参照は変更できないが、参照されているオブジェクトは操作できる
» 悲惨な結果になる変更が可能
» (例)長さが0ではない配列 → 可変
// Potential security hole!
public static final Thing[] VALUES = { ... };
項目13 クラスとメンバーへのアクセス可能性を最小限にする
• public static final の配列の修正:2通りの解決 – publicの不変リスト
– 配列のコピーを返すpublicのメソッド
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}
項目14 public のクラスでは、 publicのフィールドではなく、アクセッサーメソッドを使う
• public の accessor method (getter) と
• 可変クラスに対するmutator (setter)
– accessor method の提供 • クラス内部の表現形式を変更する柔軟性を保つ
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return x; }
public double getY() { return y; }
public void setX(double x) { this.x = x; }
public void setY(double y) { this.y = y; }
}
項目15 可変性を最小限にする
• 不変クラスにするための5つの規則 – 1. 状態を変更するメソッド(= mutator) を提供しない
– 2. クラスが拡張できない事を保証する
• → サブクラスに状態を変更されないように
– 3. 全てのフィールドをfinalにする
– 4. 全てのフィールドをprivateにする
– 5. 可変コンポーネントに対する exclusive access を保証する
• 可変オブジェクトを参照しているフィールドを持っている場合:
– クライアントがそのオブジェクトへの参照を取得できないように
– クライアントが提供したオブジェクト参照で初期化してはいけない
– accessor からオブジェクトの参照を返してはいけない
– defensive copy をする
» コンストラクタ
» Accessor
» readObject (項目76)
項目15 可変性を最小限にする
• 新たなインスタンスを生成して返している (算術操作メソッドの部分)
– 関数的方法
• オペランドを変更することなく、関数を適用した結果を返す
// 複素数を表すクラス
public finalclass Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
// 実数部分・虚数部分へのアクセサーを提供
// アクセサー のみ (対応する mutator を持たない)
public double realPart() { return re; }
public double imaginaryPart() { return im; }
// 4つの算術操作:足し算・引き算・掛け算・割り算を提供
public Complex add(Complex c) {
return new Complex(re + c.re, im + c.im);
} (…以下省略)
項目15 可変性を最小限にする • 不変オブジェクト
– スレッドセーフ。同期を必要としない。
– 制限なく共有できる
• コピー(#cloneやコピーコンストラクタ)を行う必要が無い
– コピー元と同値なので
– 既存のクラスを再利用
• 頻繁に使用される値 → 定数を提供
– 頻繁に要求されるインスタンスをキャッシュするstatic ファクトリメソッドを提供できる(項目1)
– 内部も共有できる
• 【例】 BigInteger の negate メソッド
– 同じ大きさで反対の符号のBigIntegerを返す
– 大きさ=int[] をコピーする必要がない
public static final Complex ZERO = new Complex(0, 0);
public static final Complex ONE = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);
public class BigInteger extends Number … {
final int signum; // -1 for negative, 0 for zero, or 1 for positive
final int[] mag;
…
public BigInteger negate() {
return new BigInteger(this.mag, -this.signum);
項目15 可変性を最小限にする • 不変オブジェクトの欠点
– 異なる値に対して、別々のオブジェクトが必要
– オブジェクトが大きい場合 • コストが高くつく
– (例) BigInteger で 100万ビットのデータの最下位ビットを反転
» 元のインスタンスと1ビットしか違わないのに
» → 100万ビットの新たなBigIntegerインスタンスを生成→ 生成コスト高
– Bitset(可変)の場合
» 最下位ビットの1ビットの状態を変更 → 一定時間で可能
– 複数ステップの処理 • 各ステップで新たなオブジェクトを生成 → 最終結果以外の全てのオブジェクトを破棄
→ パフォーマンスの問題
• 対処法2つ
– 複数ステップを1つの操作で提供する
– public の可変 companion class を提供する
» (主な例)StringとStringBuilder
» (ある状況では)BigInteger と Bitsetもその関係
項目15 可変性を最小限にする • 不変オブジェクトの設計
– 不変性を保証するために、サブクラス化の禁止 • 方法1: class を final にする
• 方法2: コンストラクタを全て private or package-private にして、public static factory(項目1)を追加
• static factoryの利点:
– Flexible » 後からキャッシュ機能を追加したり
– 名前が明確 (例: #valueOf と #valueOfPolar) » コンストラクタだけの場合、別の生成手段で、同じシグニチャになってしまう事がある
// コンストラクタの代わりに、 static factoryを持つ不変クラス
public class Complex {
private final double re;
private final double im;
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im){
return new Complex(re, im);
}
...
}
項目15 可変性を最小限にする • 不変オブジェクト:まとめ
– できるだけクラスは不変にする • 特に小さなオブジェクトは常に不変にすべき
• 大きなオブジェクトも不変にすることを検討
– パフォーマンスを得るために必要な時だけ、 public の可変 companion class を提供
– クラスを不変にできない場合 • その可変性をできるだけ制限
– 理由がない限り、全てのフィールドをfinalにする
• コストが高い計算結果(例: hashCode計算)をキャッシュするために 、finalでない冗長なフィールドを持つ場合あり。
– → String でも使用されている
項目16 継承よりコンポジションを選ぶ
• 継承はカプセル化を破る – Superclassの実装詳細に依存
• 後から superclass の実装が変更されたら、subclassが機能しなくなる可能性
// 継承の不適切な利用
public class InstrumentedHashSet<E> extends HashSet<E> {
// 要素の挿入回数
private int addCount = 0;
…
@Override public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
項目16 継承よりコンポジションを選ぶ
• ニ重にカウントされる
– getAddCount → 3ではなく6が返ってくる
– HashSet#addAll (AbstractCollection#addAll)は内部で #add を使用しているため
– 修理する事もできるが、、、、
• #addAll の Override を止める or
– HashSet#addAll が #addを使用して実装されている事実に依存
» → 自己利用 “self-use”
• Javaプラットフォームの全ての実装で行われている保証はない
• リリースごとに変更される可能性
• #addAll の中で 要素をiterateして #add 呼び出し
– HashSet#addAll を呼び出さない
» → superclass のメソッドを再実装するのに等しい
項目16 継承よりコンポジションを選ぶ
• Composition – 既存クラスを extend する代わりに、 既存クラスを private field にする。
– 新クラスのインスタンスメソッド
• 既存クラスの対応するメソッドを呼ぶ
• forwarding method と呼ばれる
// Wrapper class - 継承の代わりに composition を使用
public class InstrumentedSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public InstrumentedSet(Set<E> s) {
super(s);
} …
}
// 再利用可能な forwarding class
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s; } …
public boolean add(E e) { return s.add(e); } …
public boolean addAll(Collection<? extends E> c)
{ return s.addAll(c); } …
}
項目16 継承よりコンポジションを選ぶ
• InstrumentedSet は 他のSetインスタンスをラップする
– wrapper class とも呼ばれる
– Decorator pattern
– 既に使用されているSetインスタンスを一時的に計測
• 委譲(delegation) != (composition と forwarding の組み合わせ) – wrapper object が自分自身を wrapped object へ渡さない限り、委譲ではない
Set<Date> s = new InstrumentedSet<Date>(new TreeSet<Date>(cmp));
Set<E> s2 = new InstrumentedSet<E>(new HashSet<E>(capacity));
static void walk(Set<Dog> dogs) {
InstrumentedSet<Dog> iDogs = new InstrumentedSet<Dog>(dogs);
// このメソッド内では、dogsの代わりに、iDogsを使用
}
項目16 継承よりコンポジションを選ぶ
• 継承が適切の場合 – 本当のサブタイプ関係がある場合だけ
– クラスBは、クラスAとの間に「is-a」関係が存在している場合のみ、クラスAを拡張
• 「全てのBは本当にAであるか」
– javaのライブラリで原則を破っている例
• Stack (extends Vector)
• Properties (extends Hashtable)
• → どちらの場合も composition にするほうが適切だった
項目17 継承のために設計および文章化する、 でなければ継承を禁止する
• [オーバライド可能なメソッド]を呼び出すメソッド – 呼び出しに関する記述は「This implementation」で始まる
– #iterator をオーバライドすることによって、#remove に影響を与える事が記述されている
public abstract class AbstractCollection<E> implements Collection<E> {
…
public abstract Iterator<E> iterator();
…
/**
* {@inheritDoc}
*
* <p>This implementation iterates over the collection looking for the
* specified element. If it finds the element, it removes the element
* from the collection using the iterator's remove method.
*
* …
*/
public boolean remove(Object o) {
Iterator<E> it = iterator();
if (o==null) {
…
項目17 継承のために設計および文章化する、 でなければ継承を禁止する
• 継承のために設計されたクラス
– →サブクラスを書いてテスト
• 継承を可能にするための制約
– コンストラクタは、オーバライド可能なメソッドを呼び出してはいけない
public class Super {
// コンストラクタがオーバライド可能なメソッドを呼び出し
public Super() {
overrideMe();
}
public void overrideMe() {}
}
public final class Sub extends Super {
private final Date date; // Blank final, set by constructor
Sub() { date = new Date();} // コンストラクタ
@Override public void overrideMe() {
System.out.println(date);
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.overrideMe(); }
}
項目17 継承のために設計および文章化する、 でなければ継承を禁止する
• 実行結果例
• Cloneable または Serializable する場合にもコンストラクタと似た振る舞い
– #clone
– #readObject – 上記メソッドからオーバライド可能なメソッドを呼び出してはいけない
• 文書化されていないクラスのサブクラス化の禁止 – 方法1:クラスをfinal にする
– 方法2:コンストラクタを全て private or package-private にして 、public static factory を提供
null
Tue Feb 04 11:01:18 JST 2014
項目18 abstract class よりも interface を選ぶ
• abstract class
– メソッドの実装を含む
• Interface
– メソッドの実装を含まない • (※)java8 では default 実装を定義できるそうですが…
• javaは単一継承のみ – クラスは2つ以上の親は持てない
– 2つのクラスに同じ抽象クラスを拡張させたければ → 高い位置(両方のクラスの先祖) に定義する必要あり
項目18 abstract class よりも interface を選ぶ
• Interface
– mixin を定義するのに理想的
– 階層を持たない型
public interface Singer {
AudioClip sing(Song s);
}
public interface Songwriter {
Song compose(boolean hit);
}
public interface SingerSongwriter extends Singer, Songwriter {
AudioClip strum();
void actSensitive();
}
項目18 abstract class よりも interface を選ぶ
• 骨格実装 (skeletal implementation) – interface ごとに
– 実装補助を提供
– 慣例として、AbstractInterface と呼ばれる
• Collections Framework で提供している骨格実装の例
– AbstractCollection, AbstractSet, AbstractList, AbstractMap
• 骨格実装の書き方
– interface を調べる
– 基本操作を決める
• → abstract method にする
– 他の全てのメソッド
• →具体的な実装を提供
項目18 abstract class よりも interface を選ぶ
• 骨格実装の例
– int[] を List<Integer> として見せる Adapter
– static factory の中に隠蔽されている anonymous class
• ※パフォーマンスはそれほど良いわけではない (boxing 、unboxing をしているため)
static List<Integer> intArrayAsList(final int[] a) {
if (a == null)
throw new NullPointerException();
return new AbstractList<Integer>() {
public Integer get(int i) {
return a[i]; // Autoboxing (Item 5)
}
@Override public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val; // Auto-unboxing
return oldVal; // Autoboxing
}
public int size() {
return a.length;
}
};
}
項目18 abstract class よりも interface を選ぶ
• Map.Entryの骨格実装例
public abstract class AbstractMapEntry<K,V> implements Map.Entry<K,V> {
// Primitive operations
public abstract K getKey();
public abstract V getValue();
// 変更可能な Mapの Entries は、このメソッドを override しなければならない
public V setValue(V value) {
throw new UnsupportedOperationException();
}
// Implements the general contract of Map.Entry.equals
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (! (o instanceof Map.Entry))
return false;
Map.Entry<?,?> arg = (Map.Entry) o;
return equals(getKey(), arg.getKey()) &&
equals(getValue(), arg.getValue());
}
…
}
項目19 型を定義するためだけに interface を使用する
• interface は 型を定義するためだけに使用すべき
• 定数を提供するために使用すべきではない – ダメな例:
• 定数インタフェース (constant interface)
• 選択肢 – 既存の class または interface に強く結びついている場合:
• その class または interface に定数を追加
– (例)数字(Integer や Floatなど)クラス の MIN_VALUE や MAX_VALUE
– 定数が列挙型のメンバーとして見なされるのがbestならば:
• enum type で定数を提供 (項目30)
– そうでなければ…
• インスタンス化不能な utility class で定数を提供
項目20 タグ付クラスよりクラス階層を選ぶ
// Tagged class - クラス階層より、かなり劣る
class Figure {
enum Shape { RECTANGLE, CIRCLE };
// Tag field - the shape of this figure
final Shape shape;
// These fields are used only if shape is RECTANGLE
double length;
double width;
// This field is used only if shape is CIRCLE
double radius;
// Constructor for circle
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// Constructor for rectangle
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
項目20 タグ付クラスよりクラス階層を選ぶ
• 欠点 – 決まりきったコードで散らかっている
• enum 宣言
• tag field
• switch 文
– メモリ量が増加 • 他の特性に属するフィールドを抱えているため
– 特性の追加時にソースの修正が必要 • switch 文 に case を追加しなければならない
double area() {
switch(shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError();
}
}
}
項目20 タグ付クラスよりクラス階層を選ぶ
• サブタイプ化
• 各特性の実装に対して → 独自のクラスを定義
– 自分と無関係なデータフィールドを負っていない
• 全ての field が final
// クラス階層による置き換え
abstract class Figure {
abstract double area();
}
class Circle extends Figure {
final double radius;
Circle(double radius) { this.radius = radius; }
double area() { return Math.PI * (radius * radius); }
}
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() { return length * width; }
}
項目21 戦略を表現するために関数オブジェクトを使用する
• java は 関数ポインタを提供していないが、オブジェクトへの参照を使用できる
– [他のオブジェクト]に対して操作を行うメソッドを持つオブジェクトを定義
• 他のオブジェクトがそのメソッドに明示的に渡される
• そのようなメソッドを1つだけ公開
• 関数オブジェクト(function object)と呼ばれる
• 戦略(Strategy)パターンを実装
– 戦略を表すインタフェース(Strategy interface)
– 個々の具象戦略クラス(Concrete strategy class)
» StringLengthComparatorの戦略:
• 辞書順ではなく、文字列の長さで順序付け
public interface Comparator<T> {
public int compare(T t1, T t2);
}
class StringLengthComparator implements Comparator<String> {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
項目21 戦略を表現するために関数オブジェクトを使用する
• Stateless – シングルトンにするのに適している
• anonymous classで使用
– → 呼び出し毎に新たなインスタンス生成
• 繰り返し実行する場合は private static final にして再利用を検討
class StringLengthComparator {
private StringLengthComparator() { }
public static final StringLengthComparator
INSTANCE = new StringLengthComparator();
public int compare(String s1, String s2) {
return s1.length() - s2.length(); }
}
Arrays.sort(stringArray, new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
項目21 戦略を表現するために関数オブジェクトを使用する
• concrete strategy class は public にする必要はない
– “host class” の private nested class にできる
– Stringクラス の CASE_INSENSITIVE_ORDER はこのパターン
class Host {
private static class StrLenCmp implements Comparator<String>, Serializable {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
public static final Comparator<String>
STRING_LENGTH_COMPARATOR = new StrLenCmp();
… }
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
…
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
public int compare(String s1, String s2) {
…
項目22 非staticのメンバークラスより staticのメンバークラスを選ぶ
• nested class
– 他のclass内に定義されたclass
– enclosing class に対して仕えるためだけに存在すべき
– 4種類
• static member class
• nonstatic member class
• anonymous class
• local class
• enclosing instance への参照が必要ならば nonstatic にする
• →そうでなければ、staticにする。
• クラスがメソッド内に属していて、1箇所からのみインスタンスを生成する必要があり、
そのクラスを特徴づける型が存在すれば、anonymous class にする