PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : (Java) Klassen nachladen?


Gast
2008-05-29, 20:42:58
Hallo!

Ich habe ein "Hauptklasse", die Objekte/Instanzen anderer, selbstgeschriebener Klassen laden bzw erzeugen soll. Die Hauptklasse weiss aber nicht von sich aus wie die zu ladenden Klassen heissen, kennt aber ihren "Typ".
So ist das gedacht:
//hauptklasse
Tier t1 = new Hund();
Tier t2 = new Affe();
Die Hauptklasse kennt nun natürlich nicht den Namen "Hund" und "Affe" der Klassen. Die Namen der zu ladenden Klassen stehen einfach in einer Textdatei:
Hund
Affe

Funktioniert das überhaupt?
Falls ja, wie geht das möglichst einfach?

Sephiroth
2008-05-29, 21:05:38
Mit dem ClassLoader (http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html) und dessen Methode loadClass (http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html#loadClass(java.lang.String)). Darauf dann ein newInstance (http://java.sun.com/javase/6/docs/api/java/lang/Class.html#newInstance()).

Monger
2008-05-29, 21:07:43
Ja, das geht. Schau dir mal den ClassLoader (http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html) genauer an. Das Problem ist nur: wie sprichst du diese Klassen an, wenn du keine Ahnung von ihnen hast?

Klassen die dynamisch nachgeladen werden sollen, erfüllen deshalb sinnvollerweise ein Interface, oder erben von einer (abstrakten) Klasse. Dann kannst du Instanzen dieser Klasse aufs Interface casten, und darauf dann arbeiten. Sonst müsstest du arg mit Reflections rumeiern, um da was zu Stande zu kriegen.

Sephiroth
2008-05-29, 21:11:20
Klassen die dynamisch nachgeladen werden sollen, erfüllen deshalb sinnvollerweise ein Interface, oder erben von einer (abstrakten) Klasse.
Richtig, aber dem bisserl Code nach zu urteilen, dürfte das erfüllt sein.

Dr.Doom
2008-05-29, 21:33:43
Danke schön!

Ja, "Tier" ist eine abstrakte Klasse, damit die Hauptklasse weiss, welche Methoden sie erwarten bzw benutzen kann.

"Mit dem ClassLoader und dessen Methode loadClass. Darauf dann ein newInstance."
Das reicht?! Ich habe in meinem uralten coreJava-Buch etwas nachgelesen, und eine ziemlich übel-komplizierte Sache mit Bytecode lesen usw gefunden...

(Gast da oben bin ich, eben noch am Notebook gesessen.)

Monger
2008-05-29, 21:40:36
Ne, das geht wirklich relativ einfach. Wenn du im Speicher der Runtime rummähren musst (um z.B. da dynamisch Klassen zu verändern, rauszuschmeißen, auszutauschen etc.) wird es schnell hässlich, aber der normale Lademechanismus ist simpel.

Dr.Doom
2008-05-29, 22:02:08
Und was ist davon zu halten (gerade nochmal gesucht, obwohl ich eigentlich für heute Schluss machen wollte *g*):

-> http://www.dpunkt.de/java/Die_Sprache_Java/Objektorientierte_Programmierung_mit_Java/66.html

public Object createInstance(String name)
throws Exception {
// Class-Objekt holen
Class c = Class.forName(name);
// Exemplar erzeugen und zurückliefern
return c.newInstance();
}
Da wird nichtmal ein ClassLoader bemüht.

Sephiroth
2008-05-29, 23:24:44
nutzt auch intern den classloader

dürfte etwa folgendem entsprechend

ClassLoader loader = ClassLoader.getSystemClassLoader();
Object myClassObject = loader.loadClass("myClass").newInstance();

hab grad noch was gefunden:
Get a load of that name! Subtle differences in various ways you can dynamically load a class (http://www.javaworld.com/javaworld/javaqa/2003-03/01-qa-0314-forname.html)
Why do Class.forName and ClassLoader.loadClass behave Different? (http://blog.bjhargrave.com/2007/07/why-do-classforname-and.html)
Understanding Class.forName (http://www.theserverside.com/tt/articles/content/dm_classForname/DynLoad.pdf)

Dr.Doom
2008-05-30, 11:11:02
nutzt auch intern den classloader

dürfte etwa folgendem entsprechend

ClassLoader loader = ClassLoader.getSystemClassLoader();
Object myClassObject = loader.loadClass("myClass").newInstance();Ich komme derzeit leider nicht dazu, das selber auszuprobieren, aber im Grunde kann ich diese beiden Zeilen als Template für meine nachzuladenden Klassen 'Hund' und 'Affe' benutzen, die von 'Tier' abgeleitet wurden?

ClassLoader loader = ClassLoader.getSystemClassLoader();
Tier t1 = loader.loadClass("hund").newInstance();
Tier t2 = loader.loadClass("affe").newInstance();

Ich vermute, dass 'newInstance()' nur den Standard-Konstruktor aufruft. Gibt es newInstance auch mit Parametern oder wäre es einfacher, den erzeugten Klassen nötige Parameter mit Setter-Methoden nach der Erzeugung zu übergeben?

hab grad noch was gefunden:
Get a load of that name! Subtle differences in various ways you can dynamically load a class (http://www.javaworld.com/javaworld/javaqa/2003-03/01-qa-0314-forname.html)
Why do Class.forName and ClassLoader.loadClass behave Different? (http://blog.bjhargrave.com/2007/07/why-do-classforname-and.html)
Understanding Class.forName (http://www.theserverside.com/tt/articles/content/dm_classForname/DynLoad.pdf)Danke, werde ich demnächst durchlesen.

Der_Donnervogel
2008-06-02, 03:53:11
Ich vermute, dass 'newInstance()' nur den Standard-Konstruktor aufruft. Gibt es newInstance auch mit Parametern oder wäre es einfacher, den erzeugten Klassen nötige Parameter mit Setter-Methoden nach der Erzeugung zu übergeben?Ja sowas kann man machen. Hier mal als kleines Beispiel die Tier/Hund Sache aus dem Eingangsposting mit einem Konstruktor der zwei Parameter benötigt.
package tiere;

public class Hund extends Tier {
private String m_Name;
private int m_Alter;

public Hund(String name, Integer alter) {
m_Name = name;
m_Alter = alter;
}

public String getInfos() {
return m_Name + " ist " + m_Alter + " Jahre alt.";
}
}
package tiere;

import java.lang.reflect.Constructor;

public abstract class Tier {

public abstract String getInfos();

public static void main(String[] args) {
try {
Class tierClass = Class.forName("tiere.Hund");

Constructor hund = tierClass.getConstructor(
new Class[] {
Class.forName("java.lang.String"),
Class.forName("java.lang.Integer")
}
);

Tier tier = (Tier)hund.newInstance(new Object[] {
"Waldi",
4
}
);
System.out.println(tier.getInfos());
} catch (Exception e) {
e.printStackTrace();
}
}
}

Dr.Doom
2008-06-04, 23:51:46
Ich hab's heute mal ausprobiert, aber es funktioniert nicht.

ClassLoader loader = ClassLoader.getSystemClassLoader( );
try {
Object b = loader.loadClass("B").newInstance( );
} catch ( Exception e ) {
e.printStackTrace( );
}

public class A {
public A() {
System.out.println("A gebaut");
}
}

public class B extends A {
public B() {
System.out.println("B gebaut");
}
}


Java Console/Exception/StackTrace:
Java Plug-in 1.6.0_03
Verwendung der JRE-Version 1.6.0_03 Java HotSpot(TM) Client VM
...
java.lang.ClassNotFoundException: B
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at gapplet.actionPerformed(gapplet.java:287)
at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
at java.awt.Component.processMouseEvent(Unknown Source)
at javax.swing.JComponent.processMouseEvent(Unknown Source)
at java.awt.Component.processEvent(Unknown Source)
at java.awt.Container.processEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Container.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
at java.awt.Container.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)

Klassen A(.class) und B(.class) liegen im selben Verzeichnis wie alle anderen Klassendateien.

--> ???

Der_Donnervogel
2008-06-05, 00:23:19
Klassen A(.class) und B(.class) liegen im selben Verzeichnis wie alle anderen Klassendateien.

--> ???"Verzeichnisse" haben bei Java eine Bedeutung. Das nennt sich Packages und muss immer angegeben werden sofern man auf eine Klasse zugreift. Entweder man macht dies über ein Import-Kommando am Anfang der Klasse oder man muss den vollen Pfad qualifizieren. Aus diesem Grund habe ich im Beispiel auch nicht einfach "Hund" geschrieben sondern "tiere.Hund". Ich habe das Beispiel mal in einen Anhang gegeben. Wenn man es entpackt, kann man es anschließend über Kommandozeile mit

java tiere.Tier

von dem Verzeichnis aus in dem sich der Ordner tiere befindet starten. Ich habe auch noch schnell das Beispiel mit A und B dazukopiert. Einfach Tier.java anschauen.

Dr.Doom
2008-06-05, 00:57:45
- Wenn ich Klasse B in Verzeichnis/Package 'Tiere' mit javac übersetzte, findet javac Klasse A nicht (can not find symbol A )...

- Ich habe keine Applikation, sondern ein Applet, 'java xyz' gibt's nicht.

- Alle meine Klassen (.class) liegen im selben Verzeichnis ("E:/java/") bzw gehören dem "default-package" an. Wobei das eigentlich nicht korrekt bzw ungenau ist, denn: alle Klassendateien liegen in einem jar-Archiv, das zudem noch vollkommen korrekt signiert ist (das Applet schreibt XML-Dateien ins lokale Dateisystem).

Das Klassennachladen muss jetzt nur noch irgendwie funktionieren... hab davon nur leider keinen Schimmer. :(

Der_Donnervogel
2008-06-05, 01:56:36
- Wenn ich Klasse B in Verzeichnis/Package 'Tiere' mit javac übersetzte, findet javac Klasse A nicht (can not find symbol A )...Java ist case-sensitive. Das Package/Verzeichnis muss also mit einem Kleinbuchstaben anfangen. Als Applikationscode funktioniert das ganze auch:
D:\Temp\Test>del tiere\*.class

D:\Temp\Test>dir tiere /B
A.java
B.java
Hund.java
Tier.java

D:\Temp\Test>javac tiere\*.java
Note: tiere\Tier.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

D:\Temp\Test>java tiere.Tier
Waldi ist 4 Jahre alt.
A gebaut
B gebaut- Ich habe keine Applikation, sondern ein Applet, 'java xyz' gibt's nicht.Also bei Applets muss ich leider passen. Ich habe da fast nichts gemacht und das ist schon länger her.

Dr.Doom
2008-06-05, 13:38:22
Ok, danke, ich habe mein Applet einfach zur Applikation umgestrickt, nun geht's erstmal mit den Testklassen A und B.

Ich hätte es zwar lieber, wenn das Ganze als Applet auch funktioneren würde, aber ehrlich gesagt ist mir das dann doch zu kompliziert und unintuitiv. Schade drum. :/

EDIT: Ulkigerweise hat sich auch das Problem mit dem leeren JFrame ( http://www.forum-3dcenter.org/vbulletin/showthread.php?t=415443 ) von selber gelöst. Ich habe meinen alten, auskommentierten Code wieder aktiviert, und es funktionierte sofort ohne daran was geändert zu haben. *Akte X-Melodie aus dem Off spielen hör*