Definición de enumeración de Java
Pensé que entendía bastante bien los genéricos de Java, pero luego encontré lo siguiente en java.lang.Enum:
class Enum<E extends Enum<E>>
¿Alguien podría explicar cómo interpretar este parámetro de tipo? Puntos de bonificación por proporcionar otros ejemplos de dónde se podría utilizar un parámetro de tipo similar.
Significa que el argumento de tipo para enum tiene que derivar de una enumeración que a su vez tiene el mismo argumento de tipo. ¿Cómo puede suceder esto? Haciendo que el argumento de tipo sea el nuevo tipo en sí. Entonces, si tengo una enumeración llamada StatusCode, sería equivalente a:
public class StatusCode extends Enum<StatusCode>
Ahora, si verificas las restricciones, tenemos Enum<StatusCode>
- entonces E=StatusCode
. Comprobemos: ¿se E
extiende Enum<StatusCode>
? ¡Sí! Estamos bien.
Quizás te preguntes cuál es el objetivo de esto :) Bueno, significa que la API para Enum puede referirse a sí misma; por ejemplo, puede decir que Enum<E>
implementa Comparable<E>
. La clase base puede hacer comparaciones (en el caso de enumeraciones), pero puede asegurarse de que solo compara el tipo correcto de enumeraciones entre sí. (EDITAR: Bueno, casi; vea 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<TMessage, TBuilder>
where TMessage : IMessage<TMessage, TBuilder>
where TBuilder : IBuilder<TMessage, TBuilder>
public interface IMessage<TMessage, TBuilder>
where TMessage : IMessage<TMessage, TBuilder>
where TBuilder : IBuilder<TMessage, TBuilder>
Esto significa que de un mensaje puede obtener un constructor apropiado (por ejemplo, para tomar una copia de un mensaje y cambiar algunos bits) y de un constructor puede obtener un mensaje apropiado cuando haya terminado de construirlo. Es un buen trabajo, los usuarios de la API no necesitan preocuparse por esto: es tremendamente complicado y requirió varias iteraciones para llegar a donde está.
EDITAR: Tenga en cuenta que esto no le impide crear tipos impares que utilicen un argumento de tipo que en sí mismo está bien, pero que no es del mismo tipo. El propósito es brindar beneficios en el caso correcto en lugar de protegerlo del caso equivocado .
Entonces, si Enum
de todos modos no se manejara "especialmente" en Java, podría (como se indica en los comentarios) crear los siguientes tipos:
public class First extends Enum<First> {}
public class Second extends Enum<First> {}
Second
implementaría Comparable<First>
en lugar de Comparable<Second>
... pero First
en sí mismo estaría bien.
La siguiente es una versión modificada de la explicación del libro Java Generics and Collections : Tenemos una Enum
declaración
enum Season { WINTER, SPRING, SUMMER, FALL }
que se ampliará a una clase
final class Season extends ...
donde ...
será la clase base parametrizada de alguna manera para Enums. Averigüemos qué tiene que ser eso. Bueno, uno de los requisitos Season
es que se debe implementar Comparable<Season>
. Entonces vamos a necesitar
Season extends ... implements Comparable<Season>
¿Qué podrías usar para ...
que esto permita que esto funcione? Dado que tiene que ser una parametrización de Enum
, la única opción es Enum<Season>
, para que puedas tener:
Season extends Enum<Season>
Enum<Season> implements Comparable<Season>
También Enum
está parametrizado en tipos como Season
. Resuma de Season
y obtendrá que el parámetro de Enum
es cualquier tipo que satisfaga
E extends Enum<E>
Maurice Naftalin (coautor, Colecciones y genéricos de Java)
Esto se puede ilustrar con un ejemplo sencillo y una técnica que se puede utilizar para implementar llamadas a métodos encadenados para subclases. En el siguiente ejemplo, setName
se devuelve un Node
valor por lo que el encadenamiento no funcionará para 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 declaración genérica, de modo que City
ahora devuelva el tipo correcto:
abstract class Node<SELF extends Node<SELF>>{
String name;
SELF setName(String name) {
this.name = name;
return self();
}
protected abstract SELF self();
}
class City extends Node<City> {
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!
}
}
No eres el único que se pregunta qué significa eso; consulte el blog Caótico de 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 extiende esta clase con el mismo parámetro E".