0Pricing
Java Academy · Урок

Абстрактные методы в перечислениях

Задавайте поведение для каждой константы

«Абстрактные методы в перечислениях» — бесплатный урок Java Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Поведение для каждой константы

Иногда каждой константе перечисления требуется собственное поведение, а не только собственные данные.

Java позволяет объявить в перечислении метод abstract и переопределить его для каждой константы. Это шаблон тела метода, специфичного для константы.

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));
    }
}

Как это работает

Каждая константа с телом становится анонимным подклассом перечисления.

Метод abstract требует, чтобы каждая константа предоставила реализацию, поэтому компилятор гарантирует полноту.

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));
    }
}

Полноценный калькулятор

Четыре арифметические операции представлены константами, и у каждой есть собственное тело 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));
    }
}

Почему бы не использовать оператор выбора

Можно реализовать apply с помощью одного оператора выбора по константе, но подход с методом abstract безопаснее.

Если позднее добавить константу, компилятор заставит Вас предоставить для неё тело. Оператор выбора может незаметно не учитывать новый вариант.

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));
    }
}

Конкретные методы и методы abstract

В перечислении можно сочетать методы abstract и конкретные методы. Общую логику следует помещать в конкретный метод, а изменяющуюся оставлять в методе abstract.

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);
    }
}

Быстрая проверка

Проверьте, насколько хорошо Вы поняли методы abstract в перечислениях.

Итоги

Вы изучили поведение для каждой константы:

  • Объявляйте метод abstract и переопределяйте его в теле каждой константы.
  • Каждая константа становится анонимным подклассом.
  • Компилятор обеспечивает полноту, что надёжнее оператора выбора.
  • Сочетайте поля, конкретные реализации по умолчанию и интерфейсы для создания функциональных перечислений.

Далее — высокопроизводительный 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());
    }
}

Часто задаваемые вопросы

Урок «Абстрактные методы в перечислениях» бесплатный?

Да — полный текст урока «Абстрактные методы в перечислениях» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «Абстрактные методы в перечислениях»?

Задавайте поведение для каждой константы Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Абстрактные методы в перечислениях»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Перечисления с полями и методами
  2. Абстрактные методы в перечислениях
  3. EnumSet
  4. EnumMap
← Назад к Java Academy