effective javaを今更読む

第4章

クラスとメンバーへのアクセス可能性を最小限にする

  • 各クラスやメンバーをできる限りアクセスできないようにすべきです。
  • インスタンスフィールドは、決してpublicにすべきではありません。
  • publicの可変フィールドを持つクラスは、スレッドセーフではありません。
  • クラスがpublic static finalの配列フィールドやそのようなフィールドを返すアクセッサーを持つのは、ほとんど誤り。

publicクラスでは、publicのフィールドではなく、アクセッサーメソッドを使う

  • クラスがそのパッケージの外からアクセス可能ならば、アクセッサーメソッドを提供してください。
  • クラスがパッケージプライペート、あるいは、pirvateのネストしたクラスの場合には、そのデータフィールドを直接公開することは本質的に問題ありません。

可変性を最小限にする

  • クラスを不変にする5つの規則
  1. オブジェクトの状態を変更するためのいかなるメソッドも提供しない。
  2. クラスが拡張できないことを保証する。
  3. すべてのフィールドをfainalにする。
  4. すべてのフィールドをprivateにする。
  5. 可変コンポーネントに対する独占的にアクセスを保証する。
  • 不変オブジェクトは、本質的にスレッドセーフです。つまり、同期を必要としません。
  • 不変オブジェクトは、制限無く共有出来ます。
  • 不変オブジェクトを共有出来るだけでなく、その内部を共有できます。
  • 不変オブジェクトは、他のオブジェクトに対する素晴らしい建築ブロックを作り出します。
  • 不変クラスの唯一の実質的欠点は、個々の異なる値に対しての別々のオブジェクトを必要とすることです。
  • 可変にすべきかなり正当な理由がない限り、クラスは不変であるべきです。
  • もし、クラスを不変にできないのであれば、その可能性はをできるだけ制限すべきです。
  • fainalにできないやむを得ない理由がない限り、すべてのフィールドをfinalにしてください。

継承よりコンポジションを選ぶ

  • メソッド呼び出しと異なり、継承はカプセル化を破ります。
  • メソッドのオーバーライドは自己利用をしているかもしれないし、していないかもしれないスーパークラスのメソッドを再実装するのに等しく、それは困難であり、時間を要し、誤りやすいです。更にこの方法は常に可能とは限りません。メソッドによっては、サブクラスがアクセス出来ないprivateのフィールドへのアクセスなしでは実装出来ないからです。
  • サブクラスのもろさの原因の1つは、スーパークラスは後のリリースで新たなメソッドを追加出来る事。
  • 既存のクラスを拡張する代わりに、既存のクラスのインスタンスを参照するprivateのフィールドを新たなクラスに持たせる設計をコンポジションと呼ばれる。
/**再利用可能な転送クラス**/
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;

public ForwardingSet(Set<E> s){this.s =s;}
public boolean contains(Object o) {return s.contains(o);}
public boolean isEmpty(){return s.isEmpty();}
public int size(){return s.size();}
public boolean add(E e){return s.add(e);}
}


/**ラッパークラス 継承の代わりに、コンポジションを利用**/
public class InstrumentedSet<E> extends ForwardingSet<E> {
  private int addCount =0;

  public InsrumentedSet(Set<E> s){
    super(s);
  }
  @Override public boolean add(E e){
    addCount++;
    return super.add(e);
  }
  public int getAddCount(){
    return addCount;
  }
}

継承の為に設計及び文書化する、でなければ継承を禁止する

  • クラスはオーバーライド可能なメソッドの自己利用(self-use)を文書化しなければなりません。
  • クラスは、賢く選択されたprotectedのメソッドの形で、クラス内部の動作へのフックを提供しなければならないかもしれません。
  • 継承のために設計されたクラスをテストする唯一の方法は、サブクラスを書く事です。
  • リリースする前に、サブクラスを書く事でクラスをテストしなければなりません。
  • clone及びreadObjectは、オーバーライド可能なメソッドを、直接的、間接的のどちらであっても呼び出ししてはいけない。
  • readResolveメソッドやwriteReplaceメソッドを、privateではなくprotectedにしなれかばなりません。
  • 継承の為にクラスを設計することは、そのクラスにかなりの制限を課す
  • 最前の解決策は、安全にサブクラス化されるために設計と文書化をされていないクラスでのサブクラス化の禁止です。
  • コンストラクタは、オーバーライド可能なメソッドを呼び出してはいけません。
public class Super {
  /**コンストラクタがオーバーライド可能なメソッドを呼び出してはいけません。**/
  public Super() {
    overrideMe();
  }
  public void overrideMe() {
  }
}
public final class Sub extends Super {
  private final Date date;

  Sub(){
    date = new Date();
  }
  @Override public void overrideMe(){
    System.out.println(date);
  }
  public static void main(String[] args){
    Sub sub = new Sub();
    sub.overrideMe();
  }
}

このプログラムは日付を2度表示されるのを期待されるかもしれませんが、コンストラクタSubがdateフィールドを初期化する機会が与えられる前に、メソッドoverrideMeがコンストラクタSuperに呼び出されますので、最初はnullが表示されます。

抽象クラスよりインターフェースを選ぶ

  • 既存のクラスを、新たなインターフェースを実装するように変更する事は容易に出来ます。
  • クラスが「本来の型」に加えて、なんらかの任意の振る舞いを提供していることを宣言する為に、クラスが実装する型がミックスインです。
  • インターフェースはミックインを定義するには理想的です。
  • インターフェースは、階層を持たない型フレームワークを構築する事を可能にしています。
  • インターフェースは、安全で強力な機能エンハンスを可能にします。
  • 抽象骨格実装インターフェースと抽象クラスの長所を組み合わせる事が出来ます。
  • インターフェースを実装させるよりは、抽象クラスを発展させる方が、はるかに容易。
  • 一旦、インターフェースがリリースされて広く実装されたら、インターフェースを変更する事はほとんど不可能

型を定義する為だけにインターフェースを使用する

  • 定数インターフェースパターンは、インターフェースの下手な使い方です。

タグ付クラスよりクラス階層を選ぶ

  • タグ付クラスは、冗長で、誤りやすく、非効率。
  • タグ付クラスは、クラス階層の面白みのない模倣にすぎない。
/**タグ付クラス**/
class Figure {
  enum Shape {RECTANGLE,CIRCLE};

  final Shape shape;

  double length;
  double width;

  double radius

  Figure(double radius){
    shipe = Shape.CIRCLE;
    this.radius = radius;
  }
  Figure(dobule length,dobule width){
    shape = Shape.RECTANGLE;
    this.length = length;
    this.width = width;
  }
  double area(){
    switch(shape){
      case RECTANGLE:
        return length * width;
      case CIRCLE:
        return Math.PI * (radius * radius);
      default:
        throw new AssertionError();
    }
  }
}

/**クラス階層に置き換え**/
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}
}

戦略を表現する為に関数オブジェクトを使用する

非staticのメンバークラスよりstaticのメンバークラスを選ぶ

  • エンクロージングインスタンスをアクセスする必要がないメンバークラスを宣言するのであれば、その宣言にstatic修飾子を常につける