Почему Enum должен реализовывать интерфейс?
Я только что узнал, что Java позволяет перечислениям реализовывать интерфейс. Что было бы хорошим вариантом использования для этого?
17 ответов
Перечисления не просто должны представлять пассивные наборы (например, цвета). Они могут представлять более сложные объекты с функциональностью, и поэтому вы, вероятно, захотите добавить к ним дополнительную функциональность - например, у вас могут быть интерфейсы, такие как Printable
, Reportable
и т.д. и компоненты, которые поддерживают их.
Вот один пример (похожий / лучший пример находится в 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()));
Итак, здесь вы используете интерфейс для имитации расширяемых перечислений (что было бы невозможно без использования интерфейса).
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
}
Есть случай, которым я часто пользуюсь. у меня есть 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);
Поскольку Enums может реализовывать интерфейсы, их можно использовать для строгого обеспечения одноэлементного шаблона. Попытка сделать стандартный класс синглтоном позволяет...
- для возможности использования методов отражения, чтобы выставить частные методы как публичные
- для наследования от вашего синглтона и переопределения методов вашего синглтона чем-то другим
Перечисления как одиночные помогают предотвратить эти проблемы безопасности. Это могло быть одной из причин, по которой Enums выступал в качестве классов и реализовывал интерфейсы. Просто предположение.
См. /questions/24586917/kakov-nailuchshij-podhod-dlya-ispolzovaniya-enum-v-kachestve-singltona-v-java и класс Singleton в Java для более подробного обсуждения.
Это требуется для расширяемости - если кто-то использует разработанный вами API, перечисляемые вами определения являются статическими; они не могут быть добавлены или изменены. Однако, если вы позволите ему реализовать интерфейс, человек, использующий API, может разработать свой собственный enum, используя тот же интерфейс. Затем вы можете зарегистрировать это перечисление в менеджере перечислений, который объединяет перечисления вместе со стандартным интерфейсом.
Редактировать: @Helper Method имеет прекрасный пример этого. Подумайте о том, чтобы другие библиотеки определяли новые операторы, а затем сообщали управляющему классу: "Эй, это перечисление существует - зарегистрируйте его". В противном случае вы сможете определить Операторы только в своем собственном коде - возможности расширения не будет.
В посте выше, в котором упоминались стратегии, не говорилось о том, что выгодно предлагает вам легкая реализация шаблона стратегии с использованием enums:
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();
Наиболее распространенным способом использования этого было бы объединить значения двух перечислений в одну группу и обращаться с ними аналогично. Например, посмотрите, как присоединиться к фруктам и овощам.
Перечисления - это просто замаскированные классы, поэтому по большей части все, что вы можете сделать с классом, вы можете сделать с перечислением.
Я не могу придумать причину, по которой перечисление не может быть в состоянии реализовать интерфейс, и в то же время я не могу придумать для них и вескую причину.
Я бы сказал, что как только вы начнете добавлять такие вещи, как интерфейсы или методы, в перечисление, вам стоит подумать о том, чтобы сделать его классом. Конечно, я уверен, что есть веские случаи для выполнения нетрадиционных перечислений, и, поскольку ограничение будет искусственным, я за то, чтобы люди могли делать то, что они хотят.
Например, если у вас есть перечисление Logger. Тогда у вас должны быть методы logger, такие как отладка, информация, предупреждение и ошибка в интерфейсе. Это делает ваш код слабо связанным.
Один из лучших вариантов использования enum с интерфейсом - фильтры предиката. Это очень элегантный способ исправить отсутствие типичности коллекций 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);
}
}
При создании констант в файле 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();
}
}
Еще одна возможность:
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);
Перечисления похожи на Java-классы, они могут иметь конструкторы, методы и т. Д. Единственное, что вы не можете с ними сделать, это new EnumName()
, Экземпляры предопределены в вашем объявлении enum.
Вот моя причина, почему...
Я заполнил JavaFX ComboBox значениями Enum. У меня есть интерфейс Identifiable (указывающий один метод: идентифицировать), который позволяет мне указать, как любой объект идентифицирует себя в моем приложении для целей поиска. Этот интерфейс позволяет мне сканировать списки объектов любого типа (любое поле, которое объект может использовать для идентификации) для совпадения идентификаторов.
Я хотел бы найти соответствие для значения идентификатора в моем списке ComboBox. Чтобы использовать эту возможность в моем ComboBox, содержащем значения Enum, я должен иметь возможность реализовать интерфейс Identifiable в моем Enum (который, как это бывает, тривиально реализовать в случае Enum).
Я использовал внутреннее перечисление в интерфейсе, описывающем стратегию, чтобы сохранить контроль над экземпляром (каждая стратегия - Singleton) оттуда.
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 выступать в качестве прокси для своего вложенного экземпляра. который также реализует интерфейс.
это своего рода стратегия EnumProxy:P чистый код выглядит так:
VectorizeStrategy.ConcreteStrategy.DEFAULT.processImage(img);
Если бы он не реализовал интерфейс, это было бы:
VectorizeStrategy.ConcreteStrategy.DEFAULT.getInstance().processImage(img);
Да, Enum реализует интерфейс на Java, он может быть полезен, когда нам нужно реализовать некоторую бизнес-логику, тесно связанную с дискриминационным свойством данного объекта или класса.
Взгляните на пример ниже, он прост для понимания и показывает особые случаи операций, которые выполняются только с некоторыми конкретными свойствами перечислений.
interface EnumInterface {
int calculate(int first, int second);
}
enum EnumClassOperator implements EnumInterface {
ADD {
@Override
public int calculate(int first, int second) {
return first + second;
}
},
SUBTRACT {
@Override
public int calculate(int first, int second) {
return first - second;
}
};
}
class Operation {
private int first, second;
private EnumClassOperator operator;
public Operation(int first, int second, EnumClassOperator operator) {
this.first = first;
this.second = second;
this.operator = operator;
}
public int calculate() {
return operator.calculate(first, second);
}
}
// Main Class
public class EnumTest {
public static void main (String [] args) {
Operation add = new Operation(20, 10, EnumClassOperator.ADD);
Operation subtract = new Operation(20, 10, EnumClassOperator.SUBTRACT);
System.out.println("Addition: " + add.calculate());
System.out.println("Subtraction: " + subtract.calculate());
}
}
Вот еще один базовый пример:
// Java program to demonstrate
// how an enum implements an
// interface
// Defining an interface
interface week {
// Defining an abstract method
public int day();
}
// Initializing an enum which
// implements the above interface
enum Day implements week {
// Initializing the possible
// days
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday;
public int day()
{
return ordinal() + 1;
}
}
// Main Class
public class Daycount {
public static void main(String args[])
{
System.out.println("It is day number "
+ Day.Wednesday.day()
+ " of a week.");
}
}