2010-04-06 10 views
8

Ho un paio di classi, come mostrato quiottenere blocco di inizializzazione statico per l'esecuzione in un Java senza caricare 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 
    } 
} 

Come posso cambiare TrueFalseQuestion classe in modo che il il metodo statico viene sempre eseguito in modo da ottenere 1 anziché 0 quando eseguo il metodo principale? Non voglio alcun cambiamento nel metodo principale.

Attualmente sto cercando di implementare gli schemi di fabbrica in cui le sottoclassi si registrano con la fabbrica ma ho semplificato il codice per questa domanda.

risposta

5

Per registrare la classe TrueFalseQuestion in fabbrica, è necessario chiamare il suo inizializzatore statico. Per eseguire l'inizializzatore statico della classe TrueFalseQuestion, la classe deve essere referenziata o deve essere caricata dalla riflessione prima di chiamare QuestionFactory.map.size(). Se si desidera mantenere intatto il metodo main, è necessario fare riferimento o caricarlo mediante la riflessione nell'inizializzatore statico QuestionFactory. Non penso che sia una buona idea, ma risponderò semplicemente alla tua domanda :) Se non ti dispiace che il QuestionFactory conosca tutte le classi che implementano Question per costruirle, puoi semplicemente farle riferimento direttamente o caricarle attraverso riflessione. Qualcosa di simile:

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

Assicurarsi dichiarazione sicuro che l' map e la costruzione è prima del blocco static. Se non si desidera che QuestionFactory abbia una conoscenza delle implementazioni di Question, è necessario elencarle in un file di configurazione che viene caricato da QuestionFactory. L'unico altro (forse insano) modo in cui potrei pensare di farlo, sarebbe quello di esaminare l'intero classpath per le classi che implementano Question :) Questo potrebbe funzionare meglio se tutte le classi che implementavano Question appartenessero allo stesso pacchetto - NOTA: non sto approvando questa soluzione;)

il motivo per cui non penso di fare nulla di tutto questo nel inizializzatore statico QuestionFactory è perché le classi come TrueFalseQuestion hanno un proprio inizializzatore statico che chiama in QuestionFactory, che a quel punto è un oggetto costruito in modo incompleto, che chiede solo guai. Avere un file di configurazione che elenca semplicemente le classi che si desidera QuestionFactory per sapere come costruire, quindi registrarle nel suo costruttore è una soluzione eccellente, ma significherebbe cambiare il metodo main.

+0

Per riferimento, questo progetto è nato per evitare la necessità della fabbrica di conoscere le classi di domande (qui: http : //stackoverflow.com/questions/2582357/augment-the-factory-pattern-in-java). –

+1

Non avevo visto quella domanda. Qualcosa deve sapere circa le implementazioni dell'interfaccia di domande, sia che si tratti della fabbrica direttamente, sia attraverso qualche tipo di file di configurazione. L'unico altro modo è, come ho detto, passare attraverso tutte le classi sul classpath e vedere se implementano la domanda. Si noti anche l'avvertenza di avere una fabbrica incompletamente costruita nella mia risposta sopra. Potrebbe funzionare ora, ma non ci sono garanzie sullo stato dell'oggetto in futuro (o anche nel presente, attraverso le piattaforme). –

6

È possibile chiamare:

Class.forName("yourpackage.TrueFalseQuestion"); 

Questo caricherà la classe senza di te toccarlo, ed eseguirà il blocco di inizializzazione statico.

+0

dove chiamo questo metodo? Ogni classe è in un file diverso. – randomThought

+0

prima che sia effettivamente necessario l'inizializzatore 'TrueFalseQuestion' da eseguire. Nel tuo esempio - all'inizio del metodo principale – Bozho

+0

Esiste un modo per ottenerlo senza cambiare nulla nel metodo principale perché ciò creerebbe una sorta di dipendenza di questa classe nel metodo principale che voglio evitare. – randomThought

3

L'inizializzatore statico per la classe non può essere eseguito se la classe non viene mai caricata.

Quindi è necessario caricare tutte le classi corrette (che saranno difficili, dal momento che non si conoscono tutte in fase di compilazione) o eliminare il requisito per l'inizializzatore statico.

Un modo per eseguire quest'ultima è utilizzare ServiceLoader.

Con ServiceLoader è sufficiente inserire un file in META-INF/services/package.Question ed elencare tutte le implementazioni. È possibile avere più file, uno per file .jar. In questo modo è possibile spedire facilmente ulteriori implementazioni Question separate dal programma principale.

Nel QuestionFactory è quindi possibile utilizzare semplicemente ServiceLodaer.load(Question.class) per ottenere un ServiceLoader, che implementa Iterable<Question> e può essere utilizzato in questo modo:

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

Al fine di eseguire i inizializzatori statici, le classi devono essere caricati. Affinché questo accada, la tua classe "principale" deve dipendere (direttamente o indirettamente) dalle classi, o deve direttamente o indirettamente farle caricare dinamicamente; per esempio. utilizzando Class.forName(...).

Penso che si stia cercando di evitare le dipendenze incorporate nel codice sorgente. Pertanto, le dipendenze statiche non sono accettabili e anche le chiamate a Class.forName(...) con nomi di classi hardcoded non sono accettabili.

Questo ti lascia due alternative:

  • scrivere del codice disordinato per scorrere i nomi delle risorse in qualche pacchetto, e quindi utilizzare Class.forName(...) per caricare quelle risorse che sembrano le classi. Questo approccio è complicato se si dispone di un classpath complicato e impossibile se il classpath effettivo include un URLClassLoader con un URL remoto (ad esempio).

  • Creare un file (ad esempio una risorsa classloader) contenente un elenco dei nomi di classe che si desidera caricare e scrivere un codice semplice per leggere il file e utilizzare Class.forName(...) per caricarne uno.

Problemi correlati