заставьте статический блок инициализации работать в Java, не загружая класс

У меня есть несколько классов как показано здесь

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

Как я могу измениться TrueFalseQuestion класс так, чтобы статический метод всегда выполнялся так, чтобы я добрался 1 вместо 0, когда я выполняю свой основной метод? Я не хочу изменения в основном методе.

Я на самом деле пытаюсь реализовать шаблоны "фабрика", где подклассы регистрируются в фабрике, но я упростил код для этого вопроса.

8
задан Joachim Sauer 6 April 2010 в 06:49
поделиться

4 ответа

Чтобы зарегистрировать класс TrueFalseQuestion в фабрике, необходимо вызвать его статический инициализатор.Чтобы выполнить статический инициализатор класса TrueFalseQuestion , на класс необходимо либо ссылаться, либо он должен быть загружен путем отражения до вызова QuestionFactory.map.size () . Если вы хотите оставить метод main нетронутым, вы должны ссылаться на него или загрузить его путем отражения в статическом инициализаторе QuestionFactory . Я не думаю, что это хорошая идея, но я просто отвечу на ваш вопрос :) Если вы не возражаете, QuestionFactory знает обо всех классах, которые реализуют Question для построения их, вы можете просто сослаться на них напрямую или загрузить их через отражение. Примерно так:

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

Убедитесь, что объявление и конструкция map находятся перед блоком static . Если вы не хотите, чтобы QuestionFactory знал о реализациях Question , вам нужно будет указать их в файле конфигурации, который загружается с помощью QuestionFactory ].Единственный другой (возможно, безумный) способ, который я мог придумать, - это просмотреть весь путь к классам в поисках классов, реализующих Question :) Это могло бы работать лучше, если бы все классы, реализующие Question должны принадлежать к одному пакету - ПРИМЕЧАНИЕ: я не поддерживаю это решение;)

Я не думаю, что делать что-либо из этого в статическом инициализаторе QuestionFactory потому, что классы например, TrueFalseQuestion имеют свой собственный статический инициализатор, который вызывает QuestionFactory , который на тот момент является не полностью сконструированным объектом, который просто вызывает проблемы. Имея файл конфигурации, в котором просто перечисляются классы, которые вы хотите QuestionFactory уметь создавать, а затем регистрировать их в его конструкторе, это прекрасное решение, но это будет означать изменение вашего метода main .

5
ответ дан 5 December 2019 в 10:40
поделиться

Вы можете вызвать:

Class.forName("yourpackage.TrueFalseQuestion");

, это загрузит класс, не касаясь его, и выполнит блок статического инициализатора.

6
ответ дан 5 December 2019 в 10:40
поделиться

Статический инициализатор класса не может быть выполнен, если класс никогда не загружается.

Таким образом, вам нужно либо загрузить все правильные классы (что будет сложно, поскольку вы не знаете их все во время компиляции), либо избавитесь от требования статического инициализатора.

Один из способов сделать последнее - использовать ServiceLoader .

С помощью ServiceLoader вы просто помещаете файл в META-INF / services / package.Question и перечисляете все реализации. У вас может быть несколько таких файлов, по одному на файл .jar. Таким образом, вы можете легко поставлять дополнительные реализации Question отдельно от вашей основной программы.

В QuestionFactory вы можете просто использовать ServiceLodaer.load (Question.class) , чтобы получить ServiceLoader , который реализует Iterable и может использоваться следующим образом:

for (Question q : ServiceLoader.load(Question.class)) {
    System.out.println(q);
}
3
ответ дан 5 December 2019 в 10:40
поделиться

Для запуска статических инициализаторов необходимо загрузить классы. Для этого либо ваш «основной» класс должен зависеть (прямо или косвенно) от классов, либо он должен прямо или косвенно вызывать их динамическую загрузку; например используя Class.forName (...) .

Я думаю, вы пытаетесь избежать зависимостей , встроенных в ваш исходный код . Таким образом, статические зависимости недопустимы, и вызовы Class.forName (...) с жестко заданными именами классов также недопустимы.

Это оставляет вам две альтернативы:

  • Напишите какой-нибудь беспорядочный код для перебора имен ресурсов в каком-нибудь пакете, а затем используйте Class.forName (...) для загрузки тех ресурсов, которые выглядят как ваши классы.Этот подход сложен, если у вас сложный путь к классам, и невозможен, если ваш эффективный путь к классам включает URLClassLoader с удаленным URL-адресом (например).

  • Создайте файл (например, ресурс загрузчика классов), содержащий список имен классов, которые вы хотите загрузить, и напишите простой код для чтения файла и используйте Class.forName (...) для загрузки каждый.

2
ответ дан 5 December 2019 в 10:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: