¿Por qué un Enum implementaría una interfaz?

Respuestas:

130

Las enumeraciones no solo tienen que representar conjuntos pasivos (por ejemplo, colores). Pueden representar objetos más complejos con funcionalidad y, por lo tanto, es probable que desee agregar más funcionalidad a estos, por ejemplo, puede tener interfaces como Printable, Reportableetc. y componentes que los admitan.

Brian Agnew
fuente
259

Aquí hay un ejemplo (uno similar / mejor se encuentra en 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 :-/
}

Ahora para obtener una lista de los operadores simples + complejos:

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

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

Entonces, aquí usa una interfaz para simular enumeraciones extensibles (lo que no sería posible sin usar una interfaz).

método de ayuda
fuente
3
Los operadores complejos pueden ser "pow" y similares.
Pimgd
2
La primera vez que veo esa sintaxis de Enum (con las llaves después de los miembros), y he estado programando con Java durante 6 años. TIL
jrahhali
23

El Comparableejemplo dado por varias personas aquí es incorrecto, ya que Enumya lo implementa. Ni siquiera puedes anularlo.

Un mejor ejemplo es tener una interfaz que defina, digamos, un tipo de datos. Puede tener una enumeración para implementar los tipos simples y tener clases normales para implementar tipos complicados:

interface DataType {
  // methods here
}

enum SimpleDataType implements DataType {
  INTEGER, STRING;

  // implement methods
}

class IdentifierDataType implements DataType {
  // implement interface and maybe add more specific methods
}
Jorn
fuente
2
Si, raro !! Enum ya implementa la interfaz comparable y no permite anular. Busqué el código fuente de Java para la clase enum y no pude encontrar la implementación compareTo. ¿Alguien puede decirme qué se compara dentro de un método compareTo un ENUM?
AKh
2
@AKh compara los ordinales, lo que significa que el orden natural es el orden de las constantes enum en el archivo fuente.
Jorn
Los enum vars son finales y, por lo tanto, la comparación ocurre con ==, ¿verdad?
djangofan
@djangofan sí.
Jorn
17

Hay un caso que uso a menudo. Tengo una IdUtilclase con métodos estáticos para trabajar con objetos implementando una Identifiableinterfaz muy simple :

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

Si creo un 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;
        }

    }

Entonces puedo obtenerlo de idesta manera:

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

Dado que Enums puede implementar interfaces, se pueden usar para aplicar estrictamente el patrón singleton. Intentar hacer de una clase estándar un singleton permite ...

  • para la posibilidad de usar técnicas de reflexión para exponer métodos privados como públicos
  • para heredar de su singleton y anular los métodos de su singleton con otra cosa

Las enumeraciones como singletons ayudan a prevenir estos problemas de seguridad. Esta podría haber sido una de las razones contribuyentes para permitir que Enums actúe como clases e implemente interfaces. Solo una suposición.

Vea /programming/427902/java-enum-singleton y la clase Singleton en java para más discusión.

Tansir1
fuente
1
for inheriting from your singleton and overriding your singleton's methods with something else. puedes usar un final classpara evitar eso
Dylanthepiguy
10

Es necesario para la extensibilidad: si alguien usa una API que ha desarrollado, las enumeraciones que defina son estáticas; no se pueden agregar ni modificar. Sin embargo, si deja que implemente una interfaz, la persona que usa la API puede desarrollar su propia enumeración usando la misma interfaz. Luego puede registrar esta enumeración con un administrador de enumeraciones que conglomera las enumeraciones junto con la interfaz estándar.

Editar: @Helper Method tiene el ejemplo perfecto de esto. Piense en tener otras bibliotecas que definan nuevos operadores y luego decirle a una clase de administrador que 'oye, esta enumeración existe, regístrala'. De lo contrario, solo sería capaz de definir Operadores en su propio código; no habría extensibilidad.

Chris Dennett
fuente
7

Las enumeraciones son solo clases disfrazadas, por lo que, en su mayor parte, todo lo que puede hacer con una clase puede hacerlo con una enumeración.

No puedo pensar en una razón por la cual una enumeración no debería poder implementar una interfaz, al mismo tiempo, tampoco puedo pensar en una buena razón para que lo hagan.

Yo diría que una vez que comience a agregar cosas como interfaces o métodos a una enumeración, debería considerar convertirla en una clase. Por supuesto, estoy seguro de que hay casos válidos para hacer cosas de enumeración no tradicionales, y dado que el límite sería artificial, estoy a favor de dejar que las personas hagan lo que quieran allí.

TofuBeer
fuente
44
"... así que, en su mayor parte, todo lo que puedes hacer con una clase lo puedes hacer con una enumeración", sin embargo, no puedo decir que mi enumeración herede de una clase abstracta. Así que sí, énfasis en "en su mayor parte".
Adam Parkin el
55
Esto se debe a que todas las enumeraciones extienden java.lang.Enum, y las clases Java solo pueden extenderse desde una clase.
TofuBeer
6

Por ejemplo, si tiene una enumeración Logger. Entonces debe tener los métodos de registro como la depuración, información, advertencia y error en la interfaz. Hace que su código esté débilmente acoplado.

Johan
fuente
6

El uso más común para esto sería fusionar los valores de dos enumeraciones en un grupo y tratarlos de manera similar. Por ejemplo, vea cómo unir Frutas y Verduras .

husayt
fuente
5

Uno de los mejores casos de uso para que yo use enum con interfaz es Filtros de predicados. Es una forma muy elegante de remediar la falta de tipicidad de las colecciones de apache (si no se pueden usar otras bibliotecas).

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);
    }
}
Anton Bessonov
fuente
5

La publicación anterior que mencionó las estrategias no enfatizó lo suficiente lo que una buena implementación liviana del patrón de estrategia usando enumeraciones le brinda:

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

Puede tener todas sus estrategias en un lugar sin necesidad de una unidad de compilación separada para cada una. Obtiene un buen envío dinámico solo con:

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

¡Hace que Java se lea casi como un lenguaje agradable escrito a máquina!

Robert Moskal
fuente
3

Las enumeraciones son como las clases de Java, pueden tener constructores, métodos, etc. Lo único que no puede hacer con ellas es new EnumName(). Las instancias están predefinidas en su declaración de enumeración.

Cosmin Cosmin
fuente
¿Qué hay de enum Foo extends SomeOtherClass? Entonces, no es lo mismo que una clase regular, de hecho, es bastante diferente.
Adam Parkin el
@ Adam: Diría que las formas en que Enums se parecen a las clases son mucho más numerosas que las formas en que difieren de las clases. Pero no, no son idénticos.
scottb
Si descompila una enumeración, verá que es una clase que extiende la enumeración y tiene las "constantes" ya instanciadas en los campos de constructor / inicialización.
Cosmin Cosmin
3

Otra posibilidad:

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

y usando:

Predicate<Number> p = IS_NOT_NULL.and(IS_NOT_AN_INTEGER).and(IS_POSITIVE);
Jorge Aranda
fuente
3

Al crear constantes en un archivo jar, a menudo es útil permitir que los usuarios extiendan los valores de enumeración. ¡Usamos enumeraciones para las claves de PropertyFile y nos atascamos porque nadie podía agregar ninguna nueva! A continuación habría funcionado mucho mejor.

Dado:

public interface Color {
  String fetchName();
}

y:

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

uno podría tener una enumeración en el frasco:

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

y un usuario podría extenderlo para agregar sus propios colores:

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

Aquí está mi razón por la cual ...

He rellenado un JavaFX ComboBox con los valores de una enumeración. Tengo una interfaz, Identificable (que especifica un método: identificación), que me permite especificar cómo se identifica cualquier objeto en mi aplicación con fines de búsqueda. Esta interfaz me permite escanear listas de cualquier tipo de objeto (cualquier campo que el objeto pueda usar para identidad) para una coincidencia de identidad.

Me gustaría encontrar una coincidencia para un valor de identidad en mi lista de ComboBox. Para utilizar esta capacidad en mi ComboBox que contiene los valores de Enum, debo poder implementar la interfaz identificable en mi Enum (que, como sucede, es trivial de implementar en el caso de un Enum).

scottb
fuente
1

Usé una enumeración interna en una interfaz que describe una estrategia para mantener el control de la instancia (cada estrategia es un Singleton) a partir de ahí.

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

El hecho de que el enum implemente la estrategia es conveniente para permitir que la clase enum actúe como proxy de su instancia adjunta. que también implementa la interfaz.

es una especie de estrategia EnumProxy: P el código clent se ve así:

VectorizeStrategy.ConcreteStrategy.DEFAULT.processImage(img);

Si no implementara la interfaz hubiera sido:

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