definição Java Enum
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.
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.