Подтвердить что ты не робот

Как решены объектные зависимости между статическими блоками?

Недавно я наткнулся на это на работе. Хотя я не уверен, что это действительно хорошая идея, я не понимаю, как обрабатываются статические блоки компилятором.

Вот пример:

Учтите, что у вас есть классы A и B:

public class A {

    public final static List<Integer> list;
    static {
        list = new ArrayList<>();
    }
}

public class B {

    public final static int dependsOnA;
    static {
        dependsOnA = A.list.size();
    }
}

И основной класс, который просто читает B.dependsOnA.

Статический блок в B зависит от значения в A, так как он использует статическую переменную list.

Теперь код выполняется правильно и во время выполнения не создается NullPointerException. Но каков механизм, обеспечивающий инициализацию list до того, как он будет потенциально использован в другом месте?

4b9b3361

Ответ 1

Механизм подробно описан здесь, но пять наиболее важных моментов:

  • Прежде чем ссылаться на класс, его необходимо инициализировать.
  • Если инициализация класса уже началась (или завершена), она не будет предпринята повторно.
  • Прежде чем инициализировать класс, все его суперклассы и суперинтерфейсы должны быть сначала инициализированы.
  • Статические инициализаторы в пределах одного класса выполняются в текстовом порядке.
  • Реализованные интерфейсы инициализируются в том порядке, в котором они отображаются в предложении implements.

Эти правила полностью определяют порядок выполнения статических блоков.

Ваш случай довольно прост: перед тем, как вы получите доступ к B.dependsOnA, необходимо инициализировать B (правило 1), статический инициализатор пытается получить доступ к A.list, который запускает инициализацию класса A ( снова правило 1).

Обратите внимание, что ничто не мешает вам создавать круговые зависимости таким образом, что вызовет интересные вещи:

public class Bar {
    public static int X = Foo.X+1;

    public static void main(String[] args) {
        System.out.println( Bar.X+" "+Foo.X); // 
    }

}

class Foo {
    public static int X = Bar.X+1;
}

Результат здесь 2 1, потому что способ инициализации происходит следующим образом:

  • Bar начинается инициализация.
  • Bar.X оценивается начальное значение, которое требует инициализации Foo first
  • Foo начинается инициализация.
  • Foo.X оценивается начальное значение, но поскольку инициализация Bar уже выполняется, она не инициализируется снова, используется Bar.X "текущее" значение, которое равно 0, поэтому Foo.X инициализируется 1.
  • Мы вернулись к оценке значения Bar.X, Foo.X равно 1, поэтому Bar.X становится 2.

Это работает, даже если оба поля были объявлены final.

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

Ответ 2

"Механизм" - это загрузчик классов JVM, который обеспечит выполнение блоков инициализации класса (с глобальной блокировкой по всей JVM), прежде чем возвращать поток управления туда, где первый класс ссылался. Он сначала загрузит класс A только после ссылки, в этом случае, когда B ссылки блока init A.list.

Ответ 3

Во время выполнения блока static B среда выполнения встречается A в первый раз и будет вызывать блок static A, прежде чем обратится к A.list.

Ответ 4

Независимо от того, как вы пишете код, блок static является блоком static, и он будет выполняться как часть загрузки JVM класса.

Когда вы скажете B.dependsOnA, класс начинает запускать загрузку JVM, а блок static в B вызывается где-то во время этого процесса. Когда вы говорите dependsOnA = A.list.size();, класс A начинает загружаться JVM, а блок static в будет выполняться где-то во время этого процесса, который инициализирует list. Статус list.size() будет выполняться только после того, как класс A полностью загрузится JVM. Впоследствии JVM может завершить класс загрузки B после завершения статического блока в B.

Ответ 5

Это задача загрузчика классов. Загрузка класса в java начинается с загрузчика классов bootstrap. Этот загрузчик классов сначала загружает все классы в стандартную java-библиотеку, rt.jar.

Затем вызывается расширение classloader. Это загружает все классы из файлов расширения jar, установленных в каталоге JVM ext. Теперь, наконец, вызывается класс classloader.

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

Таким образом, нет никаких шансов на то, что вызовы для незагруженных классов произойдут. Давайте посмотрим на это с вашим собственным примером, код которого выглядит так:

class A
{
    public final static List<Integer> list;
    static
    {
        System.out.println("Loaded Class A");
        list = new ArrayList<>();
    }
}

class B
{
    public final static int dependsOnA;
    static
    {
        System.out.println("Loaded Class B");
        dependsOnA = A.list.size();
    }
}

Здесь в этом примере фактически нет основного метода, поэтому эти классы фактически не будут загружены в память. Предположим, добавим следующий класс к указанному выше коду.

class C
{
    static
    {
        System.out.println("Loaded Class C");
    }

    public static void main(String[] args)
    {
        System.out.println(B.dependsOnA);
    }
}

Посмотрим, что это произведет на выходе: http://ideone.com/pLg3Uh

Loaded Class C
Loaded Class B
Loaded Class A
0

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

Теперь в основном методе мы напечатали значение dependsOnA класса B. Теперь загрузчик классов перестает выполнять этот оператор и загружает класс B и выполняет его статический блок, который, в свою очередь, назначает dependsOnA variable со значением количества элементов в списке класса A, который не загружен.

Итак, загрузчик классов перескакивает оттуда, загружает класс сейчас и вызывает статический блок класса A, и создается список. Теперь, поскольку больше нет загружаемых классов, загрузчик классов возвращается к статическому блоку класса B, и назначение завершено. Теперь, наконец, теперь элемент управления имеет основной метод, а значение dependsOnA выводится на консоль.

Надеюсь, что это поможет.

Ответ 6

Здесь мы имеем некоторое объяснение Статический блок в Java

Если вы сначала вызываете класс A, вызывается A static и A.list существует и будет, когда B назовет его.

Если вы сначала вызываете класс B, вызывается статический B, который каскадирует на вызов A, вызывая его статический блок, где создается A.list.

Мы видели, что это самый сложный способ: B > B.static > A > A.static > A.list существует

Ответ 7

Работает очень простой загрузчик классов JVM, который гарантирует, что статические блоки класса выполняются, когда класс ссылается на первый класс.
1. Если у вас есть исполняемые операторы в статическом блоке, JVM будет автоматически выполнять эти инструкции, когда класс загружается в JVM.
2.Если вы ссылаетесь на некоторые статические переменные/методы из статических блоков, эти операторы будут выполняться после того, как класс загрузится в JVM, как указано выше, т.е. теперь будут переданы статические переменные/методы и будет выполняться статический блок.