Pergunta

Eu pensei que eu entendi genéricos Java muito bem, mas, em seguida, me deparei com o seguinte em java.lang.Enum:

class Enum<E extends Enum<E>>

Alguém poderia explicar como interpretar este tipo de parâmetro? Os pontos de bónus para a prestação de outros exemplos de onde poderia ser usado um parâmetro de tipo semelhante.

Foi útil?

Solução

Isso significa que o argumento tipo para enum tem que derivar de uma enumeração que se tem o mesmo tipo de argumento. Como isso pode acontecer? Ao fazer o argumento de tipo novo próprio tipo. Então, se eu tenho um enum chamado StatusCode, seria equivalente a:

public class StatusCode extends Enum<StatusCode>

Enum<StatusCode> Agora, se você verificar as restrições, nós temos - assim E=StatusCode. Vamos verificação: O E estender Enum<StatusCode>? Sim! Estamos bem.

Você pode muito bem estar se perguntando o que o ponto deste é :) Bem, isso significa que a API para Enum pode se referir a si mesmo - por exemplo, ser capaz de dizer que implementos Enum<E> Comparable<E>. A classe base é capaz de fazer as comparações (no caso de enums), mas pode ter certeza de que ele só compara o tipo certo de enums uns com os outros. (EDIT:. Bem, quase - veja a edição na parte inferior)

Eu usei algo semelhante no meu C # porta de ProtocolBuffers. Há "mensagens" (imutáveis) e "construtores" (mutáveis, usados ??para construir uma mensagem) - e eles vêm como pares de tipos. As interfaces envolvidas são:

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>

Isto significa que a partir de uma mensagem que você pode receber um construtor apropriado (por exemplo, para fazer uma cópia de uma mensagem e alterar alguns bits) e de um construtor que você pode obter uma mensagem apropriada quando você terminar sua construção. É uma usuários bom trabalho da API não precisa realmente se preocupam com isso embora. - É terrivelmente complicado, e tomou várias iterações para chegar onde é

EDIT: Note que isso não impedi-lo de criação de tipos estranhos que usam o tipo de argumento que em si é bom, mas que não é do mesmo tipo. O objetivo é dar benefícios no direito caso, em vez de protegê-lo do errado caso.

Então, se Enum não foram manipulados "especialmente" em Java de qualquer maneira, você poderia (como observado nos comentários) criar os seguintes tipos:

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

Second iria implementar Comparable<First> em vez de Comparable<Second> ... mas First si seria ótimo.

Outras dicas

A seguir é uma versão modificada da explicação do livro Java Generics e Coleções : Temos um Enum declarou

enum Season { WINTER, SPRING, SUMMER, FALL }

que será expandido para uma classe

final class Season extends ...

onde ... é ser a classe base de alguma forma-parametrizado para enumerações. Vamos trabalhar o que isso tem que ser. Bem, um dos requisitos para Season é que ele deve implementar Comparable<Season>. Então, nós estamos indo para necessidade

Season extends ... implements Comparable<Season>

O que você poderia usar para ... que permitiria a este trabalho? Tendo em conta que ele tem que ser uma parametrização de Enum, a única opção é Enum<Season>, de modo que você pode ter:

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

Assim Enum é parametrizado em tipos como Season. Resumo de Season e você começa que o parâmetro de Enum é qualquer tipo que satisfaz

 E extends Enum<E>

Maurice Naftalin (co-autor, Java Generics e Coleções)

Isto pode ser ilustrado por um exemplo simples e uma técnica que pode ser utilizada para implementar as chamadas de método encadeadas para sub-classes. Em um exemplo a seguir setName retorna um Node tão encadeamento não vai funcionar para o 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
}

Assim, poderíamos fazer referência a uma sub-classe em uma declaração genérica, de modo que o City agora retorna o tipo correto:

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!
    }
}

Você não é o único perguntando o que isso significa; consulte Chaotic Java blogue .

“Se uma classe herda essa classe, ele deve passar um parâmetro E. O parâmetro limites de E são de uma classe que estende esta classe com o mesmo parâmetro E”.

Este post foi totalmente esclarecida para mim estes problemas de 'tipos genéricos recursiva'. Eu só queria acrescentar outro caso em que esta estrutura em particular é necessário.

Suponha que você tenha nós genéricos em um 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();
}

Em seguida, você pode ter 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(){...}
}

Se você olhar para o código-fonte Enum, tem o seguinte:

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);
    } 
}

Primeiro de tudo, o que faz E extends Enum<E> média? Isso significa que o parâmetro de tipo é algo que se estende de Enum, e não é parametrizado com um tipo de matéria (é parametrizada por si só).

Isso é relevante se você tiver um enum

public enum MyEnum {
    THING1,
    THING2;
}

que, se eu sei corretamente, é traduzido para

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

Então isso significa que MyEnum recebe os seguintes 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;
}

E ainda mais 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;
    }

Isso faz getDeclaringClass() elenco para o objeto Class<T> adequada.

exemplo uma forma mais clara é a que eu respondi na esta questão onde você não pode evitar esta construção se você quiser especificar um genérico limite.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top