Знижка 20% на курс GRASP & GOF Design Patterns
Дізнатися більше
24.01.2025
6 хвилин читання

Наслідування в Generics

Generics у Java дозволяють створювати гнучкі та типобезпечні класи, методи та інтерфейси. Вони допомагають уникнути помилок із неправильним приведенням типів та покращують читабельність і підтримуваність коду. Однак, з generics пов’язано чимало нюансів, які можуть бути неочевидними для новачків, особливо коли справа стосується наслідування та використання generics із інтерфейсами.

Наслідування в generics дещо відрізняється від звичайної ієрархії класів. Generics не тільки підвищують безпеку типів, але й додають певні обмеження, пов’язані з використанням параметрів типів у класах і методах. Важливо розуміти, як працює наслідування з generics, щоб уникати поширених помилок у коді.

До речі, якщо вас цікавлять якісні курси програмування та більш глибоке розуміння теми generics, компанія FoxmindEd пропонує відмінні навчальні  та просунуті програми. Вони спеціалізуються на сучасних ІТ-технологіях і допоможуть вам розвинути ваші професійні навички.

Бажаєте освоїти професію Java Developer? Приєднуйтесь до програми “Від 0 до Strong Java Junior за 12 місяців”. Скористайтесь вигідною пропозицією від FoxmindEd!
Зареєструватись

У Java generics можна використовувати не лише для створення окремих класів, але й для наслідування. Якщо батьківський клас параметризований певним типом, дочірні класи можуть або конкретизувати цей тип, або залишити його параметризованим. Наслідування з generics надає гнучкість при роботі з різними типами даних.

Однією з важливих особливостей є те, що ми можемо наслідувати непараметризований клас параметризованим, а також конкретизувати або продовжити параметризацію в дочірніх класах. Це дає змогу створювати класи з різними рівнями абстракції та використовувати generics у різноманітних сценаріях.

Уявімо, що у нас є клас Container, який може зберігати деякий елемент типу T. Ми можемо створити різні класи коробок (Box), що будуть наслідувати Container, але по-різному працюватимуть з узагальненими типами (generics). Кожен з цих класів буде наслідувати Container та по-своєму обробляти generics, що дозволить реалізувати різні підходи до зберігання та роботи з даними. 

@Data

class Container<T> {

  private T item;

}

Якщо клас Box наслідує Container без вказівки типу (Box extends Container), це викличе попередження про використання сирого типу Container. Java інтерпретує це як використання “сирих” типів, що може призвести до проблем із безпекою типів.

public class Box extends Container{

   public Box(Object item) {

       super(item);

   }

}

public class Main {

   public static void main(String[] args) {

       Box box = new Box("Item"); // Box наслідує сирий тип Container

       Object item = box.getItem(); // Повернеться Object, а не конкретний тип

       Box<String> box = new Box<>("Item"); // Буде помилка компіляції, адже ми не вказали, що наш Box параметризований

   }

}

У цьому випадку клас Box1 наслідує Container, але також підтримує узагальнений тип T. Це найбільш безпечний і гнучкий спосіб наслідування, адже при створенні Box ми зможемо вказати тип, що там буде зберігатися.

class Box1<T> extends Container<T>{

public Box(T item) {

   super(item);

    }

}

public class Main {

   public static void main(String[] args) {

       Box1<String> box = new Box1<>("Item");

       String item = box.getItem(); // Повернеться тип String

   }

}

Box2 наслідує Container, фіксуючи тип T як String. Це означає, що всі екземпляри Box2 будуть зберігати лише рядки (String):

public class Box2 extends Container<String>{

   public Box2(String item) {

       super(item);

   }

}

public class Main {

   public static void main(String[] args) {

       Box2 box = new Box2("Item");

       String item = box.getItem(); // Повернеться тип String

       Box2 box2 = new Box2(5); // буде помилка компіляції, адже Box2 зберігає лише тип String

   }

}

У даному класі Box3 підтримує кілька узагальнених типів (T, V, K), але наслідує Container з типом T. Це означає, що T буде використовуватися для поля item, тоді як V та K можуть використовуватися для інших цілей

@Getter

@Setter

class Box3<T, V, K> extends Container<T> {

   private V extraValue;

   private K anotherValue;

   public Box3(T item, V extraValue, K anotherValue) {

       super(item);

       this.extraValue = extraValue;

       this.anotherValue = anotherValue;

   }

}

public class Main {

   public static void main(String[] args) {

       Box3<String, Integer, Double> box = new Box3<>("Hello", 42, 3.14);

       String item = box.getItem(); // Отримаємо String

       Integer extra = box.getExtraValue(); // Отримаємо Integer

       Double another = box.getAnotherValue(); // Отримаємо Double

   }

}

Наслідування з обмеженнями

Ми розглянули стандартне наслідування з використанням generics, де класи-нащадки можуть працювати з будь-яким типом даних, що передається як параметр. Це забезпечує гнучкість та універсальність у використанні, але іноді виникає потреба обмежити типи, з якими може працювати клас.

Саме для таких випадків generics дозволяють застосовувати наслідування з обмеженнями, використовуючи ключове слово extends. Це особливо корисно, коли потрібно працювати лише з певною групою типів, наприклад, усіма класами, які наслідуються від конкретного суперкласу або реалізують певний інтерфейс. Такий підхід називається обмеженням параметра типу (bounded type parameter) і дозволяє створювати більш безпечний і структурований код, обмежуючи використання generics певними рамками.

Коли ми використовуємо ключове слово extends, це означає, що параметр типу може бути або самим вказаним типом, або його підтипом. Розглянемо простий приклад:

class Animal {}

class Dog extends Animal {}

class Cat extends Animal {}

class Container<T extends Animal> {

   private T item;

   public Container(T item) {

       this.item = item;

   }

   public T getItem() {

       return item;

   }

}

У цьому прикладі клас Container параметризований типом T, який обмежений класом Animal. Це означає, що T може бути або Animal, або будь-яким його підтипом, таким як Dog або Cat. Отже, ми можемо створювати екземпляри контейнера тільки для тварин:

Container<Dog> dogContainer = new Container<>(new Dog());

Container<Cat> catContainer = new Container<>(new Cat());

Container<Animal> animalContainer = new Container<>(new Animal());

Однак, якщо ми спробуємо передати тип, який не є підкласом Animal, це призведе до помилки компіляції:

Container<Book> book Container = new Container<>(new Book()); // буде помилка компіляції

Наслідування з декількома обмеженнями

У Java також можна встановити кілька обмежень на параметр типу. Це робиться за допомогою ключового слова &, що дозволяє обмежувати типи класами та інтерфейсами одночасно. Наприклад, ми можемо обмежити параметр типу таким чином, щоб він був підтипом певного класу і одночасно реалізовував один або кілька інтерфейсів:

interface Flyable {

   void fly();

}
Підпишіться на наш Ютуб-канал! Корисні відео для програмістів чекають на вас! YouTube
Оберіть свій курс програмування! Шлях до кар’єри програміста починається тут! Подивитись

Це простий інтерфейс із методом fly(). Класи, які реалізують цей інтерфейс, повинні надавати свою власну реалізацію цього методу. Інтерфейс позначає, що об’єкт може літати.

class Parrot extends Animal implements Flyable {

   @Override

   public void fly() {

       System.out.println("Parrot is flying");

   }

}

class Lion extends Animal {

}

class Sparrow extends Animal implements Flyable {

   @Override

   public void fly() {

       System.out.println("Sparrow is flying");

   }

}

Всі класи тваринок наслідують клас Animal, проте не всі імплементують Flyable та надають власну реалізацію метода fly. 

class FlyingContainer<T extends Animal & Flyable> {

   private T item;

   public FlyingContainer(T item) {

       this.item = item;

   }

   public void letFly() {

       item.fly();

   }

}

У цьому прикладі клас FlyingContainer обмежує тип T так, що він має бути підтипом Animal і реалізовувати інтерфейс Flyable. Це означає, що можна використовувати тільки ті класи, які наслідуються від Animal і реалізовують інтерфейс Flyable (наприклад, Parrot або Sparrow):

FlyingContainer<Parrot> parrotContainer = new FlyingContainer<>(new Parrot());

parrotContainer.letFly(); // Виведе "Parrot is flying"

FlyingContainer<Sparrow> sparrowContainer = new FlyingContainer<>(new Sparrow());

sparrowContainer.letFly(); // Виведе "Sparrow is flying"

FlyingContainer<Lion> lionContainer = new FlyingContainer<>(new Lion()); // буде помилка  компіляції

Передати в контейнер тип Lion ми вже не можем, адже хоча наш Lion і тварина, проте він не імплементує інтерфейс Flyable. 

Цей приклад показує, як generics можна використовувати для обмеження типів за допомогою T extends Animal & Flyable, забезпечуючи, що тільки ті класи, які є підтипами певного класу (в даному випадку Animal) і одночасно реалізують певний інтерфейс (Flyable), можуть бути передані в generics-клас. Це дозволяє гарантувати типобезпечність і уникати помилок під час компіляції. FoxmindEd пропонує комплексний курс JAVA, де ви можете дізнатися більше про використання generics, а також багато других аспектів програмування на Java.

FAQ
Що таке generics у Java?

Generics дозволяють створювати типобезпечні класи, методи та інтерфейси, уникаючи помилок із приведенням типів.

Чи можна наслідувати generics-клас із вказівкою конкретного типу?

Так, дочірній клас може конкретизувати тип батьківського generics-класу або залишити його параметризованим.

Що відбувається, якщо наслідувати generics-клас без параметризації?

Використання сирих типів викликає попередження компілятора і може призвести до помилок безпеки типів.

Як обмежити тип у generics?

За допомогою ключового слова extends, generics можуть приймати тільки підтипи певного класу або типи, що реалізують інтерфейс.

Чи можна задати декілька обмежень для generics?

Так, використовуючи T extends Клас & Інтерфейс, generics обмежується класом і одним чи кількома інтерфейсами.

Що робити, якщо клас має кілька типів generics?

Можна використовувати різні параметри типів для різних цілей, забезпечуючи гнучкість і типобезпечність.

🤔 Залишилися запитання щодо наслідування в Generics? - Сміливо задавайте нижче! 💬

Додати коментар

Ваш імейл не буде опубліковано. Обов'язкові поля відзначені *

Зберегти моє ім'я, імейл та адресу сайту у цьому браузері для майбутніх коментарів