Effetive Java 読んでみた

  • 19
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

項目1 コンストラクタの代わりにstatic ファクトリーメソッドを検討する

メリット

名前を持っていること

  • 意図を持たせるこExとができる
    Ex:
    BigInteger.probablePrime(bitLength, rnd)

  • 特定のシグニチャを持つメソッドを複数生成できる

メソッドを呼ぶたびに新たなインスタンスを作成しなくてもよい

  • 無駄なインスタンスの生成を回避することにより、パフォーマンスの向上(インスタンス生成のコストが高いほど有効)

サブタイプのオブジェクトを返すことができる

  • 実装クラスの隠蔽(インターフェースベースのフレームワーク)
    Ex:コレクションフレームワーク

  • パラメータの値に応じて、呼び出しごとにそのクラスを返すことができる
    Ex:EnumSet(64個以下の要素であればRegularEnumSet、それ以上の場合はJunmboEnumSetを返す)

  • staticファクトリーメソッドを含むクラスが書かれた時点で、返されるオブジェクトのクラスが存在しなくてもよい
    Ex:JDBC等のサービスプロバイダーフレームワーク

  • インスタンス生成時のパラメータ指定の面倒さの低減
    Java1.7以降ではダイヤモンド構文により解決

デメリット

publicやprotectedのコンストラクタを持たないクラスのサブクラスを作れない

一見不便な見えるが、継承よりコンポジションを使うことを促すため問題ない
コンポジション・・・(-_-)

他のstaticメソッドと区別がつきにくい

命名規約に従おう
- valueOf, of, getInstance, newInstance, getType, newType

まとめ

使用するメリットが多いため、コンストラクタを使う前にstaticファクトリーメソッドを使うべきか考えよう(コンストラクタの数が増えてきたりとか、同じシグニチャのコンストラクタを作りたいとき)

項目2 数多くのコンストラクタパラメータに直面した時にはビルダーを検討する

パラメータの数が多い時、コードが長くなってしまい、可読性が下がる。そんな時にビルダーパターンを使う


public static class Builder {
        //必須パラメータ
        private final int servingSize;
        private final int servings;

        //オプションパラメータ
        private int caloreis = 0;
        private int fat = 0;
        private int carbohydrate = 0;
        private int sodium = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            caloreis = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }

    }

    public NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.caloreis;
        fat = builder.fat;
        carbohydrate = builder.carbohydrate;
        sodium = builder.sodium;
        checkParam();
    }

    /**
     * パラメータのチェックメソッド。コピー元ではなく生成されたオブジェクトに対して行うこと。
     * パラメータが不正な場合、どのパラメータがエラーかわかるようにIllegalArgumentExceptionを
     * throwする。
     */
    private void checkParam() {
        if (servingSize < 0) {
            throw new IllegalArgumentException(
                    "servingSize is minus servingSize = " + servingSize);
        }

        if (servings < 0) {
            throw new IllegalArgumentException(
                    "servings is minus servings = " + servings);
        }
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                .calories(100).sodium(35).carbohydrate(27).build();

    }

メリット

  • 可読性が高い
  • 書きやすい
  • かっこいい(´∀`)

デメリット

  • Builder自体の生成のコストがかかる

  • コードが長くなる
    → パラメータの数が多くなるときだけ使用する。

まとめ

パラメータが多いときにはBuilderの使用を検討しよう。
WebだとFormとかで検証メソッドを作れるが、バッチとかだと検証メソッドの呼び出しを強制させるのに便利

項目3 privateのコンストラクタかenum型でシングルトンの特性を強制する

Java1.5以降を使っているならEnumを使用するのがよい
→privateのコンストラクタを使用するより簡潔で、かつシリアライズの機構を提供している。また、リフレクションやシリアライズ攻撃にも対応している。

いつシングルトンを使うか?

項目マスタデータ、最近ではサービスクラスとかでも(DIを使うから)

Java1.5以降(-_-)

項目4 privateのコンストラクタでインスタンス化不可能を強制する

utiltyクラス等、インスタンス化する必要のないクラスを作る際に使う。
明示的なコンストラクタがないと、publicなパラメータなしのコンストラクタが自動で作成されてしまうため、うまくいかない。
副作用として、クラスのサブクラスが作成されてしまうことも防ぐ。
→サブクラスのコンストラクタはスーパークラスのコンストラクタを呼び出さなければならないため。

項目5 不必要なオブジェクトの生成を避ける

  • 再利用の方が早く、スマート。不変(immutable)オブジェクト(String等)であれば、常に再利用できる。

  • staticファクトリーメソッドで不必要なオブジェクトの生成を回避
    Ex:Boolean.valueOf(String)

  • 変更されないとわかっているオブジェクトの再利用
    Ex:

//やってはいけない!
public class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    public boolean isBabyBoomer() {
        //コストの高いオブジェクトの不必要な生成
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomStart = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomEnd = gmtCal.getTime();
        return birthDate.compareTo(boomStart) >= 0 &&
                birthDate.compareTo(boomEnd) < 0;
    }

↑をstatic修飾子を使用して改善

public class Person {
    private final Date birthDate;

    private static final Date BOOM_START;
    private static final Date BOOM_END;

    static {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_START = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_END = gmtCal.getTime();
    }

    public boolean isBabyBoom() {
        return birthDate.compareTo(BOOM_START) >= 0 &&
                birthDate.compareTo(BOOM_END) < 0;
    }
}
  • ラッパークラスよりもプリミティブ型を積極的に使うこと → 意図しないオートボクシングを防ぐため

まとめ

オブジェクトの生成コストは高く、不必要なオブジェクト生成はスタイルに影響するため、不要なオブジェクトの生成は避けること

項目6 廃れたオブジェクト参照を取り除く

ガーベッジコレクションを持つ言語でもメモリ管理は必要!

オブジェクト参照が意図されることなく保持されると、そのオブジェクト参照や、その先のオブジェクトが参照しているオブジェクトも同様に回収されないこととなる。
→ パフォーマンスへの影響

廃れた参照にはnullを設定することによりメモリを解放できる。
ただし過度に対処はしないこと。
→ スコープを狭くすることによりnull設定と同様のことをさせる

クラスが独自のメモリ管理をしている時に注意

要素が解放されたらnullを設定

キャッシュに注意

キャッシュにオブジェクト参照を入れてしまうと忘れがち。
弱い参照(WeakHahMap)を使用して対処。

リスナーやコールバック

まとめ

メモリリークは見つかりにくいため、事前に予想し、発生前に防ぐこと

項目7 ファイナライザを避ける

  • 予期しない動作をすることがある

  • 不安定な振る舞い、パフォーマンス、移植性の問題の原因になりうる

普段は使わない!

てかそもそも使ったことない・・・

ファイナライザの欠点

  • すぐに実行されるとは限らない →時間的制約のあることをファイナライザでは行わない

Ex:
ファイルのクローズ

  • そもそも実行されることが保証されていない
    (´・ω・`)

  • パフォーマンスのペナルティ

じゃあファイルやスレッド等の終了をしなければいけない資源の解放はどうすればいいか?

明示的終了メソッド(InputStreamのclose()メソッドとか)を使う

終了を保証するためにtry-finaly構文を使う

じゃあいつ使うのか?

明示的終了メソッドを呼び出し忘れた際のセーフティネットとして使う

→前述したコストの問題もあるため、よく検討する。
 また、クライアントの資源が解放されていなかったことを明示的にするため、ログを記録する。

ネイティブピアを持つオブジェクトの解放

ファイナライザの連鎖は自動的に実行されない

サブクラスでファイナライザを持っている場合、スーパークラスのファイナライザは呼ばれないため、手作業で呼ぶ必要がある
→サブクラスのファイナライザ内でfinally句を使用

ファイナライザガーディアン

そもそもスーパークラスにファイナライザがあるのに気づかなかった時のために。

public class Foo {
    private final Object finalizerGuardian = new Object() {
        @Override
        protected void finalize() throws Throwable {
            // 外側のFooオブジェクトをファイナライズする
        }
    }
}

ガーディアンのファイナライザがエンクロージングインスタンスのファイナライズを行う。

まとめ

基本的に使わない!
もしどうしても使いたい場合はログの記述、ファイナライザガーディアンの使用を忘れないように!

サンプルコード:
https://github.com/marhan/effective-java-examples