0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Effective Java 第3版 第6章enumとアノテーション

Last updated at Posted at 2019-06-13

Java中級者以上の必須本である、Effective Java 第3版に Kindle版が出たので、まとめる。

前:Effective Java 第3版 第5章ジェネリックス
次:Effective Java 第3版 第7章ラムダとストリーム

項目34 intの代わりにenumを使う

  • enumは、public static finalフィールドを通して、各列挙定数に対して一つのインスタンスを公開しているクラス。
  • enumはアクセス可能なコンストラクタを持っていないので、事実上final。
  • enum型は、基本的に単一要素のenumであるシングルトンを汎用化したもの。
  • enum型には、任意のメソッドやフィールドを追加できる。
データと振る舞いを持つenum型
public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS(4.869e+24, 6.052e6),
    EARTH(5.975e+24, 6.378e6),
    MARS(6.419e+23, 3.393e6);
    // 省略

    private final double mass; // 質量
    private final double radius; // 半径
    private final double surfaceGravity; // 表面重力

    private static final double G = 6.67300E-11; // 万有引力定数
    // enum定数に、データを関連づけるコンストラクタ
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
        surfaceGravity = G * mass / (radius * radius);
    }
    public double mass() {
        return mass;
    }
    public double radius() {
        return radius;
    }
    public double surfaceGravity() {
        return surfaceGravity;
    }
    // 
    public double getSurfaceWeight(double mass) {
        return mass * surfaceGravity;   // F = ma
    }
    // 呼び出し例
    public static void main(String[] args) {
     // 地球上で60kgは他の惑星ではどれくらいの重さになるか?
        double earthWeight = Double.parseDouble("60.0");
        double mass = earthWeight / Planet.EARTH.surfaceGravity();
        for (Planet p: Planet.values()) {
            System.out.printf("Weight on %s is %f%n", p, p.getSurfaceWeight(mass));
        }
    }
}

// 結果
Weight on MERCURY is 22.674402
Weight on VENUS is 54.303060
Weight on EARTH is 60.000000
Weight on MARS is 22.776240
  • 定数固有クラス本体と定数固有データを持つenum型
定数固有クラス本体と定数固有データを持つenum型
public enum Operation {
    PLUS("+") {
        // applyをオーバーライドする
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            return x / y;
        }
    };
    private final String symbol;

    @Override
    public String toString() {
        return symbol;
    }
    // コンストラクタ
    Operation(String symbol) {
        this.symbol = symbol;
    }
    // abstractでメソッドを定義すれば、各euum定数はメソッドのオーバーライドが義務付けられる
    public abstract double apply(double x, double y);

    // 呼び出し例
    public static void main(String[] args) {
        double x = 2.0;
        double y = 4.0;
        for (Operation op : Operation.values()) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }
}

項目35 序数の代わりにインスタンスフィールドを使う

  • enum型には、自動的にint値が割り当てられるが、、欠番や重複が出た時に対応できないので使うべきではない。int値を使う場合は、明示的に割り当てる。
インスタンスフォールドに値を保存する
public enum Ensemble {
    SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
    SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
    NONET(0), DECTET(10), TRIPLE_QUARTET(12);

    private final int numberOfMusicians;

    // 数値を持たせたいときは、コンストラクタに定義する
    Ensemble(int size) {
        this.numberOfMusicians = size;
    }

    public int getNumberOfMusicians() {
        return numberOfMusicians;
    }

    // 呼び出し例
    public static void main(String[] args) {
        for (Ensemble e : Ensemble.values()) {
            System.out.printf("%s %s %n",e.name(), e.numberOfMusicians);
        }
    }
}
// 実行結果
SOLO 1 
DUET 2 
TRIO 3 
QUARTET 4 
QUINTET 5 
SEXTET 6 
SEPTET 7 
OCTET 8 
DOUBLE_QUARTET 8 
NONET 0 
DECTET 10 
TRIPLE_QUARTET 12 

項目36 ビットフィールドの代わりにEnumSetを使う

  • ビットフィールドとは、a=1、b=2、c=4、d=8 とした場合、abcを1+2+4=7と表現するやり方。
  • EnumSetは複数のenum型を持てるSetである。
EnumSetの例
public class Text {
  public enum Style { BOLD, ITALIC, UNDERLINE STRIKETHROUGH }
  // SetでEnumSetを受け取る
  public void applyStyles(Set<Style> styles) { ... }
}
// EnumSetの使用例
text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC);

項目37 序数インデックスの代わりにEnumMapを使う

  • EnumSetとは、enumをキーとして使うように設計された高速なMapの実装。
EnumSetの使用例
public class Plant {

    enum LifeCycle {ANNUAL, PERENNIAL, BIENNIAL;}

    final String name;
    final LifeCycle lifeCycle;

    Plant(String name, LifeCycle lifeCycle) {
        this.name = name;
        this.lifeCycle = lifeCycle;
    }

    @Override
    public String toString() {
        return name;
    }

    // 呼び出し例
    public static void main(String[] args) {
        var garden = List.of(new Plant("annual", LifeCycle.ANNUAL),
                new Plant("biennial1", LifeCycle.BIENNIAL),
                new Plant("biennial2", LifeCycle.BIENNIAL));
        // EnumMapのインスタンスを作成する
        Map<LifeCycle, Set<Plant>> plantsByLifeCycle =
                new EnumMap<>(Plant.LifeCycle.class);
        // キー:enum型LifeCycle、値が空のHashSetを持つMapを追加する
        for (Plant.LifeCycle lc : Plant.LifeCycle.values()) {
            plantsByLifeCycle.put(lc, new HashSet<>());
        }
        // MapのHashSetに値を追加する。
        for (Plant p : garden) {
            plantsByLifeCycle.get(p.lifeCycle).add(p);
        }
        System.out.println(plantsByLifeCycle);
    }
}
// 出力結果
{ANNUAL=[annual], PERENNIAL=[], BIENNIAL=[biennial1, biennial2]}

項目38 拡張可能なenumをインタフェースで模倣する

  • enum型は拡張できないが、インタフェースをenum型で実装することは可能である。
インタフェースをenumで実装する
// インタフェースの定義
public interface Operation {
    double apply(double x, double y);
}
// インタフェースを実装する
enum BasicOperation implements Operation {
    PLUS("+") {
        // applyを実装する
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        public double apply(double x, double y) {
            return x / y;
        }
    };
    private final String symbol;

    // コンストラクタ
    BasicOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

// 拡張enum型
enum ExtendedOperation implements Operation {
    EXP("^") {
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER("%") {
        public double apply(double x, double y) {
            return x % y;
        }
    };
    private final String symbol;

    // コンストラクタ
    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

// 呼び出し例
class Main {
    public static void main(String[] args) {
        double x = 2.0;
        double y = 4.0;
        text1(BasicOperation.class, x, y);
        text1(ExtendedOperation.class, x, y);
        text2(Arrays.asList(BasicOperation.values()), x, y);
        text2(Arrays.asList(ExtendedOperation.values()), x, y);
    }

    // enumかつOperationのサブタイプとして渡す
    private static <T extends Enum<T> & Operation> void text1(Class<T> opEnumType, double x, double y) {
        for (Operation op : opEnumType.getEnumConstants()) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }

    // 境界ワイルドカード型を使って渡す方法
    private static void text2(Collection<? extends Operation> opSet, double x, double y) {
        for (Operation op : opSet) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }
}
// 実行結果
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000
2.000000 ^ 4.000000 = 16.000000
2.000000 % 4.000000 = 2.000000
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000
2.000000 ^ 4.000000 = 16.000000
2.000000 % 4.000000 = 2.000000

項目39 命名パターンよりもアノテーションを選ぶ

  • メソッド名などに命名パターンによる特別な性質を持たせることは欠点があるため、アノテーションを使うべきである。(例:testSafetyOverrideはtestから始まるのでテストメッド、は悪い例)
  • Testアノテーションの定義

// @Testアノテーションの定義
@Retention(RetentionPolicy.RUNTIME) // Testアノテーションが実行時に保持されるべき
@Target(ElementType.METHOD) // メソッド宣言に対してのみ許される
@interface Test {
}

// テスト対象クラス
class Sample {
    @Test
    public static void m1() {
    }

    public static void m2() {
    }

    @Test
    public static void m3() {
        throw new RuntimeException("Boom");
    }

    public static void m4() {
    }

    @Test
    public static void m5() {
    }

    public static void m6() {
    }

    @Test
    public static void m7() {
        throw new RuntimeException("Crash");
    }

    public static void m8() {
    }
}

/// テストアノテーション処理クラス
class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName("sec39.Sample");
        // テスト対象内の全メソッドをループする
        for (Method m : testClass.getDeclaredMethods()) {
            // @Testアノテーションが付いているか?
            if (m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    // テスト対象メソッド呼び出し
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " failed: " + exc);
                } catch (Exception e) {
                    System.out.println("Invalid @Test: " + m);
                }
            }
        }
        System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
    }
}

// 実行結果
public static void sec39.Sample.m7() failed: java.lang.RuntimeException: Crash
public static void sec39.Sample.m3() failed: java.lang.RuntimeException: Boom
Passed: 2, Failed: 2

項目40 常にOverrideアノテーションを使う

  • オーバーライド時に、@Overrideアノテーションを付けることは、オーバーライドされていなかったことを検知してくれる。
  • @Overrideアノテーションを付けないでオーバーライドしようとすると、IDEは警告を出してくれるので、意図しないオーバーライドを避けることができる。

項目41 型を定義するためにマーカーインタフェースを使う

  • マーカーインタフェースとは、メソッド宣言を含んでいない(空っぽの)インタフェースで、そのインタフェースを実装しているクラスは何らかの特性を持っていることを指定(マーク)する。
  • 関連づけられた新たなメソッドを持たない型を定義したいなら、マーカーインタフェースであるべき。
  • クラスとインタフェース以外のプログラム要素をマークしたい、あるいはアノテーション型をすでに多用しているフレームワーク内にマーカーを適合させたいなら、マーカーアノテーションを選ぶべき。
0
3
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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?