Pregunta

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.

¿Fue útil?

Solución

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. Verifiquemos: ¿E extiende Enum<E>? ¡Sí! Estamos bien.

Bien puede preguntarse cuál es el punto de esto :) Bueno, significa que la API de Enum puede referirse a sí misma, por ejemplo, poder decir que Comparable<E> implementa Enum. 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 & Quot; mensajes & Quot; (inmutable) y " constructores " (mutable, usado 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, 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 impares que usan un argumento de tipo que 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 Second no se manejaron " especialmente " en Java de todos modos, 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> {}

Comparable<First> implementaría Comparable<Second> en lugar de First ... pero <=> en sí estaría bien.

Otros consejos

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 expandirá a una clase

final class Season extends ...

donde ... debe ser la clase base de alguna manera parametrizada para Enums. Vamos a trabajar fuera de lo que tiene que ser. Bueno, uno de los requisitos para Season es que debe implementar Comparable<Season>. Entonces vamos a necesitar

Season extends ... implements Comparable<Season>

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

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

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

 E extends Enum<E>

Maurice Naftalin (coautor, Java Generics and Collections)

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 un ejemplo a continuación, setName devuelve un Node para que el encadenamiento no funcione para el 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
}

Para que podamos hacer referencia a una subclase en una declaración genérica, para que <=> 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; ver Blog caótico de Java .

& # 8220; Si una clase extiende esta clase, debe pasar un parámetro E. El parámetro E & # 8217; los límites son para una clase que extiende esta clase con el mismo parámetro E & # 8221 ;.

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

Suponga 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(){...}
}

Si observa el código fuente Enum, 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é significa E extends Enum<E>? 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 tiene 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();
}

Entonces 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 getDeclaringClass() se convierta en el objeto Class<T> apropiado.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top