Я періодично чув, що з дженериками, Java не зрозуміла. (найближча довідка, тут )
Пробачте мою недосвідченість, але що б зробило їх краще?
Я періодично чув, що з дженериками, Java не зрозуміла. (найближча довідка, тут )
Пробачте мою недосвідченість, але що б зробило їх краще?
Відповіді:
Погано:
List<byte>дійсно підтримується, byte[]наприклад, бокс не потрібен)Добре:
Найбільша проблема полягає в тому, що дженерики Java - це лише час компіляції, і ви можете підривати її під час виконання. C # хвалить, тому що він більше перевіряє час виконання. У цій публікації є дійсно хороша дискусія , і вона посилається на інші дискусії.
Classоб'єкти навколо.
Основна проблема полягає в тому, що Java насправді не має дженериків під час виконання. Це час компіляції.
Під час створення загального класу в Java вони використовують метод під назвою "Тип стирання", щоб фактично видалити всі загальні типи з класу і по суті замінити їх на Object. Версія загальної версії для дженерики полягає в тому, що компілятор просто вставляє касти у вказаний загальний тип кожного разу, коли він з’являється в тілі методу.
Це має багато недоліків. Одним з найбільших, IMHO, є те, що ви не можете використовувати рефлексію для огляду родового типу. Типи насправді не є загальними в байтовому коді, і тому їх не можна перевіряти як генеричні.
Чудовий огляд відмінностей тут: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) призводить до дуже дивної поведінки. Найкращий приклад, про який я можу придумати, - це. Припустимо:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
потім оголосити дві змінні:
MyClass<T> m1 = ...
MyClass m2 = ...
Тепер зателефонуйте getOtherStuff():
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
Другий аргумент загального типу позбавлений компілятором, оскільки це необроблений тип (тобто параметризований тип не надається), хоча він не має нічого спільного з параметризованим типом.
Я також згадаю мою улюблену декларацію від JDK:
public class Enum<T extends Enum<T>>
Крім диких карт (це мішана сумка) я просто думаю, що .Net generics є кращими.
public class Redundancy<R extends Redundancy<R>>;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>спочатку може здатися дивним / зайвим, але насправді досить цікавим, принаймні в рамках обмежень Java / це generics. Енуми мають статичний values()метод, який дає масив їх елементів, введених як enum, а не Enum, і цей тип визначається загальним параметром, тобто ви хочете Enum<T>. Звичайно, це типування має сенс тільки в контексті перераховується типу, і всі перерахування є підкласами Enum, що ви хочете Enum<T extends Enum>. Однак Java не любить змішувати необроблені типи з загальними, тобто Enum<T extends Enum<T>>для послідовності.
Я збираюся викинути дійсно суперечливу думку. Дженріки ускладнюють мову та ускладнюють код. Наприклад, скажімо, що у мене є карта, яка відображає рядок до списку рядків. За старих часів я міг це оголосити просто як
Map someMap;
Тепер я маю оголосити це як
Map<String, List<String>> someMap;
І кожен раз, коли я передаю це в якийсь метод, я мушу повторювати цю велику довгу заяву знову і знову. На мою думку, все те, що зайве набирає тексту, відволікає розробника і виводить його із «зони». Крім того, коли код наповнений великою кількістю суворості, іноді важко повернутися до нього пізніше і швидко просіяти всю суть, щоб знайти важливу логіку.
У Java вже є погана репутація, що вона є однією з найбільш багатослівних мов у загальному користуванні, і генеричні файли просто додають до цієї проблеми.
А що ви насправді купуєте за всю цю зайву багатослівність? Скільки разів у вас виникли проблеми, коли хтось помістив Integer у колекцію, яка повинна містити Strings, або де хтось намагався витягнути String із колекції Integers? За мій 10-річний досвід роботи над створенням комерційних програм Java, це ніколи не було великим джерелом помилок. Отже, я не дуже впевнений, що ви отримуєте для додаткової багатослівності. Це дійсно просто вражає мене додатковим бюрократичним багажем.
Тепер я вийду по-справжньому суперечливим. Я вважаю найбільшою проблемою колекцій у Java 1.4 необхідність скрізь друкувати. Я розглядаю ці шрифти як додаткові, багатослівні, які мають багато тих же проблем, що і генеричні. Так, наприклад, я не можу просто зробити
List someList = someMap.get("some key");
я маю зробити
List someList = (List) someMap.get("some key");
Причина, звичайно, в тому, що get () повертає Об'єкт, який є супертипом Список. Таким чином, призначення не може бути виконано без набору тексту. Знову ж таки, подумайте, наскільки насправді купує це правило. З мого досвіду, не багато.
Я думаю, що у Java було б набагато краще, якби 1) вона не додала дженерики, але 2) натомість дозволила мати неявне кастинг із супертипу на підтип. Нехай неправильні касти будуть спіймані під час виконання. Тоді я міг би мати простоту визначення
Map someMap;
а пізніше робити
List someList = someMap.get("some key");
вся сукупність пішла б, і я дійсно не думаю, що я б вводив нове велике джерело помилок у свій код.
Іншим побічним ефектом, який полягає в тому, що вони складають час компіляції, а не час виконання, - це те, що ви не можете викликати конструктор загального типу. Тож ви не можете використовувати їх для впровадження загальної фабрики ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Java-дженерики перевіряються на правильність під час компіляції, а потім видаляється вся інформація про тип (процес називається стиранням типу . Таким чином, загальна інформація List<Integer>буде зведена до її необробленого типу , негенеральногоList , який може містити об'єкти довільного класу.
Це призводить до можливості вставлення до списку довільних об'єктів до списку під час виконання, а також неможливо сказати, які типи використовувались як загальні параметри. Останнє в свою чергу призводить до
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Я б хотів, щоб це була вікі, щоб я міг додати інших людей ... але ...
Проблеми:
<? Розширює MyObject >[], але мені це не дозволено)Ігноруючи стирання стирання всього типу, дженерики, як зазначено, просто не працюють.
Це компілює:
List<Integer> x = Collections.emptyList();
Але це синтаксична помилка:
foo(Collections.emptyList());
Де foo визначається як:
void foo(List<Integer> x) { /* method body not important */ }
Отже, чи перевірка типу виразу залежить від того, призначається вона локальній змінній або фактичному параметру виклику методу. Як божевільний це?
Впровадження генерики в Java було складним завданням, оскільки архітектори намагалися збалансувати функціональність, простоту використання та зворотну сумісність із застарілим кодом. Цілком очікувано, що потрібно було піти на компроміси.
Є й такі, хто також вважає, що реалізація дженериків Java збільшила складність мови до неприйнятного рівня (див. " Генерики вважаються шкідливими "). Часті запитання про дженерики Angelika Langer дає досить гарне уявлення про те, як складні речі можуть стати.
Java не застосовує Generics під час виконання, лише під час компіляції.
Це означає, що ви можете робити цікаві речі, такі як додавання неправильних типів до загальних колекцій.
Java-дженерики є лише компільованим часом і збираються в негенеричний код. У C # фактично складений MSIL є загальним. Це має величезні наслідки для продуктивності, тому що Java все ще працює під час виконання. Дивіться тут докладніше .
Якщо ви слухаєте Java Posse № 279 - Інтерв'ю з Джо Дарсі та Алексом Баклі , вони говорять про це питання. Це також посилання на публікацію в блозі Ніла Гафтера під назвою Reified Generics for Java, яка говорить:
Багато людей незадоволені обмеженнями, спричиненими способом впровадження дженериків на Java. Зокрема, вони незадоволені тим, що параметри загального типу не повторно змінюються: вони недоступні під час виконання. Загальна версія реалізована за допомогою стирання, в якому параметри загального типу просто видаляються під час виконання.
Ця публікація в блозі посилається на старішу статтю " Загадка через стирання: відповідь" , в якій наголошувалося на сумісності міграцій у вимогах.
Метою було забезпечення зворотної сумісності як вихідного, так і об'єктного коду, а також сумісності міграції.