Полиморфизм в Java


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

Полиморфизм — это один и тот же интерфейс, использующий разные экземпляры для выполнения разных операций, как показано на рисунке:

Полиморфизм — это проявление объектов в нескольких формах.

В реальной жизни, например, действие нажатия клавиши F1:

  • Если вы сейчас находитесь в интерфейсе Flash, открывается справочная документация AS 3;
  • Если вы сейчас находитесь в Word, открывается справка Word;
  • В Windows открывается справка и поддержка Windows.

Одно и то же событие, происходящее с разными объектами, даёт разные результаты.

Преимущества полиморфизма

  • 1. Устранение связанности между типами
  • 2. Заменяемость
  • 3. Расширяемость
  • 4. Интерфейсные возможности
  • 5. Гибкость
  • 6. Упрощение

Три необходимых условия полиморфизма

  • Наследование
  • Переопределение
  • Ссылка на родительский класс указывает на объект подкласса:Parent p = new Child();

class Shape {
    void draw() {}
}
 
class Circle extends Shape {
    void draw() {
        System.out.println("Circle.draw()");
    }
}
 
class Square extends Shape {
    void draw() {
        System.out.println("Square.draw()");
    }
}
 
class Triangle extends Shape {
    void draw() {
        System.out.println("Triangle.draw()");
    }
}

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

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

Ниже приведена демонстрация примера полиморфизма. Подробное объяснение см. в комментариях:

Код файла Test.java:

public class Test { public static void main(String[] args) { show(new Cat()); //Вызовите метод show с объектом Cat show(new Dog()); //Вызовите метод show с объектом Dog Animal a = new Cat(); //Восходящее преобразование a.eat(); //Вызывает eat класса Cat Cat c = (Cat)a; //Нисходящее преобразование c.work(); //Вызывает work класса Cat } public static void show(Animal a) { a.eat(); //Проверка типа if (a instanceof Cat) { //Что делают кошки Cat c = (Cat)a; c.work(); } else if (a instanceof Dog) { //Что делают собаки Dog c = (Dog)a; c.work(); } } } abstract class Animal { abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("Ест рыбу"); } public void work() { System.out.println("Ловит мышей"); } } class Dog extends Animal { public void eat() { System.out.println("Ест кости"); } public void work() { System.out.println("Охраняет дом"); } }

Выполните приведённую выше программу, результат вывода:

eat fish
catch mice
eat bones
guard the house
eat fish
catch mice

Виртуальные функции

Виртуальные функции существуют для полиморфизма.

На самом деле в Java нет понятия виртуальных функций. Обычные функции в Java эквивалентны виртуальным функциям C++, а динамическое связывание является поведением по умолчанию в Java. Если вы не хотите, чтобы функция обладала характеристиками виртуальной функции в Java, можно добавить ключевое слово final, чтобы сделать её невиртуальной функцией.

Переопределение

Мы расскажем, как поведение переопределённых методов влияет на полиморфизм при проектировании классов в Java.

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

Когда объект подкласса вызывает переопределённый метод, он вызывает метод подкласса, а не переопределённый метод в родительском классе.

Чтобы вызвать переопределённый метод в родительском классе, необходимо использовать ключевое словоsuper。

Код файла Employee.java:

/*Имя файла : Employee.java*/ public class Employee { private String name; private String address; private int number; public Employee(String name, String address, int number) { System.out.println("Конструктор Employee"); this.name = name; this.address = address; this.number = number; } public void mailCheck() { System.out.println("Отправка чека по адресу:" + this.name + " " + this.address); } public String toString() { return name + " " + address + " " + number; } public String getName() { return name; } public String getAddress() { return address; } public void setAddress(String newAddress) { address = newAddress; } public int getNumber() { return number; } }

Предположим, что следующий класс наследует класс Employee:

Код файла Salary.java:

/*Имя файла : Salary.java*/ public class Salary extends Employee { private double salary; //Годовая зарплата public Salary(String name, String address, int number, double salary) { super(name, address, number); setSalary(salary); } public void mailCheck() { System.out.println("метод mailCheck класса Salary"); System.out.println("Отправка чека по адресу:" + getName() + ", зарплата:" + salary); } public double getSalary() { return salary; } public void setSalary(double newSalary) { if(newSalary >= 0.0) { salary = newSalary; } } public double computePay() { System.out.println("Рассчитать зарплату, выплатить:" + getName()); return salary/52; } }

Теперь давайте внимательно прочитаем следующий код и попробуем определить результат его вывода:

Код файла VirtualDemo.java:

/*Имя файла : VirtualDemo.java*/ public class VirtualDemo { public static void main(String [] args) { Salary s = new Salary("Сотрудник A", "Пекин", 3, 3600.00); Employee e = new Salary("Сотрудник B", "Шанхай", 2, 2400.00); System.out.println("Вызов mailCheck с использованием ссылки Salary --"); s.mailCheck(); System.out.println("\nВызов mailCheck с использованием ссылки Employee--"); e.mailCheck(); } }

Результат компиляции и выполнения приведенного выше примера выглядит следующим образом:

Employee 构造函数
Employee 构造函数
使用 Salary 的引用调用 mailCheck -- 
Salary 类的 mailCheck 方法 
邮寄支票给:员工 A ,工资为:3600.0

使用 Employee 的引用调用 mailCheck--
Salary 类的 mailCheck 方法 
邮寄支票给:员工 B ,工资为:2400.0

Разбор примера

  • В примере создаются два объекта Salary: один с использованием ссылки Salary s, а другой — с использованием ссылки Employee e.

  • При вызове s.mailCheck() компилятор на этапе компиляции находит mailCheck() в классе Salary, а во время выполнения JVM вызывает mailCheck() класса Salary.

  • e — это ссылка на Employee, но ссылка e в итоге вызывает метод mailCheck() класса Salary.

  • Во время компиляции компилятор использует метод mailCheck() в классе Employee для проверки оператора, но во время выполнения виртуальная машина Java (JVM) вызывает метод mailCheck() в классе Salary.

Весь описанный выше процесс называется вызовом виртуального метода, а сам метод — виртуальным методом.

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


Способы реализации полиморфизма

Способ 1: Переопределение:

Этот материал уже подробно рассматривался в предыдущей главе, поэтому дополнительных пояснений не даётся. Подробнее см.:Переопределение и перегрузка в Java。

Способ 2: Интерфейсы

  • 1. Самый наглядный пример интерфейса в жизни — розетка. Например, вилку с тремя штырьками можно вставить в розетку с тремя отверстиями, потому что в каждой стране свои правила интерфейса. За границей это может не работать, потому что в других странах определяют свои типы интерфейсов.

  • 2. Интерфейс в Java похож на интерфейс в жизни; это набор сигнатур методов, но без реализаций методов. Подробнее см.Интерфейсы в Javaв этой главе.

Способ 3: Абстрактные классы и абстрактные методы

Подробнее см. в главеАбстрактные классы Java.

Другие расширения