Definição Java Enum

Eu achava que entendia muito bem os genéricos Java, mas depois me deparei com o seguinte em java.lang.Enum:

class Enum<E extends Enum> 

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

Isso significa que o argumento de tipo para enum tem que derivar de um enum que tem o mesmo argumento de tipo. Como isso pode acontecer? Tornando o argumento type o novo tipo em si. Então, se eu tiver um enum chamado StatusCode, seria equivalente a:

 public class StatusCode extends Enum 

Agora, se você verificar as restrições, nós temos Enum – então E=StatusCode . Vamos verificar: o E estende o Enum ? Sim! 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 o Enum implementa o Comparable . A class base é capaz de fazer as comparações (no caso de enums), mas pode se certificar de que compara apenas o tipo certo de enums entre si. (EDIT: Bem, quase – veja a edição na parte inferior.)

Eu usei algo semelhante na minha porta c # de ProtocolBuffers. Existem “mensagens” (imutáveis) e “construtores” (mutáveis, usadas para construir uma mensagem) – e elas vêm como pares de tipos. As interfaces envolvidas são:

 public interface IBuilder where TMessage : IMessage where TBuilder : IBuilder public interface IMessage where TMessage : IMessage where TBuilder : IBuilder 

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

EDIT: Note que isso não impede que você crie tipos ímpares que usam um argumento de tipo que em si é bom, mas que não é o mesmo tipo. O objective é dar benefícios no caso certo , em vez de protegê-lo do caso errado .

Então, se o Enum não fosse tratado “especialmente” em Java, você poderia (como observado nos comentários) criar os seguintes tipos:

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

Second , implementaria Comparable ao invés de Comparable … mas o próprio First estaria bem.

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

 enum Season { WINTER, SPRING, SUMMER, FALL } 

que será expandido para uma class

 final class Season extends ... 

onde ... deve ser a class base de alguma forma parametrizada para Enums. Vamos descobrir o que tem que ser. Bem, um dos requisitos para a Season é que ela implemente Comparable . Então vamos precisar

 Season extends ... implements Comparable 

O que você poderia usar para ... isso permitiria que isso funcionasse? Dado que tem que ser uma parametrização de Enum , a única escolha é Enum , para que você possa ter:

 Season extends Enum Enum implements Comparable 

Então Enum é parametrizado em tipos como Season . Resumo da Season e você percebe que o parâmetro de Enum é qualquer tipo que satisfaça

  E extends Enum 

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

Isso pode ser ilustrado por um exemplo simples e uma técnica que pode ser usada para implementar chamadas de método encadeadas para subclasss. Em um exemplo abaixo, setName retorna um Node então o encadeamento não funcionará para a 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 subclass em uma declaração genérica, para que a City agora retorne o tipo correto:

 abstract class Node>{ String name; SELF setName(String name) { this.name = name; return self(); } protected abstract SELF self(); } class City extends Node { 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 a se perguntar o que isso significa; veja o blog Caotic Java .

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

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

Suponha que você tenha nós genéricos em um gráfico genérico:

 public abstract class Node> { public void addNeighbor(T); public void addNeighbors(Collection< ? extends T> nodes); public Collection getNeighbor(); } 

Então você pode ter charts de tipos especializados:

 public class City extends Node { public void addNeighbor(City){...} public void addNeighbors(Collection< ? extends City> nodes){...} public Collection getNeighbor(){...} } 

No caso de Enum , é inútil. Tudo funcionaria da mesma forma se fosse declarado como

 class Enum 

Se você observar o código-fonte Enum , ele terá o seguinte:

 public abstract class Enum> implements Comparable, Serializable { public final int compareTo(E o) { Enum< ?> other = (Enum< ?>)o; Enum 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 getDeclaringClass() { Class< ?> clazz = getClass(); Class< ?> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class)clazz : (Class)zuper; } public static > T valueOf(Class 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, primeiro, o que E extends Enum significa? Isso significa que o parâmetro type é algo que se estende do Enum e não é parametrizado com um tipo bruto (é 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 { public static final MyEnum THING1 = new MyEnum(); public static final MyEnum THING2 = new MyEnum(); } 

Então isso significa que MyEnum recebe os seguintes methods:

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

Isso faz com que getDeclaringClass() convertido no object Class apropriado.

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