LoginSignup
0
0

More than 3 years have passed since last update.

Effevtive Java読書会 3日目 Part.2

Last updated at Posted at 2021-01-03

(注)こちらは6年前の投稿です。

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

複数の実装を許す型を定義するために、インタフェースと抽象クラスの2つの仕組みがある。

それぞれの特徴

抽象クラス

  • いくつかのメソッドに対する実装が許される
  • 抽象クラスで定義された型の実装には、クラスはその抽象クラスのサブクラスで無ければいけない

インターフェース

  • メソッドの実装が許されていない
  • 要求されるメソッドを全て定義し、一般契約に従っているクラスであれば、どのクラス階層のであっても実装することが許される
インターフェースの利点
  1. 既存クラスを、新たなインターフェースを実装するように変更することが容易
     (->抽象クラスの場合、型階層関係の問題があり、容易にできない)

  2. ミックスインに理想的
    ミックスインとは ・・・クラスの「本来の型」に加え、何らかの任意の振る舞い(機能)を提供していることを宣言するためにクラスが実装する型のこと
    ->型本来の機能に対して任意の機能を混ぜ合わせる(組み込む)事が容易

  3. インターフェースは階層を持たない型フレームワークを構築することを可能にしている
    要は、インターフェースは同士の継承は複数可能。また1つクラスに対しても複数インターフェースを実装することが出来る

  4. ラッパークラスを通して、インターフェースは安全で強力な機能エンハンスを可能にする
    ->項目16で書いたコンポジションの実装が良い例です。

  5. 抽象骨格実装クラスを提供することで、インターフェースと抽象クラスの長所も組み合わせることが出来る
    抽象骨格実装クラスとは・・AbstractList・AbstractMap等
    その他骨格実装クラス、疑似多重継承など(具体例はこちら[http://d.hatena.ne.jp/Kappuccino/20080812/1218513214])
    これら骨格実装クラスによって、プログラマのインターフェース独自の実装の提供を容易にする

    -> 但し勿論使用は任意
インターフェースの欠点
# 発展が容易ではない

 インターフェースにメソッドを追加した場合、それを実装しているクラス達に
 必ず影響が起きる
 ->一旦インターフェースがリリースされ、広く実装されたら、変更するのは不可能
 ->外部公開には非常に注意するべき。

項目19  型を定義するためだけにインターフェースを使用する

インターフェースで定数を提供するな
  • インターフェースはクラスのインスタンスを参照するのに使用する型として機能するもの
  • インターフェースは実装の詳細は取り扱わない
  • インターフェースは何が出来るかについて述べること(機能の概要を記述したもの)

悪い例

PhysicalConstats.java
// Constant interface antipattern - do not use!
public interface PhysicalConstants {
    // Avogadro's number (1/mol)
    static final double AVOGADROS_NUMBER = 6.02214199e23;
    // Boltzmann constant (J/K)
    static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
    // Mass of the electron (kg)
    static final double ELECTRON_MASS = 9.10938188e-31;
}

正しい定数宣言例

PhysicalConstats.java
// Constant utility class
package com.effectivejava.science;
public class PhysicalConstants {
    private PhysicalConstants() { } // Prevents instantiation
    public static final double AVOGADROS_NUMBER = 6.02214199e23;
    public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
    public static final double ELECTRON_MASS = 9.10938188e-31;
}

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

下記のような、インスタンスに2つの特性を持ち、その特性を示すためにタグフィールドを持つクラス。
このようなタグ付きのクラスは欠点が多くほとんどの場合適切でない

Figure.java
// 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;
    }
    double area() {
        switch(shape) {
            case RECTANGLE:
                return length * width;
            case CIRCLE:
                return Math.PI * (radius * radius);
            default:
                throw new AssertionError();
        }
    }
}
タグ付きクラスが良くない理由
  • 複数の実装が単一クラスにあり、読みやすさが損なわれる。
  • 自分の特性以外の属性も保持することになり、メモリが増加。
  • コンストラクタで自分以外の特性の属性まで初期化出来ないので、全部をfinalにできない。
  • 特性の追加毎のswitch-case分の修正が必要
クラス階層のススメ

上の問題をすっきり解決します。


各特性ごとに独自のクラスが割り当てられ、無関係なデータフィールドを含んでいない

Figure.java
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  戦略を表現するために関数オブジェクトを使用する

(ここでいう戦略とはデザインパターンの一つ戦略パターン(http://www.techscore.com/tech/DesignPattern/Strategy.html/)のこと)
他の言語には関数ポインタ・委譲・ラムダ式といった、プログラムが特定の関数を保存して、その関数を呼び出せるようになっている
->JAVAにはそれがない。が、 同様の効果を得るオブジェクト参照( 関数オブジェクト )を使用できる

具体的には

他のオブジェクトに対して一つだけ操作を行い、その操作を公開しているクラスのインスタンス
フィールドをもたない->状態を保持しない

例)

StringLengthComparator.java
class StringLengthComparator {
    private StringLengthComparator() { }
    public static final StringLengthComparator
        INSTANCE = new StringLengthComparator();
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
}

ただし、このままでは特定の適切のパラメータしか渡せない
よってこのような具象クラスと一緒に使用する 戦略インターフェース を定義する必要がある

public interface Comparator<T> {
    public int compare(T t1, T t2);
}

class StringLengthComparator implements Comparator<String> {
    ... // class body is identical to the one shown above
}

よく使われるケースとして、下記のように多くの場合は、無名クラスで実装している

Arrays.sort(stringArray, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
});

が、無名クラスの場合、呼び出されるたびにインスタンス生成をすることに注意
繰り返し使うのであれば、以下のようにprivate static finalフィールドに
保存し再利用をすすめる

Strategy.java
class Host {
    private static class StrLenCmp implements Comparator<String>, Serializable {
        public int compare(String s1, String s2) {
            return s1.length() - s2.length();
        }
    }

    // Returned comparator is serializable
    public static final Comparator<String>
        STRING_LENGTH_COMPARATOR = new StrLenCmp();
        ... // Bulk of class omitted
}

とここまで書きましたが、
最近リリースされた

JAVA8

で、ラムダ式採用されました
http://www.atmarkit.co.jp/ait/articles/1402/18/news010.html

項目22  非staticのメンバークラスよりstaticなメンバークラスを選ぶ

ネストクラスについて

ネストクラスは4種類ある

  • staticのメンバークラス
  • 非staticのメンバークラス
  • 無名クラス
  • 無名クラス

このうちstaticのメンバークラス以外を内部クラス(インナークラス)という

ネストクラスの考え方
  • エンクロージングクラスに対して仕えるためだけに存在すべき。
  • エンクロージングクラス以外のクラスに有用ならば、トップレベルにするべき。
staticなメンバークラスとは
  • エンクロージングクラスのメンバーのすべて にアクセスできる通常のクラスと変わらないクラス
  • 他のstaticなメンバーと同じアクセス可能性規則を持つ
  • privateな場合はエンクロージングクラスからのみアクセスが可能
使用方法

外部クラスと一緒に使用すると有用なpublicなヘルパークラスとして

(一つのメソッドが外からも見える必要があったり、メソッド内に問題なく入れるのには長すぎる場合に使う)

非staticなメンバークラスとは
  • メンバークラス生成時に、そのエンクロージングインスタンスと関連付けられる
  • エンクロージングインスタンスに対してメソッドを呼び出すことが可能
  • エンクロージングインスタンス修飾したthis構文を使用して呼び出すことが可能
  • 関連付けは非staticメンバークラスのインスタンスでメモリを消費し、インスタンスの生成時間が増加する。
使用方法

エンクロージングクラスのインスタンスの関係のないインスタンスとしてみなすことを可能にする アダプターを定義する事
(※デザインパターンの一つアダプターパターン(http://www.techscore.com/tech/DesignPattern/Adapter/Adapter1.html/)の事(だと思う・・。))
(メンバークラスの個々のインスタンスが、エンクロージングインスタンスの参照が必要な場合に使う)

public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted
    public Iterator<E> iterator() {
        return new MyIterator();
    }
    private class MyIterator implements Iterator<E> {
        ...
    }
}
エンクロージングインスタンスへアクセスする必要がないメンバークラスを宣言するならstaticメンバークラスにすべき

理由としては

  • エンクロージングインスタンスへ関係のない参照を持ってしまう
  • 参照を保存することは時間とメモリを必要
  • 本来ガベージコレクションの対象になるはずなのに、対象からもれてしまう可能性がある
無名クラスとは
  • その名の通り名前のないクラス
  • エンクロージングクラスのメンバーでもない
  • 宣言されたと同時にインスタンス化される
  • 式が許されている場所であればコードのどこでも許される
制限の多いクラスでもあります・・・
  • 宣言された場所以外でインスタンス化出来ない
  • 例えstaticの文脈内で書かれてたとしてもstaticなメンバーは持てない
  • instanseof検査やクラスの名前を指定する必要のある処理は出来ない
  • 1つしか実装(implements)できない。
  • スーパータイプのメソッドし以外呼べない
基本的には10行以内がベスト
使用方法
  • 21章の関数オブジェクトとして

(クラスがメソッド内に属するべきで、一か所からのみインスタンスを生成する必要があり、そのクラスを特徴づける型が存在している場合)

ローカルクラスとは
  • もっとも使用されないクラス
  • ローカル変数が使用できる箇所で宣言できる
  • 一応名前を持っているので繰り返し使える
  • staticなメンバーは持てない
  • 非staticな文脈で使用された場合だけ、エンクロージングインスタンスを持てる
0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0