web-dev-qa-db-fra.com

Java Définition d'énumération

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.

144
Dónal

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.

103
Jon Skeet

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

26
Maurice Naftalin

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!
    }
}
5
Andrey Chaschev

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

2
kpirkkal

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(){...}
}
1
nozebacle

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.

1
EpicPandaForce