Які переваги стирання типів Java?


98

Сьогодні я прочитав твіт, в якому говорилося:

Забавно, коли користувачі Java скаржаться на стирання типів, і це єдине, що Java отримала правильно, ігноруючи все те, що вона помилилася.

Отже, моє запитання:

Чи є користь від стирання типу Java? Які переваги технічного або стилю програмування він (можливо) пропонує, крім переваг реалізації JVM, щодо зворотної сумісності та продуктивності виконання?


6
Якщо ви не хочете, щоб ваше запитання було закрите як "засноване на думці", вам слід задати його інакше. (Як "які технічні переваги стирання типу"). А ще краще - запитайте в твітері, що він насправді говорить.
Stephen C

3
Видаліть "чому це добре". "Добре" - це явно суб'єктивна характеристика, і (очевидно) виникає питання, що стирання типу - це добре ... що є спірним.
Stephen C

3
Боже, допоможіть нам, якщо цей сайт перетвориться на запитання про все, що коли-небудь було розміщено у Twitter. Принаймні, цитуйте авторитетне джерело для свого запитання.
Маркіз Лорн

21
Чому питання має бути з "поважного джерела"? Це просто безглуздо.
vertti

4
Ну, більшість проблем тут не з БУДЬ-ЯКОГО джерела, за винятком запитань, які мають власну проблему з конкретним програмним забезпеченням / мовою / чим-небудь, і, на щастя, є багато професіоналів, які "витрачають" свій час на допомогу цим людям.
vertti

Відповіді:


90

Тип стирання - це добре

Дотримуймося фактів

Наразі багато відповідей надмірно стосуються користувача Twitter. Корисно зосереджуватися на повідомленнях, а не на месенджері. Існує досить послідовне повідомлення навіть із лише згаданими на даний момент уривками:

Забавно, коли користувачі Java скаржаться на стирання типів, і це єдине, що Java отримала правильно, ігноруючи все те, що вона помилилася.

Я отримую величезні переваги (наприклад, параметричність) і нульову вартість (передбачувана вартість - це межа уяви).

new T - зламана програма. Це ізоморфно твердженню, що "всі положення є істинними". Мені це не до душі.

Мета: розумні програми

Ці твіти відображають точку зору, яку цікавить не те, чи можемо ми змусити машину щось робити , а більше, чи можемо ми міркувати, що машина зробить те, що ми насправді хочемо. Вагомі міркування є доказом. Докази можуть бути вказані у формальній нотації або щось менш формальне. Незалежно від мови специфікації, вони повинні бути чіткими і суворими. Неформальні специфікації неможливо правильно структурувати, але часто мають недоліки в практичному програмуванні. У підсумку ми отримуємо такі виправлення, як автоматизовані та дослідницькі тести, щоб компенсувати проблеми, які ми маємо, за допомогою неформальних міркувань. Це не означає, що тестування є внутрішньо поганою ідеєю, але цитований користувач Twitter припускає, що існує набагато кращий спосіб.

Отже, наша мета - мати правильні програми, про які ми можемо чітко і суворо міркувати таким чином, що відповідає тому, як машина насправді виконуватиме програму. Однак це не єдина мета. Ми також хочемо, щоб наша логіка мала ступінь виразності. Наприклад, існує лише стільки, що ми можемо висловити за допомогою логічної пропозиції. Приємно мати універсальну (∀) та екзистенційну (∃) кількісну оцінку з чогось типу логіки першого порядку.

Використання систем типу для міркувань

Ці цілі можуть бути дуже добре вирішені системами типів. Це особливо ясно через листування Каррі-Говарда . Ця відповідність часто виражається з такою аналогією: типи стосуються програм, як теореми - доказів.

Це листування є дещо глибоким. Ми можемо приймати логічні вирази та перекладати їх за допомогою відповідності типам. Тоді, якщо у нас є програма з компіляцією того самого типу, ми довели, що логічний вираз є універсальним істинним (тавтологія). Це пов’язано з тим, що листування є двостороннім. Перетворення між світом типу / програми та теореми / доказу є механічним, і в багатьох випадках може бути автоматизованим.

Каррі-Говард чудово розігрує те, що ми хотіли б зробити зі специфікаціями програми.

Чи корисні системи типів у Java?

Навіть маючи розуміння Каррі-Говарда, деяким людям легко заперечити значення системи типу, коли вона є

  1. надзвичайно важко працювати
  2. відповідає (через Каррі-Говарда) логіці з обмеженою виразністю
  3. порушено (що потрапляє до характеристики систем як "слабких" або "сильних").

Що стосується першого пункту, можливо, IDE роблять систему типів Java досить простою для роботи (це дуже суб'єктивно).

Що стосується другого пункту, Java, як правило, майже відповідає логіці першого порядку. Дженерики використовують систему типів, еквівалентну універсальній кількісній оцінці. На жаль, узагальнюючі символи дають нам лише незначну частину екзистенціальної кількісної оцінки. Але універсальна кількісна оцінка - досить хороший початок. Приємно мати можливість сказати, що функції для List<A>роботи працюють універсально для всіх можливих списків, оскільки A абсолютно не обмежена. Це призводить до того, про що говорить користувач Twitter щодо "параметричності".

Часто цитувана стаття про параметричність - це теореми Філіпа Уодлера безкоштовно! . Що цікаво у цій роботі, так це те, що лише лише підпис типу ми можемо довести кілька дуже цікавих інваріантів. Якби ми писали автоматизовані тести для цих інваріантів, ми б дуже витрачали свій час. Наприклад, для List<A>, від підпису типу лише дляflatten

<A> List<A> flatten(List<List<A>> nestedLists);

ми можемо це міркувати

flatten(nestedList.map(l -> l.map(any_function)))
     flatten(nestList).map(any_function)

Це простий приклад, і ви, напевно, можете міркувати про це неофіційно, але ще приємніше, коли ми отримуємо такі докази офіційно безкоштовно із системи типів і перевіряємо компілятором.

Не стирання може призвести до зловживань

З точки зору реалізації мови, загальні засоби Java (які відповідають універсальним типам) дуже сильно впливають на параметричність, яка використовується для отримання доказів того, що роблять наші програми. Це стосується третьої згаданої проблеми. Усі ці здобутки доказів і правильності вимагають звукової системи, реалізованої без дефектів. Java безумовно має деякі мовні особливості, які дозволяють нам зруйнувати наші міркування. До них належать, але не обмежуються:

  • побічні ефекти із зовнішньою системою
  • рефлексія

Нестирані дженерики багато в чому пов’язані з рефлексією. Без стирання існує інформація про час виконання, яка передається разом із реалізацією, яку ми можемо використовувати для розробки наших алгоритмів. Це означає, що статично, коли ми роздумуємо про програми, ми не маємо повної картини. Роздуми серйозно загрожують правильності будь-яких доказів, про які ми аргументуємо статично. Не випадково відображення також призводить до різноманітних хитрих дефектів.

Отже, якими способами незмиті дженерики можуть бути "корисними"? Давайте розглянемо використання, згадане в твіті:

<T> T broken { return new T(); }

Що станеться, якщо у T немає конструктора no-arg? У деяких мовах те, що ви отримуєте, є нульовим. Або, можливо, ви пропускаєте нульове значення і переходите безпосередньо до створення винятку (до якого нульові значення все одно призводять). Оскільки наша мова Тьюрінга повна, неможливо міркувати про те, які виклики brokenвключатимуть "безпечні" типи з конструкторами no-arg, а які ні. Ми втратили впевненість у тому, що наша програма працює універсально.

Стирання означає, що ми обгрунтували (тож давайте стермо)

Отже, якщо ми хочемо міркувати щодо наших програм, нам настійно рекомендується не використовувати мовні функції, які сильно загрожують нашим міркуванням. Як тільки ми це робимо, то чому б просто не скинути типи під час виконання? Вони не потрібні. Ми можемо досягти певної ефективності та простоти із задоволенням, що жоден привід не провалиться або що методи можуть бути відсутніми при виклику.

Стирання спонукає до міркувань.


7
У той час, коли я взяв цю відповідь, деякі інші люди відповіли подібними відповідями. Але написавши стільки, я вирішив розмістити його, а не відкинути.
Sukant Hajra

32
З повагою, стирання типу було непомітною спробою перенести якусь функцію на Java, не порушуючи зворотної сумісності. Це не сила. Якщо основним занепокоєнням було те, що ви можете спробувати створити екземпляр об’єкта без конструктора без параметрів, правильна відповідь полягає в тому, щоб дозволити обмеження «типів з конструкторами без параметрів», а не скалічити мовну функцію.
Basic

5
Основне, з повагою, ви не робите згуртованих аргументів. Ви просто стверджуєте, що стирання "калічить", повністю нехтуючи значенням доказів часу компіляції як вагомого інструменту для міркувань про програми. Крім того, ці переваги повністю ортогональні кривому шляху та мотивації, яку використовувала Java для впровадження дженериків. Крім того, ці переваги належать до мов із набагато більш освітленою реалізацією стирання.
Сукант Хайра

44
Аргумент, представлений тут, не проти стирання, він проти рефлексії (і перевірки типу виконання в цілому) - він в основному стверджує, що відображення є поганим, тому той факт, що стирання погано з ними грає, це добре. Це обґрунтоване питання самостійно (хоча багато людей не погоджуються); але це не має великого сенсу в контексті, оскільки Java вже має перевірки відображення / виконання, і вони не зникли і не зникнуть. Тож мати конструкцію, яка не відповідає цій існуючій, застарілій частині мови, є обмеженням, як би ти на це не дивився.
Павло Мінаєв

10
Ваша відповідь просто не в темі. Ви вводите довготермінове (хоч і цікаве з власних термінів) пояснення того, чому стирання типу як абстрактне поняття, як правило, корисно при проектуванні систем типів, але ця тема полягає в перевазі конкретної характеристики Java Generics з написом "стирання типу" ", який лише зовні нагадує концепцію, яку ви описуєте, не вдавшись повністю у наданні цікавих інваріантів і вводячи необгрунтовані обмеження.
Марко Топольник

40

Типи - це конструкція, яка використовується для написання програм таким чином, що дозволяє компілятору перевірити правильність програми. Тип - це пропозиція щодо значення - компілятор перевіряє, чи ця пропозиція відповідає дійсності.

Під час виконання програми не повинно бути потреби в інформації про тип - це вже перевірено компілятором. Компілятор повинен мати можливість вільно відкидати цю інформацію, щоб виконати оптимізацію коду - пришвидшити його роботу, створити менший двійковий файл тощо. Видалення параметрів типу полегшує це.

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

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

Стирання важливо для підтримки властивості "параметричності" типу даних. Скажімо, у мене є тип "List", параметризований над компонентом типу T. тобто List <T>. Цей тип є припущенням, що цей тип List працює однаково для будь-якого типу T. Той факт, що T є абстрактним, необмеженим параметром типу, означає, що ми нічого не знаємо про цей тип, тому нам заборонено робити щось особливе для особливих випадків T.

наприклад, скажімо, у мене є список xs = asList ("3"). Додаю елемент: xs.add ("q"). Я закінчую з ["3", "q"]. Оскільки це параметрично, я можу припустити, що List xs = asList (7); xs.add (8) закінчується [7,8] Я знаю з типу, що він не робить одне для String, а одне для Int.

Крім того, я знаю, що функція List.add не може вигадувати значення T на пустому місці. Я знаю, що якщо до мого asList ("3") додано "7", єдино можливі відповіді будуть побудовані зі значень "3" та "7". Немає можливості додати до списку "2" або "z", оскільки функція не зможе його побудувати. Жодне з цих інших значень не було б розумно додавати, а параметричність перешкоджає побудові цих неправильних програм.

В основному стирання запобігає деяким засобам порушення параметричності, тим самим виключаючи можливості некоректних програм, що є метою статичного набору тексту.


15

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


Суперечливо, чи заслуговує процес, виконаний на Java Generics, на назву "стирання типу". Оскільки загальні типи не стираються, а замінюються на аналогічні аналоги, кращим вибором видається "каліцтво типу".

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

Видалення типу Java не досягає цього - воно калічить компілятор, як у цьому прикладі:

void doStuff(List<Integer> collection) { 
}

void doStuff(List<String> collection) // ERROR: a method cannot have 
                   // overloads which only differ in type parameters

(Наведені вище два оголошення згортаються в один і той же підпис методу після стирання.)

З іншого боку, середовище виконання все ще може перевірити тип об’єкта та причини для нього, але оскільки його розуміння справжнього типу скалічене стиранням, порушення статичного типу тривіальні для досягнення та важкі для запобігання.

Щоб зробити речі ще більш заплутаними, оригінальний та стираний підписи типу існують разом і розглядаються паралельно під час компіляції. Це пов’язано з тим, що весь процес полягає не в видаленні інформації про тип із середовища виконання, а у взутті загальної системи типів у застарілу необроблену систему типу для підтримки зворотної сумісності. Цей самоцвіт є класичним прикладом:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

(Надлишок extends Objectпотрібно було додати, щоб зберегти зворотну сумісність стираного підпису.)

Тепер, маючи це на увазі, давайте переглянемо цитату:

Смішно, коли користувачі Java скаржаться на стирання типів, і це єдине, що Java отримала правильно

Що саме Java отримала правильно? Це саме слово, незалежно від значення? Для порівняння погляньте на скромний intтип: жодна перевірка типу виконання ніколи не виконується, а то й можливо, а виконання завжди ідеально безпечне для типу. Ось так виглядає стирання типу, коли все зроблено правильно: ви навіть не знаєте, що воно там.


10

Єдине, що я взагалі не бачу тут розглянутим, - це те, що поліморфізм часу виконання ООП принципово залежить від реіфікації типів під час виконання. Коли мова, основу якої утримують визначені типи, вводить значне розширення до своєї системи типів і засновує її на стиранні типів, когнітивний дисонанс є неминучим результатом. Саме це сталося з спільнотою Java; саме тому стирання типів викликало стільки суперечок і, зрештою, чому в планах скасувати його в майбутньому випуску Java . Знайти щось смішне в цій скарзі користувачів Java видає або чесне нерозуміння духу Java, або свідомо зневажливий жарт.

Твердження "стирання - це єдине, що Java отримала правильно" передбачає твердження, що "всі мови, засновані на динамічному відправленні проти аргументу функції типу виконання, є принципово недосконалими". Хоча, безумовно, є законним позовом сам по собі, і таким, який можна навіть розглядати як вагому критику всіх мов ООП, включаючи Java, він не може вважати себе ключовим моментом, з якого слід оцінювати та критикувати особливості в контексті Java , де поліморфізм виконання є аксіоматичним.

Підводячи підсумок, хоча можна обгрунтовано стверджувати, що "стирання типу - це шлях у дизайні мови", позиції, що підтримують стирання типів у Java, втрачаються просто тому, що це занадто пізно, і це вже було навіть в історичний момент коли Дуб був обійнятий Сонцем і перейменований на Яву.



Щодо того, чи саме статичне введення тексту є правильним напрямком у розробці мов програмування, це вписується в набагато ширший філософський контекст того, що, на наш погляд, становить діяльність з програмування . Одна школа думок, чітко випливаючи з класичної традиції математики, розглядає програми як екземпляри тієї чи іншої математичної концепції (пропозиції, функції тощо), але існує зовсім інший клас підходів, які розглядають програмування як спосіб поговорити з машиною і пояснити, що ми хочемо від неї. З цієї точки зору програма є динамічною, органічно зростаючою суттю, суттєвою протилежністю ретельно побудованому апарату статично набраної програми.

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


Дякую за це. Я очікував побачити саме це питання більш глибокого розуміння філософії.
Даніель Ленґдон,

Динамічне відправлення не має ніякої залежності від реіфікованих типів. Він часто покладається на те, що література з програмування називає "тегами", але навіть тоді йому не потрібно використовувати теги. Якщо ви робите інтерфейс (для об'єкта) і робите анонімні екземпляри цього інтерфейсу, тоді ви робите динамічну диспетчеризацію без необхідності в реіфікованих типах.
Сукант Хайра,

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

Існує дуже статичний тип, розроблений саме для цього міркування, який називається "тип сум". Його також називають "варіантним типом" (у дуже хорошій книзі Бенджаміна Пірса про типи та мови програмування). Отже, це зовсім не подрібнення волосся. Крім того, ви можете використовувати катаморфізм / фолд / відвідувача для абсолютно повністю безмовного підходу.
Sukant Hajra

Дякую за урок теорії, але я не бачу доречності. Наша тема - система типів Java.
Марко Топольник

9

Подальший допис того самого користувача в тій же розмові:

new T - зламана програма. Це ізоморфно твердженню, що "всі положення є істинними". Мені це не до душі.

(Це було у відповідь на заяву іншого користувача, а саме, що "здається, в деяких ситуаціях" нове Т "було б краще", ідея полягає в тому, що new T()неможливо через стирання типу. (Це спірно - навіть якщо воно Tбуло доступне на виконання, це може бути абстрактний клас або інтерфейс, або це може бути Void, або йому може бути відсутнім конструктор no-arg, або його конструктор no-arg може бути приватним (наприклад, тому що це повинен бути однокласний клас), або його конструктор no-arg міг би вказати перевірений виняток, який загальний метод не вловлює і не вказує - але це було передумовою. Незважаючи на це, це правда, що без стирання ви могли б принаймні писати T.class.newInstance(), що вирішує ці проблеми.))

Цей погляд, що типи є ізоморфними для пропозицій, свідчить про те, що користувач має досвід у формальній теорії типів. (S) йому, швидше за все, не подобаються "динамічні типи" або "типи виконання", і він віддав би перевагу Java без збитків instanceofта роздумів тощо. (Подумайте про таку мову, як Standard ML, яка має дуже багату (статичну) систему типів і динамічна семантика якої не залежить від інформації про будь-який тип.)

До речі, варто пам’ятати, що користувач здійснює тролінг: хоча він, мабуть, щиро віддає перевагу (статично) типізованим мовам, але не щиро намагається переконати інших у цій думці. Швидше за все, основною метою оригінального твіту було знущатися з тих, хто не погоджується, і після того, як деякі з тих, хто не погоджується, прислухалися, користувач розмістив наступні твіти, наприклад "Причина, по якій у Java стирається тип, полягає в тому, що Вадлер та інші знають, що вони роблять, на відміну від користувачів Java ". На жаль, це ускладнює з’ясування того, про що він насправді думає; але на щастя, це також, ймовірно, означає, що робити це не дуже важливо. Люди з реальною глибиною своїх поглядів, як правило, не вдаються до тролів, які цілком не містять вмісту.


2
Він не компілюється на Java, оскільки має стирання типу
Mark Rotteveel

1
@MarkRotteveel: Дякую; Я додав абзац, щоб пояснити (і прокоментувати) це.
ruahh

1
Судячи з поєднання «проти» та «проти», це найбільш суперечлива відповідь, яку я коли-небудь публікував. Я дивно пишаюся.
ruahh

6

Одна хороша річ полягає в тому, що не було необхідності змінювати JVM, коли були введені дженерики. Java реалізує загальні засоби лише на рівні компілятора.


1
Зміни JVM порівняно з чим? Шаблони також не потребували б змін у JVM.
Маркіз Лорнський

5

Причиною стирання типу є хороша річ у тому, що те, що робить неможливим, є шкідливим. Запобігання інспектуванню аргументів типу під час виконання полегшує розуміння та міркування щодо програм.

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

public List<Integer> XXX(final List<Integer> l);

Які можливі реалізації цієї функції? Дуже багато. Ви можете дуже мало розповісти про те, що ця функція виконує. Це може бути зворотний список вводу. Це може бути спарювання між собою, підсумовування та повернення списку вдвічі менший. Є багато інших можливостей, які можна собі уявити. Тепер розглянемо:

public <T> List<T> XXX(final List<T> l);

Скільки реалізацій цієї функції? Оскільки реалізація не може знати тип елементів, величезна кількість реалізацій тепер може бути виключена: елементи не можна комбінувати, додавати до списку чи фільтрувати та ін. Ми обмежуємося такими речами, як: ідентичність (без змін у списку), випадання елементів або зміна списку. Про цю функцію легше міркувати, спираючись лише на її підпис.

Окрім ... у Java ви завжди можете обдурити систему шрифтів. Оскільки реалізація цього загального методу може використовувати такі речі, як instanceofперевірки та / або приведення до довільних типів, наші міркування на основі підпису типу можуть бути легко зроблені марними. Функція могла перевіряти тип елементів і виконувати будь-яку кількість речей на основі результату. Якщо ці хакі часу виконання дозволено, параметризовані підписи методів стають для нас набагато менш корисними.

Якби у Java не було стирання типу (тобто аргументи типу були переосмислені під час виконання), то це просто дозволило б збільшити кількість міркувань, що погіршують міркування цього типу. У наведеному вище прикладі реалізація може порушити очікування, встановлені підписом типу, якщо список містить хоча б один елемент; але якщо він Tбув повторно визначений, він міг би це зробити, навіть якщо список був порожнім. Переоцінені типи просто збільшать (і без того дуже багато) можливостей перешкоджати нашому розумінню коду.

Видалення типу робить мову менш «потужною». Але деякі форми "влади" насправді шкідливі.


1
Здається, ви або сперечаєтесь, що дженерики занадто складні, щоб розробники Java могли їх «розуміти і міркувати», або що їм не можна довіряти, щоб вони не стріляли собі в ногу, якщо їм надається така можливість. Останнє, мабуть, синхронізується з етосом Java (але без непідписаних типів тощо), але видається трохи поблажливим до розробників
Basic

1
@Basic Я, мабуть, висловився дуже погано, бо це зовсім не те, що я мав на увазі. Проблема не в тому, що дженерики є складними, а в тому, що такі речі, як кастинг, instanceofзаважають нашій здатності міркувати про те, що робить код на основі типів. Якби Java реактивувала аргументи типу, це просто погіршило б цю проблему. Видалення типів під час виконання робить систему типів більш корисною.
Лахлан

@lachlan це мало для мене цілковитий сенс, і я не думаю, що звичайне його читання спричинить будь-яку образу для розробників Java.
Роб Грант

3

Це не пряма відповідь (ОП запитав "які переваги", я відповідаю "які мінуси")

У порівнянні з системою типу C # стирання типу Java є справжньою проблемою для двох сезонів

Ви не можете реалізувати інтерфейс двічі

У C # ви можете реалізувати обидва IEnumerable<T1>і IEnumerable<T2>безпечно, особливо якщо два типи не мають спільного предка (тобто їх предок є Object ).

Практичний приклад: у Spring Framework ви не можете реалізувати ApplicationListener<? extends ApplicationEvent>кілька разів. Якщо вам потрібні різні форми поведінки, виходячи з цього, Tвам потрібно пройти тестуванняinstanceof

Ви не можете зробити новий T ()

(а для цього вам потрібно посилання на клас)

Як коментували інші, зробити еквівалент new T()можна лише за допомогою відображення, лише викликаючи екземпляр Class<T>, переконавшись у параметрах, необхідних конструктору. C # дозволяє це робити, new T() лише якщо ви обмежуєтесь Tбезпараметричним конструктором. Якщо Tце обмеження не поважається, виникає помилка компіляції .

На Java вас часто змусять писати методи, схожі на наступні

public <T> T create(....params, Class<T> classOfT)
{

    ... whatever you do
    ... you will end up
    T = classOfT.newInstance();


    ... or more advanced reflection
    Constructor<T> parameterizedConstructorThatYouKnowAbout = classOfT.getConstructor(...,...);
}

Недоліками наведеного вище коду є:

  • Class.newInstance працює лише з безпараметричним конструктором. Якщо такої немає, ReflectiveOperationExceptionкидається під час виконання
  • Відображений конструктор не виділяє проблем під час компіляції, як зазначено вище. Якщо ви рефакторируете, ви обміняєтеся аргументами, ви будете знати лише під час виконання

Якби я був автором C #, я б ввів можливість вказувати одне або кілька обмежень конструктора, які легко перевірити під час компіляції (тому я можу вимагати, наприклад, конструктор з string,stringпараметрами). Але останнє - це спекуляції


2

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

public class GenericClass<T>
{
     private Class<T> targetClass;
     public GenericClass(Class<T> targetClass)
     {
          this.targetClass = targetClass;
     }

Потім цей клас може робити все, що було б досяжним за замовчуванням, якби Java не використовувала стирання: він може виділяти нові Ts (припускаючи, що Tмає конструктор, що відповідає шаблону, який він очікує використовувати), або масиви Ts, він може динамічно перевіряти під час роботи, чи є конкретний об’єкт, Tі змінювати поведінку залежно від цього тощо.

Наприклад:

     public T newT () { 
         try {
             return targetClass.newInstance(); 
         } catch(/* I forget which exceptions can be thrown here */) { ... }
     }

     private T value;
     /** @throws ClassCastException if object is not a T */
     public void setValueFromObject (Object object) {
         value = targetClass.cast(object);
     }
}

Хтось хоче пояснити причину проти? Це, наскільки я бачу, цілком гарне пояснення того, чому передбачуваний недолік системи стирання типу Java насправді взагалі не є реальним обмеженням. То в чому проблема?
Жуль

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

3
Java - це мова соляних шахт, що робить це гострою необхідністю, враховуючи відсутність інформації про тип виконання. Сподіваємось, стирання типу буде скасовано в майбутній версії Java, що зробить генерики набагато кориснішою функцією. На даний момент консенсус полягає в тому, що їх співвідношення тяги / ваги нижче 1.
Марко Топольник

Ну, звичайно ... Поки ваш TargetType не використовує загальні засоби. Але це здається досить обмежуючим.
Basic

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

0

уникає с ++-подібного здуття коду, оскільки один і той же код використовується для декількох типів; однак для стирання типу потрібна віртуальна відправка, тоді як підхід c ++ - code-bloat може робити не віртуально відправлені загальні засоби


3
Однак більшість з цих віртуальних депешів перетворюються на статичні під час виконання, тому це не так вже й погано, як може здатися.
Piotr Kołaczkowski

1
дуже правда, наявність компілятора під час виконання дозволяє Java робити багато цікавих речей (і досягати високої продуктивності) щодо c ++.
некромант

вау, дозвольте мені записати це десь Java досягає високої продуктивності щодо cpp ..
jungle_mole

1
@jungle_mole так, дякую. мало хто усвідомлює переваги наявності компілятора під час виконання, щоб знову скомпілювати код після профілювання. (або що збір сміття є великим (не сміттям), а не наївним і неправильним переконанням, що збір сміття є великим о (сміттям), (або, що хоча збір сміття є невеликим зусиллям, це компенсується, дозволяючи нульову вартість виділення)). Це сумний світ, коли люди наосліп приймають те, у що вірить їх громада, і перестають міркувати з перших принципів.
necromancer

0

Більшість відповідей більше стурбовані філософією програмування, ніж власне технічними деталями.

І хоча цьому питанню вже більше 5 років, питання все ще залишається: Чому стирання типу бажано з технічної точки зору? Зрештою, відповідь досить проста (на вищому рівні): https://en.wikipedia.org/wiki/Type_erasure

Шаблони C ++ не існують під час виконання. Компілятор видає повністю оптимізовану версію для кожного виклику, тобто виконання не залежить від інформації про тип. Але як JIT має справу з різними версіями однієї і тієї ж функції? Чи не краще було б просто мати одну функцію? Не хотів би, щоб JIT повинен був оптимізувати всі різні його версії. Ну, а як же тоді безпека типу? Вгадайте, що це має вийти з вікна.

Але почекайте секунду: як це робить .NET? Відображення! Таким чином, їм потрібно лише оптимізувати одну функцію, а також отримати інформацію про тип виконання. І саме тому .NET-узагальнення раніше були повільнішими (хоча вони стали набагато кращими). Я не сперечаюся, що це не зручно! Але це дорого і його не слід використовувати, коли це не є абсолютно необхідним (воно не вважається дорогим у мовах, що динамічно набираються, оскільки компілятор / інтерпретатор в будь-якому випадку покладається на відображення).

Таким чином, загальне програмування зі стиранням типів наближається до нульових накладних витрат (деякі перевірки / трансляції виконання ще потрібні): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.