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



Я только что узнал, что Java позволяет перечислениям реализовать интерфейс. Что было бы хорошим прецедентом для этого?

661   14  

14 ответов:

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

вот один пример (аналогичный / лучший из них находится в эффективном 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()));

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

The 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
}

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

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

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

см.https://stackoverflow.com/questions/427902/java-enum-singleton и одноэлементный класс в java для более детального обсуждения.

есть случай, который я часто использую. У меня есть 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;
    }
}

если я создам Identifiableenum:

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

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

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

например, если у вас есть регистратор перечисление. Затем вы должны иметь методы регистратора, такие как debug, info, warning и error в интерфейсе. Это делает ваш код слабо связанным.

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

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

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

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

один из лучших вариантов использования для меня, чтобы использовать перечисления с интерфейсом является предикат фильтры. Это очень элегантный способ исправить отсутствие типичности коллекций 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);
    }
}

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

вот моя причина, почему ...

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

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

другая возможность:

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

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

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

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

это своего рода strategyEnumProxy :P код clent выглядит так:

VectorizeStrategy.ConcreteStrategy.DEFAULT.processImage(img);

если он не реализовал интерфейс, который у него был было:

VectorizeStrategy.ConcreteStrategy.DEFAULT.getInstance().processImage(img);

Comments

    Ничего не найдено.