167 votes

Définition d'un Enum Java

Je pensais avoir bien compris les génériques de Java, mais je suis tombé sur ce qui suit dans java.lang.Enum :

class Enum<E extends Enum<E>>

Quelqu'un pourrait-il m'expliquer comment interpréter ce paramètre de type ? Des points supplémentaires sont accordés si vous fournissez d'autres exemples d'utilisation d'un paramètre de type similaire.

10 votes

Voici l'explication que je préfère : Enum groking (aka Enum<E extends Enum<E>>)

0 votes

Cette question a de meilleures réponses : stackoverflow.com/a/3068001/2413303

1 votes

111voto

Jon Skeet Points 692016

Cela signifie que l'argument de type de l'enum doit dériver d'un enum qui a lui-même le même argument de type. Comment cela peut-il se produire ? 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<StatusCode>

Maintenant, si vous vérifiez les contraintes, nous avons Enum<StatusCode> - donc E=StatusCode . Vérifions : est-ce que E étendre Enum<StatusCode> ? Oui ! Nous allons bien.

Vous vous demandez peut-être quel est l'intérêt de tout ceci :) Eh bien, cela signifie que l'API pour Enum peut se référer à elle-même - par exemple, être capable de dire que Enum<E> met en œuvre Comparable<E> . La classe de base est capable de faire les comparaisons (dans le cas des enums) mais elle peut s'assurer qu'elle ne compare que le bon type d'enums entre eux. (EDIT : Enfin, presque - voir l'édition en bas de page).

J'ai utilisé quelque chose de similaire dans mon portage C# de ProtocolBuffers. Il y a des "messages" (immuables) et des "constructeurs" (mutables, utilisés pour construire un message) - et ils sont présentés sous forme de paires de types. Les interfaces concerné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 constructeur approprié (par exemple, pour prendre une copie d'un message et en modifier certains éléments) et à partir d'un constructeur, vous pouvez obtenir un message approprié lorsque vous avez fini de le construire. C'est une bonne chose que les utilisateurs de l'API n'aient pas besoin de s'en préoccuper - c'est horriblement 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 bizarres qui utilisent un argument de type qui est lui-même correct, mais qui n'est pas le même type. Le but est de donner des avantages dans le droite plutôt que de vous protéger de la mauvais cas.

Donc si Enum ne sont pas traités "spécialement" en 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 mettrait en œuvre Comparable<First> plutôt que Comparable<Second> ... mais First lui-même serait bien.

0 votes

Je suis vraiment intéressé par l'utilité de ce genre de chose par rapport à sa complexité. Quand je C#'ifie votre description anglaise, j'obtiens ceci : <pre> interface IBuilder<TMessage> { IMessage<TMessage> GetMessage() ; } interface IMessage<TMessage> { IBuilder<TMessage> GetBuilder() ; } </pre>

1 votes

@artsrc : Je ne me rappelle pas de mémoire pourquoi il faut être générique à la fois dans le constructeur et dans le message. Je suis presque sûr que je n'aurais pas suivi cette voie si je n'en avais pas eu besoin :)

0 votes

@JonSkeet : J'arrive peut-être trop tard, mais je n'ai pas pu m'empêcher de demander. Est-ce que le <E extends Enum<E>> m'empêche vraiment de passer un argument de type qui n'est pas le nouveau type défini ? Je veux dire, par exemple, je pourrais définir deux sous-types - l'un est public class First extends Enum<First> et l'autre est public class Second extends Enum<First> . La déclaration ne m'empêche pas de mélanger les sous-types ici.

30voto

Maurice Naftalin Points 1718

Ce qui suit est une version modifiée de l'explication du livre. Génériques et collections Java : Nous avons un Enum déclaré

enum Season { WINTER, SPRING, SUMMER, FALL }

qui sera étendu à une classe

final class Season extends ...

donde ... est d'être la classe de base paramétrée pour les Enums. Travaillons ce que cela doit être. Eh bien, l'une des exigences pour Season est qu'il doit mettre en œuvre Comparable<Season> . Donc nous allons 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>

Alors Enum est paramétrée 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)

0 votes

Bien sûr, je suis d'accord que Season extends Enum<Season> . Mais cela n'explique pas pourquoi Enum a un lien sur son paramètre de type, au lieu de simplement class Enum<E>

0 votes

@newacct Parce qu'il n'y a rien de spécial à propos de Season --l'argument ci-dessus s'applique à cualquier type qui Enum peuvent être paramétrés. Ainsi, Weekday (ou autre) doit s'étendre Enum<Weekday> etc., et en général, tout E doit s'étendre Enum<E> . Et c'est exactement ce que dit la déclaration.

0 votes

Tu n'as toujours pas compris ce que j'ai dit. Le fait que la déclaration soit satisfaite n'explique pas pourquoi elle est nécessaire.

23voto

Alan Moore Points 39365

Voici l'explication que je préfère : Enum groking (aka Enum<E extends Enum<E>>)

3voto

kpirkkal Points 244

Vous n'êtes pas le seul à vous demander ce que cela signifie, voir Blog de Chaotic Java .

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

1voto

nozebacle Points 165

Cet article a totalement clarifié pour moi le 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();
}

On peut alors avoir des graphes 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(){...}
}

0 votes

Il me permet toujours de créer un class Foo extends Node<City> où Foo n'est pas lié à City.

1 votes

Bien sûr, et c'est mal ? Je ne le pense pas. Le contrat de base fourni par Node<City> est toujours honoré, seulement votre sous-classe Foo est moins utile puisque vous commencez à travailler avec des Foos mais obtenez des Cities hors de l'ADT. Il peut y avoir un cas d'utilisation pour cela, mais il est probablement plus simple et plus utile de rendre le paramètre générique identique à celui de la sous-classe. Mais dans tous les cas, le concepteur a le choix.

0 votes

@mdma : Je suis d'accord. Alors, quelle est l'utilité du lien, par rapport à juste class Node<T> ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X