Почему Enum реализует интерфейс?


Ответы:


131

Перечисления не должны просто представлять пассивные наборы (например, цвета). Они могут представлять более сложные объекты с функциональностью, поэтому вы, вероятно, захотите добавить к ним дополнительные функции - например, у вас могут быть интерфейсы, такие как Printableи Reportableт. Д., И компоненты, которые их поддерживают.


263

Вот один пример (похожий / лучший находится в Effective Java 2nd Edition):

public interface Operator {
    int apply (int a, int b);
}

public enum SimpleOperators implements Operator {
    PLUS { 
        int apply(int a, int b) { return a + b; }
    },
    MINUS { 
        int apply(int a, int b) { return a - b; }
    };
}

public enum ComplexOperators implements Operator {
    // can't think of an example right now :-/
}

Теперь, чтобы получить список простых и сложных операторов:

List<Operator> operators = new ArrayList<Operator>();

operators.addAll(Arrays.asList(SimpleOperators.values()));
operators.addAll(Arrays.asList(ComplexOperators.values()));

Итак, здесь вы используете интерфейс для имитации расширяемых перечислений (что было бы невозможно без использования интерфейса).


3
Сложные операторы могут быть "pow" и т.п.
Pimgd

2
Впервые увидел синтаксис Enum (с фигурными скобками после членов), и я программировал на Java уже 6 лет. TIL.
jrahhali

23

ComparableПример , приведенный несколько людей здесь не так, так какEnum как уже реализует это. Вы даже не можете отменить это.

Лучшим примером является интерфейс, который определяет, скажем, тип данных. У вас может быть перечисление для реализации простых типов и обычные классы для реализации сложных типов:

interface DataType {
  // methods here
}

enum SimpleDataType implements DataType {
  INTEGER, STRING;

  // implement methods
}

class IdentifierDataType implements DataType {
  // implement interface and maybe add more specific methods
}

2
Да, странно !! Enum уже реализует интерфейс Comparable и не позволяет переопределить. Я просмотрел исходный код java для класса enum и не смог найти реализацию compareTo. Может ли кто-нибудь сказать мне, что сравнивается внутри метода compareTo ENUM?
Akh

2
@AKh он сравнивает порядковые номера, то есть естественный порядок - это порядок, в котором константы перечисления находятся в исходном файле.
Йорн

Перечислимые переменные являются окончательными, поэтому сравнение происходит с помощью ==, верно?
djangofan

@djangofan да.
Йорн

18

Есть чехол, которым я часто пользуюсь. У меня есть IdUtilкласс со статическими методами для работы с объектами, реализующими очень простой Identifiableинтерфейс:

public interface Identifiable<K> {
    K getId();
}


public abstract class IdUtil {

    public static <T extends Enum<T> & Identifiable<S>, S> T get(Class<T> type, S id) {
        for (T t : type.getEnumConstants()) {
            if (Util.equals(t.getId(), id)) {
                return t;
            }
        }
        return null;
    }

    public static <T extends Enum<T> & Identifiable<S>, S extends Comparable<? super S>> List<T> getLower(T en) {
        List<T> list = new ArrayList<>();
        for (T t : en.getDeclaringClass().getEnumConstants()) {
             if (t.getId().compareTo(en.getId()) < 0) {
                 list.add(t);
            }
        }
        return list;
    }
}

Если я создам Identifiable enum:

    public enum MyEnum implements Identifiable<Integer> {

        FIRST(1), SECOND(2);

        private int id;

        private MyEnum(int id) {
            this.id = id;
        }

        public Integer getId() {
            return id;
        }

    }

Тогда я могу получить это idследующим образом:

MyEnum e = IdUtil.get(MyEnum.class, 1);

Именно то, что мне нужно!
Петронелла

13

Поскольку Enums могут реализовывать интерфейсы, их можно использовать для строгого соблюдения одноэлементного шаблона. Попытка сделать стандартный класс singleton позволяет ...

  • для возможности использования методов отражения для предоставления частных методов как общедоступных
  • для наследования от вашего синглтона и переопределения методов вашего синглтона чем-то еще

Перечисления как синглтоны помогают предотвратить эти проблемы с безопасностью. Это могло быть одной из причин, по которым Enums могли действовать как классы и реализовывать интерфейсы. Просто догадка.

См. Https://stackoverflow.com/questions/427902/java-enum-singleton и класс Singleton в java для более подробного обсуждения.


1
for inheriting from your singleton and overriding your singleton's methods with something else. вы можете просто использовать a, final classчтобы предотвратить это
Dylanthepiguy

10

Это требуется для расширяемости - если кто-то использует разработанный вами API, перечисления, которые вы определяете, являются статическими; они не могут быть добавлены или изменены. Однако, если вы позволите ему реализовать интерфейс, человек, использующий API, сможет разработать собственное перечисление, используя тот же интерфейс. Затем вы можете зарегистрировать это перечисление с помощью диспетчера перечислений, который объединяет перечисления вместе со стандартным интерфейсом.

Изменить: @Helper Method имеет прекрасный пример этого. Подумайте о том, чтобы другие библиотеки определяли новые операторы, а затем сообщали классу менеджера, что «эй, это перечисление существует - зарегистрируйте его». В противном случае вы могли бы определять операторы только в своем собственном коде - расширяемости не было бы.


7

Перечисления - это просто замаскированные классы, поэтому по большей части все, что вы можете делать с классом, вы можете делать с помощью перечисления.

Я не могу придумать причину, по которой перечисление не может реализовывать интерфейс, в то же время я не могу придумать для них веской причины.

Я бы сказал, что как только вы начнете добавлять в перечисление такие вещи, как интерфейсы или метод, вам действительно стоит подумать о том, чтобы вместо этого сделать его классом. Конечно, я уверен, что есть допустимые случаи для выполнения нетрадиционных вещей enum, и, поскольку ограничение будет искусственным, я за то, чтобы люди могли делать там то, что они хотят.


4
«... так что по большей части все, что вы можете сделать с классом, вы можете сделать с помощью перечисления», но я не могу сказать, что мое перечисление наследуется от абстрактного класса. Так что да, акцент на «по большей части».
Адам Паркин

5
Это связано с тем, что все перечисления расширяют java.lang.Enum, а классы Java могут расширяться только от одного класса.
TofuBeer

6

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



6

В сообщении выше, в котором упомянутые стратегии, недостаточно подчеркивается, что дает вам хорошая легкая реализация шаблона стратегии с использованием перечислений:

public enum Strategy {

  A {
    @Override
    void execute() {
      System.out.print("Executing strategy A");
    }
  },

  B {
    @Override
    void execute() {
      System.out.print("Executing strategy B");
    }
  };

  abstract void execute();
}

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

Strategy.valueOf("A").execute();

Делает java читаемым почти как приятный слабо типизированный язык!


5

Один из лучших вариантов использования enum с интерфейсом - фильтры Predicate. Это очень элегантный способ исправить недостаточную типичность коллекций apache (если другие библиотеки нельзя использовать).

import java.util.ArrayList;
import java.util.Collection;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;


public class Test {
    public final static String DEFAULT_COMPONENT = "Default";

    enum FilterTest implements Predicate {
        Active(false) {
            @Override
            boolean eval(Test test) {
                return test.active;
            }
        },
        DefaultComponent(true) {
            @Override
            boolean eval(Test test) {
                return DEFAULT_COMPONENT.equals(test.component);
            }
        }

        ;

        private boolean defaultValue;

        private FilterTest(boolean defautValue) {
            this.defaultValue = defautValue;
        }

        abstract boolean eval(Test test);

        public boolean evaluate(Object o) {
            if (o instanceof Test) {
                return eval((Test)o);
            }
            return defaultValue;
        }

    }

    private boolean active = true;
    private String component = DEFAULT_COMPONENT;

    public static void main(String[] args) {
        Collection<Test> tests = new ArrayList<Test>();
        tests.add(new Test());

        CollectionUtils.filter(tests, FilterTest.Active);
    }
}

3

Перечисления похожи на классы Java, они могут иметь конструкторы, методы и т. Д. Единственное, что вы не можете с ними делать - это new EnumName(). Экземпляры предопределены в вашем объявлении перечисления.


О чем enum Foo extends SomeOtherClass? Так что это не совсем то же самое, что и обычный класс, на самом деле совсем другое.
Адам Паркин

@ Адам: Я бы сказал, что способы, которыми Enums похожи на классы, намного более многочисленны, чем способы, которыми они отличаются от классов. Но нет, они не идентичны.
Скотт

если вы декомпилируете перечисление, вы увидите, что это класс, расширяющий Enumeration и имеющий «константы», уже созданные в полях конструктора / инициализации.
Cosmin Cosmin

3

Еще одна возможность:

public enum ConditionsToBeSatisfied implements Predicate<Number> {
    IS_NOT_NULL(Objects::nonNull, "Item is null"),
    IS_NOT_AN_INTEGER(item -> item instanceof Integer, "Item is not an integer"),
    IS_POSITIVE(item -> item instanceof Integer && (Integer) item > 0, "Item is negative");

    private final Predicate<Number> predicate;
    private final String notSatisfiedLogMessage;

    ConditionsToBeSatisfied(final Predicate<Number> predicate, final String notSatisfiedLogMessage) {
        this.predicate = predicate;
        this.notSatisfiedLogMessage = notSatisfiedLogMessage;
    }

    @Override
    public boolean test(final Number item) {
        final boolean isNotValid = predicate.negate().test(item);

        if (isNotValid) {
            log.warn("Invalid {}. Cause: {}", item, notSatisfiedLogMessage);
        }

        return predicate.test(item);
    }
}

и используя:

Predicate<Number> p = IS_NOT_NULL.and(IS_NOT_AN_INTEGER).and(IS_POSITIVE);

3

При создании констант в файле jar часто бывает полезно разрешить пользователям расширять значения перечисления. Мы использовали перечисления для ключей PropertyFile и застряли, потому что никто не мог добавить новые! Ниже сработало бы намного лучше.

Дано:

public interface Color {
  String fetchName();
}

а также:

public class MarkTest {

  public static void main(String[] args) {
    MarkTest.showColor(Colors.BLUE);
    MarkTest.showColor(MyColors.BROWN);
  }

  private static void showColor(Color c) {
    System.out.println(c.fetchName());
  }
}

в банке могло быть одно перечисление:

public enum Colors implements Color {
  BLUE, RED, GREEN;
  @Override
  public String fetchName() {
    return this.name();
  }
}

и пользователь мог расширить его, добавив свои собственные цвета:

public enum MyColors implements Color {
  BROWN, GREEN, YELLOW;
  @Override
  public String fetchName() {
    return this.name();
  }
}

2

Вот почему ...

Я заполнил JavaFX ComboBox значениями Enum. У меня есть интерфейс Identifiable (с указанием одного метода: identify), который позволяет мне указать, как любой объект идентифицирует себя для моего приложения в целях поиска. Этот интерфейс позволяет мне сканировать списки объектов любого типа (какое бы поле объект ни использовал для идентификации) на предмет совпадения.

Я хотел бы найти соответствие значению идентичности в моем списке ComboBox. Чтобы использовать эту возможность в моем ComboBox, содержащем значения Enum, я должен иметь возможность реализовать интерфейс Identifiable в моем Enum (который, как оказалось, тривиально реализовать в случае Enum).


1

Я использовал внутреннее перечисление в интерфейсе, описывающем стратегию сохранения управления экземпляром (каждая стратегия - это синглтон) оттуда.

public interface VectorizeStrategy {

    /**
     * Keep instance control from here.
     * 
     * Concrete classes constructors should be package private.
     */
    enum ConcreteStrategy implements VectorizeStrategy {
        DEFAULT (new VectorizeImpl());

        private final VectorizeStrategy INSTANCE;

        ConcreteStrategy(VectorizeStrategy concreteStrategy) {
            INSTANCE = concreteStrategy;
        }

        @Override
        public VectorImageGridIntersections processImage(MarvinImage img) {
            return INSTANCE.processImage(img);
        }
    }

    /**
     * Should perform edge Detection in order to have lines, that can be vectorized.
     * 
     * @param img An Image suitable for edge detection.
     * 
     * @return the VectorImageGridIntersections representing img's vectors 
     * intersections with the grids.
     */
    VectorImageGridIntersections processImage(MarvinImage img);
}

Тот факт, что перечисление реализует стратегию, удобно, позволяя классу перечисления действовать как прокси для его вложенного экземпляра. который также реализует интерфейс.

это своего рода стратегия EnumProxy: P чистый код выглядит так:

VectorizeStrategy.ConcreteStrategy.DEFAULT.processImage(img);

Если бы он не реализовал интерфейс, это было бы:

VectorizeStrategy.ConcreteStrategy.DEFAULT.getInstance().processImage(img);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.