Definición de Java Enum

Creí entender los generics de Java bastante bien, pero luego me encontré con lo siguiente en java.lang.Enum:

class Enum<E extends Enum> 

¿Podría alguien explicar cómo interpretar este parámetro de tipo? Puntos de bonificación por proporcionar otros ejemplos de dónde podría usarse un parámetro de tipo similar.

Significa que el argumento de tipo para enum tiene que derivar de una enumeración que tiene el mismo argumento de tipo. ¿Cómo puede pasar esto? Al hacer que el argumento de tipo sea el nuevo tipo en sí mismo. Entonces, si tengo una enumeración llamada StatusCode, sería equivalente a:

 public class StatusCode extends Enum 

Ahora, si comprueba las restricciones, tenemos Enum – entonces E=StatusCode . Comprobamos: ¿ E extiende Enum ? ¡Sí! Estamos bien

Es posible que se pregunte qué sentido tiene esto: bueno, significa que la API para Enum puede referirse a sí misma, por ejemplo, poder decir que Enum implementa Comparable . La clase base puede hacer las comparaciones (en el caso de las enumeraciones), pero puede asegurarse de que solo compara el tipo correcto de enumeraciones entre sí. (EDITAR: Bueno, casi – ver la edición en la parte inferior.)

He usado algo similar en mi puerto C # de ProtocolBuffers. Hay “mensajes” (inmutables) y “constructores” (mutables, utilizados para crear un mensaje), y vienen en pares de tipos. Las interfaces involucradas son:

 public interface IBuilder where TMessage : IMessage where TBuilder : IBuilder public interface IMessage where TMessage : IMessage where TBuilder : IBuilder 

Esto significa que a partir de un mensaje puede obtener un constructor apropiado (por ejemplo, para tomar una copia de un mensaje y cambiar algunos bits) y desde un constructor puede obtener un mensaje apropiado cuando haya terminado de comstackrlo. Sin embargo, es un buen trabajo que los usuarios de la API no tengan que preocuparse por esto realmente: es horrendo y complicado, y tomó varias iteraciones para llegar a donde está.

EDITAR: Tenga en cuenta que esto no le impide crear tipos impares que utilizan un argumento de tipo que, en sí mismo, está bien, pero que no es del mismo tipo. El objective es brindar beneficios en el caso correcto en lugar de protegerlo del caso incorrecto .

Entonces, si Enum no se manejó “especialmente” en Java de todos modos, podría (como se señala en los comentarios) crear los siguientes tipos:

 public class First extends Enum {} public class Second extends Enum {} 

Second implementaría Comparable lugar de Comparable … pero First sí estaría bien.

La siguiente es una versión modificada de la explicación del libro Java Generics and Collections : Tenemos un Enum declarado

 enum Season { WINTER, SPRING, SUMMER, FALL } 

que se ampliará a una clase

 final class Season extends ... 

donde ... debe ser la clase base parametrizada de algún modo para Enums. Vamos a ver qué tiene que ser eso. Bueno, uno de los requisitos de Season es que debería implementar Comparable . Entonces vamos a necesitar

 Season extends ... implements Comparable 

¿Qué podrías usar para ... eso permitiría que esto funcione? Dado que tiene que ser una parametrización de Enum , la única opción es Enum , para que pueda tener:

 Season extends Enum Enum implements Comparable 

Entonces Enum está parametrizado en tipos como Season . Resumen de Season y obtienes que el parámetro de Enum es de cualquier tipo que satisfaga

  E extends Enum 

Maurice Naftalin (coautor, Java Generics and Collections)

Esto se puede ilustrar con un ejemplo simple y una técnica que se puede usar para implementar llamadas a métodos encadenados para subclases. En un ejemplo a continuación setName devuelve un Node para que el encadenamiento no funcione para la City :

 class Node { String name; Node setName(String name) { this.name = name; return this; } } class City extends Node { int square; City setSquare(int square) { this.square = square; return this; } } public static void main(String[] args) { City city = new City() .setName("LA") .setSquare(100); // won't compile, setName() returns Node } 

Entonces, podríamos hacer referencia a una subclase en una statement genérica, de modo que la City ahora devuelva el tipo correcto:

 abstract class Node>{ String name; SELF setName(String name) { this.name = name; return self(); } protected abstract SELF self(); } class City extends Node { int square; City setSquare(int square) { this.square = square; return self(); } @Override protected City self() { return this; } public static void main(String[] args) { City city = new City() .setName("LA") .setSquare(100); // ok! } } 

Usted no es el único que se pregunta qué significa eso; ver el blog Chaotic Java .

“Si una clase extiende esta clase, debe pasar un parámetro E. Los límites del parámetro E son para una clase que amplía esta clase con el mismo parámetro E”.

Esta publicación me ha aclarado totalmente el problema de los “tipos generics recursivos”. Solo quería agregar otro caso donde esta estructura particular es necesaria.

Supongamos que tiene nodos generics en un gráfico genérico:

 public abstract class Node> { public void addNeighbor(T); public void addNeighbors(Collection< ? extends T> nodes); public Collection getNeighbor(); } 

Entonces puedes tener gráficos de tipos especializados:

 public class City extends Node { public void addNeighbor(City){...} public void addNeighbors(Collection< ? extends City> nodes){...} public Collection getNeighbor(){...} } 

En el caso de Enum , es inútil. Todo funcionaría igual si fuera declarado como

 class Enum 

Si miras el código fuente de Enum , tiene lo siguiente:

 public abstract class Enum> implements Comparable, Serializable { public final int compareTo(E o) { Enum< ?> other = (Enum< ?>)o; Enum self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } @SuppressWarnings("unchecked") public final Class getDeclaringClass() { Class< ?> clazz = getClass(); Class< ?> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class)clazz : (Class)zuper; } public static > T valueOf(Class enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); } } 

Primero lo primero, ¿qué significa E extends Enum ? Significa que el parámetro de tipo es algo que se extiende desde Enum, y no está parametrizado con un tipo crudo (está parametrizado por sí mismo).

Esto es relevante si tienes una enumeración

 public enum MyEnum { THING1, THING2; } 

que, si lo sé correctamente, se traduce a

 public final class MyEnum extends Enum { public static final MyEnum THING1 = new MyEnum(); public static final MyEnum THING2 = new MyEnum(); } 

Esto significa que MyEnum recibe los siguientes métodos:

 public final int compareTo(MyEnum o) { Enum< ?> other = (Enum< ?>)o; Enum self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } 

Y aún más importante,

  @SuppressWarnings("unchecked") public final Class getDeclaringClass() { Class< ?> clazz = getClass(); Class< ?> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class)clazz : (Class)zuper; } 

Esto hace que getDeclaringClass() al objeto apropiado de la Class .

Un ejemplo mucho más claro es el que respondí en esta pregunta donde no se puede evitar este constructo si se desea especificar un límite genérico.