2010-04-06 7 views
8

Tengo algunas clases, como se muestra aquíobtener bloque de inicialización estática para funcionar en un java sin cargar la clase

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

¿Cómo puedo cambiar TrueFalseQuestion clase para que la El método estático siempre se ejecuta para que obtenga 1 en lugar de 0 cuando ejecuto mi método principal. No quiero ningún cambio en el método principal.

Estoy tratando de implementar los patrones de fábrica donde las subclases se registran en la fábrica, pero he simplificado el código para esta pregunta.

Respuesta

5

Para registrar la clase TrueFalseQuestion con la fábrica, es necesario llamar al inicializador estático. Para ejecutar el inicializador estático de la clase TrueFalseQuestion, se debe hacer referencia a la clase o se debe cargar por reflexión antes de llamar al QuestionFactory.map.size(). Si desea dejar intacto el método main, debe hacer referencia a él o cargarlo por reflexión en el QuestionFactory inicializador estático. No creo que sea una buena idea, pero responderé a su pregunta :) Si no le importa que QuestionFactory conozca todas las clases que implementan Question para construirlas, puede simplemente consultarlas directamente o cargarlas a través de reflexión. Algo así como:

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

hacen declaración seguro map 's y la construcción es antes del bloque static. Si no desea que QuestionFactory tenga conocimiento de las implementaciones de Question, deberá enumerarlas en un archivo de configuración que se carga por QuestionFactory. La única otra forma (posiblemente loca) en la que podría pensar sería buscar en todo el classpath las clases que implementen Question :) Eso podría funcionar mejor si todas las clases que implementaron Question debieran pertenecer al mismo paquete: NOTA: no estoy apoyando esta solución;)

la razón por la que no pienso hacer nada de esto en el inicializador estático QuestionFactory se debe a que las clases como TrueFalseQuestion tienen su propia inicializador estático que pone en QuestionFactory, que en ese momento es un objeto incompletamente construido, que solo está buscando problemas. Tener un archivo de configuración que simplemente enumera las clases que desea QuestionFactory para saber cómo construir, y luego registrarlas en su constructor es una buena solución, pero significaría cambiar su método main.

+0

Como referencia, este diseño surgió para evitar la necesidad de que la fábrica conociera las clases de preguntas (aquí: http : //stackoverflow.com/questions/2582357/augment-the-factory-pattern-in-java). –

+1

No había visto esa pregunta. Algo necesita saber sobre las implementaciones de la interfaz de la pregunta, ya sea directamente en la fábrica o mediante algún tipo de archivo de configuración. La única otra forma es, como dije, pasar por todas las clases en el classpath y ver si implementan Question. También tenga en cuenta la advertencia sobre tener una fábrica incompletamente construida en mi respuesta anterior. Puede funcionar ahora, pero no hay garantías sobre el estado del objeto en el futuro (o incluso en el presente, a través de plataformas). –

6

Usted puede llamar a:

Class.forName("yourpackage.TrueFalseQuestion"); 

Esto cargará la clase sin que tocarlo, y se ejecutará el bloque inicializador estático.

+0

¿dónde llamo este método? Cada clase está en un archivo diferente. – randomThought

+0

antes de que realmente necesite ejecutar el inicializador 'TrueFalseQuestion'. En su ejemplo, al comienzo del método principal – Bozho

+0

¿Hay alguna forma de que pueda lograr esto sin cambiar nada en el método principal porque esto crearía una especie de dependencia de esta clase en el método principal que quiero evitar? – randomThought

3

El inicializador estático de la clase no se puede ejecutar si la clase nunca se carga.

Por lo tanto, debe cargar todas las clases correctas (lo que será difícil, ya que no las conoce todas en tiempo de compilación) o deshacerse del requisito del inicializador estático.

Una forma de hacer esto último es usar el ServiceLoader.

Con el ServiceLoader, simplemente coloca un archivo en META-INF/services/package.Question y enumera todas las implementaciones. Puede tener múltiples tales archivos, uno por archivo .jar. De esta forma, puede enviar implementaciones adicionales Question de forma separada de su programa principal.

En el QuestionFactory a continuación, puede simplemente usar ServiceLodaer.load(Question.class) para obtener una ServiceLoader, que implementa Iterable<Question> y se puede utilizar como esto:

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

Con el fin de ejecutar los inicializadores estáticos, las clases tienen que ser cargado. Para que esto suceda, su clase "principal" debe depender (directa o indirectamente) de las clases, o debe hacer que se carguen directa o indirectamente; p.ej. usando Class.forName(...).

Creo que está intentando evitar las dependencias incrustadas en su código fuente. Por lo tanto, las dependencias estáticas son inaceptables y las llamadas a Class.forName(...) con nombres de clase codificados también son inaceptables.

Esto le deja dos alternativas:

  • escribir algo de código desordenado para iterar sobre los nombres de recursos en algún paquete y, a continuación, utilizar Class.forName(...) para cargar esos recursos que se parecen a sus clases. Este enfoque es complicado si tiene un classpath complicado, e imposible si su classpath efectiva incluye un URLClassLoader con un URL remoto (por ejemplo).

  • Cree un archivo (por ejemplo, un recurso de cargador de clases) que contenga una lista de los nombres de clases que desea cargar y escriba un código simple para leer el archivo y use Class.forName(...) para cargar cada uno.

Cuestiones relacionadas