W3docs

Java Class-Objekte

Class<T>-Objekte in Java erhalten mit Object.getClass(), .class-Literalen und Class.forName.

Alles in der Reflection beginnt mit einem Class-Objekt. Für jeden Typ, den die JVM lädt — jede Klasse, jedes Interface, jeden Array-Typ, jedes Enum, jede Annotation und sogar jeden Primitiven — gibt es genau eine Class-Instanz, die ihn beschreibt. Dieses Objekt ist Ihr Zugang zur Struktur des Typs: sein Name, seine Oberklasse, seine Member, seine Annotationen. Dieses Kapitel behandelt die drei Wege, ein Class-Objekt zu erhalten, was Class<T> enthält und die kleinen Überraschungen, über die man stolpern kann.

Drei Wege, ein Class-Objekt zu erhalten

Es gibt genau drei Routen, und jede passt zu einer anderen Situation.

1. Das .class-Literal — der Typ ist zur Kompilierzeit bekannt.

Class<String> c1 = String.class;
Class<int[]> c2 = int[].class;
Class<Integer> c3 = int.class == Integer.class ? null : int.class;   // see "primitives" below

Dies ist typsicher zur Kompilierzeit und am schnellsten — es gibt keine Suche, der Compiler bakt eine Referenz ein. Verwenden Sie es, wann immer Sie den Typ benennen können.

2. Object.getClass() — Sie haben eine Instanz.

Object o = "hello";
Class<?> c = o.getClass();        // class java.lang.String

getClass() gibt die Laufzeit-Klasse des Objekts zurück, die eine Unterklasse des deklarierten Variablentyps sein kann. Object o = new ArrayList<>() liefert o.getClass() == ArrayList.class, nicht Object.class. Sein statischer Typ ist Class<?>, weil der Compiler nur weiß, dass o irgendein Object ist.

3. Class.forName(String) — Sie haben nur einen Namen.

Class<?> c = Class.forName("java.util.ArrayList");

Dies ist der dynamische Weg: ein vollqualifizierter Klassenname als String, der zur Laufzeit aufgelöst wird. Es wirft ClassNotFoundException, wenn keine solche Klasse geladen werden kann. Das ist, was Plugin-Loader und JDBC-Treiber verwenden. Eine geladene, aber nicht initialisierte Variante existiert: Class.forName(name, false, classLoader) überspringt statische Initialisierer, bis die Klasse zum ersten Mal aktiv verwendet wird.

Was Class<T> enthält

Ein Class-Objekt ist eine reichhaltige Beschreibung. Die wichtigsten Methoden:

Class<?> c = ArrayList.class;

c.getName();              // "java.util.ArrayList"        (binary name)
c.getSimpleName();        // "ArrayList"
c.getCanonicalName();     // "java.util.ArrayList"        (source-like)
c.getPackageName();       // "java.util"
c.getSuperclass();        // class java.util.AbstractList
c.getInterfaces();        // [List, RandomAccess, Cloneable, Serializable]
c.getModifiers();         // int bitset → Modifier.isPublic(...) etc.
c.isInterface();          // false
c.isEnum(); c.isArray(); c.isPrimitive(); c.isAnnotation();

Von einem Class-Objekt aus erreichen Sie jedes Member: getDeclaredFields(), getDeclaredMethods(), getDeclaredConstructors(), sowie ihre öffentlichen/geerbten get…-Gegenstücke (die Aufteilung aus dem Einführungskapitel). Die späteren Kapitel gehen auf jedes einzeln ein.

Binärname vs. einfacher Name vs. kanonischer Name

Die Benennungsmethoden unterscheiden sich auf eine Art, die beim Loggen oder Vergleichen Probleme bereitet:

TypgetName()getSimpleName()getCanonicalName()
Stringjava.lang.StringStringjava.lang.String
int[][Iint[]int[]
String[][Ljava.lang.String;String[]java.lang.String[]
verschachteltes Map.Entryjava.util.Map$EntryEntryjava.util.Map.Entry
anonyme KlasseOuter$1"" (leer)null

getName() ist der Binärname — die interne Form der JVM, mit $ für Verschachtelungen und der kryptischen [I / [L…;-Array-Kodierung. Das ist, was Class.forName erwartet. getCanonicalName() ist die Quellform, die man schreiben würde, und sie ist null für Typen, die man im Quellcode nicht benennen kann (lokale, anonyme Klassen). Verwenden Sie getName() für forName-Round-Trips; verwenden Sie getSimpleName()/getCanonicalName() für menschenlesbare Ausgaben.

Primitive und Arrays haben ebenfalls Class-Objekte

Jeder primitive Typ hat sein eigenes Class-Objekt, das sich von seinem Wrapper unterscheidet:

int.class == Integer.class      // false — two different Class objects
int.class.getName()             // "int"
Integer.TYPE == int.class       // true — TYPE is the primitive Class

void hat sogar void.class (und Void.TYPE). Array-Klassen werden von der JVM synthetisiert: int[].class, String[][].class. arrayClass.getComponentType() schält eine Dimension ab (String[].class.getComponentType() ist String.class). Diese Unterschiede sind wichtig, wenn Sie Parametertypen in getMethod abgleichen — getMethod("foo", int.class) und getMethod("foo", Integer.class) finden verschiedene Überladungen.

Class-Identität und Klassenloader

Die Identität eines Class-Objekts ist nicht nur sein Name — es ist das Paar (Name, definierender Klassenloader). Dieselbe .class-Datei, die von zwei verschiedenen Klassenloadern geladen wird, ergibt zwei verschiedene, inkompatible Class-Objekte. Ein Cast zwischen ihnen wirft ClassCastException, obwohl die Namen übereinstimmen. Das ist in einer einfachen Anwendung (ein Loader) meist unsichtbar, ist aber die Wurzel vieler "aber es ist doch dieselbe Klasse!"-Rätsel in App-Servern, OSGi und Hot-Reload-Systemen. Für alltägliche Reflection behandeln Sie Class-Objekte als Singletons pro Typ und vergleichen Sie sie mit ==.

Ein ausgearbeitetes Beispiel: Typen auf drei Arten untersuchen

Das Programm erhält Class-Objekte über alle drei Routen und fragt dann eine Handvoll Typen ab — eine reguläre Klasse, ein Interface, ein Array, einen Primitiven und einen verschachtelten Typ — um die Benennungs- und Strukturunterschiede aufzudecken.

java— editable, runs on the server

Was man aus dem Lauf mitnehmen kann:

  • Alle drei Routen lieferten dieselbe Art von Objekt: ein .class-Literal, ein getClass()-Aufruf und eine forName-Suche erzeugten jeweils ein vollständig nutzbares Class-Objekt. Die Route, die man wählt, hängt davon ab, was man weiß (den Typ, eine Instanz oder nur einen Namen) — das Ergebnis ist in seiner Fähigkeit identisch.
  • getClass() auf der Greeter g-Variable gab Robot zurück, nicht Greeter. Der deklarierte Typ ist irrelevant; getClass() meldet immer die konkrete Laufzeitklasse. Deshalb sehen polymorphe Dispatch und reflektive Inspektion denselben "echten" Typ.
  • Die drei Benennungsmethoden wichen genau dort voneinander ab, wo die Tabelle es vorhersagt: String[] druckte den Binärnamen [Ljava.lang.String; von getName(), aber das lesbare String[] aus den einfachen und kanonischen Formen. Wenn Sie einen Namen je wieder in forName einspeisen, muss er die getName()-Form haben.
  • int.class == Integer.class war false, während Integer.TYPE == int.class true war. Der primitive und sein Wrapper sind verschiedene Class-Objekte, und Integer.TYPE ist nur ein Alias für das primitive. Sie zu verwechseln ist die klassische Ursache für NoSuchMethodException, wenn man eine Überladung nach Parametertyp sucht.
  • Robot.class == new Robot().getClass() war true: innerhalb eines Klassenloaders wird ein Typ genau einem Class-Objekt zugeordnet, daher ist == der korrekte Vergleich. Sie brauchen nie .equals() auf Class-Objekten in Single-Loader-Code.

Häufige Fallstricke

  • forName führt statische Initialisierer aus (in seiner einargumentigen Form). Das Laden einer Klasse kann Nebeneffekte haben. Verwenden Sie die Drei-Argument-Form mit initialize=false, wenn Sie nur inspizieren möchten.
  • getSimpleName() kann leer sein (anonyme Klassen) und getCanonicalName() kann null sein (lokale, anonyme Klassen). Gehen Sie nicht davon aus, dass sie immer druckbare Bezeichner sind.
  • Generics werden gelöscht. List<String>.class ist illegal; es gibt nur List.class. Ein Class-Objekt trägt keine Typargumentinformationen — diese leben in Type/ParameterizedType, einer separaten (und fortgeschritteneren) Reflection-API. Siehe Einschränkungen bei Generics für den Grund, warum Erasure so funktioniert.

Mit dem Class-Objekt in der Hand öffnet das nächste Kapitel die erste Schublade der Member: Felder — sie inspizieren, lesen und schreiben, auch wenn sie privat oder final sind.

Übungen

Übung
Sie haben 'Object o = new java.util.LinkedList<String>();' als 'Object' deklariert. Sie rufen 'o.getClass().getName()' auf. Welcher String wird zurückgegeben, und warum ist es nicht 'java.lang.Object'?
Sie haben 'Object o = new java.util.LinkedList<String>();' als 'Object' deklariert. Sie rufen 'o.getClass().getName()' auf. Welcher String wird zurückgegeben, und warum ist es nicht 'java.lang.Object'?
Was this page helpful?