views:

340

answers:

4

I have a few classes as shown here

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

How can I change TrueFalseQuestion class so that the static method is always run so that I get 1 instead of 0 when I run my main method? I do not want any change in the main method.

I am actually trying to implement the factory patterns where the subclasses register with the factory but i have simplified the code for this question.

+2  A: 

You can call:

Class.forName("yourpackage.TrueFalseQuestion");

this will load the class without you actually touching it, and will execute the static initializer block.

Bozho
where do I call this method? Each class is in a different file.
TP
before you actually need the `TrueFalseQuestion` initializer to be run. In your example - in the beginning of the main method
Bozho
Is there any way I can achieve this without changing anything in the main method because this would create a sort of dependency of this class in the main method which I want to avoid.
TP
In this case you don't have a compile-time dependency. It is only a run-time dependency. Depending on the way you are "registering" your subclasses, you can make the appropriate checks.
Bozho
Is there any good way to get rid of the run time dependency also?
TP
@TP: If you could enumerate all classes in a package, you could just query for all the names of those implementing `Question` and do this. Since that can't be done (see here: http://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package-using-reflection), you're left with examining jars by hand (ugh) or using some kind of configuration file...
Martinho Fernandes
@TP it's exactly as @Martinho Fernandes says
Bozho
+1  A: 

To register the TrueFalseQuestion class with the factory, its static initializer needs to be called. To execute the static initializer of the TrueFalseQuestion class, the class needs to either be referenced or it needs to be loaded by reflection before QuestionFactory.map.size() is called. If you want to leave the main method untouched, you have to reference it or load it by reflection in the QuestionFactory static initializer. I don't think this is a good idea, but I'll just answer your question :) If you don't mind the QuestionFactory knowing about all classes that implement Question to construct them, you can just reference them directly or load them through reflection. Something like:

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

Make sure map's declaration and construction is before the static block. If you don't want QuestionFactory to have any knowledge of the implementations of Question, you'll have to list them in a configuration file that gets loaded by QuestionFactory. The only other (possibly insane) way I could think to do it, would be to look through the entire classpath for classes that implement Question :) That might work better if all classes that implemented Question were required to belong to the same package -- NOTE: I am not endorsing this solution ;)

The reason I don't think doing any of this in the QuestionFactory static initializer is because classes like TrueFalseQuestion have their own static initializer that calls into QuestionFactory, which at that point is an incompletely constructed object, which is just asking for trouble. Having a configuration file that simply lists the classes that you want QuestionFactory to know how to construct, then registering them in its constructor is a fine solution, but it would mean changing your main method.

Rob Heiser
For reference, this design came about to avoid the need of the factory knowing about the question classes (here: http://stackoverflow.com/questions/2582357/augment-the-factory-pattern-in-java).
Martinho Fernandes
I hadn't seen that question. Something needs to know about the Question interface's implementations, whether it's the factory directly, or through some kind of configuration file. The only other way is, as I said, going through all classes on the classpath and seeing if they implement Question. Also note the caveat about having an incompletely constructed factory in my answer above. It may work now, but there are no guarantees about the state of the object in the future (or even in the present, across platforms).
Rob Heiser
+2  A: 

The static initializer for the class can't be executed if the class is never loaded.

So you either need to load all the correct classes (which will be hard, since you don't know them all at compile time) or get rid of the requirement for the static initializer.

One way to do the latter is to use the ServiceLoader.

With the ServiceLoader you simply put a file in META-INF/services/package.Question and list all implementations. You can have multiple such files, one per .jar file. This way you can easily ship additional Question implementations separate from your main program.

In the QuestionFactory you can then simply use ServiceLodaer.load(Question.class) to get a ServiceLoader, which implements Iterable<Question> and can be used like this:

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

In order to run the static initializers, the classes need to be loaded. For this to happen, either your "main" class must depend (directly or indirectly) on the classes, or it must directly or indirectly cause them to be loaded dynamically; e.g. using Class.forName(...).

I think you are trying to avoid dependencies embedded in your source code. So static dependencies are unacceptable, and calls to Class.forName(...) with hard-coded class names are unacceptable as well.

This leaves you two alternatives:

  • Write some messy code to iterate over the resource names in some package, and then use Class.forName(...) to load those resources that look like your classes. This approach is tricky if you have a complicated classpath, and impossible if your effective classpath includes a URLClassLoader with a remote URL (for example).

  • Create a file (e.g. a classloader resource) containing a list of the classnames that you want loaded, and write some simple code to read the file and use Class.forName(...) to load each one.

Stephen C