Могу ли я гарантировать порядок запуска статических инициализаторов в Java? - PullRequest
5 голосов
/ 27 апреля 2009

У меня есть класс Set (это J2ME, поэтому у меня ограниченный доступ к стандартному API; просто для объяснения моего очевидного изобретения колеса). Я использую свой набор классов для создания постоянных наборов вещей в классах и подклассах. Это выглядит примерно так ...

class ParentClass
{
    protected final static Set THE_SET = new Set() {{
        add("one");
        add("two");
        add("three");
    }};
}


class SubClass extends ParentClass
{
    protected final static Set THE_SET = new Set() {{
        add("four");
        add("five");
        add("six");
        union(ParentClass.THE_SET); /* [1] */
    }};
}

Все выглядит нормально, за исключением того, что строка в [1] вызывает исключение нулевого указателя. Предположительно это означает, что статический инициализатор в подклассе выполняется раньше, чем родительский класс. Это удивило меня, потому что я думал, что он будет запускать статические блоки в любом новом импорте в первую очередь, прежде чем запускать их в подклассе.

Прав ли я в этом предположении? Есть ли способ контролировать или обходить это поведение?

Обновление:

Все еще страннее. Я попробовал это вместо этого (обратите внимание на строку 'new ParentClass ()'):

class ParentClass
{
    public ParentClass()
    {
        System.out.println(THE_SET);
    }

    protected final static Set THE_SET = new Set() {{
        add("one");
        add("two");
        add("three");
    }};
}


class SubClass extends ParentClass
{
    protected final static Set THE_SET = new Set() {{
        System.out.println("a");
        new ParentClass();
        System.out.println("b");
        add("four");
        System.out.println("c");
        add("five");
        System.out.println("d");
        add("six");
        System.out.println("e");
        union(ParentClass.THE_SET); /* [1] */
        System.out.println("f");
    }};
}

А вывод странный:

a
["one", "two", "three"]
b
c
d
e
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException

Итак, ParentClass инициализирован, но у подкласса нет доступа к нему в его статическом инициализаторе.

Ответы [ 6 ]

7 голосов
/ 27 апреля 2009

Это то, что вы пытаетесь достичь? Или вам нужна локальная реализация интерфейса Set?

class ParentClass
{
    protected final static Set THE_SET;

    static {
        THE_SET = new HashSet();
        THE_SET.add("one");
        THE_SET.add("two");
        THE_SET.add("three");
    }
}


class SubClass extends ParentClass
{
    protected final static Set THE_SECOND_SET;

    static {
        THE_SECOND_SET = new HashSet();
        THE_SECOND_SET.add("four");
        THE_SECOND_SET.add("five");
        THE_SECOND_SET.add("six");
        union(ParentClass.THE_SET); /* [1] */
    }
}
3 голосов
/ 27 апреля 2009

Нет гарантии на статический порядок инициализатора среди классов. Внутри класса они выполняются в порядке исходного кода.

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

2 голосов
/ 27 апреля 2009

Просто прекратите злоупотреблять концепцией анонимных классов для инициализации экземпляра (так называемая идиома «двойная скобка»).

2 голосов
/ 27 апреля 2009

Даже если у вас нет extends ParentClass, использование ParentClass должно привести к его инициализации.

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

Может быть, ваша реализация Java ME содержит ошибки (не неслыханно). Разделите ваши полные ParentClass ссылки на ваши ChildClass. Или, возможно, есть какая-то другая ошибка приложения / библиотеки.

В связанной заметке, если вы не -target 1.4 или более поздней версии, внешние классы внешнего класса не инициализируются так скоро, как вы могли ожидать. Как показано, ваш код использует (технически) внутренние классы в статическом контексте, так что это не должно быть проблемой.

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

0 голосов
/ 27 апреля 2009

Я думаю, что нечто подобное было написано в книге Java-головоломок или на YouTube в видео Google о трюках с Java.

0 голосов
/ 27 апреля 2009

Учитывая строку вывода ["one", "two", "three"], в принципе невозможно, чтобы ParentClass.THE_SET никогда не инициализировался.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...