0Pricing
Java Academy · レッスン

enum の抽象メソッド

定数ごとの振る舞いを定義します。

「enum の抽象メソッド」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。

定数ごとの振る舞い

場合によっては、各列挙型定数に異なるデータだけでなく、異なる振る舞いも必要になります。

Javaでは、列挙型に抽象メソッドを宣言し、各定数でそれをオーバーライドできます。これは、定数固有のメソッド本体パターンです。

public class Main {
    enum Operation {
        PLUS { public int apply(int a, int b) { return a + b; } },
        MINUS { public int apply(int a, int b) { return a - b; } };
        public abstract int apply(int a, int b);
    }
    public static void main(String[] args) {
        System.out.println(Operation.PLUS.apply(3, 4));
        System.out.println(Operation.MINUS.apply(10, 6));
    }
}

仕組み

本体を持つ各定数は、列挙型の匿名サブクラスになります。

抽象メソッドによってすべての定数に実装が必要になるため、コンパイラーが実装の完全性を保証します。

public class Main {
    enum Operation {
        TIMES { public int apply(int a, int b) { return a * b; } };
        public abstract int apply(int a, int b);
    }
    public static void main(String[] args) {
        // The constant is an instance of a generated subclass
        System.out.println(Operation.TIMES.getClass().getSimpleName().isEmpty()
            ? "anonymous subclass" : Operation.TIMES.apply(6, 7));
    }
}

完全な電卓

4つの算術演算を定数として定義し、それぞれが独自の apply 本体を持ちます。

public class Main {
    enum Operation {
        PLUS  { public double apply(double a, double b) { return a + b; } },
        MINUS { public double apply(double a, double b) { return a - b; } },
        TIMES { public double apply(double a, double b) { return a * b; } },
        DIVIDE{ public double apply(double a, double b) { return a / b; } };
        public abstract double apply(double a, double b);
    }
    public static void main(String[] args) {
        double x = 6, y = 3;
        for (Operation op : Operation.values()) {
            System.out.printf("%s -> %.1f%n", op, op.apply(x, y));
        }
    }
}

フィールドと振る舞いの組み合わせ

フィールド(記号)と定数ごとの振る舞いを組み合わせられます。フィールドが演算をラベル付けし、メソッドが演算を実行します。

public class Main {
    enum Operation {
        PLUS("+")  { public double apply(double a, double b) { return a + b; } },
        MINUS("-") { public double apply(double a, double b) { return a - b; } };
        private final String symbol;
        Operation(String symbol) { this.symbol = symbol; }
        public abstract double apply(double a, double b);
        @Override public String toString() { return symbol; }
    }
    public static void main(String[] args) {
        Operation op = Operation.PLUS;
        System.out.println("2 " + op + " 3 = " + op.apply(2, 3));
    }
}

switch ではいけない理由

定数に対する単一の switch で apply を実装することもできますが、抽象メソッドを使う方法のほうが安全です。

後から定数を追加すると、コンパイラーによってその本体の実装を強制されます。switch では新しいケースを見落としても、気付かないままになる可能性があります。

public class Main {
    enum Op {
        SQUARE { public int apply(int n) { return n * n; } },
        CUBE   { public int apply(int n) { return n * n * n; } };
        public abstract int apply(int n);
    }
    public static void main(String[] args) {
        System.out.println(Op.SQUARE.apply(5));
        System.out.println(Op.CUBE.apply(3));
    }
}

抽象メソッドと具象メソッドの併用

列挙型では、抽象メソッドと具象メソッドを組み合わせられます。共通するロジックは具象メソッドに置き、変化するロジックは抽象メソッドに残します。

public class Main {
    enum Shape {
        CIRCLE { public double area(double x) { return Math.PI * x * x; } },
        SQUARE { public double area(double x) { return x * x; } };
        public abstract double area(double x);
        public String report(double x) { return this + " area = " + area(x); }
    }
    public static void main(String[] args) {
        System.out.println(Shape.CIRCLE.report(2));
    }
}

ストラテジー列挙型

発展的な方法としてストラテジー列挙型があります。定数が直接オーバーライドするのではなく、ネストしたストラテジー列挙型に処理を委譲します。

複数の定数で同じ振る舞いを共有する場合に便利です。この例では、平日と週末で給与の計算方法が異なります。

public class Main {
    enum PayType {
        WEEKDAY { int overtime(int mins) { return mins; } },
        WEEKEND { int overtime(int mins) { return mins * 2; } };
        abstract int overtime(int mins);
        int pay(int mins) { return mins + overtime(Math.max(0, mins - 480)); }
    }
    public static void main(String[] args) {
        System.out.println("Weekend pay: " + PayType.WEEKEND.pay(540));
    }
}

インターフェースと本体を持つ列挙型

インターフェースと定数固有の本体を組み合わせることで、最大限のポリモーフィズムを実現できます。

public class Main {
    interface Command { String run(); }
    enum Action implements Command {
        START { public String run() { return "starting..."; } },
        STOP  { public String run() { return "stopping..."; } };
    }
    public static void main(String[] args) {
        for (Action a : Action.values()) System.out.println(a.run());
    }
}

本体からのフィールドへのアクセス

定数固有の本体は、その定数自身のフィールドを読み取れます。各本体は、そのインスタンスのコンテキストで実行されるためです。

public class Main {
    enum Tax {
        STANDARD(0.20) { double apply(double amt) { return amt * (1 + rate); } },
        REDUCED(0.05)  { double apply(double amt) { return amt * (1 + rate); } };
        protected final double rate;
        Tax(double rate) { this.rate = rate; }
        abstract double apply(double amt);
    }
    public static void main(String[] args) {
        System.out.printf("%.2f%n", Tax.STANDARD.apply(100));
    }
}

デフォルトとオーバーライド

列挙型本体に具象的なデフォルト実装を用意し、異なる定数に対してだけオーバーライドできます。

public class Main {
    enum Notifier {
        EMAIL,
        SMS { String send(String m) { return "SMS: " + m; } };
        String send(String m) { return "EMAIL: " + m; }
    }
    public static void main(String[] args) {
        System.out.println(Notifier.EMAIL.send("hi"));
        System.out.println(Notifier.SMS.send("hi"));
    }
}

小さなステートマシン

定数ごとの振る舞いは、ステートマシンで特に効果を発揮します。各状態が、自身の遷移を把握できます。

public class Main {
    enum State {
        IDLE    { State next() { return RUNNING; } },
        RUNNING { State next() { return DONE; } },
        DONE    { State next() { return DONE; } };
        abstract State next();
    }
    public static void main(String[] args) {
        State s = State.IDLE;
        for (int i = 0; i < 3; i++) { System.out.println(s); s = s.next(); }
        System.out.println(s);
    }
}

クイックチェック

列挙型の抽象メソッドについての理解度を確認しましょう。

まとめ

定数ごとの振る舞いについて学びました。

  • 抽象メソッドを宣言し、各定数の本体でオーバーライドします。
  • 各定数は匿名サブクラスになります。
  • コンパイラーが実装の完全性を強制するため、switch よりも安全です。
  • フィールド、具象的なデフォルト実装、インターフェースを組み合わせて、柔軟な列挙型を作れます。

次は、高性能な EnumSet です。

public class Main {
    enum E { A { int v() { return 1; } }; abstract int v(); }
    public static void main(String[] args) {
        System.out.println("Abstract enum recap: " + E.A.v());
    }
}

よくある質問

「enum の抽象メソッド」レッスンは無料ですか?

はい。「enum の抽象メソッド」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。

「enum の抽象メソッド」で何を学びますか?

定数ごとの振る舞いを定義します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Java Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「enum の抽象メソッド」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このJava Academyレッスンでコードを書いて実行できますか?

はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. フィールドとメソッドを持つ enum
  2. enum の抽象メソッド
  3. EnumSet
  4. EnumMap
← Java Academyに戻る