Спецификация Java 11: 12.4. Инициализация классов и интерфейсов

Инициализация класса состоит из выполнения его статических инициализаторов и инициализаторов для статических полей (переменных класса), объявленных в классе.

Инициализация интерфейса заключается в выполнении инициализаторов для полей (констант), объявленных в интерфейсе.

12.4.1. Когда происходит инициализация

Класс или тип интерфейса T будет инициализирован непосредственно перед первым появлением любого из следующих событий:

  • T - это класс, и создается экземпляр T.
  • Вызывается статический метод, объявленный T.
  • Назначено статическое поле, объявленное T.
  • Используется статическое поле, объявленное T, и поле не является постоянной переменной (§4.12.4).

Когда класс инициализируется, инициализируются его суперклассы (если они не были ранее инициализированы), а также любые суперинтерфейсы (§8.1.5), которые объявляют любые методы по умолчанию (§9.4.3) (если они не были ранее инициализированы). Инициализация интерфейса сама по себе не вызывает инициализацию какого-либо из его суперинтерфейсов.

Ссылка на статическое поле (§8.3.1.1) вызывает инициализацию только того класса или интерфейса, который его фактически объявляет, даже если на него можно ссылаться через имя подкласса, подинтерфейса или класса, реализующего интерфейс.

Вызов определенных отражающих методов в классе Class и в пакете java.lang.reflect также вызывает инициализацию класса или интерфейса.

Класс или интерфейс не будут инициализированы ни при каких других обстоятельствах.

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

Цель состоит в том, чтобы у класса или типа интерфейса был набор инициализаторов, которые переводят его в согласованное состояние, и что это состояние является первым состоянием, которое наблюдается другими классами. Статические инициализаторы и инициализаторы переменных класса выполняются в текстовом порядке и могут не ссылаться на переменные класса, объявленные в классе, объявления которых появляются в текстовом виде после использования, даже если эти переменные класса находятся в области видимости (§8.3.3). Это ограничение предназначено для обнаружения во время компиляции большинства циклических или иным образом искаженных инициализаций.

Тот факт, что код инициализации не ограничен, позволяет создавать примеры, в которых значение переменной класса можно наблюдать, когда она все еще имеет свое начальное значение по умолчанию, до того, как будет оценено ее инициализирующее выражение, но такие примеры на практике редки. (Такие примеры также могут быть построены для инициализации переменной экземпляра (§12.5).) В этих инициализаторах доступна вся мощь языка программирования Java; программисты должны проявлять осторожность. Эта сила возлагает дополнительную нагрузку на генераторы кода, но эта нагрузка возникнет в любом случае, потому что язык программирования Java является конкурентным (§12.4.2).

Пример суперклассов инициализирующихся перед подклассами:

class Super {
    static { System.out.print("Super "); }
}
class One {
    static { System.out.print("One "); }
}
class Two extends Super {
    static { System.out.print("Two "); }
}
class Test {
    public static void main(String[] args) {
        One o = null;
        Two t = new Two();
        System.out.println((Object)o == (Object)t);
    }
}

Эта программа производит вывод:

Super Two false

Класс One никогда не инициализируется, потому что он не используется активно и, следовательно, никогда не связан. Класс Two инициализируется только после инициализации его суперкласса Super.

Пример - инициализируется только класс, объявляющий статическое поле:

class Super {
    static int taxi = 1729;
}
class Sub extends Super {
    static { System.out.print("Sub "); }
}
class Test {
    public static void main(String[] args) {
        System.out.println(Sub.taxi);
    }
}

Эта программа печатает только:

1729

потому что класс Sub никогда не инициализируется; ссылка на Sub.taxi - это ссылка на поле, фактически объявленное в классе Super, и не запускает инициализацию класса Sub.

Пример - инициализация интерфейса не инициализирует суперинтерфейсы:

interface I {
    int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
    int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
    int k = Test.out("k", 5);
}
class Test {
    public static void main(String[] args) {
        System.out.println(J.i);
        System.out.println(K.j);
    }
    static int out(String s, int i) {
        System.out.println(s + "=" + i);
        return i;
    }
}

Эта программа производит вывод:

1
j=3
jj=4
3

Ссылка на J.i относится к полю, которое является постоянной переменной (§4.12.4); следовательно, это не вызывает инициализацию I (§13.4.9).

Ссылка на K.j - это ссылка на поле, фактически объявленное в интерфейсе J, которое не является постоянной переменной; это вызывает инициализацию полей интерфейса J, но не полей его суперинтерфейса I или интерфейса K.

Несмотря на то, что имя K используется для обозначения поля j интерфейса J, интерфейс K не инициализируется.


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки