Définition Java Enum

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 ... 

... 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 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 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.