erhalten statische Initialisierung Block in einem Java zu laufen, ohne die Klasse geladen
-
24-09-2019 - |
Frage
Ich habe ein paar Klassen, wie hier gezeigt
public class TrueFalseQuestion implements Question{
static{
QuestionFactory.registerType("TrueFalse", "Question");
}
public TrueFalseQuestion(){}
}
...
public class QuestionFactory {
static final HashMap<String, String > map = new HashMap<String,String>();
public static void registerType(String questionName, String ques ) {
map.put(questionName, ques);
}
}
public class FactoryTester {
public static void main(String[] args) {
System.out.println(QuestionFactory.map.size());
// This prints 0. I want it to print 1
}
}
Wie kann ich TrueFalseQuestion
Klasse ändern, so dass die statische Methode wird immer so läuft, dass ich 1 statt 0, wenn ich meine Hauptmethode laufen? Ich will keine Änderung im Hauptverfahren.
Ich versuche tatsächlich die Fabrik Muster zu implementieren, wo die Unterklassen mit der Fabrik zu registrieren, aber ich habe den Code für diese Frage vereinfacht.
Lösung
Um die TrueFalseQuestion
Klasse mit dem Werk zu registrieren, seine statische Initialisierer Bedürfnisse aufgerufen werden. Um die statische auszuführen initializer der TrueFalseQuestion
Klasse, um die Klasse Bedürfnisse entweder referenziert werden oder es muss durch Reflexion geladen werden, bevor QuestionFactory.map.size()
genannt wird. Wenn Sie die main
Methode unangetastet lassen wollen, müssen Sie es in den QuestionFactory
statischen Initialisierer durch Reflexion Referenz oder zu laden. Ich glaube nicht, das ist eine gute Idee, aber ich werde einfach Ihre Frage beantworten :) Wenn Sie nicht die QuestionFactory
nichts dagegen über alle Klassen zu wissen, dass Question
implementieren, sie zu konstruieren, können Sie sie einfach direkt referenzieren oder laden Sie sie durch Betrachtung. So etwas wie:
public class QuestionFactory {
static final HashMap<String, String > map = new HashMap<String,String>();
static {
this.getClassLoader().loadClass("TrueFalseQuestion");
this.getClassLoader().loadClass("AnotherTypeOfQuestion"); // etc.
}
public static void registerType(String questionName, String ques ) {
map.put(questionName, ques);
}
}
Stellen Sie sicher, map
Erklärung und Konstruktion ist vor dem static
Block. Wenn Sie nicht QuestionFactory
keine Kenntnis von den Implementierungen von Question
haben wollen, müssen Sie sie in einer Konfigurationsdatei aufzulisten, die von QuestionFactory
geladen wird. Die einzige andere (möglicherweise verrückt), wie ich denken konnte, es zu tun, wäre für die Klassen durch die gesamte Classpath zu suchen, die Question
:) Das könnte funktionieren besser, wenn alle Klassen implementieren, die umgesetzt Question
zu demselben Paket gehören erforderlich waren - Hinweis: ich bin nicht diese Lösung befürwortet;)
Der Grund, warum ich glaube nicht, dass dies in der QuestionFactory
statischen tun Initializer ist, weil Klassen wie TrueFalseQuestion
ihre eigene statische haben initializer, dass Anrufe in QuestionFactory
, die an diesem Punkt ist ein unvollständig konstruiertes Objekt, das nur Ärger bittet . eine Konfigurationsdatei hat, die einfach die Klassen Listen, die Sie QuestionFactory
wissen wollen, wie dann zu konstruieren, so dass sie in seinem Konstruktor Registrierung ist eine feine Lösung, aber es würde bedeuten, dass Ihre main
Methode zu ändern.
Andere Tipps
Sie können nennen:
Class.forName("yourpackage.TrueFalseQuestion");
Dies wird die Klasse laden, ohne dass Sie es tatsächlich zu berühren, und wird die statischen Initialisierungsblocks auszuführen.
Die statischen Initialisierer für die Klasse kann nicht ausgeführt werden, wenn die Klasse nie geladen wird.
So müssen Sie entweder alle die richtigen Klassen laden (die schwer sein werden, da Sie sie nicht wissen alle zum Zeitpunkt der Kompilierung) oder die Anforderung loszuwerden für die statischen Initialisierer.
Eine Möglichkeit, diese zu tun ist, die ServiceLoader
.
Mit dem ServiceLoader
Sie einfach eine Datei in META-INF/services/package.Question
setzen und alle Implementierungen aufzulisten. Sie können haben mehrere solche Dateien, eine pro .jar-Datei. Auf diese Weise können leicht zusätzliche Question
Implementierungen getrennt von Ihrem Hauptprogramm versenden können.
In der QuestionFactory
können Sie dann einfach ServiceLodaer.load(Question.class)
verwenden, um eine ServiceLoader
zu erhalten, die Geräte Iterable<Question>
und können wie folgt verwendet werden:
for (Question q : ServiceLoader.load(Question.class)) {
System.out.println(q);
}
Um die statischen Initialisierungen auszuführen, müssen die Klassen geladen werden. Damit dies geschehen kann, entweder die „main“ Klasse muss (direkt oder indirekt) von den Klassen ab, oder er muss sie direkt oder indirekt verursacht dynamisch geladen werden; z.B. mit Class.forName(...)
.
Ich glaube, Sie versuchen, um Abhängigkeiten zu vermeiden eingebettet in Ihrem Quellcode . So statische Abhängigkeiten sind nicht akzeptabel, und Anrufe Class.forName(...)
mit hartcodierte Klassennamen sind nicht akzeptabel als auch.
Dies lässt Sie zwei Alternativen:
-
Schreiben Sie etwas chaotisch Code zu iterieren den Ressourcennamen in einem Paket, und dann
Class.forName(...)
verwenden, um diese Ressourcen zu laden, die aussehen wie Ihre Klassen. Dieser Ansatz ist schwierig, wenn Sie eine komplizierte Classpath haben, und unmöglich, wenn Ihr effektiven Classpath eine URLClassLoader mit einer Remote-URL enthält (zum Beispiel). -
Erstellen Sie eine Datei (zB ein Classloader Ressource) eine Liste der Klassennamen enthält, dass Sie geladen werden soll, und schreiben einige einfache Code die Datei und Verwendung
Class.forName(...)
lesen jeden zu laden.