Je pensais avoir assez bien compris les génériques Java, mais j’ai découvert ce qui suit dans java.lang.Enum:
class Enum<E extends Enum>
Quelqu’un pourrait-il expliquer comment interpréter ce paramètre de type? Points bonus pour fournir d’autres exemples où un paramètre de type similaire pourrait être utilisé.
Cela signifie que l’argument de type pour enum doit dériver d’une énumération qui a elle-même le même argument de type. Comment cela peut-il arriver? En faisant de l’argument de type le nouveau type lui-même. Donc, si j’ai un enum appelé StatusCode, ce serait équivalent à:
public class StatusCode extends Enum
Maintenant, si vous vérifiez les contraintes, nous avons Enum
– donc E=StatusCode
. Vérifions: E
étend-il Enum
? Oui! Nous allons bien.
Vous vous demandez peut-être à quoi ça sert 🙂 Eh bien, cela signifie que l’API d’Enum peut se référer à elle-même – par exemple, être capable de dire que Enum
implémente Comparable
. 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 de énumération. (EDIT: Eh bien, presque – voir l’édition en bas.)
J’ai utilisé quelque chose de similaire dans mon port C # de ProtocolBuffers. Il y a des “messages” (immuables) et des “constructeurs” (modifiables, utilisés pour créer un message) – et ils se présentent sous la forme de paires de types. Les interfaces impliquées sont:
public interface IBuilder where TMessage : IMessage where TBuilder : IBuilder public interface IMessage where TMessage : IMessage where TBuilder : IBuilder
Cela signifie qu’à partir d’un message, vous pouvez obtenir un constructeur approprié (par exemple, pour prendre une copie d’un message et modifier certains bits) et, à partir d’un générateur, vous pouvez obtenir un message approprié lorsque vous avez fini de le construire. C’est un bon travail que les utilisateurs de l’API n’ont pas besoin de s’en préoccuper – c’est terriblement compliqué et il a fallu plusieurs itérations pour en arriver là.
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 du même type. Le but est de donner des avantages dans le bon cas plutôt que de vous protéger du mauvais cas.
Donc, si Enum
n’était pas traité “spécialement” en Java, vous pourriez (comme indiqué dans les commentaires) créer les types suivants:
public class First extends Enum {} public class Second extends Enum {}
Second
implémenterait Comparable
plutôt que Comparable
… mais First
soi 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 paramétrée pour les Enums. Examinons ce que cela doit être. Eh bien, l’une des exigences de la Season
est qu’elle doit implémenter Comparable
. Donc, nous allons avoir besoin
Season extends ... implements Comparable
Que pourriez-vous utiliser pour ...
cela permettrait à cela de fonctionner? Étant donné qu’il doit s’agir d’un paramétrage d’ Enum
, le seul choix est Enum
, de sorte que vous pouvez avoir:
Season extends Enum Enum implements Comparable
Donc, Enum
est paramétré sur les types comme Season
. Résumé de la Season
et vous obtenez que le paramètre de Enum
est tout type qui satisfait
E extends Enum
Maurice Naftalin (co-auteur, Java Generics and Collections)
Cela peut être illustré par un exemple simple et une technique qui peut être utilisée pour implémenter des appels de méthode en chaîne pour des sous-classes. Dans un exemple ci-dessous, setName
renvoie un Node
afin que le chaînage ne fonctionne pas pour la City
:
class Node { Ssortingng name; Node setName(Ssortingng 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(Ssortingng[] args) { City city = new City() .setName("LA") .setSquare(100); // won't comstack, setName() returns Node }
Nous pourrions donc référencer une sous-classe dans une déclaration générique, de sorte que la City
renvoie maintenant le type correct:
abstract class Node>{ Ssortingng name; SELF setName(Ssortingng 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(Ssortingng[] args) { City city = new City() .setName("LA") .setSquare(100); // ok! } }
Vous n’êtes pas le seul à vous demander ce que cela signifie. voir le blog Java chaotique .
“Si une classe étend cette classe, elle devrait 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”.
Cet article m’a totalement clarifié le problème des «types génériques récursifs». Je voulais juste append un autre cas où cette structure particulière est nécessaire.
Supposons que vous ayez des nœuds génériques dans un graphique générique:
public abstract class Node> { public void addNeighbor(T); public void addNeighbors(Collection extends T> nodes); public Collection getNeighbor(); }
Ensuite, vous pouvez avoir des graphiques de types spécialisés:
public class City extends Node { public void addNeighbor(City){...} public void addNeighbors(Collection extends City> nodes){...} public Collection getNeighbor(){...} }
Dans le cas d’ Enum
, c’est inutile. Tout fonctionnerait pareil si elle était déclarée comme
class Enum
Si vous regardez le code source Enum
, il comporte les éléments suivants:
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, Ssortingng 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); } }
Première chose en premier, qu’est-ce que E extends Enum
veut dire? Cela signifie que le paramètre type est quelque chose qui s’étend de 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 correctement, est traduit en
public final class MyEnum extends Enum { 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 self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; }
Et plus important encore,
@SuppressWarnings("unchecked") public final Class getDeclaringClass() { Class> clazz = getClass(); Class> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class )clazz : (Class )zuper; }
Cela fait que getDeclaringClass()
object Class
approprié.
Un exemple plus clair est celui auquel j’ai répondu sur cette question où vous ne pouvez pas éviter cette construction si vous souhaitez spécifier une limite générique.