Java getClass () est incompatible avec les génériques (ou les variables de type)

J'ai remarqué une spécification du langage Java légèrement contre-intuitive, je vais donc en prendre note. L'environnement de vérification est OpenJDK 11 (build 28) pour Windows.

Ce que tu ne peux pas faire

public static <T> void hoge(@NonNull T value){
    Class<? extends T> type = value.getClass(); //Impossible de compiler
    //Je veux travailler avec le type
}

Puisque la valeur devrait être «T» ou sa sous-classe, cela semble être correct à première vue, mais ce code ne peut pas être compilé.

javac


GenericGetClass.java:3:Erreur:Type incompatible: Class<CAP#1>Classe<? extends T>Ne peut pas être converti en:
        Class<? extends T> type = value.getClass();
                                                ^
Si T est une variable de type:
Méthode<T>hoge(T)T étend l'objet déclaré dans
  CAP#Si 1 est une nouvelle variable de type:
    ?CAP de la capture de l'objet étend#1 extends Object
1 erreur

Je ne comprends pas le message d'erreur ...

Qu'est-ce qui ne va pas?

Je vais en essayer.

//Bien sûr OK
public static void hoge(CharSequence value){
    Class<? extends CharSequence> type = value.getClass();
}
//C'est aussi OK
public static <T extends CharSequence> void fuga(T value){
    Class<? extends CharSequence> type = value.getClass();
}
//Cela ne peut pas être compilé
public static <T extends CharSequence> void piyo(T value){
    Class<? extends T> type = value.getClass();
}

Il semble qu'une erreur se produira si la variable de type «T» est utilisée pour le type à attribuer. Après avoir essayé jusqu'à présent, je me suis souvenu de l'histoire selon laquelle la gestion du type de ʻObject # getClass () `est spéciale, alors je l'ai vérifiée à nouveau.

Spécifications de getClass ()

Selon les Spécifications de l'API

Le type de résultat réel est Classe<? extends |X|>est. ici,|X|Élimination du type statique de l'expression pour laquelle getClass est appelé.

En premier lieu, la classe ʻObject est définie comme Public final Class <?> GetClass () `, donc

CharSequence cs = ...;
Class<?> type = cs.getClass()

C'est possible, mais si vous le traitez comme une méthode normale

Class<? extends CharSequence> type = cs.getClass()

Il ne sera pas possible de remplacer comme. Par conséquent, il est traité spécialement en utilisant le langage, et dans ce cas, le type de getClass () est considéré comme Class <? Extends CharSequence>. Plus précisément, la limite de type est déterminée par le type de la variable au moment de la compilation (= "type statique").

Le problème ici est qu'il "efface le type statique". "Erase" semble être une traduction subtile, mais Spécification du langage §4.6 C'est probablement l'effacement de. Il semble indiquer List pour List <String>, CharSequence pour T whenclass Hoge <T extend CharSequence>, et ainsi de suite.

Dans cet exemple?

public static <T extends CharSequence> void fuga(T value){
    Class<? extends CharSequence> type = value.getClass(); // OK
}

Dans le cas de, puisque value est de type T et que sa spécification ʻextends [^ 1] est CharSequence, la gomme de T est CharSequence, qui est la valeur de retour de getClass ()`. Le type est «? Etend CharSequence». Ce n'est pas nécessairement «T» ou ses sous-classes, il ne peut donc pas être attribué à «Classe <? Etend T>».

[^ 1]: Il est relié le plus à gauche en anglais, mais y a-t-il une bonne traduction en japonais?

Dans le premier exemple

public static <T> void hoge(@NonNull T value){
    Class<? extends T> type = value.getClass(); //Impossible de compiler
}

Puisque la gomme de T est ʻObject, le type de getClass () ʻest? Extends Object.

Pourquoi est-ce une telle spécification

À partir de là, la supposition de l'auteur est tout à fait incluse, mais la raison pour laquelle la gomme est spécifiée au lieu du type générique en premier lieu est que cela se produit avec le type générique.

ArrayList<String> list = new ArrayList<>();
Class<? extends ArrayList<String>> type = list.getClass(); //En fait une erreur de compilation

J'ai un mauvais pressentiment.

Une instance de la classe Class n'a pas d'informations de type détaillées en raison de son mécanisme [^ 2], donc les objets de type Class <ArrayList <String >> et du typeClass <ArrayList <Integer >>sont en fait Ce sera une instance de la même Class <ArrayList>. La méthode de Class <ArrayList> ne peut pas savoir à quoi la variable de type T de ʻArrayList ` est définie.

Autrement dit, les variables de type «Class <ArrayList >» sont dangereuses. Si l'exemple ci-dessus peut être compilé,

[^ 2]: Puisqu'il existe un objet de classe unique pour chaque classe (vous pouvez donc comparer avec ==)

ArrayList<Integer> another = new ArrayList<>();
another.add(123);
ArrayList<String> casted = type.cast(another); //Au moment de l'exécution, c'est juste un cast vers ArrayList, donc ça passe
// ...
String str = casted.get(0);//Erreur d'exécution ici?

Il est probable que vous obteniez une ClassCastException à un emplacement inattendu, même si vous l'avez écrite et compilée sans utiliser le type brut globalement.

Je comprends pourquoi vous ne pouvez pas utiliser de types génériques. Cela dit, je pensais que je n'aurais pas à supprimer la variable de type, mais c'est la même chose si la variable de type «T» est affectée à «List » sur l'appelant. Par conséquent, il semble que les programmes comme celui du début ne soient pas autorisés.

De côté

J'ai remarqué ce comportement

@SuppressWarnings("unchecked")
public static <K extends Enum<K>> EnumMap<K,String> of(@NonNull K key, String value){
    Class<K> type = (Class<K>) key.getClass(); //Erreur de compilation si non cast
    EnumMap<K,String> map = new EnumMap<>(type); //Passer une constante Enum étendue entraînera ici une erreur d'exécution
    map.put(key, value);
    return map;
}

C'était quand j'écrivais du code comme [^ 4]. Dans ce cas, ʻenumestfinal, donc j'ai pensé que c'était correct, mais si vous y réfléchissez bien, si vous définissez une méthode pour chaque constante de ʻenum, ce sera une classe distincte, donc elle était sortie.

[^ 4]: En fait, je faisais quelque chose comme une implémentation originale d'EnumMap.

Épilogue

J'ai écrit un article sur Qiita pour la première fois, mais si vous y réfléchissez et l'écrivez, cela vous aidera certainement à organiser vos connaissances. Veuillez signaler tous les points qui ne peuvent être atteints.

Recommended Posts

Java getClass () est incompatible avec les génériques (ou les variables de type)
Qu'est-ce que Thread Safe (avec Java)
[Note personnelle] Le type de données Java est ennuyeux
Type de classe interne: avec ou sans statique
[Java] Présentation du type générique de limite générique
À propos de la signification des variables de type, E, T, etc. utilisées dans les génériques utilisés en Java
[Java] Génériques
Le type d'intersection introduit dans Java 10 est incroyable (?)
Utilisez Matplotlib depuis Java ou Scala avec Matplotlib4j
Quel est le modificateur volatile pour les variables Java?
[Lire Java efficace] Chapitre 2 Item 3 "Forcer les caractéristiques de singleton avec un constructeur privé ou un type enum"