Спецификация Java 11: 12.5. Создание экземпляров новых классов

Новый экземпляр класса создается явно, когда оценка выражения создания экземпляра класса (§15.9) вызывает создание экземпляра класса.

Новый экземпляр класса может быть неявно создан в следующих ситуациях:

  • Загрузка класса или интерфейса, содержащего строковый литерал (§3.10.5), может создать новый объект String для представления литерала. (Этого не произойдет, если строка, обозначающая ту же последовательность кодовых точек Unicode, ранее была интернирована.)
  • Выполнение операции, вызывающей преобразование упаковки (§5.1.7). Преобразование упаковки может создать новый объект класса-оболочки (Boolean, Byte, Short, Character, Integer, Long, Float, Double), связанный с одним из примитивных типов.
  • Выполнение оператора конкатенации строк + (§15.18.1), который не является частью константного выражения (§15.28), всегда создает новый объект String для представления результата. Операторы конкатенации строк также могут создавать временные объекты-оболочки для значения примитивного типа.
  • Оценка выражения ссылки на метод (§15.13.3) или лямбда-выражения (§15.27.4) может потребовать создания нового экземпляра класса, реализующего тип функционального интерфейса.

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

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

Если недостаточно места для выделения памяти для объекта, создание экземпляра класса завершается внезапно с ошибкой OutOfMemoryError. В противном случае все переменные экземпляра в новом объекте, включая объявленные в суперклассах, инициализируются значениями по умолчанию (§4.12.5).

Непосредственно перед тем, как в результате будет возвращена ссылка на вновь созданный объект, указанный конструктор обрабатывается для инициализации нового объекта с использованием следующей процедуры:

  1. Назначьте аргументы конструктора вновь созданным переменным параметров для этого вызова конструктора.
  2. Если этот конструктор начинается с явного вызова конструктора (§8.8.7.1) другого конструктора в том же классе (с использованием this), затем оцените аргументы и обработайте этот вызов конструктора рекурсивно, используя те же пять шагов. Если вызов конструктора завершается внезапно, эта процедура завершается внезапно по той же причине; в противном случае перейдите к шагу 5.
  3. Этот конструктор не начинается с явного вызова конструктора другого конструктора в том же классе (используя this). Если этот конструктор предназначен для класса, отличного от Object, то этот конструктор начнется с явного или неявного вызова конструктора суперкласса (с использованием super). Оцените аргументы и обработайте этот вызов конструктора суперкласса рекурсивно, используя те же пять шагов. Если вызов конструктора завершается внезапно, эта процедура завершается внезапно по той же причине. В противном случае перейдите к шагу 4.
  4. Выполните инициализаторы экземпляра и инициализаторы переменных экземпляра для этого класса, присвоив значения инициализаторов переменных экземпляра соответствующим переменным экземпляра в порядке слева направо, в котором они появляются в текстовом виде в исходном коде для класса. Если выполнение любого из этих инициализаторов приводит к возникновению исключения, дальнейшие инициализаторы не обрабатываются, и эта процедура завершается внезапно с тем же исключением. В противном случае перейдите к шагу 5.
  5. Выполните остальную часть тела этого конструктора. Если это выполнение завершается внезапно, то эта процедура завершается внезапно по той же причине. В противном случае эта процедура завершается нормально.

В отличие от C++, язык программирования Java не определяет измененных правил для диспетчеризации методов во время создания нового экземпляра класса. Если вызываются методы, которые переопределяются в подклассах инициализируемого объекта, то используются эти методы переопределения, даже до того, как новый объект будет полностью инициализирован.

Пример. Оценка создания экземпляра

class Point {
    int x, y;
    Point() { x = 1; y = 1; }
}
class ColoredPoint extends Point {
    int color = 0xFF00FF;
}
class Test {
    public static void main(String[] args) {
        ColoredPoint cp = new ColoredPoint();
        System.out.println(cp.color);
    }
}

Здесь создается новый экземпляр ColoredPoint. Во-первых, для новой ColoredPoint выделяется пространство для хранения полей x, y и color. Затем все эти поля инициализируются значениями по умолчанию (в данном случае 0 для каждого поля). Затем сначала вызывается конструктор ColoredPoint без аргументов. Поскольку ColoredPoint не объявляет конструкторов, неявно объявляется конструктор по умолчанию следующей формы:

ColoredPoint() { super(); }

Затем этот конструктор вызывает конструктор Point без аргументов. Конструктор Point не начинается с вызова конструктора, поэтому компилятор Java обеспечивает неявный вызов своего конструктора суперкласса без аргументов, как если бы он был написан:

Point() { super(); x = 1; y = 1; }

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

У класса Object нет суперкласса, поэтому на этом рекурсия заканчивается. Затем вызываются любые инициализаторы экземпляра и инициализаторы переменных экземпляра Object. Затем выполняется тело конструктора Object, которое не принимает аргументов. Такой конструктор не объявлен в Object, поэтому компилятор Java предоставляет конструктор по умолчанию, который в этом особом случае:

Object() { }

Этот конструктор выполняется без эффекта и возвращается.

Затем выполняются все инициализаторы для переменных экземпляра класса Point. Как это часто бывает, объявления x и y не предоставляют никаких выражений инициализации, поэтому на этом шаге примера никаких действий не требуется. Затем выполняется тело конструктора Point, устанавливая x равным 1 и y равным 1.

Затем выполняются инициализаторы для переменных экземпляра класса ColoredPoint. На этом шаге color присваивается значение 0xFF00FF. Наконец, выполняется остальная часть тела конструктора ColoredPoint (часть после вызова super); в остальной части тела нет операторов, поэтому дальнейшие действия не требуются, и инициализация завершена.

Пример. Динамическая отправка во время создания экземпляра

class Super {
    Super() { printThree(); }
    void printThree() { System.out.println("three"); }
}
class Test extends Super {
    int three = (int)Math.PI;  // То есть, 3
    void printThree() { System.out.println(three); }

    public static void main(String[] args) {
        Test t = new Test();
        t.printThree();
    }
}

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

0
3

Это показывает, что вызов printThree в конструкторе для класса Super не вызывает определение printThree в классе Super, а скорее вызывает переопределение определения printThree в классе Test. Поэтому этот метод запускается до того, как будут выполнены инициализаторы полей Test, поэтому первое выходное значение равно 0, значение по умолчанию, которым инициализируется поле 3 Test. Более поздний вызов printThree в методе main вызывает то же определение printThree, но к этому моменту инициализатор для переменной экземпляра 3 был выполнен, и поэтому печатается значение 3.


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


Комментарии

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

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

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

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