Java getClass () ist nicht kompatibel mit Generika (oder Typvariablen).

Ich habe eine etwas eingängige Java-Sprachspezifikation bemerkt, daher werde ich sie notieren. Die Überprüfungsumgebung ist OpenJDK 11 (Build 28) für Windows.

Was du nicht kannst

public static <T> void hoge(@NonNull T value){
    Class<? extends T> type = value.getClass(); //Kann nicht kompiliert werden
    //Ich möchte mit Typ arbeiten
}

Da der Wert "T" oder seine Unterklasse sein sollte, scheint er auf den ersten Blick in Ordnung zu sein, aber dieser Code kann nicht kompiliert werden.

javac


GenericGetClass.java:3:Error:Inkompatibler Typ: Class<CAP#1>Klasse<? extends T>Kann nicht konvertiert werden:
        Class<? extends T> type = value.getClass();
                                                ^
Wenn T eine Typvariable ist:
Methode<T>hoge(T)T erweitert das in deklarierte Objekt
  CAP#Wenn 1 eine neue Typvariable ist:
    ?CAP von der Erfassung des erweiterten Objekts#1 extends Object
1 Fehler

Ich verstehe die Fehlermeldung nicht ...

Was ist los?

Ich werde es versuchen.

//Natürlich OK
public static void hoge(CharSequence value){
    Class<? extends CharSequence> type = value.getClass();
}
//Dies ist auch in Ordnung
public static <T extends CharSequence> void fuga(T value){
    Class<? extends CharSequence> type = value.getClass();
}
//Dies kann nicht kompiliert werden
public static <T extends CharSequence> void piyo(T value){
    Class<? extends T> type = value.getClass();
}

Es scheint, dass ein Fehler auftritt, wenn die Typvariable "T" für den zuzuweisenden Typ verwendet wird. Nachdem ich es bisher versucht hatte, erinnerte ich mich an die Geschichte, dass die Behandlung des Typs "Object # getClass ()" etwas Besonderes ist, also überprüfte ich es erneut.

Spezifikationen von getClass ()

Gemäß den API-Spezifikationen

Der tatsächliche Ergebnistyp ist Klasse<? extends |X|>ist. Hier,|X|Ist die Eliminierung des statischen Typs des Ausdrucks, für den getClass aufgerufen wird.

In erster Linie wird die Klasse "Object" als "public final Class <?> GetClass ()" definiert

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

Ist möglich, aber wenn Sie es wie eine normale Methode behandeln

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

Es wird nicht möglich sein, wie zu ersetzen. Daher wird es speziell unter Verwendung der Sprache behandelt, und in diesem Fall wird der Typ von "getClass ()" als "Class <? Extends Char Sequence>" angesehen. Insbesondere wird die Typbegrenzung durch den Typ der Variablen zur Kompilierungszeit bestimmt (= "statischer Typ").

Das Problem hierbei ist, dass der statische Typ "gelöscht" wird. "Löschen" scheint eine subtile Übersetzung zu sein, aber Sprachspezifikation §4.6 Es ist wahrscheinlich die Löschung von. Es scheint "List" für "List ", "CharSequence" für "T" anzuzeigen, wenn "Klasse Hoge " erweitert, und so weiter.

In diesem Beispiel?

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

Im Fall von, da "Wert" vom Typ "T" ist und seine "erweiterte" Spezifikation [^ 1] "CharSequence" ist, ist der Radierer von "T" "CharSequence", was der Rückgabewert von "getClass ()" ist. Der Typ ist "? Erweitert CharSequence". Dies ist nicht unbedingt "T" oder seine Unterklassen, daher können Sie es nicht "Klasse <? Erweitert T>" zuweisen.

[^ 1]: Es ist auf Englisch ganz links gebunden, aber gibt es eine gute japanische Übersetzung?

Im ersten Beispiel

public static <T> void hoge(@NonNull T value){
    Class<? extends T> type = value.getClass(); //Kann nicht kompiliert werden
}

Da der Radierer von "T" "Objekt" ist, ist der Typ von "getClass ()" "? Erweitert Objekt".

Warum ist es so eine Spezifikation

Von hier aus ist die Vermutung des Autors durchaus enthalten, aber der Grund, warum der Radiergummi anstelle des generischen Typs angegeben wird, ist, dass dies mit dem generischen Typ geschieht.

ArrayList<String> list = new ArrayList<>();
Class<? extends ArrayList<String>> type = list.getClass(); //Eigentlich ein Kompilierungsfehler

Ich habe ein schlechtes Gefühl.

Da Instanzen der Klasse "Class" aufgrund des Mechanismus [^ 2] keine detaillierten Typinformationen haben, sind Objekte vom Typ "Class <ArrayList >" und vom Typ "Class <ArrayList >" tatsächlich vorhanden Es wird eine Instanz derselben Klasse sein. Die Methode von "Class " kann nicht wissen, auf was die Typvariable "T" von "ArrayList " gesetzt ist.

Das heißt, Variablen vom Typ "Class <ArrayList >" sind gefährlich. Wenn das obige Beispiel kompiliert werden kann,

[^ 2]: Da es für jede Klasse ein eindeutiges Klassenobjekt gibt (damit Sie es mit == vergleichen können)

ArrayList<Integer> another = new ArrayList<>();
another.add(123);
ArrayList<String> casted = type.cast(another); //Zur Laufzeit ist es nur eine Umwandlung in ArrayList, also geht es vorbei
// ...
String str = casted.get(0);//Laufzeitfehler hier?

Es scheint, dass "ClassCastException" an einem unerwarteten Ort auftritt, obwohl es geschrieben und kompiliert wird, ohne den Rohtyp als Ganzes zu verwenden.

Ich verstehe, warum Sie keine generischen Typen verwenden können. Das heißt, ich dachte, ich müsste die Typvariable nicht löschen, aber es ist dasselbe, wenn die Typvariable T im Aufrufer der Liste ` zugewiesen ist. Deshalb sind Programme wie das am Anfang nicht erlaubt.

Beiseite

Ich habe dieses Verhalten bemerkt

@SuppressWarnings("unchecked")
public static <K extends Enum<K>> EnumMap<K,String> of(@NonNull K key, String value){
    Class<K> type = (Class<K>) key.getClass(); //Kompilierungsfehler, wenn nicht umgewandelt
    EnumMap<K,String> map = new EnumMap<>(type); //Das Übergeben einer erweiterten Enum-Konstante führt hier zu einem Laufzeitfehler
    map.put(key, value);
    return map;
}

Es war, als ich Code wie [^ 4] schrieb. In diesem Fall ist "enum" "final", also dachte ich, dass es in Ordnung ist, aber wenn Sie sorgfältig darüber nachdenken, wenn Sie eine Methode für jede Konstante von "enum" definieren, wird es eine separate Klasse sein, also war es out.

[^ 4]: Eigentlich habe ich so etwas wie eine ursprüngliche Implementierung von EnumMap gemacht.

Nachwort

Ich habe zum ersten Mal einen Artikel über Qiita geschrieben, aber wenn Sie darüber nachdenken und ihn schreiben, wird er Ihnen sicherlich dabei helfen, Ihr Wissen zu organisieren. Bitte weisen Sie auf Punkte hin, die nicht erreicht werden können.

Recommended Posts

Java getClass () ist nicht kompatibel mit Generika (oder Typvariablen).
Was ist Thread-sicher (mit Java)
[Persönliches Memo] Der Java-Datentyp ist ärgerlich
Interner Klassentyp: Mit oder ohne Statik
[Java] Einführung in den Wildcard-Typ "Generics Boundary Wildcard"
Informationen zur Bedeutung von Typvariablen, E, T usw., die in in Java verwendeten Generika verwendet werden
[Java] Generika
Der in Java 10 eingeführte Schnittpunkttyp ist erstaunlich (?)
Verwenden Sie Matplotlib aus Java oder Scala mit Matplotlib4j
Was ist der flüchtige Modifikator für Java-Variablen?
[Read Effective Java] Kapitel 2 Punkt 3 "Singleton-Eigenschaften mit privatem Konstruktor oder Aufzählungstyp erzwingen"