Definição de Java Enum

151

Pensei entender bem os genéricos Java, mas me deparei com o seguinte em java.lang.Enum:

class Enum<E extends Enum<E>>

Alguém poderia explicar como interpretar esse parâmetro de tipo? Pontos de bônus por fornecer outros exemplos de onde um parâmetro de tipo semelhante pode ser usado.

Dónal
fonte
9
Aqui está a explicação que eu mais gosto: Groking Enum (aka Enum & E estende Enum & E >>)
Alan Moore
Esta questão tem melhores respostas: stackoverflow.com/a/3068001/2413303
EpicPandaForce
dê uma olhada no sitepoint.com/self-types-with-javas-generics
Arne Burmeister

Respostas:

105

Isso significa que o argumento de tipo para enum deve derivar de um enum que possui o mesmo argumento de tipo. Como isso pode acontecer? Tornando o argumento de tipo o novo tipo em si. Portanto, se eu tenho uma enumeração chamada StatusCode, seria equivalente a:

public class StatusCode extends Enum<StatusCode>

Agora, se você verificar as restrições, temos Enum<StatusCode>- então E=StatusCode. Vamos verificar: se Eestende Enum<StatusCode>? Sim! Nós estamos bem.

Você pode estar se perguntando qual é o sentido disso :) Bem, isso significa que a API do Enum pode se referir a si mesma - por exemplo, ser capaz de dizer que Enum<E>implementa Comparable<E>. A classe base é capaz de fazer as comparações (no caso de enumerações), mas pode garantir que apenas compare o tipo certo de enumeração entre si. (EDIT: Bem, quase - veja a edição na parte inferior.)

Eu usei algo semelhante na minha porta C # do ProtocolBuffers. Existem "mensagens" (imutáveis) e "construtores" (mutáveis, usadas para criar uma mensagem) - e elas 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>

Isso significa que, a partir de uma mensagem, você pode obter um construtor apropriado (por exemplo, para tirar uma cópia de uma mensagem e alterar alguns bits) e, a partir de um construtor, você pode receber uma mensagem apropriada quando terminar de construí-la. É um bom trabalho que os usuários da API não precisem realmente se preocupar com isso - é terrivelmente complicado e levou várias iterações para chegar onde está.

Edição: Observe que isso não impede você de criar tipos ímpares que usam um argumento de tipo que por si só é bom, mas que não é do mesmo tipo. O objetivo é oferecer benefícios no caso certo , em vez de protegê-lo do caso errado .

Portanto, se Enumnão fosse tratado "especialmente" em Java, você poderia (como observado nos comentários) criar os seguintes tipos:

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

Secondimplementaria em Comparable<First>vez de Comparable<Second>... mas em Firstsi seria bom.

Jon Skeet
fonte
1
@artsrc: Não me lembro de imediato por que ele precisa ser genérico no construtor e na mensagem. Eu tenho certeza que eu não teria ido por esse caminho se eu não tivesse precisava dele embora :)
Jon Skeet
1
@SayemAhmed: Sim, isso não impede que esse aspecto misture os tipos. Vou adicionar uma observação sobre isso.
Jon Skeet
1
"Eu usei algo semelhante na minha porta C # do ProtocolBuffers." Mas isso é diferente porque os construtores têm métodos de instância que retornam o tipo de parâmetro type. Enumnão possui nenhum método de instância que retorne o tipo de parâmetro type
newacct
1
@ JonSkeet: Dado que as classes enum são sempre geradas automaticamente, estou afirmando que isso class Enum<E>é suficiente em todos os casos. E em genéricos, você só deve usar um limite mais restritivo se for realmente necessário para garantir a segurança do tipo.
newacct
1
@JonSkeet: Além disso, se Enumsubclasses nem sempre foram gerada automaticamente, a única razão que você precisaria de class Enum<E extends Enum<?>>mais class Enum<E>é a capacidade de acesso ordinalpara compareTo(). No entanto, se você pensar sobre isso, não faz sentido do ponto de vista do idioma permitir comparar dois tipos diferentes de enumerações por meio de seus ordinais. Portanto, a implementação desses Enum.compareTo()usos ordinalsó faz sentido no contexto de Enumsubclasses geradas automaticamente. Se você pudesse subclasses manualmente Enum, compareToprovavelmente teria que ser abstract.
newacct
27

A seguir, uma versão modificada da explicação do livro Java Generics and Collections : Temos uma Enumdeclaração

enum Season { WINTER, SPRING, SUMMER, FALL }

que será expandido para uma classe

final class Season extends ...

onde ...deve ser a classe base de alguma forma parametrizada para Enums. Vamos descobrir o que isso tem que ser. Bem, um dos requisitos para Seasonisso é que ele deve ser implementado Comparable<Season>. Então, vamos precisar

Season extends ... implements Comparable<Season>

O que você poderia usar para ...permitir que isso funcionasse? Dado que deve ser uma parametrização Enum, a única opção é Enum<Season>, para que você possa ter:

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

Então, Enumé parametrizado em tipos como Season. Resumo de Seasone você obtém que o parâmetro de Enumé qualquer tipo que satisfaça

 E extends Enum<E>

Maurice Naftalin (co-autor, Java Generics and Collections)

Maurice Naftalin
fonte
1
@ newacct OK, eu entendi agora: você gostaria que todas as enums fossem instâncias de Enum <E>, certo? (Como se são instâncias de um subtipo de Enum, o argumento acima se aplica.) Mas você não terá mais enumerações com segurança de tipo, perdendo assim o ponto de ter enumerações.
Maurice Naftalin
1
@ newacct Você não quer insistir que Seasonimplementa Comparable<Season>?
Maurice Naftalin
2
@newacct Veja a definição de Enum. Para comparar uma instância com outra, é necessário comparar seus ordinais. Portanto, o argumento do compareTométodo deve ter sido declarado como um Enumsubtipo, ou o compilador (corretamente) dirá que não possui um ordinal.
Maurice Naftalin
2
@MauriceNaftalin: Se o Java não proibisse a subclassificação manual Enum, seria possível class OneEnum extends Enum<AnotherEnum>{}, mesmo com o que Enumé declarado agora. Não faria muito sentido para ser capaz de comparar um tipo de enumeração com o outro, por isso, em seguida, Enum's compareTonão faria sentido, conforme declarado de qualquer maneira. Os limites não fornecem nenhuma ajuda para isso.
New251
2
@MauriceNaftalin: Se o ordinal foi o motivo, public class Enum<E extends Enum<?>>também seria suficiente.
newacct
6

Isso pode ser ilustrado por um exemplo simples e uma técnica que pode ser usada para implementar chamadas de método em cadeia para subclasses. Em um exemplo abaixo, setNameum Nodeencadeamento não 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
}

Assim, poderíamos referenciar uma subclasse em uma declaração genérica, para que Cityagora retorne 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!
    }
}
Andrey Chaschev
fonte
Sua solução possui um elenco não verificado: return (CHILD) this;considere adicionar um método getThis (): protected CHILD getThis() { return this; } Consulte: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland
@Roland obrigado por um link, eu emprestei uma idéia dele. Você poderia me explicar ou dirigir a um artigo explicando por que essa é uma prática ruim nesse caso em particular? O método no link requer mais digitação e esse é o principal argumento para evitar isso. Eu nunca vi erros de conversão neste caso + sei que existem alguns erros de conversão inevitáveis ​​- ou seja, quando alguém armazena objetos de vários tipos em uma mesma coleção. Portanto, se elencos não verificados não são críticos e o design é algo complexo ( Node<T>não é o caso), eu os estou ignorando para economizar tempo.
Andrey Chaschev
Sua edição não é diferente de antes, além de adicionar algum açúcar sintático, considere que o código a seguir será compilado, mas gera um erro em tempo de execução: `Nó <Cidade> nó = novo Nó <Cidade> () .setName (" nó "). setSquare (1); `Se você olhar para o código de java byte, verá que, devido ao apagamento do tipo, a instrução return (SELF) this;é compilada return this;, para que você possa deixar de fora.
Roland
@Roland obrigado, é isso que eu precisava - atualizará o exemplo quando estiver livre.
Andrey Chaschev
O link a seguir também é bom: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland
3

Você não é o único se perguntando o que isso significa; consulte o blog Java caótico .

“Se uma classe estender essa classe, deve passar o parâmetro E. Os limites do parâmetro E são para uma classe que estende essa classe com o mesmo parâmetro E”.

kpirkkal
fonte
1

Este post me esclareceu totalmente esse problema de 'tipos genéricos recursivos'. Eu só queria adicionar outro caso em que essa estrutura específica seja necessária.

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

Então 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(){...}
}
nozebacle
fonte
Ainda me permite criar um local class Foo extends Node<City>onde Foo não tem relação com a City.
newacct
1
Claro, e isso está errado? Acho que não. O contrato base fornecido pelo Nó <Cidade> ainda é respeitado, apenas sua subclasse Foo é menos útil desde que você começa a trabalhar com Foos, mas tira Cidades do ADT. Pode haver um caso de uso para isso, mas provavelmente é mais simples e mais útil apenas para tornar o parâmetro genérico igual à subclasse. Mas de qualquer forma, o designer tem essa escolha.
Mdma
@mdma: eu concordo. Então, para que serve o limite, apenas class Node<T>?
newacct
1
@ nozebacle: Seu exemplo não demonstra que "essa estrutura específica é necessária". class Node<T>é totalmente consistente com o seu exemplo.
Newacct
1

Se você olhar para o Enumcódigo-fonte, ele possui 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);
    } 
}

Primeira coisa, o que E extends Enum<E>significa? Isso significa que o parâmetro type é algo que se estende do Enum e não é parametrizado com um tipo bruto (ele é parametrizado 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();
}

Portanto, isso significa que o 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 o getDeclaringClass()lançamento para o Class<T>objeto apropriado .

Um exemplo muito mais claro é o que eu respondi nesta pergunta em que você não pode evitar essa construção se desejar especificar um limite genérico.

EpicPandaForce
fonte
Nada do que você mostrou compareToou getDeclaringClassexige o extends Enum<E>limite.
Newacct
0

Segundo a wikipedia, esse padrão é chamado de padrão de modelo curiosamente recorrente . Basicamente, usando o padrão CRTP, podemos facilmente nos referir ao tipo de subclasse sem conversão de tipo, o que significa que, usando o padrão, podemos imitar a função virtual.

Hanzhou Tang
fonte