Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторная работа №2 2014.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
340.48 Кб
Скачать

Перегрузка

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

Например, в дополнение к конструктору, который уже есть в классе Dog, мы можем описать конструктор без параметров:

public Dog() {

name = "Незнакомец";

}

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

Dog dog1 = new Dog("Тузик", 2); // Собака по кличке Тузик, возраст 2 года

Dog dog2 = new Dog(); // Собака по кличке «Незнакомец», возраст 0

Dog dog3 = new Dog(10); // Неверно! Не существует конструктора с такими параметрами

Нельзя создавать несколько одноименных методов с одинаковым числом и типом параметров.

Frame2

Полиморфизм

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

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

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

class BigDog extends Dog {

public void voice() {

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

System.out.print("ГАВ-");

}

}

}

Теперь создадим в методе main() двух разных собак: обычную и большую и заставим их лаять.

Dog dog = new Dog("Тузик", 2);

dog.voice();

BigDog bigdog = new BigDog();

bigdog.voice();

Объект подкласса всегда будет одновременно являться объектом любого из своих суперклассов. Поэтому в том же примере мы могли бы обойтись и одной переменной:

Dog dog = new Dog("Тузик", 2);

dog.voice();

dog = new BigDog();

dog.voice();

Т. е. переменная dog имеет тип Dog, но в третьей строке она начинает указывать на объект класса BigDog, то есть БОЛЬШУЮ собаку, которая при вызове метода voice() будет лаять как БОЛЬШАЯ собака. Это одна из впечатляющих возможностей объектно-ориентированного программирования.

Приемы программирования: наследование и полиморфизм

Главное преимущество полиморфизма – это возможность работать с объектами разных классов, происходящих от одного общего предка так, как будто бы они относились к одному классу.

Рассмотрим типичный пример.

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

Время от времени программа должна выполнять над этими фигурами какие-то действия. Например, когда окно программы сворачивается, а потом снова разворачивается, надо заново нарисовать все эти фигуры. Когда пользователь щелкает по фигуре мышкой, ее надо выделить, а когда пользователь перетаскивает границы фигуры – изменить ее размеры.

Придерживаясь методологии объектно-ориентированного программирования, мы приходим к выводу, что каждая фигура должна рисовать себя «сама». То есть, команды для прорисовки круга выполняются в одном из методов класса Circle, например, в методе paint(). Действительно, все параметры фигуры должны храниться в полях ее класса, поэтому легко можно написать такой метод. Аналогично, фигура «сама» рисует себе выделение – для этого есть метод paintSelection() – и передвигается – метод move(int x, int y). Задача основной программы – просто обращаться к этим методам при необходимости.

Программа должна где-то хранить объекты, которые создаст пользователь. Поскольку заранее неизвестно, сколько будет этих объектов, необходимо воспользоваться какой-нибудь структурой для хранения множества объектов, например массивом. Но при создании массива требуется указать тип его элементов. А в нашей программе пользователь может создавать самые разные объекты. Так что придется завести несколько массивов: один для точек, один для кругов и так далее. Если понадобится заново нарисовать все объекты на экране, нужно будет перебрать все элементы в каждом из этих массивов:

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

points[i].paint();

}

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

circles[i].paint();

}

... и так далее, для каждого типа фигуры.

Более того, если пользователь щелкнул мышкой по экрану, чтобы выбрать фигуру, программа, получившая координаты мыши, должна найти фигуру, в которую попадают эти координаты. Предположим, каждая фигура сама может осуществить проверку с помощью метода checkPoint(int x, int y), который возвращает значение true, если точка с координатами x, y находится внутри этой фигуры. Но для того, чтобы вызвать этот метод, снова придется перебрать все массивы. И так для каждой операции, что очень неудобно.

Благодаря наследованию мы имеем две прекрасные возможности. Для того, чтобы ими воспользоваться, нам нужно создать класс Figure и описать в нем методы, общие для всех фигур: paint(), checkPoint(int x, int y) и так далее. Не обязательно программировать эти методы, мы все равно не будем обращаться к ним. Важно, чтобы они были.

Первая возможность: мы можем присваивать объекты классов-потомков переменным любого из классов-предков.

Это вполне логично. Ведь если класс Кошка унаследован от класса Животное, то объект Мурзик является одновременно объектом класса Кошка и объектом класса Животное.

Следовательно, мы можем создать один большой массив для хранения объектов класса Figure:

Figure[] figures = new Figure[100]; // создаем массив для хранения 100 фигур

Теперь мы можем помещать в этот массив любые фигуры:

figures[0] = new Point(30, 30); // добавили в массив точку с координатами 30, 30

figures[1] = new Circle(60, 20, 10); // добавили круг с координатами 60, 20 радиуса 10

figures[2] = new Rectangle(0, 0, 30, 40); // добавили прямоугольник ...

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

Мы можем нарисовать все фигуры, хранящиеся в нашем массиве:

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

if (figures[i]!= null) figures[i].paint();

}

В массиве хранятся элементы типа Figure. В этом классе есть метод paint(), поэтому мы вполне можем к нему обратиться. Но в самом классе Figure этот метод не делает ничего (ведь мы не могли разработать процедуру рисования, подходящую для всех без исключения фигур). Зато в классе Point, унаследованном от класса Figure, мы переопределили этот метод – написали его заново так, чтобы он рисовал точку (координаты точки хранятся в скрытых атрибутах класса Point). А в первом элементе массива figures[0] у нас хранится именно точка. Хотя мы обращаемся с ней как с просто фигурой, Java знает, что при вызове метода paint() нужно использовать именно тот вариант, который переопределен в классе Point. Аналогично команда figures[1].paint(); нарисует круг, а figures[2].paint(); нарисует прямоугольник.

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