Починаючи з самого появи мова Java зазнав масу змін, які, безсумнівно, привнесли позитивні моменти в його функціональність. Одним з таких важливих змін є введення Java Generic або узагальнення. Дана функціональність зробила мова не тільки гнучкіше і універсальніше, але і набагато безпечніше в плані приведення типів даних.
Справа в тому, що до введення дженериків узагальнений код в Java можна було створювати, тільки оперуючи посиланнями типу Object. Такі посилання можна присвоювати будь-якого об'єкта. Адже всі класи в Java є неявними спадкоємцями класу Object. Однак такий підхід є потенційним джерелом багатьох помилок, пов'язаних з безпекою типів при явному перетворення об'єкта Object до цільового типу. При використанні узагальнень всі приведення виконуються неявно і автоматично, що виключає навіть потенційну можливість виникнення помилок.
Java Узагальнення: опис і приклад
Розберемо простий приклад застосування узагальнення до звичайного класу на малюнку нижче. І вже потім приступимо до детального розгляду всіх тонкощів і нюансів Java Generic.
Зверніть увагу на те, яким чином відбувається оголошення класу Pair. Відразу після імені класу відкриваються кутові дужки, в яких вказується літера T. Вона являє собою своєрідний заповнювач, який в процесі створення екземпляра даного класу буде замінений конкретним типом. Виглядає це наступним чином: Pair obj = new Pair (). Слід зазначити, що замість T можна вказувати будь-яку букву, але, як правило, використовують T, V або E.
Примітка: починаючи з восьмої версії Java, вказавши цільової тип при оголошенні посилання, кутові дужки в конструкторі можна залишити порожніми. Так наведений вище приклад можна переписати наступним чином: Pair obj = new Pair <> (). Коли клас оголошений таким чином, далі в його тілі замість конкретних типів полів, посилань і повертаються методами об'єктів можна використовувати цю букву. Оскільки T при створенні об'єкта класу замінюється конкретним типом, поля first і second в даному випадку будуть мати тип Integer. Слідуючи логіці, аргументи firstItem і secondItem, передаються відповідним конструктору, також повинні мати тип Integer або його підкласи. Якщо ви спробуєте надіслати тип даних, відрізняється від того, що був вказаний при створенні об'єкта, компілятор не пропустить цю помилку. Так, конструктор з аргументами при створенні об'єкта буде мати наступний вигляд: Pair obj = new Pair <> (new Integer(1), new Integer(2)). Те ж саме відноситься до аргументів методів setFirst і setSecond. І як ви вже, напевно, здогадалися, методи getFirst і getSecond будуть повертати значення типу Integer.
Узагальнений клас з кількома параметрами типів
В узагальнених класах також можна оголошувати декілька параметрів типу, які задаються в кутових дужках через кому. Перероблений під такий випадок клас Pair представлений на малюнку нижче.
Як бачимо, при створенні екземпляра такого класу в кутових дужках слід вказувати кількість типів, що і параметрів. Якщо ви знайомі з таким видом структури даних, як Map, то ви могли помітити, що там використовується точно такий же принцип. Там перший аргумент визначає тип ключа, а другий – тип значення. Слід зазначити, що типи передаються при створенні об'єкта аргументів можуть збігатися. Так, наступне оголошення екземпляра класу Pair є абсолютно коректним: Pair obj.
Деякі особливості узагальнень
Перед тим як йти далі, слід зазначити, що компілятор Java не створює жодних різних версій класу Pair. Насправді в процесі компіляції вся інформація про узагальнений тип видаляється. Замість цього виконується приведення відповідних типів, створюючи спеціальну версію класу Pair. Однак у самій програмі, як і раніше, існує єдина узагальнена версія даного класу. Цей процес називається в Java Generic очищення типу. Відзначимо важливий момент. Посилання на різні версії одного і того ж java generic класу не можуть вказувати на один і той же об'єкт. Тобто, припустимо, у нас є два посилання: Pair obj1 і Pair obj2. Отже, у рядку obj1 = obj2 виникне помилка. Хоча обидві змінні відносяться до типу Pair об'єкти , на які вони посилаються, різні. Це яскравий приклад забезпечення безпеки типів Java Generic.
Обмеження, що накладаються на узагальнені класи
Важливо знати, що узагальнення можуть застосовуватися тільки до посилальних типів, тобто передається параметру generic class java аргумент обов'язково повинен бути типом класу. Такі прості типи, як, наприклад, або long double, передавати не можна. Іншими словами, наступна рядок оголошення класу Pair неприпустима: Pair obj. Тим не менше дане обмеження не становить серйозної проблеми, так як в Java для кожного примітивного типу є відповідний клас-оболонка. Строго кажучи, якщо в класі Pair ви хочете инкапсулировать ціле і логічне значення, автоупаковка зробить все за вас: Pair obj = new Pair <> (25 true).
Ще одним серйозним обмеженням є неможливість створення екземпляра параметра типу. Так, наступна рядок викличе помилку компіляції: T first = new T(). Це очевидно, оскільки ви наперед не знаєте, чи як аргумент передаватися повноцінний клас або абстрактний, або зовсім інтерфейс. Те ж саме стосується створення масивів.
Обмежені типи
Досить часто виникають ситуації, коли необхідно обмежити перелік типів, які можна передавати в якості аргументу java generic класу. Припустимо, що в нашому класі Pair ми хочемо инкапсулировать виключно числові значення для подальших математичних операцій над ними. Для цього нам необхідно вказати верхню межу параметра типу. Реалізується це за допомогою оголошення суперкласу, що успадковується всіма аргументами, що передаються в кутових дужках. Це буде виглядати наступним чином: class Pair . Таким способом компілятор дізнається, що замість параметра T можна підставляти або клас Number або один з його підкласів. Це поширений прийом. Такі обмеження часто використовуються для забезпечення сумісності параметрів типу в одному і тому ж класі. Розглянемо приклад на нашому класі Pair: class Pair . Тут ми повідомляємо компілятору, що тип Т може бути довільним, а тип V обов'язково повинен бути типом Т, або одним з його підкласів. Обмеження «знизу» відбувається точно таким же чином, але замість слова extends пишеться слово super. Тобто оголошення class Pair говорить про те, що замість Т може бути підставлений або ArrayList, або будь-який клас або користувача, які він наслідує.
Generic методи Java і конструктори
В Java узагальнення можна застосовувати не тільки щодо класів, але і методів. Так, узагальнений метод може бути оголошений у звичайному класі.
Як видно на малюнку вище, в оголошенні узагальненого методу немає нічого складного. Досить перед її обчислене методом типом поставити кутові дужки і вказати в них параметри типів. У разі конструктора все робиться аналогічно:
Кутові дужки в цьому випадку ставляться перед назвою конструктора, так як він не повертає ніякого значення. Результатом роботи обох програм буде: Integer String