Calendar
Februar 2012
M D M D F S S
« Mrz    
 12345
6789101112
13141516171819
20212223242526
272829  
Kategorien
Links
Archive

Etwas Generic bitte…

Ich wollte eine Klasse schreiben, die jeweils eine Instanz diverser Subklassen erzeugen kann und diese dann “irgendwo” registriert. Dies sollte nur anhand dieser Subklasse funktionieren. Bei einer erneuten Erzeugung sollte weder ein neues Objekt erstellt noch registriert werden. Das ganze sollte dann am besten noch so möglich sein, dass es keine Instanz der registrierenden Klasse gibt.

So, das hört sich ja lustig an.

Wie erzeuge ich nun aus der übergebene Klasse ein Objekt?
Wie stelle ich sicher, dass die übergebene Klasse auch der entspricht, die ich überhaupt “irgendwo” registrieren darf?
Wie merke ich mir, dass die übergebene Klasse schon ein Objekt erzeugt hat?

Diese Fragen stellte ich mir und im Folgenden möchte ich natürlich zeigen, wie man sie lösen kann.

Allgemein gilt, wenn eine Klasse und ihr Default-Konstruktor sichtbar sind, kann man davon eine Instanz erstellen auch ohne zu wissen um welche Klasse es sich dabei handelt. Die entsprechende Methode bekommt also als Parameter eine Klasse (Class). Damit jemand diese Methode aufrufen kann, ohne von unserer Klasse eine Instanz zu erstellen, müssen wir diese Methode static und public kennzeichnen.

public static void register(Class clazz)
{
}

Es muss beachtet werden, dass der Parameter nicht mit “class” benannt wird, weil dies ja in Java ein Schlüsselwort ist. Wenn die oben genannten Bedingungen erfüllt sind, kann man auf diesem Parameter eine Methode aufrufen, die eine neues Objekt der Klasse erzeugt.

public static void register(Class clazz) {

Object obj = null;

try {

obj = clazz.newInstance();

}
catch (InstantiationException e) {

// do something

}
catch (IllegalAccessException e) {

// do something

}

}

Da wir zu diesem Zeitpunkt nicht wissen, um was für eine Klasse es sich genau handelt, kann die erzeugte Instanz erst einmal nur einem Object zugeordnet werden ohne eine ClassCastException heraufzubeschwören. Beim Erzeugen einer neuen Instanz muss man auch zwei mögliche Exceptions behandeln, die InstantiationException und die IllegalAccessException.
Da wir derzeitig nicht wissen, um welches Objekt es sich bei der Instanziierung handelt, können wir auch nicht wissen, ob wir diese Objekt “irgendwo” registrieren können, denn die Methode der “Irgendwoklasse” möchte einen speziellen Parameter. Damit kommen wir auch zur zweiten Frage.

Wie stelle ich sicher, dass die übergebene Klasse auch der entspricht, die ich überhaupt “irgendwo” registrieren darf?

Das bekommen wir mit Hilfe von Generics hin. Mehr darüber kann man hier: http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf erfahren. An dieser Stelle sei nur soviel gesagt, dass man dem Parameter selber einen Typparameter geben kann. In meinem Fall sieht der Code dann so aus.

public static void register(Class clazz) {

MyFactory obj = null;

try {

obj = clazz.newInstance();

}
catch (InstantiationException e) {

// do something

}
catch (IllegalAccessException e) {

// do something

}

return obj;

}

Was hat sich geändert? Wir definieren A. A ist eine Kindklasse von MyFactory. Den Übergabeparameter Class clazz haben wir in Class clazz geändert. Das bedeutet, dass wir nun nur eine Klasse als Parameter akzeptieren, die von MyFactory ableitet. Damit können wir auch sicherstellen, dass die Erstellung einer Instanz auch tatsächlich ein Objekt von MyFactory ist. Ohne die Verwendung von Generics könnte man nach Methodenaufruf prüfen, ob der Übergabeparameter einer bestimmten Klasse entspricht oder von ihr abgeleitet ist.

Dass macht man mit:

public static void register(Class clazz) {

if ( clazz != null && !clazz.isAssignableFrom(MyFactory.class) {

throw new IllegalArgumentException("The argument " + clazz == null ? "null " : clazz.toString()
+ "is not a subclass of MyFactory.");

}

MyFactory obj = null;

try {

obj = (MyFactory) clazz.newInstance();

}
catch (InstantiationException e) {

// do something

}
catch (IllegalAccessException e) {

// do something

}

}

Erst mit der Prüfung !clazz.isAssignableFrom(MyFactory.class) stellen wir genau das sicher, was die Generics an sich schon mitbringen, nämlich dass die übergebene Klasse der Klasse MyFactory.class zuordbar ist.
Da wir nun aber keine IllegalArgumentException heraufbeschwören wollen und dem Programmierer, der unsere Klasse benutzt zur Typsicherheit zwingen wollen, entscheiden wir uns für die generische Variante.

Dann wäre ja nur noch eine Frage zu klären.

Wie merke ich mir, dass die übergebene Klasse schon ein Objekt erzeugt hat?

Nun, nichts leichter als das. Wir nehmen uns einfach eine Map und speichern darin unsere registrierten Objekte. Aber wie machen wir auch diese typsicher? Zunächst muss auch diese Map statisch sein, damit wir aus einer statischen Methode darauf zugreifen können. Natürlich könnte diese Map einfach ein “Object” als Schlüssel und Wert haben, denn wir machen sie private und greifen nur aus unseren eigenen Methoden darauf zu, dann wissen wir ja, was sich eigentlich darin befindet.
Nun dann bleibt aber das nervige Casting. Also wie funktioniert es nun?


private static Map, MyFactory> factories = new HashMap, MyFactory>();

public static void register(Class clazz) {

if (!factories.containsKey(clazz) {

try {

factories.put(clazz, clazz.newInstance());
irgendwoManager.registerFactory(factories.get(clazz));

}
catch (InstantiationException e) {

// do something

}
catch (IllegalAccessException e) {

// do something

}

}

}

Wir definieren also eine private statische Map, die bestimmte Parametertypen für den Schlüssel und den Wert verwendet. Für den Schlüssel sind Klassen erlaubt, die von MyFactoy ableiten und als Wert Objekte der Klasse MyFactory.

Wenn nun die Methode “register” aufgerufen wird, schauen wir zunächst in unsere Map, ob es einen Schlüssel von clazz schon gibt. Wenn ja, verlassen wir die Methode wieder, wenn nein, fügen wir in die Map unsere Klasse und eine frisch erzeugte Instanz davon ein. Damit werden wir kein zweites Mal eine Instanz dieser Klasse erzeugen. Diese eine Instanz registrieren wir wie wünscht “irgendwo”.

Aufgerufen wird unsere Methode nun wir folgt.

MyFactoryRegistrar.register(SubClassOfMyFactory.class);
MyFactoryRegistrar.register(AnotherSubClassOfMyFactory.class);

Wir haben nun zwei Objekte erzeugt und registriert. Würde man die selben Klassen noch einmal registrieren, würde weder ein neues Objekt erzeugt werden, noch eine Registrierung “irgendwo” stattfinden.

Natürlich kann man nun alles noch ein wenig würzen und verfeinern aber das ist dann eure Angelegenheit…

Mein erster Eintrag

So, das ist er nun…

Der erste Eintrag in meinem Blog.