Question

Je pensais bien comprendre les génériques Java, mais j’ai trouvé ce qui suit 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 de cas où un paramètre de type similaire pourrait être utilisé.

Était-ce utile?

La solution

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 type le nouveau type lui-même. Donc, si j'ai une énumération appelée StatusCode, cela équivaut à:

public class StatusCode extends Enum<StatusCode>

Maintenant, si vous vérifiez les contraintes, nous avons Enum<StatusCode> - donc E=StatusCode. Vérifions: E s'étend-il Enum<E>? Oui! Nous allons bien.

Vous vous demandez peut-être à quoi ça sert :) Eh bien, cela signifie que l’API pour Enum peut se référer à lui-même - par exemple, être capable de dire que Comparable<E> implémente Enum. La classe de base est capable de faire les comparaisons (dans le cas d'énums), mais elle peut s'assurer de ne comparer que le bon type d'énums les uns avec les autres. (EDIT: Eh bien, presque, voyez l'édition en bas.)

J'ai utilisé quelque chose de similaire dans mon port C # de ProtocolBuffers. Il y a & Quot; messages & Quot; (immuable) et " constructeurs " (mutable, utilisé pour construire un message) - et ils viennent comme des 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, prendre une copie d’un message et en modifier quelques bits) et d’un constructeur vous pouvez obtenir un message approprié lorsque vous avez fini de le créer. C’est un bon travail, mais les utilisateurs de l’API n’ont pas besoin de s’y intéresser, c’est horriblement compliqué et il a fallu plusieurs itérations pour arriver à l’endroit 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 est correct, mais qui n'est pas du même type. Le but est d’offrir des avantages dans le cas correct plutôt que de vous protéger du cas mauvais .

Donc si Second n'a pas été traité & "spécialement &"; de toute façon en Java, vous pouvez (comme indiqué dans les commentaires) créer les types suivants:

public class First extends Enum<First> {}
public class Second extends Enum<First> {}

Comparable<First> implémenterait Comparable<Second> plutôt que First ... mais <=> lui-même irait bien.

Autres conseils

Ce qui suit est une version modifiée de l'explication de l'ouvrage 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 ...

... doit être la classe de base quelque peu paramétrée pour Enums. Allons travailler ce que cela doit être. L’une des conditions requises pour Season est qu’il faut implémenter Comparable<Season>. Donc, nous allons avoir besoin de

Season extends ... implements Comparable<Season>

Que pourriez-vous utiliser pour Enum<Season> pour que cela fonctionne? Étant donné que cela doit être un paramétrage de <=>, le seul choix possible est <=>, de sorte que vous puissiez avoir:

Season extends Enum<Season>
Enum<Season> implements Comparable<Season>

Donc <=> est paramétré sur des types tels que <=>. Résumé de <=> et vous obtenez que le paramètre de <=> est tout type qui satisfait

 E extends Enum<E>

Maurice Naftalin (co-auteur de 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éthodes chaînées pour des sous-classes. Dans l'exemple ci-dessous, setName retourne un Node afin que l'enchaînement ne fonctionne 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 <=> 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; consultez le blog Java chaotique .

& # 8220; Si une classe étend cette classe, elle doit passer un paramètre E. Les limites du paramètre E & # 8217; s sont pour une classe qui étend cette classe avec le même paramètre E & # 8221;.

Ce message m’a totalement clarifié 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 noeuds 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 examinez le Enum code source, vous remarquerez 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 connais bien, est traduit en

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 plus important encore,

    @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 permet à getDeclaringClass() le transtypage vers le bon Class<T> objet.

Un exemple plus clair est celui sur lequel j'ai répondu le cette question où vous ne pouvez pas éviter cette construction si vous souhaitez spécifier une liaison générique.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top