Тема 10. Полиморфизм - BelyiZ/JavaCourses GitHub Wiki
- Перегрузка и переопределение методов
- Абстрактные классы и методы
- Интерфейсы
- Анонимные классы
- Параметрический полиморфизм Generics
- Список литературы/курсов
Полиморфизм, если перевести, - это значит "много форм". Например, актер в театре может примерять на себя много ролей - или принимать "много форм". Аналогично и код - благодаря полиморфизму он становится более гибким, чем в языках программирования, которые не используют принципы ООП.
Полиморфизм – это способность программы идентично использовать объекты с одинаковым интерфейсом без информации о конкретном типе этого объекта.
Если более развернуто: Полиморфизм - возможность применения одноименных методов с одинаковыми или различными наборами параметров в одном классе или в группе классов, связанных отношением наследования.
Что нам даёт полиморфизм:
- Позволяет подменять реализации объектов. На этом основано тестирование.
- Обеспечивает расширяемость программы — становится гораздо легче создавать задел на будущее. Добавление новых типов на основе существующих — наиболее частый способ расширения функциональности программ, написанных в ООП стиле.
- Позволяет объединять объекты с общим типом или поведением в одну коллекцию или массив и управлять ими единообразно.
- Гибкость при создании новых типов: вы можете выбирать реализацию метода из родителя или переопределить его в потомке.
Перегрузка Называть методы одинаково - это очень удобно. Например, если у нас есть метод, который ищет корень квадратный из числа, гораздо легче запомнить одно название (например, sqrt()), чем по одному отдельному названию на этот же метод, написанный для каждого типа. Такой принцип часто называют "Один интерфейс - много методов". Это предполагает, что мы можем заполнить одно название (один интерфейс), по которому мы сможем обращаться к нескольким методам.
Обратите внимание, что в части переопределения методов есть проблема с пересечением с темой Наследование.
Хорошая практика при работе с полиморфизмом - использование абстрактных описаний для определения базовых классов с помощью абстрактных классов, а также интерфейсов. Эта практика основана на использовании абстракции — выделении общего поведения и свойств и заключении их в рамки абстрактного класса, или выделении только общего поведения – в таком случае мы создаем интерфейс.
Построение и проектирование иерархии объектов на основе интерфейсов и наследовании классов является обязательным условием для выполнения принципа полиморфизма ООП.
При создании абстрактных классов и интерфейсов (начиная с Java 8), есть возможность написания дефолтной реализации абстрактных методов в базовых классах с помощью ключевого слова default
.
Пример:
public interface Swim {
default void swim() {
System.out.println("Просто плыву");
}
}
В Java не поддерживается множественное наследование — каждый тип может иметь одного родителя (суперкласс) и неограниченное количество наследников (подклассов). Поэтому для добавления нескольких функциональностей в классы используются интерфейсы.
Интерфейсы уменьшают связанность объектов с родителем по сравнению с наследованием и используются очень широко. В Java интерфейс является ссылочным типом, поэтому в программе может быть объявлен тип переменой типа интерфейса.
Пример: Создадим интерфейс.
public interface Swim {
void swim();
}
Возьмем разные и не связанные между собой объекты и реализуем в них интерфейс:
public class Human implements Swim {
private String name;
private int age;
public Human(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void swim() {
System.out.println(toString()+" Я плаваю с помощью надувного круга.");
}
@Override
public String toString() {
return "Я " + name + ", мне " + age + " лет. ";
}
}
public class Fish implements Swim{
private String name;
public Fish(String name) {
this.name = name;
}
@Override
public void swim() {
System.out.println("Я рыба " + name + ". Я плыву, двигая плавниками.");
}
public class UBoat implements Swim {
private int speed;
public UBoat(int speed) {
this.speed = speed;
}
@Override
public void swim() {
System.out.println("Подводная лодка плывет, вращая винты, со скоростью " + speed + " узлов.");
}
}
Метод main
:
public class Main {
public static void main(String[] args) {
Swim human = new Human("Антон", 6);
Swim fish = new Fish("кит");
Swim boat = new UBoat(25);
List<Swim> swimmers = Arrays.asList(human, fish, boat);
for (Swim s : swimmers) {
s.swim();
}
}
}
Результат выполнения полиморфного метода, определенного в интерфейсе, позволяет нам увидеть различия в поведении типов, реализующих этот интерфейс.
Они заключаются в разных результатах выполнения метода swim
.
При выполнении кода из main
:
for (Swim s : swimmers) {
s.swim();
}
Отметим, что для объектов из примера вызываются методы, определенные в этих классах и происходит выбор нужной реализации метода при выполнении программы благодаря позднему (динамическому) связыванию. Под связыванием понимают установление связи между вызовом метода и его конкретной реализацией, в классах. По сути, определяется код, какого из трех методов, определенных в классах, будет выполнен. В Java по умолчанию используется позднее связывание (на стадии выполнения программы, а не во время компиляции, как в случае с ранним связыванием). Это значит, что при компиляции кода:
for (Swim s : swimmers) {
s.swim();
}
компилятор еще не знает, код из какого класса — Human
, Fish
или Uboat
он будет исполнять в методе swim
.
Это определится только при выполнении программы благодаря механизму динамической диспетчеризации — проверки типа объекта во время выполнения программы и выбора нужной реализации метода для этого типа.
Это реализовано следующим образом: при загрузке и инициализации объектов JVM
строит таблицы в памяти, и в них связывает переменные с их значениями, а объекты — с их методами. Причем если объект наследуется или имплементирует интерфейс, в первую очередь проверяется наличие переопределенных методов в его классе. Если таковые есть, они привязываются к этому типу, если нет – ведется поиск метода, определенный в классе на ступень выше (в родителе) и так вплоть до корня при многоуровневой иерархии.
Чтобы облегчить будущие обновления интерфейса и минимизировать влияние на систему, предусмотрен метод по умолчанию, который может иметь тело метода.
Конструкция public default
: Тип возвращаемого значения Имя_метода (список параметров){
Пример:
default void bye_swimmers(String swimmersName) {
System.out.println("До скорой встрчи, "+ swimmersName);
}
Стоит отметить,что:
Метод по умолчанию можно вызвать непосредственно в созданном классе реализации. Метод по умолчанию также можно переопределить. Если возможно найти переписанный метод по умолчанию, то напрямую вызывается переписанный метод по умолчанию, если не найден, он ищет более высокий уровень.
Анонимные классы объявляются внутри методов основного класса и могут быть использованы только внутри этих методов. В отличие от локальных классов, анонимные классы не имеют названия. Главное требование к анонимному классу - он должен наследовать существующий класс или реализовывать существующий интерфейс. Анонимные классы не могут содержать определение статических полей, методов и классов (кроме констант), но могут их наследовать.
Пример:
class OuterClass
{
public OuterClass() {}
void methodWithLocalClass (final int interval)
{
// При определении анонимного класса применен полиморфизм - переменная listener содержит экземпляр
// анонимного класса, реализующего существующий интерфейс ActionListener
ActionListener listener = new ActionListener()
{
@Override
public void actionPerformed(ActionEvent event)
{
System.out.println("Эта строка выводится на экран каждые " + interval + " секунд");
}
};
Timer t = new Timer(interval, listener); // Объект анонимного класса использован внутри метода
t.start();
}
}
Стоит запомнить: Анонимные классы широко используются. Анонимные классы не имеют имени.
Классический пример анонимного класса:
new Thread(new Runnable() {
public void run() {
...
}
}).start();
На основании анонимного класса создается поток и запускается с помощью метода start
класса Thread
.
Синтаксис создания анонимного класса базируется на использовании оператора new
с именем класса (интерфейса) и телом новосозданного анонимного класса.
Основное ограничение при использовании анонимных классов - это невозможность описания конструктора, так как класс не имеет имени. Аргументы, указанные в скобках, автоматически используются для вызова конструктора базового класса с теми же параметрами.
Пример:
class Clazz
{
Clazz(int param) { }
public static void main(String[] args) {
new Clazz(1) { }; // правильное создание анонимного класса
new Clazz( ) { }; // неправильное создание анонимного класса
}
}
Так как анонимный класс является локальным классом, он имеет все те же ограничения, что и локальный класс.
Использование анонимных классов оправдано когда:
- Тело класса является очень коротким.
- Нужен только один экземпляр класса.
- Класс используется в месте его создания или сразу после него.
- Имя класса не важно и не облегчает понимание кода.
При объявлении класса, имя поля можно связать с различными типами и имя метода так же можно ассоциировать с различными параметрами и возвращаемыми типами.
Параметрический полиморфизм - это универсальный полиморфизм. Он позволяет написать настоящий универсальный код, который работает для любых типов (связанных или не связанных). Это также называется "дженерики".
В параметрическом полиморфизме часть кода написана таким образом, что она работает с любым типом. Типы не обязательно должны быть связаны между собой. Параметрический полиморфизм достигается с помощью переменной типа при написании кода, а не с использованием какого-либо определенного типа. Переменная типа предполагает определенный тип, для которого необходимо выполнить код.
Java поддерживает полиморфные объекты (например, параметризованные классы), а также полиморфные методы (параметризованные методы), которые используют параметрический полиморфизм. В Java параметрический полиморфизм достигается при использовании "дженериков".
Пример:
class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src)
}
Далее в теме Параметрический полиморфизм Generics рассмотрим подробнее.
1.https://javarush.ru/groups/posts/polimorfizm-v-java 2. https://vertex-academy.com/tutorials/ru/chto-takoe-polimorfizm-java/ 3. https://hr-vector.com/java/polimorfizm 4. https://ru.realiventblog.com/51-polymorphism-and-inheritance-in-java 5. https://javascopes.com/polymorphism-in-java-9201a309/
Тема 9. Наследование и static | Оглавление | Тема 11. Параметрический полиморфизм (Generics)