2010-04-06 16 views
8

J'ai quelques classes comme indiqué iciobtenir le bloc d'initialisation statique pour exécuter dans un java sans charger la classe

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 
    } 
} 

Comment puis-je changer TrueFalseQuestion classe afin que la méthode statique est toujours exécuté de sorte que j'obtiens 1 au lieu de 0 quand je cours ma méthode principale? Je ne veux pas de changement dans la méthode principale.

Je suis en train d'essayer de mettre en œuvre les modèles d'usine où les sous-classes s'enregistrent avec l'usine, mais j'ai simplifié le code pour cette question.

Répondre

5

Pour enregistrer la classe TrueFalseQuestion en usine, son initialisateur statique doit être appelé. Pour exécuter l'initialiseur statique de la classe TrueFalseQuestion, la classe doit être référencée ou doit être chargée par réflexion avant l'appel de QuestionFactory.map.size(). Si vous voulez laisser intacte la méthode main, vous devez la référencer ou la charger par réflexion dans l'initialiseur statique QuestionFactory. Je ne pense pas que ce soit une bonne idée, mais je vais juste répondre à votre question :) Si cela ne vous dérange pas le QuestionFactory connaître toutes les classes qui implémentent Question pour les construire, vous pouvez simplement les référencer directement ou les charger à travers réflexion. Quelque chose comme:

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); 
    } 
} 

Assurez-vous que la déclaration de la construction de map et est avant que le bloc static. Si vous ne voulez pas que QuestionFactory connaisse les implémentations de Question, vous devrez les lister dans un fichier de configuration chargé par QuestionFactory. Le seul autre moyen (possiblement insensé) que je pourrais envisager pour le faire, serait de parcourir tout le classpath pour les classes implémentant Question :) Cela pourrait fonctionner mieux si toutes les classes implémentées Question devaient appartenir au même paquet - NOTE: Je ne suis pas approuver cette solution;)

la raison pour laquelle je ne pense pas faire tout cela dans le QuestionFactory initialiseur statique est parce que les classes comme TrueFalseQuestion ont leur propre initialiseur statique qui remet en QuestionFactory, qui, à ce moment-là est un objet incomplètement construit, qui ne fait que demander des ennuis. Avoir un fichier de configuration qui liste simplement les classes que vous voulez QuestionFactory pour savoir comment construire, puis les enregistrer dans son constructeur est une bonne solution, mais cela signifierait changer votre méthode main.

+0

Pour référence, cette conception est venu d'éviter la nécessité de l'usine sachant sur les classes de questions (ici: http : //stackoverflow.com/questions/2582357/augment-the-factory-pattern-in-java). –

+1

Je n'avais pas vu cette question. Quelque chose doit être mis au courant des implémentations de l'interface de Question, que ce soit directement à l'usine ou à travers un fichier de configuration. La seule autre façon est, comme je l'ai dit, de parcourir toutes les classes du chemin de classe et de voir si elles implémentent la question. Notez également la mise en garde d'avoir une usine incomplètement construite dans ma réponse ci-dessus. Cela peut fonctionner maintenant, mais il n'y a aucune garantie sur l'état de l'objet dans le futur (ou même dans le présent, sur toutes les plateformes). –

6

Vous pouvez appeler:

Class.forName("yourpackage.TrueFalseQuestion"); 

Cela va charger la classe sans vous toucher réellement, et exécutera le bloc statique d'initialisation.

+0

Où puis-je appeler cette méthode? Chaque classe est dans un fichier différent. – randomThought

+0

avant que vous ayez réellement besoin de l'initialiseur 'TrueFalseQuestion' pour être exécuté. Dans votre exemple - au début de la méthode principale – Bozho

+0

Est-il possible de réaliser cela sans rien modifier dans la méthode principale car cela créerait une sorte de dépendance de cette classe dans la méthode principale que je veux éviter. – randomThought

3

L'initialiseur statique de la classe ne peut pas être exécuté si la classe n'est jamais chargée.

Vous devez donc charger toutes les classes correctes (ce qui sera difficile, car vous ne les connaissez pas toutes à la compilation) ou vous débarrasser de l'exigence de l'initialiseur statique.

Une façon de faire ce dernier est d'utiliser le ServiceLoader.

Avec le ServiceLoader vous mettez simplement un fichier dans META-INF/services/package.Question et lister toutes les implémentations. Vous pouvez avoir plusieurs fichiers, un fichier par fichier .jar. De cette façon, vous pouvez facilement expédier des implémentations supplémentaires de Question séparément de votre programme principal.

Dans le QuestionFactory vous pouvez alors utiliser simplement ServiceLodaer.load(Question.class) pour obtenir un ServiceLoader, qui met en œuvre Iterable<Question> et peut être utilisé comme ceci:

for (Question q : ServiceLoader.load(Question.class)) { 
    System.out.println(q); 
} 
2

Pour exécuter les initialiseurs statiques, les classes doivent être chargées. Pour que cela se produise, soit votre classe "principale" doit dépendre (directement ou indirectement) des classes, soit elle doit les charger directement ou indirectement de manière dynamique; par exemple. en utilisant Class.forName(...).

Je pense que vous essayez d'éviter les dépendances inclus dans votre code source. Les dépendances statiques sont donc inacceptables et les appels au Class.forName(...) avec des noms de classes codés en dur sont également inacceptables.

Cela vous laisse deux alternatives:

  • écrire du code en désordre à itérer sur les noms de ressources dans un paquetage, puis utiliser Class.forName(...) pour charger ces ressources qui ressemblent à vos cours. Cette approche est délicate si vous avez un chemin de classe compliqué, et impossible si votre classpath effectif inclut un URLClassLoader avec une URL distante (par exemple).

  • Créez un fichier (par exemple une ressource classloader) contenant une liste des noms de classe à charger, puis écrivez du code simple pour lire le fichier et utilisez Class.forName(...) pour charger chacun d'eux.