Java Standard: Class
Bei den bisherigen Formulierungen sind Sinn und Inhalt dieses Kapitels nicht klar. <T>
bezieht sich auf generische Typen, während Class
eine Grundlage für "alles" bietet. Beides ist wichtig und müsste genauer herausgearbeitet werden.
Klassen und generische Typen
BearbeitenDie Klasse Class repräsentiert Klassen und Interfaces in einer laufenden Java-Applikation.
Sie stellt in Java eine Möglichkeit dar, Metadaten, sprich Informationen über Informationen zu erhalten. Man erhält also Informationen über die beerbte Klasse bzw. das implementierte Interface, nicht das Objekt an sich.
Eine Anwendungsmöglichkeit ist die Verwendung von Methoden eines Objektes, die einem zum Zeitpunkt der Programmierung unbekannt sind, jedoch mittels Class zur Laufzeit ermittelt werden können (siehe auch: java.lang.reflect.Method).
Des Weiteren ist es möglich Objekte zum Zeitpunkt der Programmierung unbekannten Datentyps zur Laufzeit zu erzeugen (siehe auch: java.lang.reflect.Constructor).
Der Type Parameter <T>
BearbeitenSeit Java 1.5 muss ein Type Parameter an die Klasse Class übergeben werden. Durch die Angabe des Typs kann die Typprüfung zur Kompilierzeit vorgenommen werden.
Der Type Parameter T bestimmt welchen Datentypen ein Class-Objekt abbilden soll. Beispielsweise ist die Angabe für String Class<String>
. Wenn der Typ unbekannt ist, wird ein Fragezeichen, eine so genannte Wildcard verwandt Class<?>
.
Eine Wildcard gestattet also jeden Datentypen in einem Class-Objekt abzubilden.
Wildcards ihrerseits können eingeschränkt werden. Beispielsweise möchte man nur Erben von Number zulassen:
Integer ob = new Integer(4);
Class<? extends Number> cl = ob.getClass();
Ein Class Objekt erzeugen
BearbeitenDie Klasse Class besitzt keine Konstruktoren, die public sind. Jedes Objekt in Java besitzt jedoch eine Methode getClass()
, die ein Class-Objekt als Repräsentation der beerbten Klasse des Objektes bereitstellt:
// Ein Integer-Objekt erzeugen
Integer einObjekt = new Integer(20);
// Ein Class-Objekt der Klasse Integer erzeugen
Class<Integer> classObjekt = einObjekt.getClass();
// Namen der Klasse ausgeben
System.out.println( classObjekt.getName() );
Eine andere Möglichkeit zur Erzeugung ist die statische Methode Class.forName( "Klassenname" )
. Hierbei muss jedoch beachtet werden, dass dann nur eine Wildcard (?) als Type Parameter möglich ist, da zur Kompilierzeit noch nicht feststeht welchen Datentyp der übergebene String "Klassenname" darstellen soll:
// Ein Class-Objekt erzeugen, dass die Klasse Integer abbilden soll
Class<?> classObjekt = Class.forName( "java.lang.Integer" );
Beachte: Man muss dem Klassennamen den kompletten Paketpfad der abzubildenden Klasse voranstellen. Bsp.: java.sql.ResultSet
.
Das Abbilden primitiver Datentypen ist ebenfalls möglich:
// Den primitiven Datentypen double als Class-Objekt
Class<?> classObjekt = Class.forName("double");
// Den primitiven Datentypen int als Class-Objekt
Class<?> classObjekt = Class.forName("int");
// Den primitiven Datentypen int als Class-Objekt per TYPE-Field
Class<Integer> f = Integer.TYPE;
Die dritte Möglichkeit ist die Erzeugung über das Signalwort class
:
Class<? extends Number> cl = Integer.class;
Anwendungsbeispiel mit Method
BearbeitenNehmen wir an, man hätte folgendes Interface.
public interface MyInterface {
public String getName();
public void setName( String name );
}
Nun soll dieses Interface von irgendwem implementiert werden können und wir möchten, dass er seine Implementierung einfach nur in einen Ordner legen muss, damit die verwendende Applikation das Interface aufnehmen kann.
Hier hätten wir eine solche Implementierung.
public class MyInterfaceImplementation implements MyInterface{
private String name = "Meine Implementierung";
public String getName() {
return this.name;
}
public void setName( String name ) {
this.name = name;
}
}
Jetzt stehen wir vor dem Problem, dass wir eine Klasse mit uns unbekanntem Namen zur Laufzeit verfügbar machen müssen.
Das erledigen wir mittels java.io.File
.
String path = "C:\\Dokumente und Einstellungen\\wenGehtsWasAn\\Desktop";
File directory = new File( path );
System.out.println( directory.exists() );
if( directory.exists() ) {
// gibt alle Dateien und Verzeichnisse in einem Verzeichnis als File Array zurück
File[] files = directory.listFiles();
// Splittet jeden Dateinamen in Bezeichnung und Endung
// siehe "regular expression" und String.split()
String name[] = files[i].getName().split("\\.");
System.out.print( name[0] + "." );
System.out.println( name[1] );
}
So, nun hätten wir schonmal den Namen der Datei und die Endung. Uns interessieren natürlich nur die .class
Dateien.
Jetzt müssen wir die Klasse wie gesagt noch laden. Class.forName( "Name" ), funktioniert, solange alle zu ladenden Klassen in dem selben Verzeichnis liegen wie die aufrufende Klasse.
try {
Class<?> klasse = Class.forName( "MyInterfaceImplementation" );
MyInterface impl = ( MyInterface ) klasse.newInstance();
System.out.println( impl.getName() );
} catch ( ClassNotFoundException ex ) {
System.out.println( ex.getMessage() );
} catch ( InstantiationException ex ) {
System.out.println( ex.getMessage() );
} catch (IllegalAccessException ex) {
// Wird geworfen, wenn man einen access-modifier nicht beachtet
// Man kann mittels reflect die modifier aber auch ändern
System.out.println( ex.getMessage() );
}
Da die zu ladenden Klassen aber eher in einem anderen Verzeichnis liegen werden, kann man auf einen ClassLoader zurückgreifen.
URL sourceURL = null;
try {
// Den Pfad des Verzeichnisses auslesen
sourceURL = directory.toURI().toURL();
} catch ( java.net.MalformedURLException ex ) {
System.out.println( ex.getMessage() );
}
// Einen URLClassLoader für das Verzeichnis instanzieren
URLClassLoader loader = new URLClassLoader(new java.net.URL[]{sourceURL}, Thread.currentThread().getContextClassLoader());
Der java.net.URLClassLoader
kann auf die übergebenen URL`s zugreifen und aus diesen Quellen Klassen laden.
Das kann auch ein Webverzeichnis sein, muss also nicht auf dem ausführenden Rechner liegen.
Abschließend noch ein Beispiel für die aufrufende Klasse. Es beruft sich auf obiges Interface und die Implementierung.
import java.lang.reflect.*;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
public class MyManagement {
public static void main( String bla[] ) {
// erste Möglichkeit - Klassen liegen in selbem Verzeichnis wie diese Klasse
try {
Class<?> klasse = Class.forName( "MyInterfaceImplementation" );
MyInterface impl = ( MyInterface ) klasse.newInstance();
System.out.println( impl.getName() );
} catch ( ClassNotFoundException ex ) {
System.out.println( ex.getMessage() );
} catch ( InstantiationException ex ) {
System.out.println( ex.getMessage() );
} catch (IllegalAccessException ex) {
// Wird geworfen, wenn man einen access-modifier nicht beachtet
// Man kann mittels reflect die modifier aber auch ändern
System.out.println( ex.getMessage() );
}
String path = "C:\\Dokumente und Einstellungen\\wenGehtsWasAn\\Desktop";
File directory = new File( path );
System.out.println( directory.exists() );
if( directory.exists() ) {
File[] files = directory.listFiles();
URL sourceURL = null;
try {
// Den Pfad des Verzeichnisses auslesen
sourceURL = directory.toURI().toURL();
} catch ( java.net.MalformedURLException ex ) {
System.out.println( ex.getMessage() );
}
// Einen URLClassLoader für das Verzeichnis instanzieren
URLClassLoader loader = new URLClassLoader(new java.net.URL[]{sourceURL}, Thread.currentThread().getContextClassLoader());
// Für jeden File im Verzeichnis...
for( int i=0; i<files.length; i++ ) {
// Splittet jeden Dateinamen in Bezeichnung und Endung
// siehe "regular expression" und String.split()
String name[] = files[i].getName().split("\\.");
// Nur Class-Dateien werden berücksichtigt
if( name[1].equals("class") ){
try {
// Die Klasse laden
Class<?> source = loader.loadClass( name[0] );
// Prüfen, ob die geladene Klasse das Interface implementiert
// bzw. ob sie das Interface beerbt
// Das Interface darf dabei natürlich nicht im selben Verzeichnis liegen
// oder man muss prüfen, ob es sich um ein Interface handelt Class.isInterface()
if( MyInterface.class.isAssignableFrom( source ) ) {
MyInterface implementation = ( MyInterface ) source.newInstance();
Method method = source.getDeclaredMethod( "getName", new Class<?>[]{} );
System.out.println( method.invoke( implementation, new Object[]{} ) );
}
} catch (InstantiationException ex) {
// Wird geworfen, wenn die Klasse nicht "instanziert" werden kann
System.out.println( ex.getMessage() );
} catch (IllegalAccessException ex) {
// Wird geworfen, wenn man einen access-modifier nicht beachtet
// Man kann mittels reflect die modifier aber auch ändern
System.out.println( ex.getMessage() );
} catch ( NoSuchMethodException ex ) {
// Wird geworfen, wenn die Class die spezifizierte Methode nicht implementiert
System.out.println( ex.getMessage() );
} catch ( ClassNotFoundException ex ) {
// Wird geworfen, wenn die Klasse nicht gefunden wurde
System.out.println( ex.getMessage() );
} catch ( InvocationTargetException ex ) {
// Wird geworfen, wenn die aufreufene, über Method
// reflektierte Methode eine Exception wirft
System.out.println( ex.getCause().getMessage() );
}
}
}
}
}
}
Nur noch zur Erläuterung:
getDeclaredMethod()
gibt ein Method-Objekt zurück, das die über den String sowie durch das Class-Array identifizierte Methode des Class-Objektes repräsentiert.
Will man die Methode ausführen bzw. aufrufen, muss man invoke verwenden. Dabei werden das Objekt, aus dem die Methode aufgerufen werden soll und die Parameter als Object-Array übergeben.
Möchte man eine Methode aus einer statischen Klasse aufrufen, muss man statt einem Object null übergeben.
Method method = source.getDeclaredMethod( "getName", new Class<?>[]{} );
System.out.println( method.invoke( implementation, new Object[]{} ) );