Pregunta Por qué en Java enum se declara como Enum > [duplicado]


Posible duplicado:
definición de Java Enum 

Una pregunta mejor formulada, que no se considera un duplicado:
¿Qué sería diferente en Java si la declaración Enum no tuviera la parte recursiva?

si los diseñadores del lenguaje usaran simplemente Enum <E extiende Enum> ¿cómo afectaría eso al lenguaje?

La única diferencia ahora sería que alguien podría escribir

A se extiende Enum <B>

pero dado que no está permitido en Java extender enumeraciones que seguirían siendo ilegales. También estaba pensando en alguien que le proporcione a jvm un bytecode que define smth como la extensión de una enumeración, pero los genéricos no pueden afectarlo a medida que se borran.

Entonces, ¿cuál es el objetivo de tal declaración?

¡Gracias!

Editar por simplicidad, veamos un ejemplo:

interfaz MyComparable <T> {
    int myCompare (T o);
}

clase MyEnum <E extiende MyEnum> implementa MyComparable <E> {
    public int myCompare (E o) {return -1; }
}

clase FirstEnum extiende MyEnum <FirstEnum> {}

clase SecondEnum extiende MyEnum <SecondEnum> {}

¿Qué pasa con esta estructura de clase? ¿Qué se puede hacer que "MyEnum <E extiende MyEnum <E >>" restringiría?


32
2018-06-17 12:55


origen


Respuestas:


Esta es una pregunta común, y comprensiblemente. Mira esto esta parte de las preguntas frecuentes sobre medicamentos genéricos para la respuesta (y de hecho, lea todo el documento con el que se sienta cómodo, está bastante bien hecho e informativo).

La respuesta corta es que obliga a la clase a parametrizarse en sí misma; esto es necesario para que las superclases definan métodos, usando el parámetro genérico, que funcionan de manera transparente ("nativamente", si se quiere) con sus subclases.

Editar: como un ejemplo (no), por ejemplo, considere la clone() método en Object. Actualmente, está definido para devolver un valor de tipo Object. Gracias a los tipos de retorno covariantes, las subclases específicas pueden (y a menudo lo hacen) definir que devuelven una clase más específica, pero esto no se puede aplicar y, por lo tanto, no se puede inferir para una clase arbitraria.

Ahora, Si Los objetos se definieron como Enum, es decir Object<T extends Object<T>> entonces tendrías que definir todas las clases como algo así public class MyFoo<MyFoo>. Por consiguiente, clone() podría declararse que devuelve un tipo de T y puede garantizar en tiempo de compilación que el valor devuelto sea siempre exactamente la misma clase que el objeto en sí (ni siquiera las subclases coincidirían con los parámetros).

Ahora, en este caso, Object no está parametrizado de esta manera porque sería extremadamente molesto tener este equipaje en todas las clases cuando el 99% de ellas no lo utilicen en absoluto. Pero para algunas jerarquías de clase puede ser muy útil. He usado una técnica similar antes con tipos de analizadores de expresiones abstractas y recursivas con varias implementaciones. Esta construcción hizo posible escribir código que era "obvio" sin tener que enviarlo a todas partes, o copiar y pegar solo para cambiar las definiciones de clase concretas.

Editar 2 (¡Para responder tu pregunta!):

Si Enum fue definido como Enum<E extends Enum>, entonces, como dices con razón, alguien podría definir una clase como A extends Enum<B>. Esto derrota el punto de la construcción genérica, que es asegurar que el parámetro genérico sea siempre el exacto tipo de la clase en cuestión. Dando un ejemplo concreto, Enum declara su método compareTo como

public final int compareTo(E o)

En este caso, desde que definiste A extender Enum<B>, instancias de A solo podría compararse con instancias de B (lo que B es), que casi con certeza no es muy útil. Con la construcción adicional, sabes que alguna la clase que extiende Enum es comparable solo contra sí misma. Y, por lo tanto, puede proporcionar implementaciones de métodos en la superclase que siguen siendo útiles y específicas en todas las subclases.

(Sin este truco genérico recursivo, la única otra opción sería definir compareTo como public final int compareTo(Enum o). Esto no es realmente lo mismo, como entonces uno podría comparar una java.math.RoundingMode contra un java.lang.Thread.State sin quejarse el compilador, lo que de nuevo no es muy útil.)


OK, alejémonos de Enum en sí, ya que parece que nos estamos colgando. En cambio, aquí hay una clase abstracta:

public abstract class Manipulator<T extends Manipulator<T>>
{
    /**
     * This method actually does the work, whatever that is
     */
    public abstract void manipulate(DomainObject o);

    /**
     * This creates a child that can be used for divide and conquer-y stuff
     */
    public T createChild()
    {
        // Some really useful implementation here based on
        // state contained in this class
    }
}

Tendremos varias implementaciones concretas de esto: SaveToDataManipulator, SpellCheckingManipulator, lo que sea. Además, también queremos que las personas definan las suyas propias, ya que es una clase súper útil. ;-)

Ahora - notarás que estamos usando la definición genérica recursiva, y luego volvemos T desde el createChild método. Esto significa que:

1) Sabemos y el compilador sabe eso si llamo:

SpellCheckingManipulator obj = ...; // We have a reference somehow
return obj.createChild();

entonces el valor devuelto es definitivamente un SpellCheckingManipulator, a pesar de que está usando la definición de la superclase. Los genéricos recursivos aquí permiten que el compilador sepa lo que es obvio para nosotros, por lo que no tiene que seguir emitiendo los valores de retorno (como lo que a menudo tiene que ver con clone(), por ejemplo).

2) Tenga en cuenta que no declare el método como definitivo, ya que tal vez algunas subclases específicas querrán anularlo con una versión más adecuada para ellos. La definición de genéricos significa que, independientemente de quién crea una nueva clase o cómo se define, aún podemos afirmar que el retorno de, por ejemplo, BrandNewSloppilyCodedManipulator.createChild() seguirá siendo una instancia de BrandNewSloppilyCodedManipulator. Si un desarrollador descuidado intenta definirlo para devolver solo Manipulator, el compilador no los dejará. Y si intentan definir el clase como BrandNewSloppilyCodedManipulator<SpellCheckingManipulator>, tampoco los dejará.

Básicamente, la conclusión es que este truco es útil cuando se quiere proporcionar alguna funcionalidad en una superclase que de alguna manera se vuelve más específica en subclases. Al declarar la superclase así, eres bloqueando el parámetro genérico para cualquier subclase para ser la subclase misma. Es por eso que puedes escribir un genérico compareTo o createChild método en la superclase y evitar que se vuelva demasiado vago cuando se trata de subclases específicas.


33
2018-06-17 12:57



La única diferencia ahora sería que alguien podría escribir

A extends Enum<B>

pero dado que no está permitido en Java extender enumeraciones que seguirían siendo ilegales. También estaba pensando en alguien que le proporcione a jvm un bytecode que define smth como la extensión de una enumeración, pero los genéricos no pueden afectarlo a medida que se borran.

Tienes razón en que el lenguaje evitaría que alguien lo haga. Sin embargo, el beneficio de hacerlo E extends Enum<E> en lugar de solo extends Enum es que algunos de los métodos en Enum ahora devolverá el tipo correcto y específico.

El cartel pidió algo que no funcionaría. Si Enum fue definido como Enum<E>, entonces podrías hacer esto:

// Would be legal, because element types of `DaysOfWeek` and `Color` both
//   extend `Enum<E>`, not `Enum<E extends Enum<E>>`.
DaysOfWeek d = Enum.valueOf(Color.class, "Purple");

0
2018-06-17 13:02