Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лр Основные понятия ООП.docx
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
42 Кб
Скачать
☆

Приведение типов

Объект класса-потомка можно присвоить переменной типа класса-предка. При этом Java производит автоматическое преобразование типа, называемое расширением. Расширение — это переход от более конкретного типа к менее конкретному. Переход от byte к int — это тоже расширение.

Рассмотрим пример, имеющий отношение к основному заданию. В программе есть класс User, предназначенный для обработки информации о пользователях системы. В этом классе есть метод enter(String login, String password), который возвращает true, если переданные в метод логин и пароль совпадают с логином и паролем, скрытым в полях класса.

Мы наследуем от класса User два подкласса: Admin и, к примеру, Member (для программы координации участников встречи, см. задание 13). Класс Admin может понадобиться нам впоследствии для каких-то специфичных действий, связанных с управлением системой, а класс Member моделирует участников проекта, которые с помощью программы пытаются выбрать оптимальное место для встречи. Открытый метод addRequest(String place, int day, int from, int to) вызывается, когда участник проекта предлагает новый вариант времени и места встречи.

В главном классе программы мы храним массив* users, содержащий всех пользователей системы. Элементы этого массива имеют тип User, но мы можем присваивать им ссылки на объекты как класса Member, так и класса Admin. В этот момент и будет происходить расширение типа.

Member member = new Member(...);

users[3] = member; // Java проводит автоматическое преобразование типа Member к типу User, чтобы поместить переменную member в массив users

Для того, чтобы найти пользователя с введенными логином и паролем программа выполняет запрос:

for (int i = 0; i < users.length; i++) {

if (users[i].enter(log, passw)) currentUser = users[i];

}

Несмотря на то, что все объекты, добавленные в массив, сохраняют свой "настоящий" класс, программа работает с ними как с объектами класса User. Этого вполне достаточно, чтобы можно было найти нужного пользователя по логину и паролю (ведь метод enter() у них общий) и присвоить найденный объект переменной currentUser типа User. В этой переменной хранится текущий пользователь, авторизовавшийся в системе.

Предположим, нам известно, что переменная currentUser сейчас ссылается на объект класса Member и текущий пользователь предлагает встретиться у фонтана в среду с 17 до 19 часов. Необходимо вызвать метод addRequest(), но у нас не получится сделать это командой

currentUser.addRequest("Фонтан", 3, 17, 19);

поскольку в классе User нет метода addRequest().

Однако мы можем осуществить явное преобразование переменной currentUser к типу Member. Такое преобразование (переход от менее конкретного типа к более конкретному) называется сужением. Явное преобразование делается с помощью оператора, представляющего собой имя целевого типа в скобках.

((Member)currentUser).addRequest("Фонтан", 3, 17, 19);

Здесь мы, прежде чем вызвать метод addRequest(), преобразовали переменную currentUser к типу Member. Нам было позволено сделать это, поскольку Member является потомком User. Однако, если бы во время выполнения программы оказалось, что на самом деле переменная currentUser не ссылалась на объект класса Member, в программе возникла бы ошибка.

Оператор instanceof

Чтобы уточнить, соответствует ли текущее значение переменной конкретному типу, используется оператор instanceof.

Проверим, не является ли текущий пользователь администратором (в этом случае программа должна перейти в режим управления):

if (currentUser instanceof Admin) {...}

Анонимные и вложенные классы

о вложенных классах

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

Эта возможность иногда используется, чтобы подчеркнуть отношение агрегации между классами. Например, нам может понадобиться класс Eye, чтобы описать сложную структуру и поведение глаза (он может открываться и закрываться). Но объекты этого класса не будут создаваться сами по себе, они являются неотъемлемой частью объектов класса Dog и только их. Поэтому мы помещаем описание класса Eye в класс Dog:

class Dog {

...

class Eye {

private boolean opened;

public void close() {

opened = false;

System.out.println("глаз закрыт");

}

public void open() {

opened = true;

System.out.println("глаз открыт");

}

public boolean isOpened() {

return opened;

}

}

Eye rightEye = new Eye(), leftEye = new Eye();

}

Мы сразу же добавили в класс Dog два поля класса Eye и проинициализировали их вновь созданными объектами (это можно было сделать и в конструкторе). Теперь у собаки есть два глаза и она может открывать их и закрывать. Например, предположим, что все собаки лают с закрытым правым глазом. Тогда метод voice() надо переписать так:

public void voice() {

rightEye.close();

for (int i = 1; i <= age; i++) {

System.out.println("гав-гав");

}

rightEye.open();

}

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

Класс можно объявить внутри метода другого класса. В этом случае класс "виден" только внутри метода (за пределами метода нельзя объявить переменную типа этого класса).

Анонимным классом называется класс, не имеющий имени. Очевидно, если у класса имени нет, к нему нельзя обратиться из программы. Точнее, это можно сделать только один раз — в том месте, где класс объявляется.

Описание анонимного класса начинается с вызова конструктора его суперкласса, после чего в фигурных скобках описывается тело класса.

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

Пусть, например, в нашей программе собачьего питомника имеется массив dogs объектов типа Dog. И мы хотим добавить в этот массив совершенно уникальную собаку, которая не лает, а разговаривает. Необходимо описать класс, унаследованный от класса Dog, в котором будет соответствующим образом переопределен метод voice(). Но поскольку нам гарантированно понадобится только одна такая собака, мы можем описать анонимный класс прямо в месте добавления собаки в питомник (посадим ее в клетку № 10):

dogs[10] = new Dog(){

public void voice() {

System.out.println("Я уникальная говорящая собака.");

}

};

И, несмотря на то, что мы не сможем создавать переменных этого класса, мы спокойно можем пользоваться новой собакой как объектом класса Dog. А благодаря полиморфизму вызов метода

dogs[10].voice();

приведет к тому, что уникальная собака будет уникально подавать голос.

Модификатор static

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

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

Метод, объявленный с модификатором static, "дает обещание" не изменять никаких полей класса, кроме статических.

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

Упражнение

Модифицируйте класс Dog таким образом, чтобы можно было считать собак, созданных во время работы программы. Для этого введите статический атрибут count (изначально равный нулю) и увеличивайте его на единицу в каждом конструкторе. Создайте в методе main() несколько объектов класса Dog, а затем выполните для проверки команду:

System.out.println("Всего было создано собак: " + Dog.count);

Модификатор final

о модификаторе final

Любое поле класса можно объявить неизменяемым, указав перед его объявлением ключевое слово final. Неизменяемому полю можно присвоить значение только один раз (обычно это делается сразу при объявлении).

Константы в языке Java очевидным образом описываются путем совмещения модификаторов static и final. Например, мы можем объявить константу PI (лучше это делать в основном классе, а не в классе Dog*), написав:

final static double PI = 3.14;

Если ключевое слово final указать перед объявлением метода, это будет обозначать, что метод нельзя переопределять при наследовании (т.е. данная версия метода будет окончательной).

Перед объявлением класса модификатор final ставится в том случае, если необходимо запретить от него наследование.

Диаграммы классов в языке UML

Внимание! На данном этапе обучения вы уже должны владеть знаниями по этой теме. Если их нет, а материалы для повторения непонятны или недостаточны, вы с заданиями не справитесь! Необходимо срочно обратиться к литературе по данной теме.

Задание

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

15