Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
GoslingJava2.doc
Скачиваний:
129
Добавлен:
23.02.2016
Размер:
2.39 Mб
Скачать

2.5. Конструкторы

Каждый вновь созданный объект обладает некоторым исходным состоянием. Значения полей могут инициализироваться при их объявлении— иногда этого бывает достаточно. /Инициализация данных подробно рассматривается в разделе "Инициализация", однако в сущности за этим термином скрывается обычное присвоение начального значения. Если в программе полю не присваивается никакого значения, оно получит значение ноль, \u0000, false или null, в зависимости от типа./ Однако довольно часто для определения исходного состояния простой инициализации данных оказывается недостаточно; например, могут понадобиться какие-либо исходные данные, или же выполняемые операции не могут быть представлены в виде простого присваивания.

Для тех случаев, когда простой инициализации недостаточно, используются конструкторы. Имя конструктора совпадает с именем класса, который он инициализирует. Конструкторы, подобно методам, могут получать один или несколько параметров, однако они не являются методами и не могут возвращать никакого значения. Параметры конструктора (если они есть) указываются в скобках за именем типа при создании объекта оператором new. Конструктор вызывается после того, как переменным в экземпляре вновь создаваемого объекта будут присвоены начальные значения по умолчанию и будет выполнена непосредственная инициализация.

В усовершенствованной версии класса Body исходное состояние объекта частично устанавливается посредством инициализации, а частично— в конструкторе:

class Body {

public long idNum;

public String name = “”;

public Body orbits = null;

private static long nextID = 0;

Body() {

idNum = nextID++;

}

}

Конструктор класса Body вызывается без аргументов, однако он выполняет важную функцию, а именно устанавливает во вновь создаваемом объекте правильное значение поля idNum. Простейшая ошибка, возможная при работе со старой версией класса,— скажем, вы забыли присвоить значение полю idNum или не наращивали nextID после его использования— приводила к тому, что в программе возникали разные объекты класса Body с одинаковыми значениями поля idNum. В результате возникали проблемы в той части кода, которая была основана на положении контракта, гласящем: “Все значения idNum должны быть разными”.

Возлагая ответственность за генерацию значений idNum на сам класс Body, мы тем самым предотвращаем ошибки подобного рода. Конструктор Body становится единственным местом в программе, где idNum присваивается значение. Следующим шагом является объявление поля nextID с ключевым словом private, чтобы доступ к нему осуществлялся только из класса. Тем самым мы устраняем еще один возможный источник ошибок для программистов, работающих с классом Body.

Кроме того, такое решение позволит нам свободно изменять способ присвоения значений полю idNums в объектах Body. Например, будущая реализация нашего класса может просматривать базу данных известных астрономических объектов и присваивать новое значение idNum лишь в том случае, если ранее данному небесному телу не был присвоен другой идентификатор. Такое изменение никак не скажется на существующем коде программы, поскольку он никак не участвует в присвоении значения idNum.

При инициализации полям name и orbits присваиваются некоторые разумные начальные значения. Следовательно, после приведенного ниже вызова конструктора все поля нового объекта Body будут инициализированы. После этого вы можете изменить состояние объекта, присвоив его полям нужные значения:

Body sun = new Body(); // значение idNum равно 0

sun.name = “Sol”;

Body earth = new Body(); // значение idNum равно 1

earth.name = “Earth”;

earth.orbits = sun;

Конструктор Body вызывается при создании нового объекта оператором new, но после того, как полям name и orbits будут присвоены начальные значения. Инициализация поля orbits значением null означает, что sun.orbits в нашей программе не присваивается никакого значения.

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

Body(String bodyName, Body orbitsAround) {

this();

name = bodyName;

orbits = orbitsAround;

}

Как видите, из одного конструктора можно вызывать другой конструктор этого же класса— для этого первым выполняемым оператором должен быть вызов this(). Это называется “явным вызовом конструктора”. Если для вызова конструктора необходимы параметры, они могут передаваться. В нашем случае для присвоения значения полю idNum используется конструктор, вызываемый без аргументов. Теперь создание объектов происходит значительно проще:

Body sun = new Body(“Sol”, null);

Body earth = new Body(“Earth”, sun);

При желании можно задать отдельный конструктор с одним аргументом для тех случаев, когда для создаваемого объекта Body не существует центра вращения. Вызов такого конструктора равносилен применению конструктора с двумя аргументами, при котором второй из них равен null.

Для некоторых классов необходимо, чтобы создатель объекта предоставлял некоторые данные. Например, приложение может требовать, чтобы для всех объектов Body было указано их название. Чтобы убедиться, что всем создаваемым объектам Body передается название, необходимо включить соответствующий параметр во все конструкторы класса Body.

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

  • Некоторые классы не обладают разумным начальным состоянием, если не передать им параметры.

  • При конструировании объектов некоторых видов передача исходного состояния оказывается самым удобным и разумным выходом (примером может служить конструктор Body с двумя аргументами).

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

  • Конструктор, атрибут доступа которого отличается от public, ограничивает возможности создания объектов данного класса. Например, вы можете запретить программистам, работающим с вашим пакетом, расширять тот или иной класс, если сделаете все конструкторы доступными лишь из пакета. Кроме того, можно пометить ключевым словом protected те конструкторы, которые предназначены для использования исключительно в подклассах.

Конструкторы, не получающие при вызове никаких аргументов, встречаются настолько часто, что для них даже появился специальный термин: “безаргументные” (no-arg) конструкторы.

Если вы не предоставите для класса никаких конструкторов, язык создает безаргументный конструктор по умолчанию, который не делает ничего. Этот конструктор создается автоматически лишь в тех случаях, когда нет никаких других конструкторов,— существуют классы, для которых безаргументный конструктор будет работать неверно (например, класс Attr, с которым мы познакомимся в следующей главе).

Если безаргументный конструктор должен существовать наряду с одним или несколькими конструкторами, использующими аргументы, можно явно определить его. Автоматически создаваемый безаргументный конструктор класса, не имеющего суперкласса, эквивалентен следующему (как мы увидим на примере расширенного класса в главе 3):

class SimpleClass {

/** Эквивалент конструктора по умолчанию */

public SimpleClass() {

}

}

Конструктор по умолчанию имеет атрибут public, если такой же атрибут имеет класс, и не имеет его в противном случае.

Упражнение 2.6

Включите в класс Vehicleдва конструктора. Первый из них— безаргументный, а другойдолжен получать в качестве аргумента имя владельца машины. Модифицируйте метод main так, чтобы он выдавал те же результаты, что и раньше.

Упражнение 2.7

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]