Если же записать метод в подклассе с тем же именем, параметрами и типом возвращаемого значения, что и в суперклассе, например:
class Truck extends Automobile{ void moveTo(int x, int y){
// Какие-то действия...
}
// Что-то еще, содержащееся в классе Truck...
}
то он перекроет метод суперкласса.
Определив экземпляр класса Truck, например:
Truck gazel = new Truck();
и записав gazel.moveTo(25, 150), мы обратимся к методу класса Truck. Произойдет переопределение (overriding) метода.
При переопределении метода его сигнатура и тип возвращаемого значения должны полностью сохраняться. Если в подклассе мы изменим тип, количество или порядок следования параметров, то получим новый метод, не переопределяющий метод суперкласса. Если изменим только тип возвращаемого значения, то получим ошибку, которую "заметит" компилятор.
Проверку соответствия сигнатуры переопределяемого метода можно возложить на компилятор, записав перед методом подкласса аннотацию ©Override, как это сделано в листинге 2.2. В этом случае компилятор пошлет на консоль сообщение об ошибке, если сигнатура помеченного метода не будет соответствовать сигнатуре ни одного метода суперкласса с тем же именем.
При переопределении метода права доступа к нему можно только расширить, но не сузить. Открытый метод public должен остаться открытым, защищенный protected может стать открытым, но не может стать закрытым.
Можно ли внутри подкласса обратиться к методу суперкласса? Да, можно, если уточнить имя метода словом super, например super.moveTo(30, 40). Можно уточнить и имя метода, записанного в этом же классе, словом this, например this.moveTo(50, 70), но в данном случае это уже излишне. Таким же образом можно уточнять и совпадающие имена полей, а не только методов.
Данные уточнения подобны тому, как мы говорим про себя "я", а не "Иван Петрович", и говорим "отец", а не "Петр Сидорович".
Переопределение методов приводит к интересным результатам. В классе Pet мы описали метод voice (). Переопределим его в подклассах и используем в классе Chorus, как показано в листинге 2.2.
abstract class Pet{
abstract void voice();
}
class Dog extends Pet{ int k = 10;
©Override void voice(){
System.out.println("Gav-gav!");
}
}
class Cat extends Pet{
©Override void voice(){
System.out.println("Miaou!");
}
}
class Cow extends Pet{
©Override void voice(){
System.out.println("Mu-u-u!");
}
}
public class Chorus{
public static void main(String[] args){ Pet[] singer = new Pet[3]; singer[0] = new Dog(); singer[1] = new Cat(); singer[2] = new Cow();
for (Pet p: singer) p.voice();
}
}
На рис. 2.1 показан вывод этой программы. Животные поют своими голосами!
| Рис. 2.1. Результат выполнения программы Chorus |
Все дело здесь в определении поля singer [ ]. Хотя массив ссылок singer [ ] имеет тип Pet, каждый его элемент ссылается на объект своего типа: Dog, Cat, Cow. При выполнении программы вызывается метод конкретного объекта, а не метод класса, которым определялось имя ссылки. Так в Java реализуется полиморфизм.
Знатокам C++
В языке Java все методы являются виртуальными функциями.
Внимательный читатель заметил в описании класса Pet новое слово abstract. Класс Pet и метод voice () являются абстрактными.
4. Опишите в виде класса строительный подъемный кран.
5. Опишите в виде класса игровой автомат.
6. Смоделируйте в виде класса сотовый телефон.
Абстрактные методы и классы
При описании класса Pet мы не можем задать в методе voice () никакой полезный алгоритм, поскольку у всех животных совершенно разные голоса.
В таких случаях мы записываем только заголовок метода и ставим после закрывающей список параметров скобки точку с запятой. Этот метод будет абстрактным (abstract),
что необходимо указать компилятору модификатором abstract.
Если класс содержит хоть один абстрактный метод, то создать его экземпляры, а тем более использовать их, не удастся. Такой класс становится абстрактным, что обязательно надо указать модификатором abstract.
Как же использовать абстрактные классы? Только порождая от них подклассы, в которых переопределены абстрактные методы.