Типи поліморфізму Карделлі в Java

Джерело: Факти про поліморфізм у Java

1. Що таке поліморфізм у Java?

pic

Поліморфізм — це концепція, що застосовується в різних мовах програмування, але кожна з них має свої особливості. Наприклад, Python використовує Duck Typing завдяки своєму динамічному типізуванню, тоді як Java вимагає, щоб поліморфізм був явним.

2. Чотири типи поліморфізму

Вчений Лука Карделлі у своїй роботі On Understanding Types, Data Abstraction, and Polymorphism охарактеризував поліморфізм кількома типами:

Ad-hoc (для конкретної мети):

  • Примусова конверсія/Кастинг
  • Перевантаження

Універсальні типи:

  • Параметричний
  • Підтип/Включення

2.1 Ad-hoc проти примусової конверсії

Деякі люди вважають перевантаження єдиним типом Ad-hoc поліморфізму і вважають примусову конверсію окремим типом поліморфізму. Тож, якщо ви коли-небудь почуєте, як хтось згадує «Ad-hoc поліморфізм», вони мають на увазі перевантаження.

Нижче я розберу типи поліморфізму і визначу їхні етапи виконання.

3. Примусова конверсія/Кастинг

Примусова конверсія пов'язана з неявними перетвореннями (кастингами), які відбуваються на етапі компіляції (або явними на часі виконання). Наприклад, коли ви ділите Integer на Float, компілятор конвертує Integer у Float, щоб уникнути помилки. Практично код, як цей:

Integer integerVar = 1;   
Float floatVar = 1.0f;   
Float result = integerVar * floatVar;

Будуть скомпільовані так:

Integer integerVar = 1;   
Float floatVar = 1.0F;   
(float) integerVar * floatVar;

Компілятор неявно конвертує змінну integerVar.

4. Перевантаження

Перевантаження дозволяє методам з однаковим ім'ям відрізнятися за їх підписом, що відбувається на етапі компіляції. Наприклад:

public class Main {   
 private static int add(int a, int b) {   
 return a + b;   
 }   

 private static int add(int a, int b, int c) {   
 return add(a, b) + c;   
 }   

 public static void main(String[] args) {   
 System.out.println(add(1, 2));   
 System.out.println(add(1, 2, 3));   
 }  
}

Навіть з однаковим ім'ям компілятор правильно визначає методи, які слід викликати через їхній підпис. Важливо відзначити, що, окрім перевантаження методів (як показано вище), у нас також є перевантаження операторів, що дозволяє перевизначати оператори (наприклад, + і -) у класах.
Ось приклад на C++:

#include   
using namespace std;  

class MyClass {  
private:  
 float value1;  
 float value2;  

public:  
 // Конструктор  
 MyClass(float r = 0, float i = 0) : value1(r), value2(i) {}  

 // Перевантаження оператора +  
 MyClass operator + (const MyClass& obj) {  
 MyClass result;  
 result.value1 = value1 + obj.value1;  
 result.value2 = value2 + obj.value2;  
 return result;  
 }  

 // Виведення значень  
 void display() const {  
 cout << "Value 1: " << value1 << " - Value 2: " << value2 << endl;  
 }  
};  

int main() {  
 MyClass num1(2.5, 3.5);  
 MyClass num2(1.6, 2.1);  

 // Використовуємо перевантажений оператор +  
 MyClass sum = num1 + num2;  

 sum.display(); // Виведе: Value 1: 4.1 - Value 2: 5.6  

 return 0;  
}

У цьому прикладі створено клас MyClass з двома значеннями, і оператор + перевантажено так, що при додаванні двох об'єктів MyClass, як у випадку num1 + num2, код повертає новий об'єкт MyClass, в якому сумуються частини value1 і value2 обох об'єктів.

Параметричний

Параметричний поліморфізм дає можливість використовувати один і той самий код для різних типів, що відбувається на етапі компіляції. Наприклад:

public class Main {  
 // Генерична функція для фільтрації елементів у списку  
 public static <T> List<T> filterElements(Predicate<T> predicate, List<T> list) {  
 List<T> result = new ArrayList<>();  
 for (T item : list) {  
 if (predicate.test(item)) {  
 result.add(item);  
 }  
 }  
 return result;  
 }  

 public static void main(String[] args) {  
 // Фільтруємо парні числа  
 List<Integer> integers = List.of(1, 2, 3, 4, 5, 6);  
 List<Integer> evenIntegers = filterElements(x -> x % 2 == 0, integers);  

 // Фільтруємо непарні числа типу Long  
 List<Long> longs = List.of(1L, 2L, 3L, 4L, 5L, 6L);  
 List<Long> oddLongs = filterElements(x -> x % 2 != 0, longs);  

 // Фільтруємо позитивні числа типу Double  
 List<Double> doubles = List.of(-1.0, 2.0, -3.0, 4.0, -5.0, 6.0);  
 List<Double> positiveDoubles = filterElements(x -> x >= 0.0, doubles);  
 }  
}

У цьому прикладі у нас є функція, яка фільтрує список елементів незалежно від їхнього типу, виконання фільтрації та тип повернення залежать від контексту.

Підтип/Включення

Поліморфізм підтипу (або включення) відбувається на етапі виконання і дозволяє типам об'єктів бути похідними від їхніх батьківських класів. Цей тип також відомий як поліморфізм виконання через те, що "ви не знаєте" фактичний тип об'єкта, поки код не виконається.

Цей тип поліморфізму можна досягти за допомогою наслідування та/або перевизначення методів. Ось простий приклад:

public class Main {  
 public static class Feline {  
 public void meow() {  

 }  
 }  

 public static class Cat extends Feline {  
 public void meow() {  
 System.out.println("Meowing like a Cat. Meow!");  
 }  
 }  

 public static class Tiger extends Feline {  
 public void meow() {  
 System.out.println("Meowing like a Tiger. MEAOWWWWWWW!");  
 }  
 }  

 public static void main(String[] args) {  
 Feline feline = new Feline();  
 feline.meow(); // Нічого не робить  

 feline = new Cat();  
 feline.meow(); // Meowing like a Cat. Meow!  

 feline = new Tiger();  
 feline.meow(); // Meowing like a Tiger. MEAOWWWWWWW!  
 }  
}

У цьому прикладі об'єкт feline змінюється кілька разів, ставши екземпляром різних класів, і щоразу, коли викликається метод meow(), він поводиться по-різному в залежності від контексту об'єкта на той момент.

Перекладено з: Cardelli's Polymorphism Types in Java

Leave a Reply

Your email address will not be published. Required fields are marked *