Je pensais avoir bien compris Java génériques, mais je suis tombé sur les éléments suivants dans Java.lang.Enum:
class Enum<E extends Enum<E>>
Quelqu'un pourrait-il expliquer comment interpréter ce paramètre de type? Points bonus pour avoir fourni d'autres exemples d'utilisation d'un paramètre de type similaire.
Cela signifie que l'argument type pour enum doit dériver d'une énumération qui a elle-même le même argument type. Comment cela peut-il arriver? En faisant de l'argument type le nouveau type lui-même. Donc, si j'ai une énumération appelée StatusCode, ce serait équivalent à:
public class StatusCode extends Enum<StatusCode>
Maintenant, si vous vérifiez les contraintes, nous avons Enum<StatusCode>
- donc E=StatusCode
. Vérifions: E
étend-il Enum<StatusCode>
? Oui! Nous allons bien.
Vous vous demandez peut-être à quoi cela sert :) Eh bien, cela signifie que l'API pour Enum peut se référer à elle-même - par exemple, être en mesure de dire que Enum<E>
met en oeuvre Comparable<E>
. La classe de base est capable de faire les comparaisons (dans le cas des énumérations) mais elle peut s'assurer qu'elle ne compare que le bon type d'énumérations entre elles. (EDIT: Eh bien, presque - voir l'édition en bas.)
J'ai utilisé quelque chose de similaire dans mon port C # de ProtocolBuffers. Il existe des "messages" (immuables) et des "constructeurs" (modifiables, utilisés pour créer un message) - et ils se présentent sous forme de paires de types. Les interfaces impliquées sont:
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>
Cela signifie qu'à partir d'un message, vous pouvez obtenir un générateur approprié (par exemple, pour prendre une copie d'un message et modifier quelques bits) et à partir d'un générateur, vous pouvez obtenir un message approprié lorsque vous avez terminé de le créer. C'est un bon travail que les utilisateurs de l'API n'ont pas vraiment besoin de s'en préoccuper - c'est horriblement compliqué, et il a fallu plusieurs itérations pour arriver là où il se trouve.
EDIT: Notez que cela ne vous empêche pas de créer des types impairs qui utilisent un argument de type qui lui-même est correct, mais qui n'est pas le même type. Le but est de donner des avantages dans le cas droit plutôt que de vous protéger contre le cas mauvais.
Donc, si Enum
n'était pas géré "spécialement" dans Java de toute façon, vous pourriez (comme indiqué dans les commentaires) créer les types suivants:
public class First extends Enum<First> {}
public class Second extends Enum<First> {}
Second
implémenterait Comparable<First>
plutôt que Comparable<Second>
... mais First
lui-même serait bien.
Ce qui suit est une version modifiée de l'explication du livre Java Generics and Collections: Nous avons un Enum
déclaré
enum Season { WINTER, SPRING, SUMMER, FALL }
qui sera étendu à une classe
final class Season extends ...
où ...
doit être la classe de base en quelque sorte paramétrée pour Enums. Voyons ce que cela doit être. Eh bien, une des conditions requises pour Season
est qu'il doit implémenter Comparable<Season>
. Nous allons donc avoir besoin
Season extends ... implements Comparable<Season>
Que pourriez-vous utiliser pour ...
qui permettrait à cela de fonctionner? Étant donné qu'il doit s'agir d'un paramétrage de Enum
, le seul choix est Enum<Season>
, pour que vous puissiez avoir:
Season extends Enum<Season>
Enum<Season> implements Comparable<Season>
Donc Enum
est paramétré sur des types comme Season
. Résumé de Season
et vous obtenez que le paramètre de Enum
est tout type qui satisfait
E extends Enum<E>
Maurice Naftalin (co-auteur, Java Generics and Collections)
Ceci peut être illustré par un exemple simple et une technique qui peut être utilisée pour implémenter des appels de méthode chaînés pour des sous-classes. Dans un exemple ci-dessous setName
renvoie un Node
donc le chaînage ne fonctionnera pas pour le 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
}
Nous pourrions donc référencer une sous-classe dans une déclaration générique, de sorte que City
retourne maintenant le type correct:
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!
}
}
Vous n'êtes pas le seul à vous demander ce que cela signifie; voir Chaotic Java blog .
"Si une classe étend cette classe, elle doit passer un paramètre E. Les limites du paramètre E sont pour une classe qui étend cette classe avec le même paramètre E".
Ce post m'a totalement clarifié ce problème des "types génériques récursifs". Je voulais juste ajouter un autre cas où cette structure particulière est nécessaire.
Supposons que vous ayez des nœuds génériques dans un graphe générique:
public abstract class Node<T extends Node<T>>
{
public void addNeighbor(T);
public void addNeighbors(Collection<? extends T> nodes);
public Collection<T> getNeighbor();
}
Ensuite, vous pouvez avoir des graphiques de types spécialisés:
public class City extends Node<City>
{
public void addNeighbor(City){...}
public void addNeighbors(Collection<? extends City> nodes){...}
public Collection<City> getNeighbor(){...}
}
Si vous regardez le code source Enum
, il présente les éléments suivants:
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);
}
}
Tout d'abord, que signifie E extends Enum<E>
? Cela signifie que le paramètre type est quelque chose qui s'étend d'Enum et n'est pas paramétré avec un type brut (il est paramétré par lui-même).
Ceci est pertinent si vous avez une énumération
public enum MyEnum {
THING1,
THING2;
}
qui, si je sais bien, se traduit par
public final class MyEnum extends Enum<MyEnum> {
public static final MyEnum THING1 = new MyEnum();
public static final MyEnum THING2 = new MyEnum();
}
Cela signifie donc que MyEnum reçoit les méthodes suivantes:
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;
}
Et encore plus important,
@SuppressWarnings("unchecked")
public final Class<MyEnum> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<MyEnum>)clazz : (Class<MyEnum>)zuper;
}
Cela fait que getDeclaringClass()
est converti en l'objet Class<T>
Approprié.
Un exemple beaucoup plus clair est celui auquel j'ai répondu cette question où vous ne pouvez pas éviter cette construction si vous voulez spécifier une limite générique.