with values valueof type mkyong example enum define java enums if-statement code-design

values - java enum valueof



Múltiple If-else o enum, ¿cuál es preferible y por qué? (14)

Aquí está el código original:

public class FruitGrower { public void growAFruit(String type) { if ("wtrmln".equals(type)) { //do watermelon growing stuff } else if ("ppl".equals(type)) { //do apple growing stuff } else if ("pnppl".equals(type)) { //do pineapple growing stuff } else if ("rng".equals(type)) { //do orange growing stuff } else { // do other fruit growing stuff } } }

Así es como lo cambié:

public class FruitGrower { enum Fruits { WATERMELON { @Override void growAFruit() { //do watermelon growing stuff } }, APPLE { @Override void growAFruit() { //do apple growing stuff } }, PINEAPPLE { @Override void growAFruit() { //do pineapple growing stuff } }, ORANGE { @Override void growAFruit() { //do orange growing stuff } }, OTHER { @Override void growAFruit() { // do other fruit growing stuff } }; static void grow(String type) { if ("wtrmln".equals(type)) { WATERMELON.growAFruit(); } else if ("ppl".equals(type)) { APPLE.growAFruit(); } else if ("pnppl".equals(type)) { PINEAPPLE.growAFruit(); } else if ("rng".equals(type)) { ORANGE.growAFruit(); } else { OTHER.growAFruit(); } }; abstract void growAFruit(); } public void growAFruit(String type) { Fruits.grow(type); } }

Veo que el código de enums es más largo y puede no ser tan claro como el código if-else , pero creo que es mejor, ¿podría alguien decirme por qué estoy equivocado (o tal vez no)?

UPD: cambió el código fuente para que sea más específico del problema. Volveré a formular la pregunta: ¿hay alguna preocupación sobre el uso de enum en lugar de if-else?


Ya has obtenido buenas respuestas sobre cómo mejorar tu uso de los Enums. En cuanto a por qué son mejores que las constantes de cadena:

Creo que el mayor beneficio es la comprobación de errores en tiempo de compilación . Si growAFruit("watermelon") llamar a growAFruit("watermelon") , el compilador no tendría idea de que algo estaba mal. Y como lo deletreé correctamente, no va a sobresalir como un error cuando estás viendo el código. Pero si fuera a WATERMELEN.growAFruit() , el compilador puede decirle inmediatamente que lo he escrito mal.

También puedes definir growAFruit como un conjunto de métodos simples y fáciles de leer, en lugar de un gran bloque de if - then - else s. Esto se hace aún más evidente cuando tienes unas pocas docenas de frutas, o cuando comienzas a agregar harvestAFruit() , packageAFruit() , sellAFruit() , etc. Sin enums estarías copiando tu gran bloque if-else por todas partes, y si se olvidó de agregar un caso, podría caer en el caso predeterminado o no hacer nada, mientras que con las enumeraciones, el compilador puede decirle que el método no se ha implementado.

Aún más bondad de comprobación del compilador: si también tiene un método growVegetable y las constantes de cadena relacionadas, no hay nada que le growVegetable("pineapple") llamar a growVegetable("pineapple") o growFruit("peas") . Si tiene una constante de "tomato" , la única forma de saber si la considera una fruta o un vegetal es leer el código fuente de los métodos pertinentes. Una vez más, con enumeraciones, el compilador puede decirle de inmediato si ha hecho algo mal.

Otro beneficio es que agrupa las constantes relacionadas entre sí y les proporciona un hogar adecuado . La alternativa es un conjunto de campos public static final arrojados en alguna clase que los usa, o que tienen una interfaz de constantes. La interfaz llena de constantes ni siquiera tiene sentido, porque si eso es todo lo que necesita para definir la enumeración es mucho más fácil que escribir la interfaz. Además, en clases o interfaces existe la posibilidad de usar accidentalmente el mismo valor para más de una constante.

También son iterables . Para obtener todos los valores de una enumeración, simplemente puede llamar a Fruit.values() , mientras que con las constantes deberá crear y completar su propia matriz. O si solo usa literales como en su ejemplo, no hay una lista autoritaria de valores válidos.

Ronda de bonificación: IDE Support

  • Con una enumeración, puede usar la función de autocompletado de su IDE y refactorizaciones automatizadas
  • Puede usar cosas como "Buscar referencias" en Eclipse con valores enum, mientras que debería hacer una búsqueda de texto plano para encontrar literales de cadena, que generalmente también devolverá muchos falsos positivos (evento si usa constantes finales estáticas) , alguien podría haber usado la cadena literal en alguna parte)

La razón principal para no usar una enumeración sería si no conoce todos los valores posibles en tiempo de compilación (es decir, necesita agregar más valores mientras el programa se está ejecutando). En ese caso, es posible que desee definirlos como una jerarquía de clases. Además, no arroje un montón de constantes no relacionadas en una enumeración y llámela un día. Debería haber algún tipo de hilo común que conecte los valores. Siempre puedes hacer múltiples enumeraciones si es más apropiado.


En segundo lugar, Sean Patrick Floyd en que las enumeraciones son el camino a seguir, pero me gustaría añadir que se puede acortar el evento de código de forma más espectacular mediante el uso de un esquema como este:

enum Fruits { WATERMELON("watermelon fruit"), APPLE("apple fruit"); //... private final String gimme; private Fruits(String gimme) { this.gimme = gimme; } String gimmeFruit() { return this.gimme; } }

Además, el método de "crecimiento" es sospechoso. ¿No debería ser algo así como

public static String grow(Fruits f) { return f.gimmeFruit(); }


Los mensajes son el camino a seguir, pero puedes mejorar tu código de esta manera:

public static String grow(String type) { return Fruits.valueOf(type.toUpperCase()).gimmeFruit(); };

Oh, necesitas un caso predeterminado, eso lo hace un poco más difícil. Por supuesto que puedes hacer esto:

public static String grow(String type) { try{ return Fruits.valueOf(type.toUpperCase()).gimmeFruit(); }catch(IllegalArgumentException e){ return Fruits.OTHER.gimmeFruit(); } };

Pero eso es bastante feo. Supongo que tendría algo como esto:

public static String grow(String type) { Fruits /*btw enums should be singular */ fruit = Fruits.OTHER; for(Fruits candidate : Fruits.values()){ if(candidate.name().equalsIgnoreCase(type)){ fruit = candidate; break; } } return fruit.gimmeFruit(); };

Además, si todos sus métodos enum lo hacen es devolver un valor, debe refactorizar su diseño para que inicialice los valores en un constructor y los devuelva en un método definido en la clase Enum, no en los elementos individuales:

public enum Fruit{ WATERMELON("watermelon fruit"), APPLE("apple fruit") // etc. ; private final String innerName; private Fruit(String innerName){ this.innerName = innerName; } public String getInnerName(){ return this.innerName; } }


Solo has realizado la mitad de los cambios para estar más limpio. El método de crecimiento debe cambiarse así:

static String grow(Fruits type) { return type.gimmeFruit(); }

Y los Fruits deben ser renombrados como Fruit : una manzana es una fruta, no una fruta.

Si realmente necesita conservar sus tipos de cadenas, defina un método (por ejemplo, en la clase enum) que devuelva la fruta asociada a cada tipo. Pero la mayor parte del código debería usar Fruit en lugar de String.


También puede mejorarlo usando una variable para almacenar el valor de gimmeFruit e iniciar con el constructor.

(No he compilado esto así que puede haber algunos errores de sintaxis)

public class FruitGrower { enum Fruits { WATERMELON("watermelon fruit"), APPLE("apple fruit"), PINEAPPLE("pineapple fruit"), ORANGE("orange fruit"), OTHER("other fruit") private String gimmeStr; private Fruits(String gimmeText) { gimmeStr = gimmeText; } public static String grow(String type) { return Fruits.valueOf(type.toUpperCase()).gimmeFruit(); } public String gimmeFruit(String type) { return gimmeStr; } } }

EDITAR: Si el tipo para el método de crecimiento no es la misma cadena, entonces use un Mapa para definir las coincidencias de tipo con Enum y devuelva la búsqueda del mapa.


Creo que quieres un Map<String, Fruit> (o <String, FruitGrower> ).

Este mapa podría ser llenado automáticamente por los constructores de la enumeración, o por un inicializador estático. (Incluso podría mapear múltiples nombres en la misma constante enum, si algunas frutas tienen nombres de alias).

Su método de grow se ve así:

static void grow(String type) { Fruit f = map.get(type); if (f == null) { OTHER.growFruit(); } else { f.growFruit(); } }

Por supuesto, ¿realmente necesitas la cadena aquí? ¿No deberías siempre usar el objeto enum?


A menudo implemento un método en enumeraciones analizando una cadena dada y devuelve la constante de enumeración correspondiente. Siempre llamo a este método parse(String) .

A veces sobrecarga este método para analizar la constante enum por otro tipo de entrada dado también.

Su implementación es siempre la misma: itera sobre todos los valores enum () y regresa cuando tocas uno. Finalmente haga un return como fallthrough, a menudo una constante de enumeración específica o null . En la mayoría de los casos prefiero nulo.

public class FruitGrower { enum Fruit { WATERMELON("wtrmln") { @Override void grow() { //do watermelon growing stuff } }, APPLE("ppl") { @Override void grow() { //do apple growing stuff } }, PINEAPPLE("pnppl") { @Override void grow() { //do pineapple growing stuff } }, ORANGE("rng") { @Override void grow() { //do orange growing stuff } }, OTHER("") { @Override void grow() { // do other fruit growing stuff } }; private String name; private Fruit(String name) { this.name = name; } abstract void grow(); public static Fruit parse(String name) { for(Fruit f : values()) { if(f.name.equals(name)){ return f; } } return OTHER; //fallthrough value (null or specific enum constant like here.) } } public void growAFruit(String name) { Fruit.parse(name).grow(); } }

Si realmente no necesita esta Fruit.OTHER , elimínela. ¿O cómo crece una "otra fruta"? oO Devuelve null en el método parse(String) como valor de transición y verifique null antes de llamar a grow() en growAFruit(String) .

Es una buena idea agregar la anotación @CheckForNull al método parse(String) .


Para responder a su pregunta, diría que ni if-else ni enumeraciones son preferibles a su problema específico. Una buena forma de abordar este problema sería utilizar la encapsulación y la abstracción y dejar que la herencia maneje el "tipo" por usted.

La tarea de cultivar una fruta varía mucho entre cada fruto. Sería conveniente hacer de cada fruta su propia clase, lo que permite una mayor flexibilidad más adelante cuando se necesite más funcionalidad.

Aquí hay un buen ejemplo:

// Abstract Base Class (Everything common across all fruits) public abstract class Fruit { public abstract void grow(); } // Concrete class, for each "type" of fruit public class Apple extends Fruit { @override public void grow() { // Grow an apple } } public class Orange extends Fruit { @override public void grow() { // Grow an orange } } ...

Una vez definidas las clases de productos, podemos crear una clase que produzca frutos sin ninguna verificación de tipo "ify".

public class FruitGrower { public void growAFruit(Fruit fruit) { fruit.grow(); } }


La API pública es exactamente la misma. Todavía tienes el mismo bloque if-else, ahora solo está en la enumeración. Entonces creo que no es mejor. En todo caso, es peor, debido a la complejidad añadida.

¿Qué significa ''hacer fruticultura''? ¿Estás hablando de cosas que hace el productor de fruta (hasta el suelo, semillas de plantas, etc.) o cosas que hace la fruta (germinar, brotar, florecer, etc.)? En el ejemplo original, las acciones están definidas por el FruitGrower , pero en sus modificaciones están definidas por Fruit . Esto hace una gran diferencia cuando consideras subclases. Por ejemplo, podría querer definir un MasterFruitGrower que utilice diferentes procesos para hacer crecer mejor la fruta. Tener la operación grow() definida en Fruit hace que sea más difícil razonar sobre esto.

¿Cuán complejas son las operaciones de cultivo de fruta? Si le preocupa la longitud de la línea del bloque if-else, creo que una mejor idea es definir métodos de cultivo de fruta por separado ( growWatermelon() , growOrange() , ...) o definir una interfaz FruitGrowingProcedure , implementando subclases para cada tipo de fruta, y almacenarlos en un mapa o establecer en FruitGrower .


La respuesta a su pregunta es utilizar enumeraciones, o mejor aún, fábricas y polimorfismo, como se mencionó anteriormente. Sin embargo, si quieres deshacerte de los switches (que es lo que está realmente haciendo tu enunciado if-else), una buena manera, si no la mejor, para hacerlo es usar la inversión de control. Por lo tanto, sugiero usar primavera, de la siguiente manera:

public interface Fruit { public void grow(); } public class Watermelon implements Fruit { @Override public void grow() { //Add Business Logic Here } }

Ahora, cree la interfaz del localizador de frutas, de la siguiente manera:

public interface FruitLocator { Fruit getFruit(String type); }

Deje que la clase principal tenga una referencia a un objeto FruitLocator, un setter para él y simplemente llame al comando getFruit:

private FruitLocator fruitLocator; public void setFruitLocator (FruitLocator fruitLocator) { this.fruitLocator = fruitLocator; } public void growAFruit(String type) { fruitLocator.getFruit(type).grow(); }

Ahora viene la parte difícil. Defina su clase FruitGrower como un frijol de primavera, así como su FruitLocator y Fruits:

<bean id="fruitGrower" class="yourpackage.FruitGrower"> <property name="fruitLocator" ref="fruitLocator" /> </bean> <bean id="fruitLocator" class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean"> <property name="serviceLocatorInterface" value="yourpackage.FruitLocator" /> <property name="serviceMappings" ref="locatorProperties" /> </bean> <bean id="locatorProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="location" value="classpath:fruits.properties" /> </bean> <bean id="waterMelon" class="yourpackage.WaterMelon"> </bean>

Lo único que queda por hacer es crear un archivo fruits.properties en su classpath y agregar la asignación de tipo-bean, de la siguiente manera:

wtrmln=waterMelon

Ahora puede agregar tantas frutas como desee, solo necesita crear una nueva clase de fruta, definirla como un frijol y agregar una asignación a su archivo de propiedades. Mucho más escalable que buscar la lógica if-else en el código.

Sé que esto parece complejo al principio, pero creo que el tema estaría incompleto sin mencionar la Inversión de control.


No estoy seguro de que use Enums aquí. Me puede faltar algo aquí (?), Pero creo que mi solución sería algo como esto, con clases separadas para cada tipo de fruta, todas basadas en una clase de fruta de un género:

// Note: Added in response to comment below public enum FruitType { WATERMELON, WHATEVERYOUWANT, .... } public class FruitFactory { public Fruit getFruitToGrow(FruitType type) { Fruit fruitToGrow = null; switch(type){ case WATERMELON: fruitToGrow = new Watermelon(); break; case WHATEVERYOUWANT: ... default: fruitToGrow = new Fruit(); } return fruitToGrow; } } public class Fruit(){ public void grow() { // Do other fruit growing stuff } } // Add a separate class for each specific fruit: public class Watermelon extends Fruit(){ @override public void grow(){ // Do more specific stuff... } }


Bueno, el código de enumeraciones seguramente es más largo. Lo que sugiero

  • Use Strings cuando la lógica sea simple, pequeña, y se usará 1 vez.
  • Use los Enumeraciones cuando tenga que usar las constantes muchas veces, la adición o las modificaciones sean posibles, o cuando estén mezclados en un código complejo, por lo que es mejor aclararlos con Enum.

Creo que estás en el camino correcto. Me gustaría ir y agregar algunos bytes adicionales para un HashMap para deshacerme del bloque de conmutación de cadenas. Esto le da a ambos, una apariencia más limpia, menos código y muy probablemente un poco más de rendimiento.

public enum Fruit { APPLE("ppl") { public void grow() { // TODO } }, WATERMELON("wtrmln") { public void grow() { // TODO } }, // SNIP extra vitamins go here OTHER(null) { public void grow() { // TODO } }; private static Map<String, Fruit> CODE_LOOKUP; static { // populate code lookup map with all fruits but other Map<String, Fruit> map = new HashMap<String, Fruit>(); for (Fruit v : values()) { if (v != OTHER) { map.put(v.getCode(), v); } } CODE_LOOKUP = Collections.unmodifiableMap(map); } public static Fruit getByCode(String code) { Fruit f = CODE_LOOKUP.get(code); return f == null ? OTHER : f; } private final String _code; private Fruit(String code) { _code = code; } public String getCode() { return _code; } public abstract void grow(); }

Y así es como lo usas:

Fruit.getByCode("wtrmln").grow();

Simple, no es necesario un FruitGrower, pero opta por él si crees que es necesario.


Si diferentes frutas tienen diferentes comportamientos de crecimiento, no usaría un if-else o una enumeración, y en su lugar utilizaría un diseño orientado a objetos. El problema con el estilo if-else / enum (los considero equivalentes en su ejemplo) es que recopilan comportamientos de diferentes tipos de objetos en una ubicación. Si agrega una nueva fruta, debe editar su if-else / enum cada vez. Esto viola el principio de abierto y cerrado .

Considere si sus 4 frutas tuvieron 3 comportamientos (por ejemplo, crecer, madurar, pudrirse). Tendría un if-else / enum para cada comportamiento, cada uno con 4 referencias de fruta. Ahora considere agregar una quinta fruta, debe editar 3 bloques if-else / enum diferentes. El comportamiento de una sandía no tiene nada que ver con una manzana, pero están en el mismo código. Ahora considere agregar un nuevo comportamiento de fruta (por ejemplo, aroma): debe crear un nuevo if-else / enum, que tenga los mismos problemas.

Creo que la solución correcta en la mayoría de los casos es usar clases (por ejemplo, una interfaz Fruit / clase base con una clase de implementación por fruta) y poner el comportamiento en cada clase en lugar de reunirlo todo en un solo lugar. Entonces, cuando agrega una nueva fruta, el único código que se cambia es que se escribe una nueva clase de fruta.

Existe una refactorización bien conocida que puede usar para tomar su código existente y migrarlo a lo que estoy hablando. Se llama Reemplazar condicional con polimorfismo .