Глава 3
КЛАССЫ И ОБЪЕКТЫ
Любая действующая программа устарела.
Первый Закон Программирования
Класс соответствует новому типу данных как описание совокупности объ-
ектов с общими атрибутами, методами, отношениями и семантикой.
Классы — основной элемент абстракции, отвечающий за реализацию на-
значенного ему контракта и обеспечивающий сокрытие реализации. Классы
объединяются в пакеты, которые связаны друг с другом только через ограни-
ченное количество методов и классов, не имея никакого представления
о процессах, происходящих внутри классов и методов других пакетов. Имя
класса в пакете должно быть уникальным. Физически пакет представляет
собой каталог, в который помещаются программные файлы, содержащие ре-
ализацию классов.
Классы позволяют провести декомпозицию поведения сложной системы
до множества элементарных взаимодействий связанных объектов. Класс опре-
деляет структуру и/или поведение некоторого элемента предметной области.
Под элементом следует понимать как физическую сущность (например: Заказ,
Товар), так и логическую (например: УправлениеCчетом, СоставлениеОтчета).
Определение класса в общем виде без наследования и реализации интер-
фейсов (наследование по умолчанию только от Object) имеет вид:
[public] [final] [abstract] class ИмяКласса
{
{} // логические блоки
// внутренние классы
// поля
private // закрытые
public // открытые
// дружественные (по умолчанию)
protected // защищенные
// конструкторы
public // открытые
// дружественные (по умолчанию)
protected // защищенные
private // закрытые
// методы
public // открытые
КЛАССЫ И ОБЪЕКТЫ
55
// дружественные (по умолчанию)
protected // защищенные
private // закрытые
}
Спецификатор доступа public определяет внешний (enclosing) класс, как
доступный из других пакетов.
Переменные класса, экземпляра и константы
Класс создается в случае необходимости группировки данных и/или дейст-
вий (методов) под общим именем, то есть создания нового типа данных, акку-
мулирующего свойства и действия, связанные с одной предметной областью.
Классы инкапсулируют переменные и методы — члены класса. Переменные
в классе объявляются следующим образом:
[cпецификатор] Тип имя;
Со спецификатором static объявляется для всего класса статическая пере-
менная класса, которая имеет общее значение для всех экземпляров класса. Без
спецификатора static объявляются переменные экземпляра класса, имеющие
уникальные и независимые значения для каждого объекта класса. Поля класса,
как и методы, объявляются также со спецификаторами доступа public, private,
protected или по умолчанию без спецификатора. Кроме полей — членов клас-
са, в методах класса используются локальные пере менные и параметры мето-
дов. В отличие от переменных класса, инкапсули руемых нулевыми элемента-
ми, переменные методов не инициализируются по умолчанию.
Поля класса со спецификатором final являются константами и не могут
быть изменены после инициализации. Специфи катор final можно использо-
вать не только для поля класса, но и для локальной переменной, объявленной
в методе, а также для параметра метода. Это единственный спецификатор, при-
меняемый с параметром метода или локальной переменной.
В следующем примере приводятся объявление и инициализация значений
полей класса и локальных переменных метода, а также использование параме-
тров метода:
/* # 1 # типы атрибутов и переменных # Order.java */
package
by.bsu.entity;
// класс доступен из других пакетов
public
class Order {
private int id; // переменная экземпляра класса
static int bonus; // переменная класса
public final int MIN_TAX = 8 + ( int)(Math. random()*5); // константа экземпляра класса
public final static int PURCHASE_TAX = 6; // константа класса
ОСНОВЫ JAVA
56
// конструкторы
// метод
public double calculatePrice( double price, int counter) { /* параметры метода */
double amount; // локальная переменная метода не получает значения по умолчанию
// amount++; // ошибка компиляции, значение не задано
amount = (price - bonus) * counter; // инициализация локальной переменной
double tax = amount * PURCHASE_TAX /100;
return amount + tax; // возвращаемое значение
}
}
В примере в качестве переменных экземпляра класса, переменных класса
и локальных переменных метода использованы данные базовых типов, не яв-
ляющиеся ссылками на объекты. Поля классов могут быть ссылками на объект,
назначить которым реальные объекты можно с помощью оператора new.
Ограничение доступа
Язык Java предоставляет несколько уровней защиты, обеспечивающих воз-
можность настройки области видимости данных и методов. Из-за наличия па-
кетов Java работает с четырьмя категориями видимости между элементами
классов:
• private — члены класса доступны только членам данного класса;
• по умолчанию (package-private) — члены класса доступны классам, находя-
щимся в том же пакете;
• protected — члены класса доступны классам, находящимся в том же пакете,
и подклассам — в других пакетах;
• public — члены класса доступны для всех классов в этом и других пакетах.
Член класса (поле, конструктор или метод), объявленный public, доступен
из любого места вне класса. Спецификатор public для методов и конструкто-
ров public-классов обеспечивает внешний доступ к функциональности пакета,
в котором он объявлен. Если данный спецификатор отсутствует, то класс и его
методы могут использоваться только в текущем пакете.
Все, что объявлено private, доступно только конструкторам и методам вну-
три класса и нигде больше. Они выполняют служебную или вспомогательную
роль в пределах класса, и их функциональность (методов и конструкторов)
не предназначена для внешнего использования. Закрытие ( private) полей обес-
печивает инкапсуляцию.
Если у члена класса вообще не указан спецификатор уровня доступа, то та-
кой член класса будет виден и доступен из подклассов и классов того же паке-
та. Именно такой уровень доступа используется по умолчанию. Такой член
класса обеспечивает исполнение public-методами public-классов своей функ-
циональности.
КЛАССЫ И ОБЪЕКТЫ
57
Если же необходимо, чтобы элемент был доступен из другого пакета, но толь-
ко подклассам того класса, которому он принадлежит, нужно объявить такой
элемент со спецификатором protected. Спецификатор применим, если поле ча-
сто используется в подклассе или если метод предназначен для переопределе-
ния и использования его функциональности в другом пакете. Для конструктора
наличие protected обеспечивает саму возможность наследования от этого
класса в другом пакете.
Действие спецификатора доступа распространяется только на тот элемент
класса, перед которым стоит такой спецификатор.
Конструкторы
Конструктор — особого вида метод, который по имени автоматически вы-
зывается при создании экземпляра класса с помощью оператора new. При кор-
ректном проектировании класса конструктор не должен выполнять никаких
других обязанностей, кроме инициализации полей класса и проверки непроти-
воречивости конструирования объекта.
Конструктор имеет то же имя, что и класс; вызывается не просто по имени,
а только вместе с ключевым словом new при создании экземпляра класса.
Конструктор не возвращает значение, но может иметь параметры и быть пере-
гружаемым. Конструкторов в классе может быть несколько, но не менее одного.
Деструктор в языке Java не используется, объект уничтожается сборщиком
мусора после определения невозможности его дальнейшего использования
(потери ссылки). Некоторым аналогом деструктора является метод finalize(),
в тело которого помещается код по освобождению занятых объектом ресурсов.
Виртуальная машина станет вызывать его каждый раз, когда сборщик мусора
будет уничтожать объект класса, которому не соответствует ни одна ссылка.
В следующем примере объявлен класс Account c полями (атрибутами), кон-
структорами и методами для инициализации и извлечения значений атрибутов.
/* # 2 # конструкторы # Account.java */
package
by.bsu.transfer.bean;
public
class Account {
private
long id;
private
double amount;
// конструктор без параметров
public Account() { // наличие этого конструктора некорректно по смыслу класса
super();
/* если класс будет объявлен вообще без конструктора, то
компилятор предоставит его именно в таком виде */
}
// конструктор с параметром
public
Account( long id) {
ОСНОВЫ JAVA
58
super(); /* вызов конструктора суперкласса явным образом
необязателен, компилятор вставит его автоматически */
this.id = id;
}
// конструктор с параметрами
public
Account( long id, double amount) {
this.id = id;
this.amount = amount;
}
public double getAmount () {
return amount;
}
public void setAmount ( double amount) {
this.amount = amount;
}
public long getId() {
return id;
}
public void setId( long id) {
// проверка на корректность
this.id = id;
}
public
void addAmount ( double amount) {
/* данный метод в общем случае можно объявлять в другом классе */
this.amount += amount;
}
}
Кроме данных и методов каждый экземпляр класса (объект) имеет неявную
ссылку this на себя, которая передается также неявно и нестатическим методам
класса. После этого каждый метод «знает», какой объект его вызвал. Вместо
обращения к атрибуту id в методах можно писать this.id, хотя и не обязательно,
так как записи id и this.id равносильны. Но если в методе объявлена локальная
переменная или параметр метода с таким же именем, как и поле класса, то для
обращения к полю класса использование this обязательно. Без использования
указателя обращение всегда будет производиться к локальной переменной, так
как просто не существует другого способа ее идентификации.
Объект класса Account может быть создан тремя способами, вызывающими
один из конструкторов:
Account a = new Account(); /* инициализация полей значениями по умолчанию
соответствующего типа, наличие нулевого id неприемлемо для логики класса */
Account b = new Account(71L); /* инициализация одного поля переданным в конструктор
значением и другого поля по умолчанию */
Account с = new Account(71L, 0.7); /* инициализация полей переданными в конструктор
значениями */
Оператор new вызывает конструктор, поэтому в круглых скобках могут сто-
ять аргументы, передаваемые конструктору.
КЛАССЫ И ОБЪЕКТЫ
59
Если конструктор в классе явно не определен, то компилятор предоставляет
конструктор по умолчанию без параметров, который инициализирует каждое
поле класса значением по умолчанию, соответствующим его типу, например: 0,
false, null. Если же конструктор с параметрами определен, то конструктор
по умолчанию становится недоступным и для его вызова необходимо явное
объявление такого конструктора. Конструктор подкласса при его создании
всегда наделяется возможностью вызова конструктора суперкласса. Этот вы-
зов может быть явным или неявным и всегда располагается в первой строке
кода конструктора подкласса. Если конструктору суперкласса нужно передать
параметры, то необходим явный вызов из конструктора порожденного класса
super(список_параметров). Подробнее о конструкторе с параметрами будет
сказано при рассмотрении вопросов наследования классов.
Методы
Метод — основной элемент структурирования кода. Все методы в Java объ-
являются только внутри классов и используются для работы с данными класса
и передаваемыми параметрами. Простейшее определение метода имеет вид:
ВозвращаемыйТип имяМетода(список параметров) {
// тело метода
return значение; /* если нужен возврат значения (если тип возвращаемого значения не void)*/
}
Если метод не возвращает значение, ключевое слово return отсутствует или
записывается без возвращаемого значения в виде:
return
;
тип возвращаемого методом значения в этом случае будет void. Вместо пустого
списка параметров метода тип void не указывается, а только пустые скобки. Вызов
методов осуществляется из объекта или класса (для статических методов):
objectName.methodName();
Для того, чтобы создать метод, нужно внутри объявления класса написать
объявление метода и затем реализовать его тело. Объявление метода как мини-
мум должно содержать тип возвращаемого значения (включая void) и имя ме-
тода. В приведенном ниже объявлении метода элементы, заключенные в ква-
дратные скобки, являются не обязательными.
[доступ] [static] [abstract] [final] [synchronized] [native] [<параметризация>]
ВозвращаемыйТип имяМетода(список параметров) [throws список исключений]
Как и для полей класса, спецификатор доступа к методам может быть public,
private, protected и по умолчанию. При этом методы суперкласса можно пере-
гружать или переопределять в порожденном подклассе.
ОСНОВЫ JAVA
60
Объявленные в методе переменные являются локальными переменными
метода, а не членами классов и не инициализируются значениями по умолча-
нию при создании объекта класса или вызове метода.
При распределении обязанностей между классами выделяются так называ-
емые классы бизнес-логики, в которые помещаются методы, обрабатывающие
информацию из передаваемых им объектов. Такие классы могут и не иметь
атрибутов. Если атрибуты присутствуют, то они обычно играют чисто служеб-
ную роль для облегчения методам класса выполнения их функциональности.
/* # 3 # перевод денег со счета на счет # TransferAction.java */
package
by.bsu.transfer;
import
by.bsu.transfer.bean.Account;
public
class TransferAction {
private double transactionAmount;
public TransferAction( double amount) { // конструктор по умолчанию не предоставляется
if
(amount > 0) {
this
.transactionAmount = amount;
} else {
throw new
IllegalArgumentException(); // или собственное исключение
}
}
public boolean transferIntoAccount(Account from, Account to) {
// определение остатка
double demand = from.getAmount() - transactionAmount;
// проверка остатка и перевод суммы
if (demand >= 0) {
from.setAsset(demand);
to.addAsset(transactionAmount);
return true;
} else {
return false;
}
}
public double getTransactionAmount () {
return transactionAmount;
}
// вставить метод удержания процента при переводе
}
Результат взаимодействия классов для решения поставленной задачи о пе-
реводе денег со счета на счет можно представить в следующем коде.
/* # 4 # создание экземпляров и выполнение действий # Runner.java */
package
by.bsu.transfer;
import
by.bsu.transfer.bean.Account;
public
class Runner {
public static void main(String[ ] args) {
КЛАССЫ И ОБЪЕКТЫ
61
Account from = new Account(78031864L, 258.5);
Account to = new Account(58510009L, 12.1);
TransferAction action = new TransferAction(52.0);
boolean complete = action.transferIntoAccount(from, to);
if (complete) {
System.out.println("Сумма: " + action.getTransactionAmount() + " переведена успешно");
System.out.print("На счету клиента ID=" + to.getId());
System.out.println(" находится сумма: " + to.getAmount());
} else {
System.out.println("Транзакция не выполнена.");
System.out.print("На счету клиента ID=" + from.getId());
System.out.println(" недостаточно средств.");
}
}
}
В результате будет выведено:
Сумма: 52.0 переведена успешно
На счету клиента ID=58510009 находится сумма: 64.1
Класс Runner имеет недостаток — печатает отчет о выполнении транзак-
ции. Для выполнения этого действия следует создать дополнительный класс
ReportAction и возложить задачу формирования отчетов на его методы.
Объект класса может быть создан в любом пакете приложения, если кон-
структор класса объявлен со спецификатором public. Спецификатор private
не позволит создавать объекты вне класса, а спецификатор «по умолчанию» —
вне пакета. Спецификатор protected позволяет создавать объекты в текущем
пакете и делает конструктор доступным для обращения к подклассам данного
класса в других пакетах.
Статические методы и поля
Поле данных, объявленное в классе как static, является общим для всех объ-
ектов класса и называется переменной класса. Может быть использовано без
создания экземпляра класса. Если один объект изменит значение такого поля,
то это изменение увидят все объекты. Для работы со статическими атрибутами
используются статические методы, объявленные со спецификатором static.
Нестатические методы могут обращаться к статическим полям и методам на-
прямую без всяких дополнительных условий. Статические методы являются
методами класса, не привязаны ни к какому объекту и не содержат указателя
this на конкретный экземпляр, вызвавший метод. Статические методы реализу-
ют парадигму «раннего связыва ния», жестко определяющую версию метода
на этапе компиляции. По причине недоступности указателя this статические
поля и методы не могут обращаться к нестатическим полям и методам
ОСНОВЫ JAVA
62
напрямую, так как они не «знают», к какому объекту относятся, да и сам экзем-
пляр класса может быть не создан. Для обращения к статическим полям и ме-
тодам достаточно имени класса, в котором они определены. Класс
TransferAction можно переработать в класс со статическим полем и методом.
// # 5 # статические метод и поле # TransferAction.java
public
class TransferAction {
public static double transactionAmount; // статическое поле
private int id; // нестатическое поле
public static
boolean transferIntoAccount(Account from, Account to) {
// increaseAmount(); // вызвать нельзя – объекта не существует
// this.id // использовать this невозможно – объекта не существует
// id // недоступен – объекта не существует
// определение остатка
double
demand = from.getAmount() - transactionAmount;
// проверка остатка и перевод суммы
if (demand >= 0) {
from.setAsset(demand);
to.addAsset( transactionAmount);
return true;
} else {
return false;
}
}
public void increaseAmount() { // нестатический метод
transactionAmount ++;
}
}
Вызов метода transferIntoAccount() осуществляется без создания объекта:
TransferAction. transferIntoAccount(from, to);
но и переопределить статический метод уже нельзя.
Для двух объектов
TransferAction ob1 = new TransferAction ();
TransferAction ob2 = new TransferAction ();
Значение ob1.transactionAmount и ob2.transactionAmount равно 0, посколь-
ку располагается в одной и той же области памяти вне объекта. Такая запись
не очень корректна. Поэтому обращаться и изменять значение статического
поля следует непосредственно через имя класса:
TransferAction. transactionAmount = 70.5;
Вызов статического метода всегда следует осуществлять с помощью указа-
ния на имя класса, а не объекта. Статический метод можно вызывать также
с использованием имени объекта, но такой вызов снижает качество кода,
|