web-dev-qa-db-fra.com

Pourquoi les littéraux enum Java enum ne devraient-ils pas avoir des paramètres de type génériques?

Les énumérations Java sont excellentes. Les génériques aussi. Bien sûr, nous connaissons tous les limites de ce dernier en raison de l'effacement de type. Mais il y a une chose que je ne comprends pas, pourquoi ne puis-je pas créer une énumération comme celle-ci:

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

Ce paramètre de type générique <T> Pourrait à son tour être utile à divers endroits. Imaginez un paramètre de type générique pour une méthode:

public <T> T getValue(MyEnum<T> param);

Ou même dans la classe enum elle-même:

public T convert(Object o);

Exemple plus concret # 1

Étant donné que l'exemple ci-dessus peut sembler trop abstrait pour certains, voici un exemple plus réel de la raison pour laquelle je veux faire cela. Dans cet exemple, je veux utiliser

  • Enums, car alors je peux énumérer un ensemble fini de clés de propriété
  • Génériques, car alors je peux avoir une sécurité de type au niveau de la méthode pour stocker les propriétés
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

Exemple plus concret # 2

J'ai une énumération des types de données:

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

Chaque littéral enum aurait évidemment des propriétés supplémentaires basées sur le type générique <T>, Tout en étant un enum (immuable, singleton, énumérable, etc. etc.)

Question:

Personne n'y a pensé? S'agit-il d'une limitation liée au compilateur? Compte tenu du fait que le mot clé "enum" est implémenté en tant que sucre syntaxique, représentant le code généré pour la JVM, je ne comprends pas cette limitation.

Qui peut me l'expliquer? Avant de répondre, réfléchissez à ceci:

  • Je sais que les types génériques sont effacés :-)
  • Je sais qu'il existe des solutions de contournement en utilisant des objets Class. Ce sont des solutions de contournement.
  • Les types génériques entraînent des conversions de types générées par le compilateur, le cas échéant (par exemple lors de l'appel de la méthode convert ()
  • Le type générique <T> serait sur l'énumération. Il est donc lié par chacun des littéraux de l'énumération. Par conséquent, le compilateur saurait quel type appliquer lors de l'écriture de quelque chose comme String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • Il en va de même pour le paramètre de type générique dans la méthode T getvalue(). Le compilateur peut appliquer un transtypage de type lors de l'appel de String string = someClass.getValue(LITERAL1)
135
Lukas Eder

Ceci est en cours de discussion depuis JEP-301 Enhanced Enums . L'exemple donné dans le JEP est, c'est précisément ce que je cherchais:

enum Argument<X> { // declares generic enum
   STRING<String>(String.class), 
   INTEGER<Integer>(Integer.class), ... ;

   Class<X> clazz;

   Argument(Class<X> clazz) { this.clazz = clazz; }

   Class<X> getClazz() { return clazz; }
}

Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant

Malheureusement, le JEP est toujours aux prises avec des problèmes importants: http://mail.openjdk.Java.net/pipermail/amber-spec-experts/2017-May/000041.html

46
Lukas Eder

La réponse est dans la question:

en raison de l'effacement du type

Aucune de ces deux méthodes n'est possible, car le type d'argument est effacé.

public <T> T getValue(MyEnum<T> param);
public T convert(Object);

Pour réaliser ces méthodes, vous pouvez cependant construire votre énumération comme:

public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}
11
Martin Algesten

Parce que tu ne peux pas. Sérieusement. Cela pourrait être ajouté à la spécification de langue. Ça ne l'a pas été. Cela ajouterait de la complexité. Cet avantage au coût signifie qu'il n'est pas une priorité élevée.

Mise à jour: actuellement en cours d'ajout à la langue sous JEP 301: Enhanced Enums .

5
Tom Hawtin - tackline

Il existe d'autres méthodes dans ENUM qui ne fonctionneraient pas. Que renverrait MyEnum.values()?

Qu'en est-il de MyEnum.valueOf(String name)?

Pour valueOf si vous pensez que le compilateur pourrait faire une méthode générique comme

statique statique MyEnum valueOf (String name);

pour l'appeler comme MyEnum<String> myStringEnum = MyEnum.value("some string property"), ça ne marcherait pas non plus. Par exemple, que faire si vous appelez MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")? Il n'est pas possible d'implémenter cette méthode pour fonctionner correctement, par exemple pour lever une exception ou retourner null lorsque vous l'appelez comme MyEnum.<Int>value("some double property") à cause de l'effacement de type.

4
user1944408

Franchement, cela semble être plus une solution à la recherche d'un problème qu'autre chose.

L'objectif complet de l'énumération Java Java est de modéliser une énumération d'instances de type qui partagent des propriétés similaires de manière à fournir une cohérence et une richesse au-delà de celles de représentations String ou Integer comparables.

Prenons un exemple d'énumération de manuel. Ce n'est ni très utile ni cohérent:

public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

Pourquoi voudrais-je que mes différentes planètes aient des conversions de type générique différentes? Quel problème résout-il? Cela justifie-t-il de compliquer la sémantique du langage? Si j'ai besoin de ce comportement, est-ce un enum le meilleur outil pour y parvenir?

De plus, comment géreriez-vous des conversions complexes?

par exemple

public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

C'est assez facile avec StringInteger pour fournir le nom ou l'ordinal. Mais les génériques vous permettraient de fournir n'importe quel type. Comment géreriez-vous la conversion en MyComplexClass? Maintenant, vous déformez deux constructions en forçant le compilateur à savoir qu'il existe un sous-ensemble limité de types qui peuvent être fournis aux énumérations génériques et en introduisant une confusion supplémentaire dans le concept (génériques) qui semble déjà échapper à beaucoup de programmeurs.

1
nsfyn55