Перейти к основному содержимому

Спецификация конструктора в Java

· 5 мин. чтения

1. Обзор

В этом руководстве мы узнаем, как Java работает с конструкторами , и рассмотрим некоторые связанные с ними правила из Спецификации языка Java .

2. Объявления конструктора

В Java каждый класс должен иметь конструктор. Его структура похожа на метод, но у него другие цели.

Посмотрим спецификацию конструктора:

<Constructor Modifiers> <Constructor Declarator> [Throws Clause] <Constructor Body>

Рассмотрим каждую часть отдельно.

2.1. Модификаторы конструктора

Объявления конструктора начинаются с модификаторов доступа: они могут быть public , private , protected или package access в зависимости от других модификаторов доступа.

Чтобы предотвратить ошибки компиляции, объявления конструктора не могут иметь более одного модификатора доступа private , protected или public .

В отличие от методов, конструктор не может быть абстрактным , статическим , окончательным , родным или синхронизированным :

  • Нет необходимости объявлять конструктор final , потому что они не являются членами класса и не наследуются.
  • Абстракция не нужна, потому что мы должны реализовать конструкторы.
  • Статический конструктор не требуется, так как каждый конструктор вызывается с объектом.
  • Строящийся объект не следует синхронизировать , так как это заблокирует объект во время его создания, который обычно не становится доступным для других потоков, пока все конструкторы не завершат свою работу.
  • В Java нет встроенных конструкторов , потому что это решение дизайна языка предназначено для обеспечения того, чтобы конструкторы суперкласса всегда вызывались во время создания объекта.

2.2. Декларатор конструктора

Давайте рассмотрим синтаксис объявления конструктора:

Constrcutor Name (Parameter List)

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

2.3. Оговорка о бросках

Структура и поведение предложений throws для методов и конструкторов одинаковы.

2.4. Конструктор тела

Синтаксис тела конструктора:

Constructor Body: { [Explicit Constructor Invocation] [Block Statements] }

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

3. Явные вызовы конструктора

Мы можем разделить вызовы конструкторов на два типа:

  • Вызов альтернативного конструктора начинается с ключевого слова this . Они используются для вызова альтернативных конструкторов того же класса.
  • Вызов конструктора суперкласса начинается с ключевого слова super.

Давайте рассмотрим пример того, как мы можем использовать ключевые слова this и super для вызова другого конструктора:

class Person {
String name;

public Person() {
this("Arash"); //ExplicitConstructorInvocation
}

public Person(String name){
this.name = name;
}
}

Здесь первый конструктор класса Employee вызывает конструктор своего суперкласса Person , передавая идентификатор:

class Person {
int id;
public Person(int id) {
this.id = id;
}
}

class Employee extends Person {
String name;
public Employee(int id) {
super(id);
}
public Employee(int id, String name) {
super(id);
this.name = name;
}
}

4. Правила вызова конструктора

4.1. this или super Должен быть первым оператором в конструкторе

Всякий раз, когда мы вызываем конструктор, он должен вызывать конструктор своего базового класса. Кроме того, внутри класса можно вызвать другой конструктор. Java применяет это правило, делая первый вызов конструктора this или super .

Давайте рассмотрим пример:

class Person {
Person() {
//
}
}
class Employee extends Person {
Employee() {
//
}
}

Вот пример компиляции конструктора:

.class Employee
.super Person
; A constructor taking no arguments
.method <init>()V
aload_0
invokespecial Person/<init>()V
return
.end method

Компиляция конструктора аналогична компиляции любого другого метода, за исключением того, что сгенерированный метод имеет имя <init>. Одним из требований для проверки метода <init> является то, что вызов конструктора суперкласса (или какого-либо другого конструктора в текущем классе) должен быть первым шагом в методе.

Как мы видим выше, класс Person должен вызывать свой конструктор суперкласса и так далее до java.lang.Object.

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

4.2. Не используйте оба this и super в конструкторе

Представьте, если бы мы могли использовать this и super вместе в теле конструктора.

Давайте посмотрим, что произойдет на примере:

class Person {
String name;
public Person() {
this("Arash");
}

public Person(String name) {
this.name = name;
}
}

class Employee extends Person {
int id;
public Employee() {
super();
}

public Employee(String name) {
super(name);
}

public Employee(int id) {
this();
super("John"); // syntax error
this.id = id;
}

public static void main(String[] args) {
new Employee(100);
}
}

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

Давайте посмотрим на последовательность вызова конструктора:

./346ff2ad176f5e9a5e0d77adc555c37a.png

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

4.3. Вызов рекурсивного конструктора

Компилятор выдаст ошибку, если конструктор вызовет сам себя. Например, в следующем коде Java компилятор выдаст ошибку, потому что мы пытаемся вызвать один и тот же конструктор внутри конструктора:

public class RecursiveConstructorInvocation {
public RecursiveConstructorInvocation() {
this();
}
}

Несмотря на ограничение компилятора Java, мы можем скомпилировать программу, немного изменив код, но таким образом мы столкнемся с переполнением стека:

public class RecursiveConstructorInvocation {
public RecursiveConstructorInvocation() {
RecursiveConstructorInvocation rci = new RecursiveConstructorInvocation();
}

public static void main(String[] args) {
new RecursiveConstructorInvocation();
}
}

Мы создали объект RecursiveConstructorInvocation , который инициализируется вызовом конструктора. Затем конструктор создает другой объект RecursiveConstructorInvocation , который инициализируется повторным вызовом конструктора до тех пор, пока стек не переполнится.

Теперь давайте посмотрим на вывод:

Exception in thread "main" java.lang.StackOverflowError
at org.example.RecursiveConstructorInvocation.<init>(RecursiveConstructorInvocation.java:29)
at org.example.RecursiveConstructorInvocation.<init>(RecursiveConstructorInvocation.java:29)
at org.example.RecursiveConstructorInvocation.<init>(RecursiveConstructorInvocation.java:29)
//...

5. Вывод

В этом руководстве мы обсудили спецификацию конструкторов в Java и рассмотрели некоторые правила для понимания вызова конструкторов в классе и суперклассе.

Как всегда, образцы кода можно найти на GitHub .