W3docs

Конструкторы Java

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

Конструктор — это специальный метод, который выполняется один раз при создании объекта. Его задача — перевести новый экземпляр из состояния «все поля имеют значения по умолчанию» в состояние «готов к использованию». До сих пор вы задавали поля по одному после new Dog(); конструктор позволяет объединить эту работу в вызове new.

Анатомия конструктора

Конструктор похож на метод, но отличается в трёх вещах: он имеет то же имя, что и класс, не имеет типа возврата (даже void), и вызывается только через new:

public class Point {
  int x, y;

  public Point(int x, int y) {       // constructor
    this.x = x;
    this.y = y;
  }
}

Point p = new Point(3, 4);           // runs the constructor

Список аргументов передаётся в () после имени класса в выражении new. Java сопоставляет этот список аргументов с конструктором класса так же, как сопоставляет вызов метода с методом.

Конструктор по умолчанию

Если вы не объявляете ни одного конструктора, Java автоматически добавляет классу конструктор по умолчанию — конструктор без аргументов, который ничего не делает:

public class Empty { }    // compiler generates a no-arg constructor

Empty e = new Empty();    // works

Именно так new Dog() работал в предыдущих примерах, хотя мы никогда не писали public Dog() { ... }. Как только вы объявляете любой собственный конструктор, бесплатный конструктор по умолчанию исчезает:

public class Point {
  int x, y;
  public Point(int x, int y) { this.x = x; this.y = y; }
}

Point p = new Point();    // ERROR — no no-arg constructor exists

Если вам нужны оба варианта — объявите оба: смотрите раздел о перегрузке ниже.

Зачем использовать конструктор?

По двум причинам.

1. Обязательные поля. Конструктор позволяет сделать некоторые поля обязательными. При работе с открытыми полями и последующем присваивании ничто не мешает вызывающему коду создать Person без name. С конструктором Person(String name) это невозможно.

2. Инварианты. Конструктор может проверить аргументы до того, как объект будет создан в пригодном для использования виде. Если Circle требует положительного радиуса, конструктор может выбросить исключение при отрицательном значении — и некорректный объект вообще не будет создан.

public class Circle {
  double radius;
  public Circle(double radius) {
    if (radius <= 0) throw new IllegalArgumentException("radius must be > 0");
    this.radius = radius;
  }
}

Теперь вызывающий код не может получить Circle с неположительным радиусом.

Перегруженные конструкторы

Класс может иметь несколько конструкторов с разными списками параметров — точно так же, как перегруженные методы:

public class Rectangle {
  double width, height;

  public Rectangle()                      { this(1, 1); }
  public Rectangle(double side)           { this(side, side); }    // a square
  public Rectangle(double w, double h)    { this.width = w; this.height = h; }
}

Теперь все три варианта компилируются:

Rectangle a = new Rectangle();          // 1 x 1
Rectangle b = new Rectangle(5);         // 5 x 5
Rectangle c = new Rectangle(3, 4);      // 3 x 4

Конструкторы копирования

Распространённый вариант перегрузки — конструктор копирования: он принимает другой экземпляр того же класса и создаёт его независимую копию. В Java нет встроенного конструктора копирования (в отличие от C++), поэтому при необходимости вы пишете его сами:

public class Point {
  int x, y;
  public Point(int x, int y) { this.x = x; this.y = y; }
  public Point(Point other)  { this(other.x, other.y); }   // copy constructor
}

Point a = new Point(3, 4);
Point b = new Point(a);    // a separate Point with the same values

Поскольку b — это новый объект, изменение b.x в дальнейшем не затрагивает a. Для классов, поля которых сами являются изменяемыми объектами, скопируйте и поля тоже (глубокое копирование), если хотите, чтобы копия была полностью независимой.

this(...) — цепочка конструкторов

Внутри одного конструктора this(args) вызывает другой конструктор того же класса. Именно так в примере с Rectangle выше удалось избежать дублирования кода присваивания полей: два вспомогательных конструктора делегируют вызов полному конструктору.

Два правила:

  • this(...) должен быть первым оператором в теле конструктора.
  • Конструктор может делегировать вызов только одному другому конструктору.
public Rectangle()           { this(1, 1); }      // ok
public Rectangle(double s)   { System.out.println("hi"); this(s, s); }   // ERROR

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

super(...) — вызов родительского конструктора

Когда класс расширяет другой, каждый конструктор либо явно вызывает родительский конструктор через super(args), либо неявно вызывает конструктор без аргументов родительского класса. Мы полностью разберём это в главах о наследовании и ключевом слове super; пока просто знайте, что super(...) существует и подчиняется тому же правилу первого оператора, что и this(...).

Конструкторы не могут возвращать значение

Конструктор не имеет типа возврата — даже void. Если вы напишете:

public void Point(int x, int y) { ... }    // not a constructor!

…это будет обычный метод с именем Point. Компилятор не предупредит вас; тогда new Point(3, 4) не скомпилируется, потому что настоящего конструктора, который он ищет, не существует. Это на удивление распространённая опечатка.

Порядок инициализации

Для одного класса порядок следующий:

  1. Полям присваиваются значения по умолчанию (например, поля int становятся равными 0).
  2. Встроенные инициализаторы полей (int count = 5;) и любые блоки инициализации экземпляра выполняются в порядке их появления.
  3. Выполняется тело конструктора.
public class Demo {
  int a = compute("a-init", 1);
  int b;

  { b = compute("b-block", 2); }    // instance initializer

  public Demo() {
    System.out.println("constructor; a=" + a + ", b=" + b);
  }

  static int compute(String label, int v) {
    System.out.println(label);
    return v;
  }
}

new Demo() выводит a-init, b-block, затем constructor; a=1, b=2.

На практике почти вся инициализация выполняется в теле конструктора. Блоки инициализации экземпляра в реальном коде встречаются редко; они существуют главным образом для ситуаций вроде анонимных классов (рассматриваются позже).

Рабочий пример

java— editable, runs on the server

Что дальше

Внутри конструкторов вы уже видели this.field = field для разграничения параметра и поля. Глава о ключевом слове this подробно объясняет, что такое this и в каких немногочисленных случаях его нужно писать явно.

Практика

Практика
Класс объявляет только один конструктор: public Point(int x, int y). Какой вызов завершится ошибкой?
Класс объявляет только один конструктор: public Point(int x, int y). Какой вызов завершится ошибкой?
Was this page helpful?