valueof - java enum with values
¿Por qué los literales enum de Java no deberían tener parámetros genéricos? (7)
Las enumeraciones de Java son geniales. Entonces son genéricos. Por supuesto, todos conocemos las limitaciones de este último debido al borrado de tipos. Pero hay una cosa que no entiendo, ¿Por qué no puedo crear una enumeración como esta?
public enum MyEnum<T> {
LITERAL1<String>,
LITERAL2<Integer>,
LITERAL3<Object>;
}
Este parámetro de tipo genérico <T>
a su vez podría ser útil en varios lugares. Imagine un parámetro de tipo genérico para un método:
public <T> T getValue(MyEnum<T> param);
O incluso en la clase enum en sí:
public T convert(Object o);
Ejemplo más concreto # 1
Dado que el ejemplo anterior puede parecer demasiado abstracto para algunos, aquí hay un ejemplo más real de por qué quiero hacer esto. En este ejemplo, quiero usar
- Enumeraciones, porque entonces puedo enumerar un conjunto finito de claves de propiedad
- Genéricos, porque entonces puedo tener seguridad de tipo a nivel de método para almacenar propiedades
public interface MyProperties {
public <T> void put(MyEnum<T> key, T value);
public <T> T get(MyEnum<T> key);
}
Ejemplo más concreto # 2
Tengo una enumeración de tipos de datos:
public interface DataType<T> {}
public enum SQLDataType<T> implements DataType<T> {
TINYINT<Byte>,
SMALLINT<Short>,
INT<Integer>,
BIGINT<Long>,
CLOB<String>,
VARCHAR<String>,
...
}
Cada literal enum obviamente tendría propiedades adicionales basadas en el tipo genérico <T>
, mientras que al mismo tiempo, es una enumeración (inmutable, singleton, enumerable, etc., etc.)
Pregunta:
¿Nadie pensó en esto? ¿Es esto una limitación relacionada con el compilador? Teniendo en cuenta el hecho de que la palabra clave " enum " se implementa como azúcar sintáctica, que representa el código generado para la JVM, no entiendo esta limitación.
¿Quién puede explicarme esto? Antes de responder, considere esto:
- Sé que los tipos genéricos se borran :-)
- Sé que hay soluciones con el uso de objetos de Clase. Son soluciones.
- Los tipos genéricos dan como resultado conversiones de tipo generadas por el compilador cuando corresponda (por ejemplo, cuando se llama al método convert ()
- El tipo genérico <T> estaría en la enumeración. Por lo tanto, está obligado por cada uno de los literales de la enum. Por lo tanto, el compilador sabría qué tipo aplicar al escribir algo como
String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
- Lo mismo se aplica al parámetro de tipo genérico en el
T getvalue()
. El compilador puede aplicar el tipo de conversión al llamar aString string = someClass.getValue(LITERAL1)
Creo que porque básicamente los Enums no pueden ser instanciados
¿Dónde establecerías la clase T, si JVM te lo permitiera?
La enumeración es información que se supone que siempre es la misma, o al menos, que no cambiará dinámicamente.
nuevo MyEnum <> ()?
Aún el siguiente enfoque puede ser útil
public enum MyEnum{
LITERAL1("s"),
LITERAL2("a"),
LITERAL3(2);
private Object o;
private MyEnum(Object o) {
this.o = o;
}
public Object getO() {
return o;
}
public void setO(Object o) {
this.o = o;
}
}
Esto ahora se está discutiendo a partir de las entradas optimizadas JEP-301 . El ejemplo dado en el JEP es, que es precisamente lo que estaba buscando:
enum Argument<X> { // declares generic enum
STRING<String>(String.class),
INTEGER<Integer>(Integer.class), ... ;
Class<X> clazz;
Argument(Class<X> clazz) { this.clazz = clazz; }
Class<X> getClazz() { return clazz; }
}
Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant
Francamente, esto parece ser más una solución en la búsqueda de un problema que cualquier otra cosa.
El propósito completo de Java Enum es modelar una enumeración de instancias de tipo que comparten propiedades similares de una manera que proporcione consistencia y riqueza más allá de las representaciones de cadenas o números enteros comparables.
Tome un ejemplo de una enumeración de libro de texto. Esto no es muy útil o consistente:
public enum Planet<T>{
Earth<Planet>,
Venus<String>,
Mars<Long>
...etc.
}
¿Por qué querría que mis diferentes planetas tuvieran diferentes conversiones de tipo genérico? ¿Qué problema soluciona? ¿Justifica complicar la semántica del lenguaje? Si necesito este comportamiento, ¿es una enum la mejor herramienta para lograrlo?
Además, ¿cómo gestionaría las conversiones complejas?
por ejemplo
public enum BadIdea<T>{
INSTANCE1<Long>,
INSTANCE2<MyComplexClass>;
}
Es bastante fácil con String
Integer
para proporcionar el nombre o el ordinal. Pero los genéricos le permiten suministrar cualquier tipo. ¿Cómo administrarías la conversión a MyComplexClass
? Ahora estás eliminando dos construcciones al obligar al compilador a saber que hay un subconjunto limitado de tipos que se pueden suministrar a las enumeraciones genéricas e introducir una confusión adicional al concepto (Generics) que ya parece eludir a muchos programadores.
Hay otros métodos en ENUM que no funcionarían. ¿Qué MyEnum.values()
?
¿Qué pasa con MyEnum.valueOf(String name)
?
Por el valor de si crees que el compilador podría hacer un método genérico como
public static MyEnum valueOf (String name);
para llamarlo como MyEnum<String> myStringEnum = MyEnum.value("some string property")
, eso tampoco funcionaría. Por ejemplo, ¿qué MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")
si llama a MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")
? No es posible implementar ese método para que funcione correctamente, por ejemplo, lanzar una excepción o devolver nulo cuando lo llame como MyEnum.<Int>value("some double property")
debido a la MyEnum.<Int>value("some double property")
del tipo.
La respuesta está en la pregunta:
debido a la borradura de tipo
Ninguno de estos dos métodos es posible, ya que el tipo de argumento se borra.
public <T> T getValue(MyEnum<T> param);
public T convert(Object);
Para realizar esos métodos, puedes construir tu enumeración como:
public enum MyEnum {
LITERAL1(String.class),
LITERAL2(Integer.class),
LITERAL3(Object.class);
private Class<?> clazz;
private MyEnum(Class<?> clazz) {
this.clazz = clazz;
}
...
}
Porque "enum" es la abreviatura de enumeración. Es solo un conjunto de constantes con nombre que se colocan en lugar de números ordinales para hacer que el código sea mejor legible.
No veo cuál podría ser el significado previsto de una constante parametrizada por tipo.
Porque no puedes. Seriamente. Eso podría agregarse a la especificación del idioma. No ha sido. Agregaría algo de complejidad. Ese beneficio para el costo significa que no es una alta prioridad.
Actualización: actualmente se está agregando al idioma bajo JEP 301: Enumeraciones mejoradas .