Відповіді:
Generics на Java - це повністю конструкція часу компіляції - компілятор перетворює всі загальні використання в касти до потрібного типу. Це спрямовано на підтримку зворотної сумісності з попередніми періодами виконання JVM.
Це:
List<ClassA> list = new ArrayList<ClassA>();
list.add(new ClassA());
ClassA a = list.get(0);
перетворюється на (приблизно):
List list = new ArrayList();
list.add(new ClassA());
ClassA a = (ClassA)list.get(0);
Отже, все, що використовується як дженерики, повинно бути конвертоване в Object (у цьому прикладі get(0)
повертає an Object
), а примітивні типи - ні. Тому їх не можна використовувати в дженеріках.
У Java дженерики працюють так, як вони роблять ... принаймні частково ... тому, що вони були додані до мови через кілька років після розробки мови 1 . Мовленнєві дизайнери обмежувались у своїх можливостях для дженерики тим, що придумали дизайн, який був би сумісний із існуючою мовою та бібліотекою класів Java .
Інші мови програмування (наприклад, C ++, C #, Ada) дозволяють використовувати примітивні типи як типи параметрів для дженериків. Але зворотний бік цього полягає в тому, що реалізація таких мов генеричних типів (або типів шаблонів), як правило, тягне за собою створення чіткої копії загального типу для кожної параметризації типів.
1 - Причина, що дженерики не були включені в Java 1.0, була через тиск у часі. Вони відчували, що їм доведеться швидко випустити мову Java, щоб заповнити нову ринкову можливість, представлену веб-браузерами. Джеймс Гослінг заявив, що хотів би включити дженерики, якби у них був час. Як би виглядала мова Java, якби це сталося - це хтось здогадається.
У Java Java дженерики реалізовані за допомогою "Тип стирання" для зворотної сумісності. Усі загальні типи перетворюються на Об'єкт під час виконання. наприклад,
public class Container<T> {
private T data;
public T getData() {
return data;
}
}
буде сприйматися під час виконання як:
public class Container {
private Object data;
public Object getData() {
return data;
}
}
компілятор несе відповідальність за забезпечення належного складу для забезпечення безпеки типу.
Container<Integer> val = new Container<Integer>();
Integer data = val.getData()
стане
Container val = new Container();
Integer data = (Integer) val.getData()
Тепер питання полягає в тому, чому "Об'єкт" вибирається як тип під час виконання?
Відповідь: Об'єкт є надкласовим рівнем усіх об'єктів і може представляти будь-який визначений користувачем об’єкт.
Оскільки всі примітиви не успадковують " Об'єкт ", тому ми не можемо використовувати його як загальний тип.
FYI: Проект Valhalla намагається вирішити вищезазначене питання.
Колекції визначаються таким чином, щоб вимагати тип, який походить від java.lang.Object
. Базетипи просто не роблять цього.
Відповідно до документації Java , змінні загального типу можна інстанціювати лише за допомогою посилальних типів, а не примітивних типів.
Це має з'явитися на Java 10 в рамках проекту Valhalla .
У статті Брайана Геца про стан спеціалізації
Існує відмінне пояснення про причину, через яку родові не підтримували примітиву. І як це буде реалізовано в майбутніх випусках Java.
Поточна стерта реалізація Java, яка виробляє один клас для всіх посилань і не підтримує примітивні інстанції. (Це однорідний переклад, і обмеження того, що дженерики Java можуть охоплювати лише типи посилань, походить від обмежень однорідного перекладу стосовно набору байтових кодів JVM, який використовує різні байт-коди для операцій над типовими типами та примітивними типами.) Однак стерті дженерики на Java забезпечують як параметричність поведінки (загальні методи), так і параметричність даних (необроблені і незамінні знаки загальних типів).
...
була обрана гомогенна стратегія перекладу, де змінні загального типу стираються до їх меж, оскільки вони включаються в байт-код. Це означає, що незалежно від того, клас є загальним чи ні, він все ще компілюється до одного класу з тим самим іменем і підписи членів якого однакові. Безпека типу перевіряється під час компіляції, а час виконання не визначається системою загального типу. У свою чергу, це наклало обмеження, що дженерики можуть працювати лише над референтними типами, оскільки Об'єкт є найбільш загальним наявним типом, і він не поширюється на примітивні типи.
Створюючи об'єкт, ви не можете замінити примітивний тип параметром типу. Що стосується цього обмеження, це питання реалізації компілятора. Примітивні типи мають власні інструкції щодо байт-коду для завантаження та зберігання у стек віртуальної машини. Тож неможливо скласти примітивні дженерики в ці окремі шляхи байт-коду, але це ускладнить компілятор.