package
by.bsu.creator;
public
class Point3DCreator extends Point2DCreator {
@Override
public Point3D createPoint() { // метод - подставка
System.out.println("Point3D");
return new Point3D(3, 7, 8);
}
}
package
by.bsu.creator;
public
class BuildRunner {
public static void main(String[ ] args) {
Point2DCreator br = new Point3DCreator();
// Point3D p = br.createPoint(); // ошибка компиляции
Point2D p = br.createPoint(); // "раннее связывание"
System.out.println(br.createPoint().x);
System.out.println(br.createPoint().y);
// System.out.println(br.createPoint().z);
}
}
В результате будет выведено:
Point3D
Point3D
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
107
3
Point3D
7
В данной ситуации при компиляции в подклассе Point3DCreator создают-
ся три метода createPoint(). Один имеет возвращаемое значение Point3D,
другие (явно невидимые) — Point1D и Point2D. При обращении к методу cre-
atePoint() версия метода определяется «ранним связыванием» без использо-
вания полиморфизма, но при выполнении срабатывает полиморфизм и вызы-
вается метод с возвращаемым значением Point3D. Обращение к полю также
производится по типу объекта, возвращаемого методом createPoint(), т. е. к полю
класса Point3D. Для результатов обращения к полям следует в классах Point1D
и его наследниках убрать спецификатор private.
На практике методы подставки могут использоваться для расширения воз-
можностей класса по прямому извлечению (без преобразования) объектов под-
классов, инициализированных в ссылке на суперкласс.
«Переопределение» статических методов
Для статических методов принципы «позднего связывания» не использу-
ются. Динамический полиморфизм к статическим методам класса неприме-
ним, так как обращение к статическому атрибуту или методу осуществляется
по типу ссылки, а не по типу объекта, через который производится обраще-
ние. Версия вызываемого статического метода всегда определяется на этапе
компиляции. При использовании ссылки для доступа к статическому члену
компилятор при выборе метода учитывает тип ссылки, а не тип объекта, ей
присвоенного.
/* # 8 # поведение статического метода при «переопределении» # Runner.java */
package
by.bsu.sample;
class
Base {
public static void go() {
System. out.println("метод из Base");
}
}
class
Sub extends Base {
public static void go() {
System. out.println("метод из Sub");
}
}
public
class Runner {
public static void main(String[ ] args) {
Base ob = new Sub(); // !!!
ОСНОВЫ JAVA
108
// нестатический вызов статического метода
ob.go(); // предупреждение компилятора о некорректном вызове
}
}
В результате выполнения данного кода будет выведено:
метод из Base
При таком способе инициализации объекта ob метод go
() будет вызван
из класса Base. Если же спецификатор static убрать из объявления методов,
то вызов необходимо осуществлять в соответствии с принципами полиморфизма.
Статические методы всегда следует вызывать через имя класса, в котором
они объявлены, а именно:
Base.go();
Sub.go();
Вызов статических методов через объект считается нетипичным и наруша-
ющим смысл статического определения.
Абстракция и абстрактные классы
Множество моделей предметов реального мира обладают некоторым набо-
ром общих характеристик и правил поведения. Абстрактное понятие «Гео-
метрическая фигура» может содержать описание геометрических параметров
и расположения центра тяжести в системе координат, а также возможности оп-
ределения площади и периметра фигуры. Однако в общем случае дать конкрет-
ную реализацию приведенных характеристик и функциональности невозмож-
но ввиду слишком общего их определения. Для конкретного понятия, напри-
мер «Квадрат», дать описание линейных размеров и определения площади
и периметра не составляет труда. Абстрагирование понятия должно предостав-
лять абстрактные характеристики предмета реального мира, а не его ожидае-
мую реализацию. Грамотное выделение абстракций позволяет структуриро-
вать код программной системы в целом и повторно использовать абстрактные
понятия для конкретных реализаций при определении новых возможностей
абстрактной сущности.
Абстрактные классы объявляются с ключевым словом abstract и содержат
объявления абстрактных методов, которые не реализованы в этих классах,
а будут реализованы в подклассах. Объекты таких классов создать нельзя с по-
мощью оператора new, но можно создать объекты подклассов, которые реали-
зуют все эти методы. При этом допустимо объявлять ссылку на абстрактный
класс, но инициализировать ее можно только объектом производного от него
класса. Абстрактные классы могут содержать и полностью реализованные ме-
тоды, а также конструкторы и поля данных.
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
109
С помощью абстрактного класса объявляется контракт (требования к функ-
циональности) для его подклассов. Примером может служить уже рассмотрен-
ный выше абстрактный класс Number и его подклассы Byte, Float и другие.
Класс Number объявляет контракт на реализацию ряда методов по преобразо-
ванию данных к значению конкретного базового типа, например floatValue().
Можно предположить, что реализация метода будет различной для каждого
из классов-оболочек. Объект класса Number нельзя создать явно при помощи
его собственного конструктора.
/* # 9 # абстрактный класс и метод # AbstractCardAction.java */
package
by.bsu.inheritance;
public abstract
class AbstractCardAction {
private int id;
public AbstractCardAction() { // конструктор
}
// more methods
public boolean checkLimit() { // собственный метод
return true; // stub
}
public abstract void doPayment( double amountPayment);
}
/* # 10 # подкласс абстрактного класса # CreditCardAction.java # Runner.java */
package
by.bsu.inheritance;
public
class CreditCardAction extends AbstractCardAction {
// поля, конструкторы, методы
@Override // аннотация указывает на полиморфную природу метода
// метод должен быть реализован в подклассе
public void doPayment( double amountPayment) { // переопределенный метод
// реализация
System. out.println("complete from credit card!");
}
}
package
by.bsu.inheritance;
public class
Runner {
public static void main(String[ ] args) {
AbstractCardAction action; // можно объявить ссылку
// action = new AbstractCardAction(); нельзя создать объект!
action = new CreditCardAction();
action.doPayment (100);
}
}
Ссылка action на абстрактный суперкласс инициализируется объектом под-
класса, в котором реализованы все абстрактные методы суперкласса. С помо-
щью этой ссылки могут вызываться неабстрактные методы абстрактного клас-
са, если они не переопределены в подклассе.
ОСНОВЫ JAVA
110
Расширение функциональности системы
В объектно-ориентированном программировании применение наследова-
ния предоставляет возможность расширения и дополнения программного
обеспечения, имеющего сложную структуру с большим количеством классов
и методов. В задачи суперкласса в этом случае входит определение интерфейса
(как способа взаимодействия) для всех подклассов.
В следующем примере приведение к базовому типу происходит в выра-
жении:
AbstractQuest quest1 = new DragnDropQuest();
AbstractQuest quest2 = new SingleChoiceQuest();
Базовый класс AbstractQuest предоставляет общий интерфейс для своих
подклассов. Порожденные классы DragnDropQuest и SingleChoiceQuest пе-
рекрывают эти определения для обеспечения уникального поведения.
/* # 11 # полиморфизм # AbstractQuest.java # DragnDropQuest.java #
SingleChoiceQuest.java # Answer.java # QuestFactory.java # Runner.java*/
package
by.bsu.scalability;
public
abstract class AbstractQuest {
private long id;
private String questContent;
// конструкторы и методы
public abstract boolean check(Answer ans);
}
package
by.bsu.scalability;
public class
DragnDropQuest extends AbstractQuest {
// поля, конструкторы и методы
@Override
public boolean check(Answer ans) {
System.out.println("Drag'n'Drop quest");
// проверка корректности ответа (true или false)
return true; // stub
}
}
package
by.bsu.scalability;
public class
SingleChoiceQuest extends AbstractQuest {
// поля, конструкторы и методы
@Override
public boolean check(Answer ans) {
System.out.println("Single choice quest");
// проверка корректности ответа true или false
return true; // stub
}
}
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
111
package
by.bsu.scalability;
public
class Answer {
// поля и методы
}
package
by.bsu.scalability;
public
class QuestFactory { // шаблон Factory Method (упрощенный)
public static AbstractQuest getQuestFromFactory( int mode) {
switch (mode) {
case 0:
return new DragnDropQuest();
case 1:
return new SingleChoiceQuest();
default :
throw new IllegalArgumentException("illegal mode");
// assert false; // плохо
// return null; // еще хуже
}
}
}
package
by.bsu.scalability;
import
java.util.Random;
public
class TestAction {
public AbstractQuest[] generateTest( final int NUMBER_QUESTS, int maxMode) {
AbstractQuest[ ] test = new AbstractQuest[NUMBER_QUESTS];
for ( int i = 0; i < test.length; i++) {
int mode = new Random().nextInt(maxMode); // stub
/* заполнение массива объектами-вопросами */
test[i] = QuestFactory. getQuestFromFactory(mode);
}
return test;
}
public int checkTest(AbstractQuest[] test) {
int counter = 0;
for (AbstractQuest s : test) {
// вызов полиморфного метода
counter = s.check( new Answer()) ? ++counter : counter;
}
return counter;
}
}
package
by.bsu.scalability;
public
class TestRunner {
public
static void main(String[ ] args) {
TestAction bt = new TestAction();
AbstractQuest[ ] test = bt.generateTest(60, 2); // 60 вопросов 2-х видов
// здесь должен быть код процесса прохождения теста …
bt.checkTest(test); // проверка теста
}
}
ОСНОВЫ JAVA
112
В процессе выполнения приложения будет случайным образом сформиро-
ван массив-тест из вопросов разного типа, и информация об ответах на них
будет выведена на консоль.
Класс QuestFactory содержит метод getQuestFromFactory(int numMode),
который возвращает ссылку на случайно выбранный объект подкласса класса
AbstractQuest каждый раз, когда он вызывается. Приведение к базовому типу
производится оператором return, который возвращает ссылку на DragnDropQuest
или SingleChoiceQuest. Метод main() содержит массив из ссылок AbstractQuest,
заполненный с помощью вызова getQuestFromFactory(). На этом этапе извест-
но, что имеется некоторое множество ссылок на объекты базового типа и ничего
больше (не больше, чем знает компилятор). Kогда происходит перемещение
по этому массиву, метод check() вызывается для каждого случайным образом
выбранного объекта.
Если понадобится в дальнейшем добавить в систему, например, класс
MultiplyChoiceQuest, то это потребует только переопределения метода check()
и добавления одной строки в код метода getQuestFromFactory(), что делает
систему легко расширяемой.
Невозможно приравнивать ссылки на классы, находящиеся в разных ветвях
наследования, так как не существует никакого способа привести один такой
тип к другому.
Класс Object
На вершине иерархии классов находится класс Object, который является
суперклассом для всех классов. Ссылочная переменная типа Object может ука-
зывать на объект любого другого класса, на любой массив, так как массивы
реализуются как классы. В классе Object определен набор методов, который
наследуется всеми классами:
protected Object clone() — создает и возвращает копию вызывающего объекта;
public boolean equals(Object ob) — предназначен для использования
и пере определения в подклассах с выполнением общих соглашений о сравне-
нии содержимого двух объектов одного и того же типа;
public Class extends Object> getClass() — возвращает объект типа Class;
protected void finalize() — автоматически вызывается сборщиком мусора
(garbage collection) перед уничтожением объекта;
public int hashCode() — вычисляет и возвращает хэш-код объекта (число,
в общем случае вычисляемое на основе значений полей объекта);
public String toString() — возвращает представление объекта в виде строки.
Методы notify(), notifyAll() и wait(), wait(int millis) будут рассмотрены
в главе «Потоки выполнения».
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
113
Если при создании класса предполагается проверка логической эквива-
лентности объектов, которая не выполнена в суперклассе, следует переоп ре-
делить два метода: boolean equals(Object ob) и int hashCode(). Кроме того,
переопределение этих методов необходимо, если логика приложения преду-
сматривает использование элементов в коллекциях. Метод equals() при сравне-
нии двух объектов возвращает истину, если содержимое объектов эквивалент-
но, и ложь — в противном случае. Реализация метода в классе Object возвра-
щает истину только в том случае, если обе ссылки указывают на один и тот же
объект, а конкретно:
public boolean
equals(Object obj) {
return ( this == obj);
}
При переопределении метода equals() должны выполняться соглашения,
предусмотренные спецификацией языка Java, а именно:
• рефлексивность — объект равен самому себе;
• симметричность — если x.equals(y) возвращает значение true, то и y.equals(x)
всегда возвращает значение true;
• транзитивность — если метод equals() возвращает значение true при срав-
нении объектов x и y, а также y и z, то и при сравнении x и z будет возвра-
щено значение true;
• непротиворечивость — при многократном вызове метода для двух не под-
вергшихся изменению за это время объектов возвращаемое значение всегда
должно быть одинаковым;
• ненулевая ссылка при сравнении с литералом null всегда возвращает значе-
ние false.
При создании информационных классов также рекомендуется переопреде-
лять методы hashCode() и toString(), чтобы адаптировать их действия для со-
здаваемого типа.
Метод int hashCode() переопределен, как правило, в каждом классе и возвра-
щает число, являющееся уникальным идентификатором объекта, завися щим
в большинстве случаев только от значения объекта. Его следует переопре делять
всегда, когда переопределен метод equals(). Метод hashCode() возвращает хэш-
код объекта, вычисление которого управляется следующими соглашениями:
• все одинаковые по содержанию объекты одного типа должны иметь одина-
ковые хэш-коды;
• различные по содержанию объекты одного типа могут иметь различные
хэш-коды;
• во время работы приложения значение хэш-кода объекта не изменяется,
если объект не был изменен.
Один из способов создания правильного метода hashCode(), гарантирую-
щий выполнение соглашений, приведен ниже, в примере # 12.
ОСНОВЫ JAVA
114
Метод toString() следует переопределять таким образом, чтобы, кроме
стандартной информации о пакете (опционально), в котором находится класс,
и самого имени класса (опционально), он возвращал значения полей объекта,
вызвавшего этот метод (т. е. всю полезную информацию объекта), вместо хэш-
кода, как это делается в классе Object. Метод toString() класса Object возвра-
щает строку с описанием объекта в виде:
getClass().getName() + '@' + Integer.toHexString(hashCode())
Метод вызывается автоматически, когда объект выводится методами println(),
print() и некоторыми другими.
/* # 12 # переопределение методов equals(), hashCode(), toString() # Student.java */
package
by.bsu.entity;
public
class Student {
private int id;
private String name;
private int age;
public Student(int id, String name, int age){
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
115
} else if (!name.equals(other.name))
return false;
return true;
}
public int hashCode() {
return ( int)(31 * id + age + ((name == null) ? 0 : name.hashCode()));
}
public String toString() {
return getClass().getName() + "@name" + name + " id:" + id + " age:" + age;
}
}
Выражение 31 * id + age гарантирует различные результаты вычислений
при перемене местами значений полей, а именно: если id=1 и age=2, то в ре-
зультате будет получено 33, если значения поменять местами, то 63. Такой
подход применяется при наличии у классов полей базовых типов.
Метод equals() переопределяется для класса Student таким образом, чтобы
убедиться в том, что полученный объект является объектом типа Student,
а также сравнить содержимое полей id, name и age соответственно у вызыва-
ющего метод объекта и объекта, передаваемого в качестве параметра. Для под-
класса всегда придется создавать собственную реализацию метода.
Клонирование объектов
Объекты в методы передаются по ссылке, в результате чего в метод пе-
редается ссылка на объект, находящийся вне метода. Если в методе изме-
нить значение поля объекта, это изменение коснется исходного объекта.
Во избежание такой ситуации для защиты внешнего объекта следует со-
здать клон (копию) объекта в методе. Класс Object содержит protected-
метод clone(), осуществляющий побитовое копирование объекта производ-
ного класса. Однако сначала необходимо переопределить метод clone() как
public для обеспечения возможности вызова из другого пакета. В перео-
пределенном методе следует вызвать базовую версию метода super. clone(),
которая и выполняет собственно клонирование. Чтобы окончательно сде-
лать объект клонируемым, класс должен реализовать интерфейс Cloneable.
Интерфейс Cloneable не содержит методов, относится к помеченным
(tagged) интерфейсам, а его реализация гарантирует, что метод clone()
класса Object возвратит точную копию вызвавшего его объекта с воспро-
изведением значений всех его полей. В противном случае метод генериру-
ет исключение CloneNotSupportedException. Следует отметить, что при
использовании этого механизма объект создается без вызова конструктора.
В языке C++ аналогичный механизм реализован с помощью конструктора
копирования.
ОСНОВЫ JAVA
116
/* # 13 # класс, поддерживающий клонирование # Student.java */
package
by.bsu.entity;
public class
Student implements Cloneable { /* включение интерфейса */
private int id = 71;
private String name;
private int age;
/* конструкторы, методы */
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public Object clone() throws CloneNotSupportedException { // переопределение
return super.clone(); // вызов базового метода
}
}
/* # 14 # безопасная передача по ссылке # CloneRunner.java */
public
class CloneRunner {
private static void mutation(Student p) {
try {
p = (Student)p.clone(); // клонирование
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
p.setId(1000);
System.out.println("->id = " + p.getId());
}
public static void main(String[] args) {
Student ob = new Student();
System.out.println("id = " + ob.getId());
mutation(ob);
System.out.println("id = " + ob.getId());
}
}
В результате будет выведено:
id = 71
->id = 1000
id = 71
Если закомментировать вызов метода clone(), то выведено будет следующее:
id = 71
->id = 1000
id = 1000
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
117
Решение эффективно только в случае, когда поля клонируемого объекта
представляют собой значения базовых типов и их оболочек или неизменяемых
(immutable) объектных типов. Если же поле клонируемого типа является изме-
няемым объектным типом, то для корректного клонирования требуется другой
подход. Причина заключается в том, что при создании копии поля оригинал
и копия представляют собой ссылку на один и тот же объект.
В этой ситуации следует также клонировать и объект поля класса, если он
сам поддерживает клонирование.
/* # 15 # глубокое клонирование # Student.java */
package
by.bsu.entity;
import
java.util.Vector;
public
class Student implements Cloneable {
private int id = 7;
private String name;
private int age;
private Vector v = new Vector(); // список оценок – изменяемое поле
/* конструкторы, методы */
public Student clone() { // метод-подставка
Student copy = null;
try {
copy = (Student)super.clone();
copy.v = (Vector)v.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace("не реализован интерфейс Cloneable !");
}
return copy;
}
}
Клонирование возможно лишь, если тип атрибута класса также реализует
интерфейс Cloneable и переопределяет метод clone(). В противном случае вы-
зов метода невозможен, так как он просто недоступен. Следовательно, если
Рис. 4.1.
«Неглубокое» и «глубокое» клонирование
ОСНОВЫ JAVA
118
класс имеет суперкласс, то для реализации механизма клонирования текущего
класса необходимо наличие корректной реализации такого механизма в супер-
классе. При этом следует отказаться от использования объявлений final для
полей объектных типов по причине невозможности изменения их значений
при реализации клонирования.
private
static void mutation(Student p) {
p = p.clone(); // клонирование
p.setId(1000);
System.out.println("->id = " + p.getId());
}
Если заменить объявление Vector на Vector, где Mark — из-
меняемый тип, то клонирование должно затрагивать и внутреннее состояние
списка.
«Сборка мусора» и освобождение ресурсов
Так как объекты создаются динамически с помощью операции new, а унич-
тожаются автоматически, то желательно знать механизм ликвидации объектов
и способ освобождения памяти. Автоматическое освобождение памяти, зани-
маемой объектом, выполняется с помощью механизма «сборки мусора». Когда
никаких ссылок на объект не существует, т. е. все ссылки на него вышли из об-
ласти видимости программы, предполагается, что объект больше не нужен,
и память, занятая объектом, может быть освобождена. «Сборка мусора» про-
исходит нерегулярно во время выполнения программы. Форсировать «сборку
мусора» невозможно, можно лишь «рекомендовать» выполнить ее вызовом
метода System.gc() или Runtime.getRuntime().gc(), но виртуальная машина
выполнит очистку памяти тогда, когда сама посчитает это удобным. Вызов
метода System.runFinalization() приведет к запуску метода finalize() для
объектов, утративших все ссылки.
Иногда объекту нужно выполнять некоторые действия перед освобождени-
ем памяти. Например, освободить внешние ресурсы. Для обработки таких си-
туаций могут применяться два способа: конструкция try-finally и механизм
autocloseable. Указанные способы являются предпочтительными, абсолютно
надежными и будут рассмотрены в девятой главе.
Запуск стандартного механизма finalization определяется алгоритмом сбор-
ки мусора, и до его непосредственного исполнения может пройти сколь угодно
много времени. Из-за всего этого поведение метода finalize() может повлиять
на корректную работу программы, особенно при смене JVM. Если существует
возможность освободить ресурсы или выполнить другие подобные действия
без привлечения этого механизма, то лучше без него обойтись. Виртуальная
машина вызывает этот метод всегда, когда она собирается уничтожить объект
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
119
данного класса. Внутри метода protected void finalize(), вызываемого непо-
средственно перед освобождением памяти, следует определить действия, кото-
рые должны быть выполнены до уничтожения объекта.
Ключевое слово protected запрещает доступ к finalize() коду, определенно-
му вне этого класса. Метод finalize() вызывается только перед самой «сборкой
мусора», а не тогда, когда объект выходит из области видимости, т. е. заранее
невозможно определить, когда finalize() будет выполнен, и недоступный объ-
ект может занимать память довольно долго. В принципе, этот метод может
быть вообще не выполнен!
Недопустимо в приложении доверять такому методу критические по време-
ни действия по освобождению ресурсов.
/* # 16 # класс Manager с поддержкой finalization # Manager.java # FinalizeDemo.java */
package
by.bsu.gc;
public
class Manager {
private int id;
public Manager(int value) {
id = value;
}
protected void finalize() throws Throwable {
try {
// код освобождения ресурсов
System.out.println("объект будет удален, id=" + id);
} finally {
super.finalize();
}
}
}
package
by.bsu.gc;
public
class FinalizeDemo {
public
static void main(String[] args) {
Manager d1 = new Manager(1);
d1 = null;
Manager d2 = new Manager(2);
Object d3 = d2; // 1
// Object d3 = new Manager (3); // 2
d2 = d1;
// просьба выполнить "сборку мусора"
System.gc();
}
}
В результате выполнения этого кода перед вызовом метода System.gc() без
ссылки останется только один объект.
объект будет удален, id=1
ОСНОВЫ JAVA
120
Если закомментировать строку 1 и снять комментарий со строки 2, то перед
выполнением gc() ссылку потеряют уже два объекта.
объект будет удален, id=1
объект будет удален, id=2
Если не вызвать явно метод finalize() суперкласса, то он не будет вызван
автоматически. Еще одна опасность: если при выполнении данного метода
возникнет исключительная ситуация, она будет проигнорирована и приложе-
ние продолжит выполняться, что также представляет опасность для его кор-
ректной работы.
Пакеты
Любой класс Java относится к определенному пакету, который может быть
неименованным (unnamed или default package), если оператор package отсут-
ствует. Оператор package, помещаемый в начале исходного программного
файла, определяет именованный пакет, т. е. область в пространстве имен клас-
сов, в которой определяются имена классов, содержащихся в этом файле.
Действие оператора package указывает на месторасположение файла относи-
тельно корневого каталога проекта. Например:
package
by.bsu.eun.entity;
При этом программный файл будет помещен в подкаталог с названием
by.bsu.eun.entity. Имя пакета при обращении к классу из другого пакета при-
соединяется к имени класса:
by.bsu.eun.entity.Student
В проектах пакеты часто именуются следующим образом:
• обратный Интернет-адрес производителя или заказчика программного обес-
печения, а именно для www.bsu.by получится by.bsu;
• далее следует имя проекта (обычно сокращенное), например, eun;
• затем располагаются пакеты, определяющие собственно приложение.
Общая форма файла, содержащего исходный код Java, может быть следую-
щая:
• одиночный оператор package (необязателен, но крайне желателен);
• любое количество операторов import (необязательны);
• одиночный открытый (public) класс (необязателен);
• любое количество классов пакета (необязательны и нежелательны).
При использовании классов перед именем класса через точку надо добав-
лять полное имя пакета, к которому относится данный класс. На рисунке при-
веден далеко не полный список пакетов реального приложения. Из названий
пакетов можно определить, какие примерно классы в нем расположены,
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
121
не заглядывая внутрь. При создании пакета всегда следует руководствоваться
простым правилом: называть его именем простым, но отражающим смысл, ло-
гику поведения и функциональность объединенных в нем классов.
Каждый класс добавляется в указанный пакет при компиляции. Например:
/* # 17 # применение пакета # CommonObject.java */
package
by.bsu.eun.objects;
public
class CommonObject implements Cloneable {
// more code
}
Класс начинается с указания того, что он принадлежит пакету by.bsu.eun.objects.
Другими словами, это означает, что файл CommonObject.java находится в ката-
логе objects, который, в свою очередь, находится в каталоге bsu, и так далее.
Нельзя переименовывать пакет, не переименовав каталог, в котором хранятся
его классы. Чтобы получить доступ к классу из другого пакета, перед именем
by.bsu.eun
by.bsu.eun.administration.constants
by.bsu.eun.administration.dbhelpers
by.bsu.eun.common.constants
by.bsu.eun.common.dbhelpers.annboard
by.bsu.eun.common.dbhelpers.courses
by.bsu.eun.common.dbhelpers.guestbook
by.bsu.eun.common.dbhelpers.learnres
by.bsu.eun.common.dbhelpers.messages
by.bsu.eun.common.dbhelpers.news
by.bsu.eun.common.dbhelpers.prepinfo
by.bsu.eun.common.dbhelpers.statistics
by.bsu.eun.common.dbhelpers.subjectmark
by.bsu.eun.common.dbhelpers.subjects
by.bsu.eun.common.dbhelpers.test
by.bsu.eun.common.dbhelpers.users
by.bsu.eun.common.menus
by.bsu.eun.common.objects
by.bsu.eun.common.servlets
by.bsu.eun.common.tools
by.bsu.eun.consultation.constants
by.bsu.eun.consultation.dbhelpers
by.bsu.eun.consultation.objects
by.bsu.eun.core.constants
by.bsu.eun.core.dbhelpers
by.bsu.eun.core.exceptions
by.bsu.eun.core.filters
by.bsu.eun.core.managers
by.bsu.eun.core.taglibs
Рис. 4.2.
Организация пакетов приложения
ОСНОВЫ JAVA
122
такого класса указывается имя пакета: by.bsu.eun.objects.CommonObject.
Чтобы избежать таких длинных имен при создании объектов классов, исполь-
зуется ключевое слово import. Например:
import
by.bsu.eun.objects.CommonObject;
или
import
by.bsu.eun.objects.*;
Во втором варианте импортируется весь пакет, что означает возможность
доступа к любому классу пакета, но только не к подпакету и его классам. В прак-
тическом программировании следует использовать индивидуальный import
класса, чтобы при анализе кода была возможность быстро определить местора-
сположение используемого класса.
Доступ к классу из другого пакета можно осуществить еще одним способом
(не очень рекомендуемым):
/* # 18 # применение полного имени пакета при наследовании # UserStatistic.java */
package
by.bsu.eun.usermng;
public
class UserStatistic extends by.bsu.eun.objects.CommonObject {
// more code
}
Такая запись используется, если в классе нужен доступ к классам, имею-
щим одинаковые имена.
При импорте класса из другого пакета рекомендуется всегда указывать пол-
ный путь с указанием имени импортируемого класса. Это позволяет в большом
проекте легко найти определение класса, если возникает необходимость по-
смотреть исходный код класса.
/* # 19 # применение полного пути к классу при импорте # CreatorStatistic.java */
package
by.bsu.eun.action;
import
by.bsu.eun.objects.CommonObject;
import
by.bsu.eun.usermng.UserStatistic;
public
class CreatorStatistic extends CommonObject {
public UserStatistic us;
// more code
}
Если пакет не существует, то его необходимо создать до первой компиля-
ции, если пакет не указан, класс добавляется в пакет без имени (unnamed). При
этом unnamed-каталог не создается. Однако в реальных проектах классы вне
пакетов не создаются, и не существует причин отступать от этого правила.
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
123
Статический импорт
При вызове статических методов и обращении к статическим константам
приходится использовать в качестве префикса имя класса, что утяжеляет код
и снижает скорость его восприятия.
// # 20 # обращение к статическому методу и константе # ImportDemo.java
package
by.bsu.stat;
public
class ImportDemo {
public static void main(String[ ] args) {
System.out.println(2 * Math.PI * 3);
System.out.println(Math.floor(Math.cos(Math.PI / 3)));
}
}
Статические константы и статические методы класса можно использовать
без указания принадлежности к классу, если применить статический импорт,
import static
java.lang.Math.*;
как это показано в следующем примере.
// # 21 # статический импорт методов и констант # ImportDemoLux.java
package
by.bsu.stat;
import
static java.lang.Math.*;
public
class ImportDemoLux {
public static void main(String[ ] args) {
System.out.println(2 * PI * 3);
System.out.println(floor(cos(PI / 3)));
}
}
Если необходимо получить доступ только к одной статической константе
или методу, то импорт производится в следующем виде:
import static
java.lang.Math.E; // для одной константы
import static
java.lang.Math.cos; // для одного метода
Рекомендации при проектировании иерархии
При построении иерархии необходимо помнить, что отношение между
классами можно выразить как «is-a», или «является». Студент «является»
Человеком. Поля класса находятся с классом в отношении «has-a», или «содер-
жит». Студент «содержит» Номер зачетной книжки.
Наследование и переопределение методов используются для реализации от-
личий поведения. Если наследование можно заменить агрегацией, то следует
ОСНОВЫ JAVA
124
так и поступить. Нет смысла создавать подкласс Студент-заочник, если мож-
но в подкласс Студент добавить поле Форма обучения.
При наследовании в новые классы добавляются новые возможности в виде
полей и методов или переопределения методов. Если новых возможностей
не обнаруживается, то использование наследования, как правило, не имеет для
этого оснований.
Базовая функциональность должна определяться в вершине иерархии про-
ектируемых классов. Если в подклассе добавляются новые методы, характери-
зующие поведение иерархии в целом, следует заняться перепроектированием.
Но нельзя учесть все возможные изменения иерархии в процессе разработ-
ки. Избыточный функционал придется поддерживать. Лучше на поздних ста-
диях добавить методы в суперкласс, чем пытаться понять, зачем нужен метод,
которой никто еще не использовал.
Не использовать значения переменных для характерного изменения поведе-
ния. Для этих целей следует создать подкласс и переопределить метод.
Для различных семантических сущностей не создавать общую иерархию,
даже если действия для них идентичны по форме. Блокировка Теста и блоки-
ровка Студента — действия суть похожие, но отличные по своему функцио-
налу и последствиям.
Задания к главе 4
Вариант A
Создать приложение, удовлетворяющее требованиям, приведенным в зада-
нии. Наследование применять только в тех заданиях, в которых это логически
обосновано. Аргументировать принадлежность классу каждого создаваемого
метода и корректно переопределить для каждого класса методы equals(),
hashCode(), toString().
1. Создать объект класса Текст, используя классы Предложение, Слово.
Методы: дополнить текст, вывести на консоль текст, заголовок текста.
2. Создать объект класса Автомобиль, используя классы Колесо, Двигатель.
Методы: ехать, заправляться, менять колесо, вывести на консоль марку ав-
томобиля.
3. Создать объект класса Самолет, используя классы Крыло, Шасси,
Двигатель. Методы: летать, задавать маршрут, вывести на консоль маршрут.
4. Создать объект класса Государство, используя классы Область, Район,
Город. Методы: вывести на консоль столицу, количество областей, пло-
щадь, областные центры.
5. Создать объект класса Планета, используя классы Материк, Океан, Остров.
Методы: вы вести на консоль название материка, планеты, количество мате-
риков.
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
125
6. Создать объект класса Звездная система, используя классы Планета,
Звезда, Луна. Методы: вывести на консоль количество планет в звездной
системе, название звезды, добавление планеты в систему.
7. Создать объект класса Компьютер, используя классы Винчестер,
Дисковод, Оперативная память, Процессор. Методы: включить, выклю-
чить, проверить на вирусы, вывести на консоль размер винчестера.
8. Создать объект класса Квадрат, используя классы Точка, Отрезок.
Методы: задание размеров, растяжение, сжатие, поворот, изменение цвета.
9. Создать объект класса Круг, используя классы Точка, Окружность.
Методы: задание размеров, изменение радиуса, определение принадлежно-
сти точки данному кругу.
10. Создать объект класса Щенок, используя классы Животное, Собака.
Методы: вывести на консоль имя, подать голос, прыгать, бегать, кусать.
11. Создать объект класса Наседка, используя классы Птица, Кукушка.
Методы: летать, петь, нести яйца, высиживать птенцов.
12. Создать объект класса Текстовый файл, используя классы Файл,
Директория. Методы: создать, переименовать, вывести на консоль содер-
жимое, дополнить, удалить.
13. Создать объект класса Одномерный массив, используя классы Массив,
Элемент. Методы: создать, вывести на консоль, выполнить операции (сло-
жить, вычесть, перемножить).
14. Создать объект класса Простая дробь, используя класс Число. Методы:
вывод на экран, сложение, вычитание, умножение, деление.
15. Создать объект класса Дом, используя классы Окно, Дверь. Методы: за-
крыть на ключ, вывести на консоль количество окон, дверей.
16. Создать объект класса Цветок, используя классы Лепесток, Бутон.
Методы: расцвести, завять, вывести на консоль цвет бутона.
17. Создать объект класса Дерево, используя классы Лист, Ветка. Методы: за-
цвести, опасть листьям, покрыться инеем, пожелтеть листьям.
18. Создать объект класса Пианино, используя классы Клавиша, Педаль.
Методы: настроить, играть на пианино, нажимать клавишу.
19. Создать объект класса Фотоальбом, используя классы Фотография,
Страница. Методы: задать название фотографии, дополнить фотоальбом
фотографией, вывести на консоль количество фотографий.
20. Создать объект класса Год, используя классы Месяц, День. Методы: задать
дату, вывести на консоль день недели по заданной дате, рассчи тать количе-
ство дней, месяцев в заданном временном промежутке.
21. Создать объект класса Сутки, используя классы Час, Минута. Методы:
вывести на консоль текущее время, рассчитать время суток (утро, день, ве-
чер, ночь).
22. Создать объект класса Птица, используя классы Крылья, Клюв. Методы:
летать, садиться, питаться, атаковать.
ОСНОВЫ JAVA
126
23. Создать объект класса Хищник, используя классы Когти, Зубы. Методы:
рычать, бежать, спать, добывать пищу.
24. Создать объект класса Гитара, используя класс Струна, Скворечник.
Методы: играть, настраивать, заменять струну.
Вариант В
Создать консольное приложение, удовлетворяющее следующим требованиям:
• Использовать возможности ООП: классы, наследование, полиморфизм, ин-
капсуляция.
• Каждый класс должен иметь отражающее смысл название и информатив-
ный состав.
• Наследование должно применяться только тогда, когда это имеет смысл.
• При кодировании должны быть использованы соглашения об оформлении
кода java code convention.
• Классы должны быть грамотно разложены по пакетам.
• Консольное меню должно быть минимальным.
• Для хранения параметров инициализации можно использовать файлы.
1. Цветочница. Определить иерархию цветов. Создать несколько объек-
тов-цветов. Собрать букет (используя аксессуары) с определением его
стоимости. Провести сортировку цветов в букете на основе уровня све-
жести. Найти цветок в букете, соответствующий заданному диапазону
длин стеблей.
2. Новогодний подарок. Определить иерархию конфет и прочих сладостей.
Создать несколько объектов-конфет. Собрать детский подарок с определе-
нием его веса. Провести сортировку конфет в подарке на основе одного
из параметров. Найти конфету в подарке, соответствующую заданному ди-
апазону содержания сахара.
3. Домашние электроприборы. Определить иерархию электроприборов.
Включить некоторые в розетку. Подсчитать потребляемую мощность.
Провести сортировку приборов в квартире на основе мощности. Найти
прибор в квартире, соответствующий заданному диапазону параметров.
4. Шеф-повар. Определить иерархию овощей. Сделать салат. Подсчитать ка-
лорийность. Провести сортировку овощей для салата на основе одного
из параметров. Найти овощи в салате, соответствующие заданному диапа-
зону калорийности.
5. Звукозапись. Определить иерархию музыкальных композиций. Записать
на диск сборку. Подсчитать продолжительность. Провести перестановку
композиций диска на основе принадлежности к стилю. Найти композицию,
соответствующую заданному диапазону длины треков.
6. Камни. Определить иерархию драгоценных и полудрагоценных камней.
Отобрать камни для ожерелья. Подсчитать общий вес (в каратах) и стоимость.
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
127
Провести сортировку камней ожерелья на основе ценности. Найти кам-
ни в ожерелье, соответствующие заданному диапазону параметров про-
зрачности.
7. Мотоциклист. Определить иерархию амуниции. Экипировать мотоцикли-
ста. Подсчитать стоимость. Провести сортировку амуниции на основе веса.
Найти элементы амуниции, соответствующие заданному диапазону пара-
метров цены.
8. Транспорт. Определить иерархию подвижного состава железнодорожного
транспорта. Создать пассажирский поезд. Подсчитать общую численность
пассажиров и багажа. Провести сортировку вагонов поезда на основе уров-
ня комфортности. Найти в поезде вагоны, соответствующие заданному ди-
апазону параметров числа пассажиров.
9. Авиакомпания. Определить иерархию самолетов. Создать авиакомпанию.
Посчитать общую вместимость и грузоподъемность. Провести сортировку
самолетов компании по дальности полета. Найти самолет в компании, со-
ответствующий заданному диапазону параметров потребления горючего.
10. Таксопарк. Определить иерархию легковых автомобилей. Создать таксо-
парк. Подсчитать стоимость автопарка. Провести сортировку автомобилей
парка по расходу топлива. Найти автомобиль в компании, соответствую-
щий заданному диапазону параметров скорости.
11. Страхование. Определить иерархию страховых обязательств. Собрать
из обязательств дериватив. Подсчитать стоимость. Провести сортировку обя-
зательств в деривативе на основе уменьшения степени риска. Найти обяза-
тельство в деривативе, соответствующее заданному диапазону параметров.
12. Мобильная связь. Определить иерархию тарифов мобильной компании.
Создать список тарифов компании. Подсчитать общую численность клиентов.
Провести сортировку тарифов на основе размера абонентской платы. Найти
тариф в компании, соответствующий заданному диапазону параметров.
13. Фургон кофе. Загрузить фургон определенного объема грузом на опреде-
ленную сумму из различных сортов кофе, находящихся, к тому же, в разных
физических состояниях (зерно, молотый, растворимый в банках и пакети-
ках). Учитывать объем кофе вместе с упаковкой. Провести сортировку то-
варов на основе соотношения цены и веса. Найти в фургоне товар, соответ-
ствующий заданному диапазону параметров качества.
14. Игровая комната. Подготовить игровую комнату для детей разных воз-
растных групп. Игрушек должно быть фиксированное количество в преде-
лах выделенной суммы денег. Должны встречаться игрушки родственных
групп: маленькие, средние и большие машины, куклы, мячи, кубики.
Провести сортировку игрушек в комнате по одному из параметров. Найти
игрушки в комнате, соответствующие заданному диапазону параметров.
15. Налоги. Определить множество и сумму налоговых выплат физического
лица за год с учетом доходов с основного и дополнительного мест работы,
ОСНОВЫ JAVA
128
авторских вознаграждений, продажи имущества, получения в подарок де-
нежных сумм и имущества, переводов из-за границы, льгот на детей и ма-
териальной помощи. Провести сортировку налогов по сумме.
16. Счета. Клиент может иметь несколько счетов в банке. Учитывать возмож-
ность блокировки/разблокировки счета. Реализовать поиск и сортировку
счетов. Вычисление общей суммы по счетам. Вычисление суммы по всем
счетам, имеющим положительный и отрицательный балансы отдельно.
17. Туристические путевки. Сформировать набор предложений клиенту
по выбору туристической путевки различного типа (отдых, экскурсии, ле-
чение, шопинг, круиз и т. д.) для оптимального выбора. Учитывать возмож-
ность выбора транспорта, питания и числа дней. Реализовать выбор и сор-
тировку путевок.
18. Кредиты. Сформировать набор предложений клиенту по целевым креди-
там различных банков для оптимального выбора. Учитывать возможность до-
срочного погашения кредита и/или увеличения кредитной линии. Реализовать
выбор и поиск кредита.
Тестовые задания к главе 4
Вопрос 4.1.
Дан класс:
package ch04.q01;
class Quest41 {}
Укажите правильные варианты наследования от этого класса (2):
1) package ch04.q01; class Quest4 extends Quest41 {}
2) package ch04.q01._2; public class Quest42 extends Quest41 {}
3) package ch04.q01; public class Quest43 implements Quest41 {}
4) package ch04.q01._2; import ch04.q01.Quest41;
5) public class Quest44 extends Quest41 {}
6) package ch04.q01; public class Quest45 extends Quest41 {}
Вопрос 4.2.
Выберите правильные утверждения (3):
1) Класс может быть использован в качестве суперкласса для себя самого.
2) В конструкторе класса можно совместно использовать вызовы this и super.
3) Статические методы можно определять в подклассах с той же сигнатурой,
что и в базовом классе.
4) Статические методы можно перегружать в подклассах.
5) Динамическое связывание определяет версию вызываемого метода на этапе
выполнения.
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
129
Вопрос 4.3.
Дан код:
package ch04.q03;
public class Quest43 {
private final void method () {} //1
}
class Quest431 extends Quest43 {
public void method () {} //2
}
Что произойдет в результате компиляции этого кода (1)?
1) ошибка компиляции в строке 1
2) ошибка компиляции в строке 2
3) компиляция без ошибок
Вопрос 4.4.
Дан код двух классов:
// класс 1
package ch04.q04;
public class Quest41 {}
// класс 2
package ch04.q04._2;
import ch04.q04.Quest41;
public class Quest43 extends Quest41 {
public Quest43 () {
super ();
}}
С каким атрибутом доступа объявлен конструктор по умолчанию в базовом
классе Quest41 (1)?
1) public
2) private
3) protected
4) friendly
Вопрос 4.5.
Дан код:
package ch04.q05;
public class Quest51 {
public String toString () {
ОСНОВЫ JAVA
return getClass ().getSimpleName ();
}
public static void main (String [] args) {
Quest53 q = new Quest53 ();
System.out.println (q.toString ());
}}
class Quest52 extends Quest51 {}
class Quest53 extends Quest52 {}
Что выведется на консоль в результате компиляции и запуска программы (1)?
1) Quest52
2) Quest53
3) Quest51
4) ошибка компиляции
Вопрос 4.6.
Дан код:
package ch04.q06;
class Item {
public int item;
Item (int item) {
this.item = item;
}
}
public class Quest61 {
public static void main (String [] args) {
Item ar1 [] = {new Item (1), new Item (2), new Item (3)};
Item ar2 [] = ar1.clone ();
ar2 [0].item = 4;
System.out.println (ar1 [0].item +" " + ar1 [1].item + " " + ar1 [2].item);
}
}
Что выведется на консоль после компиляции и запуска этой программы (1)?
1) 1 2 3
2) 1 4 3
3) 4 2 3
4) ошибка компиляции
5) ошибка выполнения
131
Достарыңызбен бөлісу: |