Pourquoi les littéraux d'énumération Java ne pourraient-ils pas avoir des paramètres de type génériques?

148

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 peut <T>à 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 n ° 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 souhaite utiliser

  • Enums, car je peux alors é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 n ° 2

J'ai une énumération de 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 d'énumération aurait évidemment des propriétés supplémentaires basées sur le type générique <T>, tout en étant en même temps une énumération (immuable, singleton, énumérable, etc.)

Question:

Personne n'y a pensé? Est-ce 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 du code généré vers la JVM, je ne comprends pas cette limitation.

Qui peut m'expliquer cela? Avant de répondre, considérez ceci:

  • Je sais que les types génériques sont effacés :-)
  • Je sais qu'il existe des solutions de contournement utilisant des objets de classe. Ce sont des solutions de contournement.
  • Les types génériques entraînent des casts de type générés 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. Par conséquent, il est 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 commeString string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • La même chose s'applique au paramètre de type générique dans la T getvalue()méthode. Le compilateur peut appliquer la conversion de type lors de l'appelString string = someClass.getValue(LITERAL1)
Lukas Eder
la source
3
Je ne comprends pas non plus cette limitation. Je suis tombé sur ceci récemment où mon énumération contenait différents types «comparables», et avec les génériques, seuls les types comparables des mêmes types peuvent être comparés sans avertissements qui doivent être supprimés (même si au moment de l'exécution, les bons seraient comparés). J'aurais pu me débarrasser de ces avertissements en utilisant un type lié dans l'énumération pour spécifier quel type comparable était pris en charge, mais à la place, j'ai dû ajouter l'annotation SuppressWarnings - pas moyen de contourner cela! Puisque compareTo lance de toute façon une exception de cast de classe, c'est ok je suppose, mais quand même ...
MetroidFan2002
5
(+1) Je suis juste en train d'essayer de combler un écart de sécurité de type dans mon projet, arrêté net par cette limitation arbitraire. Considérez ceci: transformez le enumen un idiome "typesafe enum" que nous utilisions avant Java 1.5. Soudainement, vous pouvez paramétrer vos membres enum. C'est probablement ce que je vais faire maintenant.
Marko Topolnik
1
@EdwinDalorzo: mise à jour de la question avec un exemple concret de jOOQ , où cela aurait été extrêmement utile dans le passé.
Lukas Eder
2
@LukasEder Je vois votre point de vue maintenant. On dirait une nouvelle fonctionnalité cool. Vous devriez peut-être le suggérer dans la liste de diffusion des pièces de monnaie. Je vois d'autres propositions intéressantes sur les énumérations, mais pas une comme la vôtre.
Edwin Dalorzo
1
Entièrement d'accord. Les énumérations sans génériques sont paralysées. Votre cas n ° 1 est aussi le mien. Si j'ai besoin d'une énumération générique, j'abandonne JDK5 et je l'implémente dans le style Java 1.4. Cette approche a également un avantage supplémentaire : je ne suis pas obligé d'avoir toutes les constantes dans une classe ou même un package. Ainsi, le style package par fonctionnalité est beaucoup mieux réalisé. C'est parfait juste pour les "enums" de type configuration - les constantes sont réparties dans des packages selon leur signification logique (et si je souhaite les voir toutes, j'ai le type hieararchy affiché).
Tomáš Záluský

Réponses:

50

Ceci est maintenant discuté à partir des Enums améliorés JEP-301 . L'exemple donné dans le JEP est, et c'est précisément ce que je recherchais:

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

Lukas Eder
la source
2
En décembre 2018, il y avait à nouveau des signes de vie autour du JEP 301 , mais le survol de cette discussion montre clairement que les problèmes sont encore loin d'être résolus.
Pont
11

La réponse est dans la question:

à cause 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 suit:

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

    private Class<?> clazz;

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

    ...

}
Martin Algesten
la source
2
Eh bien, l'effacement a lieu au moment de la compilation. Mais le compilateur peut utiliser des informations de type génériques pour les vérifications de type. Et puis "convertit" le type générique en casts de type. Je vais reformuler la question
Lukas Eder
2
Je ne suis pas sûr de suivre entièrement. Prenez public T convert(Object);. J'imagine que cette méthode pourrait par exemple restreindre un tas de types différents à <T>, qui par exemple est une chaîne. La construction de l'objet String est à l'exécution - rien que le compilateur ne fait. Par conséquent, vous devez connaître le type d'exécution, tel que String.class. Ou est-ce que je manque quelque chose?
Martin Algesten
6
Je pense que vous manquez le fait que le type générique <T> est sur l'énumération (ou sa classe générée). Les seules instances de l'énumération sont ses littéraux, qui fournissent tous une liaison de type générique constante. Il n'y a donc pas d'ambiguïté sur T dans public T convert (Object);
Lukas Eder
2
Ah! Je l'ai. Vous êtes plus intelligent que moi :)
Martin Algesten
1
Je suppose que cela revient à la classe pour une énumération évaluée de la même manière que tout le reste. En java, bien que les choses semblent constantes, elles ne le sont pas. Considérez public final static String FOO;avec un bloc statique static { FOO = "bar"; }- les constantes sont également évaluées même si elles sont connues au moment de la compilation.
Martin Algesten
5

Parce que tu ne peux pas. Sérieusement. Cela pourrait être ajouté à la spécification de la langue. Cela n'a pas été le cas. Cela ajouterait une certaine complexité. Cet avantage par rapport aux coûts signifie que ce n'est pas une priorité élevée.

Mise à jour: en cours d'ajout à la langue sous JEP 301: Enums améliorés .

Tom Hawtin - Tacle
la source
Pourriez-vous nous expliquer les complications?
Mr_and_Mrs_D
4
J'y réfléchis depuis quelques jours maintenant et je ne vois toujours aucune complication.
Marko Topolnik
4

Il existe d'autres méthodes dans ENUM qui ne fonctionneraient pas. Qu'est-ce qui MyEnum.values()reviendrait?

Et quoi MyEnum.valueOf(String name)?

Pour valueOf si vous pensez que ce compilateur pourrait créer une méthode générique comme

public static MyEnum valueOf (String name);

pour l'appeler comme ça 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 qu'elle fonctionne correctement, par exemple pour lever une exception ou renvoyer null lorsque vous l'appelez comme en MyEnum.<Int>value("some double property")raison de l'effacement de type.

user1944408
la source
2
Pourquoi ne fonctionneraient-ils pas? Ils utiliseraient simplement des jokers ...: MyEnum<?>[] values()etMyEnum<?> valueOf(...)
Lukas Eder
1
Mais alors, vous ne pouvez pas faire une affectation comme celle-ci MyEnum<Int> blabla = valueOf("some double property");car les types ne sont pas compatibles. Vous souhaitez également dans ce cas obtenir null car vous souhaitez renvoyer MyEnum <Int> qui n'existe pas pour le nom de propriété double et vous ne pouvez pas faire fonctionner cette méthode correctement en raison de l'effacement.
user1944408
De plus, si vous parcourez les valeurs (), vous devez utiliser MyEnum <?>, Ce qui n'est généralement pas ce que vous voulez car, par exemple, vous ne pouvez pas parcourir uniquement vos propriétés Int. Aussi, vous auriez besoin de faire beaucoup de cast que vous voulez éviter, je suggérerais de créer différentes énumérations pour chaque type ou de créer votre propre classe avec des instances ...
user1944408
Eh bien, je suppose que vous ne pouvez pas avoir les deux. Habituellement, je recourt à mes propres implémentations "enum". C'est juste que cela Enuma beaucoup d'autres fonctionnalités utiles, et celle-ci serait facultative et très utile aussi ...
Lukas Eder
0

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

Le but entier de l'énumération java est de modéliser une énumération d'instances de type qui partagent des propriétés similaires d'une manière qui offre une cohérence et une richesse au-delà de celles des représentations String ou Integer comparables.

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

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

Pourquoi voudrais-je que mes différentes planètes aient différentes conversions de types génériques? 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 qu'une énumération est 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>;
}

Il est assez facile String Integerde 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 vers MyComplexClass? Maintenant, vous détruisez 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 (Generics) qui semble déjà échapper à beaucoup de programmeurs.

nsfyn55
la source
17
Penser à quelques exemples où cela ne serait pas utile est un terrible argument selon lequel cela ne serait jamais utile.
Elias Vasylenko
1
Les exemples soutiennent ce point. Les instances enum sont des sous-classes du type (sympa et simple) et que l'inclusion de génériques est une boîte de vers en termes de complexité pour un avantage très obscur. Si vous voulez me rejeter, vous devriez également voter contre Tom Hawtin ci-dessous, qui a dit la même chose en moins de mots
nsfyn55
1
@ nsfyn55 Les énumérations sont l'une des fonctionnalités de langage les plus complexes et les plus magiques de Java. Il n'y a pas une seule autre fonctionnalité de langage qui génère automatiquement des méthodes statiques, par exemple. De plus, chaque type enum est déjà une instance d'un type générique.
Marko Topolnik
1
@ nsfyn55 Le but de la conception était de rendre les membres enum puissants et flexibles, c'est pourquoi ils prennent en charge des fonctionnalités avancées telles que les méthodes d'instance personnalisées et même les variables d'instance mutables. Ils sont conçus pour avoir un comportement spécialisé pour chaque membre individuellement afin qu'ils puissent participer en tant que collaborateurs actifs à des scénarios d'utilisation complexes. Surtout, l'énumération Java a été conçue pour sécuriser le type d' idiome d'énumération générale , mais le manque de paramètres de type le rend malheureusement en deçà de cet objectif le plus cher. Je suis convaincu qu'il y avait une raison très précise à cela.
Marko Topolnik
1
Je ne l' ai pas remarqué votre mention contravariance ... Java ne le soutenir, que son utilisation du site, ce qui le rend un peu maniable bits. interface Converter<IN, OUT> { OUT convert(IN in); } <E> Set<E> convertListToSet(List<E> in, Converter<? super List<E>, ? extends Set<E>> converter) { return converter.convert(in); }Nous devons déterminer manuellement à chaque fois quel type est consommé et lequel produit, et spécifier les limites en conséquence.
Marko Topolnik le
-2

Becasue "enum" est l'abréviation d'énumération. C'est juste un ensemble de constantes nommées qui remplacent les nombres ordinaux pour rendre le code plus lisible.

Je ne vois pas quelle pourrait être la signification voulue d'une constante paramétrée par type.

Ingo
la source
1
Dans java.lang.String: public static final Comparator<String> CASE_INSENSITIVE_ORDER. Tu vois maintenant? :-)
Lukas Eder
4
Vous avez dit que vous ne voyez pas ce que pourrait être la signification d'une constante paramétrée par type. Je vous ai donc montré une constante paramétrée par type (pas un pointeur de fonction).
Lukas Eder
Bien sûr que non, car le langage Java ne connaît pas un pointeur de fonction. Pourtant, ce n'est pas la constante qui est paramétrée sur le type, mais c'est le type. Et le paramètre de type lui-même est constant, pas une variable de type.
Ingo
Mais la syntaxe enum n'est que du sucre syntaxique. En dessous, ils sont exactement comme CASE_INSENSITIVE_ORDER... Si Comparator<T>c'était une énumération, pourquoi ne pas avoir des littéraux avec une liaison associée <T>?
Lukas Eder
Si Comparator <T> était une énumération, nous pourrions en effet avoir toutes sortes de choses étranges. Peut-être quelque chose comme Integer <T>. Mais ce n'est pas le cas.
Ingo
-3

Je pense parce que fondamentalement Enums ne peut pas être instancié

Où définiriez-vous la classe T, si JVM vous permettait de le faire?

L'énumération est des données qui sont censées être toujours les mêmes, ou du moins, qui ne changeront pas de façon dynamique.

nouveau MyEnum <> ()?

L'approche suivante peut néanmoins être utile

public enum MyEnum{

    LITERAL1("s"),
    LITERAL2("a"),
    LITERAL3(2);

    private Object o;

    private MyEnum(Object o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }   
}
capsula
la source
8
Pas sûr que j'aime la setO()méthode sur un fichier enum. Je considère les enums constantes et pour moi cela implique immuable . Donc même si c'est possible, je ne le ferais pas.
Martin Algesten
2
Les énumérations sont instanciées dans le code généré par le compilateur. Chaque littéral est en fait créé en appelant les constructeurs privés. Par conséquent, il y aurait une chance de passer le type générique au constructeur au moment de la compilation.
Lukas Eder
2
Les setters sur les énumérations sont tout à fait normaux. Surtout si vous utilisez l'énumération pour créer un singleton (Item 3 Effective Java par Joshua Bloch)
Preston