1. Усі класи успадковано від Object
Усі класи в Java неявно (приховано) успадковано від класу Object
.
Що таке успадкування та як воно працює в Java, ми розберемо у квесті Java Core. А наразі ми розглянемо один простий факт, який із цього випливає:
Змінній типу Object
можна присвоїти об'єкт будь-якого класу. Приклад:
Код | Примітка |
---|---|
|
У змінній o збережено посилання на об'єкт типу Scanner |
|
У змінній o збережено посилання на об'єкт типу String |
|
У змінній o збережено посилання на об'єкт типу Integer |
|
У змінній o збережено посилання на об'єкт типу String |
На цьому добрі новини закінчуються. Компілятор не стежить за тим, якого саме типу об'єкт було збережено в змінній типу Object
, тому викликати методи, що були у збереженого об'єкта, але відсутні у змінної типу Object
, не можна.
Якщо треба викликати методи такого об'єкта, то спочатку посилання на нього потрібно зберегти в змінній правильного типу, а лише потім викликати методи для цієї змінної:
Код | Примітка |
---|---|
|
Програма не скомпілюється. Клас Object не має методу nextInt() . |
|
Так працюватиме. Тут ми зберігаємо посилання на об'єкт типу Scanner у змінній типу Scanner за допомогою оператора перетворення типу. |
Просто так змінну типу Object
не можна присвоїти змінній типу Scanner, навіть якщо змінна типу Object
зберігає посилання на об'єкт типу Scanner
. Натомість це можна зробити, якщо використати вже відомий вам оператор перетворення типу. У загальному випадку це має такий вигляд:
Тип ім'я1 = (Тип) ім'я2;
де ім'я1
— це ім'я змінної типу Тип
, а ім'я2
— це ім'я змінної типу Object
, яка зберігає посилання на об'єкт типу Тип
.
Перетворення типу
Якщо типи змінної та об'єкта не збігаються, виникне помилка ClassCastException
. Приклад:
Код | Примітка |
---|---|
|
Під час виконання виникне помилка: тут буде кинуто виняток ClassCastException |
У Java є спосіб обійти цю помилку: існує можливість перевірити, який тип насправді має змінна:
ім'я instanceof Тип
Оператор instanceof
перевіряє, чи є змінна ім'я
об'єктом типу Тип
.
Приклад — пошук рядка серед елементів масиву даних:
Код | Примітка |
---|---|
|
Autoboxing перетворить ці значення на Integer , String і Double .Цикл по масиву об'єктів Якщо об'єкт має тип String ,зберігаємо його в змінній типу String ,виводимо змінну на екран. |
2. Причина виникнення шаблонів (колекції)
Повертаємося до колекцій.
Коли Java-розробники тільки створювали клас ArrayList
, вони хотіли зробити його універсальним, щоб у ньому можна було зберігати об'єкти будь-якого типу. Тому для зберігання елементів вони скористалися масивом типу Object
.
Сильна сторона такого підходу в тому, що в колекцію можна додати об'єкт будь-якого типу.
Ну а слабких сторін одразу декілька.
Недолік 1.
Завжди доводилося писати оператор перетворення типу, коли діставали елементи з колекції:
Код | Примітка |
---|---|
|
Створюємо об'єкт-колекцію для зберігання посилань на об'єкти типу Object Заповнюємо колекцію числами 10 , 20 , … 100 ;Знаходимо суму елементів колекції Потрібно використовувати перетворення типу |
Недолік 2.
Не було гарантії, що в колекції зберігаються елементи певного типу
Код | Примітка |
---|---|
|
Створюємо об'єкт-колекцію для зберігання посилань на об'єкти типу Object Заповнюємо колекцію числами типу Double :0.0 , 2.5 , 5.0 , ...Знаходимо суму елементів колекції Буде помилка: тип Double не можна перетворити на тип Integer |
Дані в колекцію можуть вноситися де завгодно:
- в іншому методі
- в іншій програмі
- завантажуватися з файлу
- отримуватися з мережі
Недолік 3.
Дані колекції можна випадково змінити через незнання.
Ви можете передати колекцію, заповнену своїми даними, в якийсь метод, а цей метод, написаний зовсім іншим програмістом, додасть до вашої колекції свої дані.
З назви колекції незрозуміло, які саме типи даних можна в ній зберігати. Та навіть якщо й дати змінній зрозумілу назву, посилання на неї можна передати в десяток методів, і там уже точно про початкове ім'я змінної нічого не буде відомо.
3. Узагальнення
Усі ці проблеми усуває така чудова річ у Java, як узагальнення (generics).
Під узагальненнями в Java мають на увазі можливість додавати до типів типи-параметри. Таким чином утворюються складені типи. Такий складений тип у загальному випадку має ось який вигляд:
ОсновнийТип<ТипПараметр>
Усе разом — це саме тип. І він може використовуватися там, де зазвичай можна використовувати типи.
Код | Опис |
---|---|
|
Створення змінних |
|
Створення об'єктів |
|
Створення масивів |
У такій колекції можна зберегти тільки змінні типу Integer
:
Код | Опис |
---|---|
|
Колекція типу ArrayList з елементами типу Integer Так можна І так можна: працюватиме autoboxing
А так не можна: помилка компіляції |
Як створювати свої класи з типами-параметрами, ви дізнаєтеся у квесті Java Collections. А наразі ми розглянемо, як цим користуватися та як воно працює.
4. Як працюють generics
Насправді generics працюють надзвичайно примітивно.
Компілятор просто замінює тип із параметром на нього ж, тільки без параметра. А при взаємодії з його методами додає операцію перетворення типу на тип-параметр:
Код | Що зробить компілятор |
---|---|
|
|
|
|
|
|
|
|
Припустімо, ми мали код методу, що підсумовує числа в колекції цілих чисел:
Код | Що зробить компілятор |
---|---|
|
|
Тобто по суті узагальнення — це так само різновид синтаксичного цукру, як і autoboxing, тільки більший. Під час autoboxing компілятор за нас додає методи для перетворення типу int
на Integer
і навпаки, а для generics додає оператори перетворення типу.
Після того як компілятор скомпілював ваш код з узагальненнями, у ньому всі класи з параметрами буде перетворено на прості класи та оператори перетворення типу. Інформація про те, які типи-параметри спочатку мали змінні складних типів, загубилася. Цей ефект інакше називають стиранням типів.
Іноді програмістам, які пишуть свої класи з типами-параметрами, дуже не вистачає інформації про типи, які туди передаються як параметри. Як із цим борються та що з цього виходить, ви дізнаєтеся у квесті Java Collections.
5. Кілька фактів про узагальнення
Ще кілька цікавих фактів про узагальнення.
У класів може бути не один тип-параметр, а декілька. Отакий вигляд це має:
ОсновнийТип<ТипПараметр1, ТипПараметр2, ТипПараметр3>
Власне кажучи, у цьому немає нічого дивного. Там, де компілятор може додати оператор перетворення на один тип, він може додати й декілька таких.
Приклади:
Код | Примітка |
---|---|
|
Перший параметр методу put має тип Integer , другий — тип String |
Крім того, складні типи теж можна використовувати як параметри. Отакий вигляд це має:
ОсновнийТип<ТипПараметр<ТипПараметрПараметра>>
Припустімо, нам треба створити список, який зберігатиме списки рядків. У цьому разі ми отримаємо приблизно такий код:
// список привітань
ArrayList<String> listHello = new ArrayList<String>();
listHello.add("Привіт");
listHello.add("Hi");
// список прощань
ArrayList<String> listBye = new ArrayList<String>();
listBye.add("Бувай");
listBye.add("Good Bye");
// список списків
ArrayList<ArrayList<String>> lists = new ArrayList<ArrayList<String>>();
lists.add(listHello);
lists.add(listBye);
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ