Definición de Java Enum


151

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 tipo de parámetro? Puntos de bonificación por proporcionar otros ejemplos de dónde se podría usar un parámetro de tipo similar.



Esta pregunta tiene mejores respuestas: stackoverflow.com/a/3068001/2413303
EpicPandaForce

Respuestas:


105

Significa que el argumento de tipo para enum debe derivarse 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í. Entonces, si tengo una enumeración llamada StatusCode, sería equivalente a:

public class StatusCode extends Enum<StatusCode>

Ahora, si verifica las restricciones, tenemos Enum<StatusCode>, entonces E=StatusCode. Vamos a ver: ¿se Eextiende Enum<StatusCode>? ¡Si! Estamos bien

Puede que se pregunte cuál es el punto de esto :) Bueno, significa que la API de Enum puede referirse a sí misma, por ejemplo, poder decir que se Enum<E>implementa Comparable<E>. 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, 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 construir 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 generador apropiado (por ejemplo, para tomar una copia de un mensaje y cambiar algunos bits) y de un generador puede obtener un mensaje apropiado cuando haya terminado de construirlo. Sin embargo, es un buen trabajo que los usuarios de la API no necesitan preocuparse realmente por esto: es terriblemente complicado y tomó varias iteraciones para llegar a donde está.

EDITAR: tenga en cuenta que esto no le impide crear tipos extraños que utilizan un argumento de tipo que sí 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 incorrecto .

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

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

Secondimplementaría en Comparable<First>lugar de Comparable<Second>... pero en Firstsí estaría bien.


1
@artsrc: No recuerdo por qué tiene que ser genérico tanto en el generador como en el mensaje. Estoy bastante seguro de que no habría seguido esa ruta si no la hubiera necesitado :)
Jon Skeet

1
@SayemAhmed: Sí, no evita ese aspecto de mezclar los tipos. Agregaré una nota sobre esto.
Jon Skeet

1
"He usado algo similar en mi puerto C # de ProtocolBuffers". Pero eso es diferente porque los constructores tienen métodos de instancia que devuelven el tipo de parámetro tipo. Enumno tiene ningún método de instancia que devuelva el tipo de parámetro tipo.
newacct

1
@ JonSkeet: Dado que las clases de enumeración siempre se generan automáticamente, afirmo que class Enum<E>es suficiente en todos los casos. Y en Genéricos, solo debe usar un límite más restrictivo si es realmente necesario para garantizar la seguridad de los tipos.
newacct

1
@JonSkeet: Además, si Enumlas subclases no siempre fueron generados automáticamente, la única razón que se necesita class Enum<E extends Enum<?>>más class Enum<E>es la capacidad de acceder ordinala compareTo(). Sin embargo, si lo piensa, no tiene sentido desde el punto de vista del lenguaje permitirle comparar dos tipos diferentes de enumeraciones a través de sus ordinales. Por lo tanto, la implementación de Enum.compareTo()esos usos ordinalsolo tiene sentido en el contexto de las Enumsubclases que se autogeneran. Si pudiera subclasificar manualmente Enum, compareToprobablemente tendría que serlo abstract.
newacct

27

La siguiente es una versión modificada de la explicación del libro de Java Genéricos y Colecciones : Tenemos un Enumdeclararon

enum Season { WINTER, SPRING, SUMMER, FALL }

que se ampliará a una clase

final class Season extends ...

donde ...será la clase base de alguna manera parametrizada para Enums. Veamos qué tiene que ser eso. Bueno, uno de los requisitos Seasones que debe implementarse Comparable<Season>. Entonces vamos a necesitar

Season extends ... implements Comparable<Season>

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

Season extends Enum<Season>
Enum<Season> implements Comparable<Season>

Entonces Enumse parametriza en tipos como Season. Resumen de Seasony obtienes que el parámetro de Enumes cualquier tipo que satisfaga

 E extends Enum<E>

Maurice Naftalin (coautor, Java Generics and Collections)


1
@newacct OK, lo entiendo ahora: le gustaría que todas las enumeraciones sean instancias de Enum <E>, ¿verdad? (Porque si son instancias de un subtipo Enum, se aplica el argumento anterior). Pero entonces ya no tendrá enumeraciones de tipo seguro, por lo que perderá el punto de tener enumeraciones.
Maurice Naftalin

1
@newacct ¿No quieres insistir en que Seasonimplemente Comparable<Season>?
Maurice Naftalin

2
@newacct Mira la definición de Enum. Para comparar una instancia con otra, tiene que comparar sus ordinales. Por lo tanto, el argumento del compareTométodo debe haberse declarado como un Enumsubtipo, o el compilador (correctamente) dirá que no tiene un ordinal.
Maurice Naftalin

2
@MauriceNaftalin: Si Java no prohibiera la subclasificación manual Enum, entonces sería posible tenerlo class OneEnum extends Enum<AnotherEnum>{}, incluso con la forma en que Enumse declara en este momento. No tendría mucho sentido para poder comparar un tipo de enumeración con otro, por lo que entonces Enum's compareTono tendría sentido según lo declarado todos modos. Los límites no proporcionan ninguna ayuda para esto.
newacct

2
@MauriceNaftalin: Si el ordinal fuera la razón, entonces public class Enum<E extends Enum<?>>también sería suficiente.
newacct

6

Esto se puede ilustrar con un ejemplo simple y una técnica que se puede utilizar para implementar llamadas a métodos encadenados para subclases. En el siguiente ejemplo, se setNamedevuelve un Nodeencadenamiento que 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 Cityahora 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!
    }
}

Su solución tiene un reparto no verificado: return (CHILD) this;considere agregar un método getThis (): protected CHILD getThis() { return this; } consulte: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

@Roland gracias por un enlace, me he tomado una idea. ¿Podría explicarme o dirigirme a un artículo que explique por qué es una mala práctica en este caso particular? El método en el enlace requiere más tipeo y este es el argumento principal por el que estoy evitando esto. Nunca he visto errores de conversión en este caso + Sé que hay algunos errores de conversión inevitables, es decir, cuando uno almacena objetos de varios tipos en una misma colección. Entonces, si los moldes no controlados no son críticos y el diseño es algo complejo ( Node<T>no es el caso), los estoy ignorando para ahorrar tiempo.
Andrey Chaschev

Su edición no es tan diferente de antes además de agregar un poco de azúcar sintáctica, considere que el siguiente código realmente se compilará pero arrojará un error de tiempo de ejecución: `Nodo <Ciudad> nodo = nuevo Nodo <Ciudad> () .setName (" nodo "). setSquare (1); `Si observa el código de bytes de Java, verá que debido al borrado de tipo return (SELF) this;se compila la declaración return this;, por lo que podría omitirla .
Roland

@Roland gracias, esto es lo que necesitaba: actualizaré el ejemplo cuando esté libre.
Andrey Chaschev

El siguiente enlace también es bueno: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

3

No eres el único que se pregunta qué significa eso; ver 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".


1

Esta publicación me ha aclarado totalmente este problema de 'tipos genéricos recursivos'. Solo quería agregar otro caso donde esta estructura particular es necesaria.

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

public abstract class Node<T extends Node<T>>
{
    public void addNeighbor(T);

    public void addNeighbors(Collection<? extends T> nodes);

    public Collection<T> getNeighbor();
}

Entonces puede tener gráficos de tipos especializados:

public class City extends Node<City>
{
    public void addNeighbor(City){...}

    public void addNeighbors(Collection<? extends City> nodes){...}

    public Collection<City> getNeighbor(){...}
}

Todavía me permite crear un lugar class Foo extends Node<City>donde Foo no está relacionado con la ciudad.
newacct

1
Claro, ¿y eso está mal? No lo creo. El contrato base proporcionado por el Nodo <Ciudad> aún se cumple, solo su subclase de Foo es menos útil desde que comienza a trabajar con Foos pero saca a las Ciudades del ADT. Puede haber un caso de uso para esto, pero lo más probable es que sea más simple y más útil para hacer que el parámetro genérico sea el mismo que la subclase. Pero de cualquier manera, el diseñador tiene esa opción.
mdma

@mdma: estoy de acuerdo. Entonces, ¿qué uso proporciona el límite, más que justo class Node<T>?
newacct

1
@nozebacle: su ejemplo no demuestra que "esta estructura particular es necesaria". class Node<T>es totalmente consistente con tu ejemplo.
newacct

1

Si observa el Enumcódigo fuente, tiene lo siguiente:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> 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<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    public static <T extends Enum<T>> T valueOf(Class<T> 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);
    } 
}

Lo primero es lo primero, ¿qué E extends Enum<E>significa? Significa que el parámetro de tipo es algo que se extiende desde Enum y no está parametrizado con un tipo sin formato (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<MyEnum> {
    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<MyEnum> 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<MyEnum> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<MyEnum>)clazz : (Class<MyEnum>)zuper;
    }

Esto hace que se getDeclaringClass()lance al Class<T>objeto adecuado .

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


Nada de lo que has mostrado compareToo getDeclaringClassrequiere el extends Enum<E>límite.
newacct

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.