Кен Арнольд Джеймс Гослинг

Вид материалаДокументы

Содержание


2.3. Управление доступом и наследование
2.4. Создание объектов
Подобный материал:
1   ...   7   8   9   10   11   12   13   14   ...   81

2.3. Управление доступом и наследование


Код класса всегда может обращаться ко всем полям и методам данного класса. Для управления доступом к ним из других классов, а также для управления наследованием их в подклассах члены классов могут объявляться с одним из четырех атрибутов доступа:
  • Открытый (Public): к членам класса всегда можно обращаться из любого места, в котором доступен сам класс; такие члены наследуются в подклассах.
  • Закрытый (Private): доступ к членам класса осуществляется только из самого класса.
  • Защищенный (Protected): к данным членам разрешается доступ из подклассов и из функций, входящих в тот же пакет. Такие члены наследуются подклассами. Расширение объектов (наследование) подробно рассмотрено в главе 3.
  • Пакетный: доступ к членам, объявленным без указания атрибута доступа, осуществляется только из того же пакета. Такие члены наследуются подклассами пакета. Пакеты рассматриваются в главе 10.

Поля класса Body были объявлены с атрибутом public, потому что для выполнения поставленной задачи программистам необходим доступ к этим полям. На примере более поздних версий класса Body мы убедимся, что подобное решение обычно является неудачным.

2.4. Создание объектов


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

Body sun = new Body();

sun.idNum = Body.nextID++;

sun.nameFor = “Sol”;

sun.orbits = null; // Солнце является центром Солнечной

// системы


Body earth = new Body();

earth.idNum = Body.nextID++;

earth.nameFor = “Earth”;

earth.orbits = sun;


Сначала мы объявили две ссылки (sun и earth) на объекты типа Body. Как упоминалось выше, объекты при этом не создаются — лишь объявляются переменные, которые ссылаются на объекты. Первоначальное значение ссылок равно null, а соответствующие им объекты должны явным образом создаваться в программе.

Объект sun создается посредством оператора new. Конструкция new считается самым распространенным способом построения объектов (позднее мы рассмотрим и другие возможности). При создании объекта оператором new следует указать тип конструируемого объекта и необходимые параметры. Runtime-система выделяет область памяти, достаточную для хранения полей объекта, и инициализирует ее в соответствии с рассматриваемыми ниже правилами. После завершения инициализации runtime-система возвращает ссылку на созданный объект.

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

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

В нашем примере строится модель Солнечной системы. В ее центре находится Солнце, поэтому полю orbits объекта sun присваивается значение null — у Солнца нет объекта, вокруг которого оно бы вращалось. При создании и инициализации объекта earth (Земля) мы присвоили полю orbits значение sun. Для Луны, вращающейся вокруг Земли, поле orbits получило бы значение earth. Если бы мы строили модель Галактики, то Солнце бы также вращалось вокруг “черной дыры”, находящейся где-то в середине Млечного Пути.

Упражнение 2.4

Напишите для класса Vehicle метод main, который создает несколько объектов-автомашин и выводит значения их полей.

Упражнение 2.5

Напишите для класса LinkedList метод main, который создает несколько объектов типа Vehicle и заносит их в список.

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?