Сегодня я прочитал твит, в котором говорилось:
Забавно, когда пользователи Java жалуются на стирание типа, единственное, что в Java сделала правильно, игнорируя все ошибки.
Таким образом, мой вопрос:
Есть ли преимущества от стирания типов в Java? Какие технические преимущества или преимущества стиля программирования он (возможно) предлагает помимо предпочтения реализаций JVM для обратной совместимости и производительности во время выполнения?
java
type-erasure
вертти
источник
источник
Ответы:
Тип Стирание - это хорошо
Будем придерживаться фактов
Многие ответы на данный момент чрезмерно связаны с пользователем Twitter. Полезно сосредоточиться на сообщениях, а не на мессенджере. Существует довольно последовательное сообщение даже с уже упомянутыми отрывками:
Цель: разумные программы
Эти твиты отражают точку зрения, которая не интересует, можем ли мы заставить машину что- то делать , а больше, можем ли мы рассуждать, что машина будет делать то, что мы действительно хотим. Хорошая аргументация - доказательство. Доказательства могут быть указаны в формальных обозначениях или в чем-то менее формальном. Независимо от языка спецификации, они должны быть четкими и строгими. Неформальные спецификации не невозможно правильно структурировать, но они часто бывают ошибочными в практическом программировании. Мы заканчиваем исправлениями, такими как автоматические и исследовательские тесты, чтобы исправить проблемы, которые у нас возникают с неформальными рассуждениями. Это не означает, что тестирование по своей сути является плохой идеей, но процитированный пользователь Twitter предполагает, что есть гораздо лучший способ.
Таким образом, наша цель - иметь правильные программы, о которых мы можем ясно и строго рассуждать таким образом, чтобы это соответствовало тому, как машина на самом деле будет выполнять программу. Однако это не единственная цель. Мы также хотим, чтобы наша логика имела определенную степень выразительности. Например, с помощью логики высказываний мы можем выразить очень многое. Приятно иметь универсальную (∀) и экзистенциальную () количественную оценку на основе чего-то вроде логики первого порядка.
Использование систем типов для рассуждений
Эти цели могут быть прекрасно решены с помощью систем типов. Это особенно ясно из переписки Карри-Ховарда . Это соответствие часто выражается следующей аналогией: типы относятся к программам, как теоремы относятся к доказательствам.
Это соответствие довольно глубокое. Мы можем брать логические выражения и переводить их через соответствие типам. Затем, если у нас есть программа с той же сигнатурой типа, которая компилируется, мы доказали, что логическое выражение универсально истинно (тавтология). Это потому, что соответствие двустороннее. Преобразование между мирами типов / программ и теорем / доказательств является механическим и во многих случаях может быть автоматизировано.
Карри-Ховард прекрасно подходит для того, что мы хотели бы делать со спецификациями для программы.
Полезны ли системы типов в Java?
Даже с пониманием Карри-Ховарда некоторым людям легко не принимать во внимание ценность системы типов, когда она
Что касается первого пункта, возможно, IDE делают систему типов Java достаточно простой для работы (это очень субъективно).
Что касается второго пункта, Java почти соответствует логике первого порядка. Обобщения дают возможность использовать систему типов, эквивалентную универсальной количественной оценке. К сожалению, подстановочные знаки дают нам лишь небольшую часть экзистенциальной количественной оценки. Но универсальная количественная оценка - хорошее начало. Приятно иметь возможность сказать, что функции для
List<A>
работы универсально для всех возможных списков, потому что A полностью не ограничен. Это приводит к тому, о чем пользователь Twitter говорит о «параметричности».Часто цитируемая статья о параметричности - это бесплатные теоремы Филипа Вадлера ! . Что интересно в этой статье, так это то, что с помощью одной только сигнатуры типа мы можем доказать некоторые очень интересные инварианты. Если бы нам пришлось писать автоматические тесты для этих инвариантов, мы бы очень зря теряли время. Например, для
List<A>
, только из сигнатуры типа дляflatten
мы можем рассуждать, что
Это простой пример, и вы, вероятно, можете рассуждать об этом неформально, но еще лучше, когда мы получаем такие доказательства формально бесплатно из системы типов и проверяем компилятором.
Отсутствие стирания может привести к злоупотреблениям
С точки зрения языковой реализации универсальные типы Java (соответствующие универсальным типам) очень сильно влияют на параметричность, используемую для получения доказательств того, что делают наши программы. Это подводит нас к третьей упомянутой проблеме. Все эти достижения в области доказательства и правильности требуют наличия безупречной системы звукового типа. У Java определенно есть некоторые языковые особенности, которые позволяют нам разрушить наши рассуждения. К ним относятся, но не ограничиваются:
Неудаленные дженерики во многом связаны с рефлексией. Без стирания есть информация времени выполнения, которая переносится с реализацией, которую мы можем использовать для разработки наших алгоритмов. Это означает, что статически, когда мы рассуждаем о программах, мы не имеем полной картины. Отражение серьезно угрожает правильности любых доказательств, которые мы рассуждаем статически. Это не случайное отражение также приводит к множеству хитрых дефектов.
Так как же могут быть «полезными» нестертые дженерики? Давайте рассмотрим использование, упомянутое в твите:
Что произойдет, если у T нет конструктора без аргументов? На некоторых языках вы получаете нулевое значение. Или, возможно, вы пропустите нулевое значение и сразу перейдете к возбуждению исключения (к чему все равно приводят нулевые значения). Поскольку наш язык является полным по Тьюрингу, невозможно понять, какие вызовы
broken
будут включать «безопасные» типы с конструкторами без аргументов, а какие - нет. Мы потеряли уверенность в том, что наша программа работает повсеместно.Стирание означает, что мы рассудили (так что давайте стираем)
Поэтому, если мы хотим рассуждать о наших программах, мы настоятельно не рекомендуем использовать языковые функции, которые сильно угрожают нашим рассуждениям. Как только мы это сделаем, почему бы просто не отбросить типы во время выполнения? Они не нужны. Мы можем получить некоторую эффективность и простоту с удовлетворением, что ни одно приведение не завершится ошибкой или что методы могут отсутствовать при вызове.
Стирание побуждает к рассуждению.
источник
Типы - это конструкция, используемая для написания программ таким образом, чтобы компилятор мог проверять правильность программы. Тип - это предложение для значения - компилятор проверяет, что это утверждение истинно.
Во время выполнения программы не должно быть необходимости в информации о типе - это уже проверено компилятором. Компилятор должен иметь право отбрасывать эту информацию, чтобы выполнить оптимизацию кода - ускорить его работу, сгенерировать двоичный файл меньшего размера и т. Д. Этому способствует стирание параметров типа.
Java нарушает статическую типизацию, позволяя запрашивать информацию о типе во время выполнения - Reflection, instanceof и т. Д. Это позволяет создавать программы, которые нельзя проверить статически - они обходят систему типов. Также упускаются возможности для статической оптимизации.
Тот факт, что параметры типа стираются, предотвращает создание некоторых экземпляров этих некорректных программ, однако, больше некорректных программ было бы запрещено, если бы больше информации о типе было удалено, а средства отражения и instanceof были удалены.
Стирание важно для сохранения свойства «параметричности» типа данных. Скажем, у меня есть тип «Список», параметризованный по типу компонента 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» в список, потому что функция не сможет его построить. Ни одно из этих других значений не имеет смысла добавлять, а параметричность предотвращает создание этих некорректных программ.
По сути, стирание предотвращает некоторые способы нарушения параметричности, тем самым устраняя возможность неправильных программ, что является целью статической типизации.
источник
(Хотя я уже написал здесь ответ, но, вернувшись к этому вопросу два года спустя, я понимаю, что есть другой, совершенно другой способ ответить на него, поэтому я оставляю предыдущий ответ нетронутым и добавляю этот.)
Весьма спорно, заслуживает ли этот процесс выполняется на Java дженериков имя «типа стирания». Поскольку универсальные типы не стираются, а заменяются их необработанными аналогами, лучшим выбором кажется «искажение типа».
Квинтэссенция стирания типов в его общепринятом смысле - заставить среду выполнения оставаться в рамках статической системы типов, делая ее «слепой» к структуре данных, к которым она обращается. Это дает компилятору полную мощность и позволяет ему доказывать теоремы, основанные только на статических типах. Это также помогает программисту, ограничивая степени свободы кода, давая больше возможностей для простых рассуждений.
Стирание типов в Java не позволяет добиться этого - оно наносит вред компилятору, как в этом примере:
(Два приведенных выше объявления после стирания превращаются в одну и ту же сигнатуру метода.)
С другой стороны, среда выполнения по-прежнему может проверять тип объекта и размышлять об этом, но поскольку ее понимание истинного типа затрудняется стиранием, нарушения статического типа тривиально достичь и трудно предотвратить.
Чтобы сделать вещи еще более запутанными, исходные и стертые сигнатуры типов сосуществуют и рассматриваются параллельно во время компиляции. Это связано с тем, что весь процесс заключается не в удалении информации о типе из среды выполнения, а в включении системы универсальных типов в устаревшую систему необработанных типов для обеспечения обратной совместимости. Этот драгоценный камень - классический пример:
(Необходимо
extends Object
было добавить дубликат, чтобы сохранить обратную совместимость стертой подписи.)Имея это в виду, давайте вернемся к цитате:
Что именно сделала Java? Само ли это слово, независимо от значения? Для контраста взгляните на скромный
int
тип: проверка типа во время выполнения никогда не выполняется и даже не возможна, а выполнение всегда полностью типобезопасно. Вот как выглядит стирание шрифта, когда все сделано правильно: вы даже не знаете, что оно там есть.источник
Единственное, что я здесь не рассматриваю, - это то, что полиморфизм времени выполнения ООП фундаментально зависит от реификации типов во время выполнения. Когда язык, основу которого составляют реферированные типы, вносит существенное расширение в свою систему типов и основывает его на стирании типов, когнитивный диссонанс является неизбежным результатом. Именно это произошло с сообществом Java; именно поэтому стирание типов вызвало столько споров, и, в конечном итоге, есть планы по их отмене в будущих версиях Java . Находить что-то смешное в этой жалобе пользователей Java - либо откровенное непонимание духа Java, либо сознательно осуждающая шутка.
Утверждение «стирание - единственное, что сделала Java правильно» подразумевает утверждение, что «все языки, основанные на динамической диспетчеризации аргумента функции типа времени выполнения, в корне ошибочны». Хотя само по себе утверждение, безусловно, является законным и может даже рассматриваться как обоснованная критика всех языков ООП, включая Java, оно не может выступать в качестве ключевой точки для оценки и критики функций в контексте Java , где полиморфизм времени выполнения аксиоматичен.
Таким образом, хотя можно справедливо заявить, что «стирание типов - это путь к языковому дизайну», позиции, поддерживающие стирание типов в Java, неуместны просто потому, что для этого уже слишком поздно, а это уже было так даже в исторический момент. когда Дуб был принят Солнцем и переименован в Яву.
Что касается того, является ли статическая типизация правильным направлением в разработке языков программирования, это укладывается в гораздо более широкий философский контекст того, что, по нашему мнению, составляет деятельность программирования . Одна школа мысли, явно вытекающая из классической традиции математики, рассматривает программы как экземпляры одной или другой математической концепции (предложения, функции и т. Д.), Но есть совершенно другой класс подходов, которые рассматривают программирование как способ поговорите с машиной и объясните, что мы от нее хотим. С этой точки зрения программа представляет собой динамическую, органически развивающуюся сущность, разительно противоположную тщательно выстроенной конструкции статически типизированной программы.
Казалось бы, естественно рассматривать динамические языки как шаг в этом направлении: последовательность программы возникает снизу вверх, без каких- либо априорных констант, которые навязывали бы ее сверху вниз. Эту парадигму можно рассматривать как шаг к моделированию процесса, посредством которого мы, люди, становимся тем, что мы есть, посредством развития и обучения.
источник
Последующее сообщение того же пользователя в той же беседе:
(Это было сделано в ответ на заявление другого пользователя, а именно , что «кажется , в некоторых ситуациях„новый Т“будет лучше», идея в том , что
new T()
невозможно из - за типа стирания (Это спорно. - даже еслиT
были доступны времени выполнения, это может быть абстрактный класс или интерфейс, или он может бытьVoid
, или он может не иметь конструктора без аргументов, или его конструктор без аргументов может быть частным (например, потому что он должен быть одноэлементным классом), или его Конструктор no-arg может указать проверенное исключение, которое общий метод не перехватывает и не определяет, но это было предпосылкой. Тем не менееT.class.newInstance()
, верно, что без стирания вы могли бы хотя бы написать , что решает эти проблемы.))Эта точка зрения, согласно которой типы изоморфны предложениям, предполагает, что пользователь имеет опыт работы в формальной теории типов. (S) ему, скорее всего, не нравятся «динамические типы» или «типы времени выполнения», и он предпочел бы Java без отрицательных значений,
instanceof
и отражения, и так далее. (Подумайте о таком языке, как Standard ML, который имеет очень богатую (статическую) систему типов и чья динамическая семантика вообще не зависит от какой-либо информации о типах.)Между прочим, стоит иметь в виду, что пользователь троллит: хотя он, вероятно, искренне предпочитает (статически) типизированные языки, он не пытается искренне убедить других в этой точке зрения. Скорее, основная цель исходного твита состояла в том, чтобы высмеять тех, кто не согласен, и после того, как некоторые из этих несогласных вмешались, пользователь опубликовал последующие твиты, такие как «Причина стирания типа в Java заключается в том, что Вадлер и другие знают, что они делают, в отличие от пользователей java ". К сожалению, из-за этого трудно понять, о чем он думает на самом деле; но, к счастью, это также, вероятно, означает, что это не очень важно. Люди, у которых есть реальная глубина своих взглядов, обычно не прибегают к троллям, лишенным содержания.
источник
Хорошо то, что при появлении дженериков не было необходимости изменять JVM. Java реализует универсальные шаблоны только на уровне компилятора.
источник
Стирание шрифтов - это хорошо, потому что то, что оно делает невозможным, вредно. Предотвращение проверки аргументов типа во время выполнения упрощает понимание программ и рассуждения о них.
Наблюдение, которое я нашел несколько противоречащим интуиции, заключается в том, что, когда сигнатуры функций более общие, их становится легче понять. Это потому, что количество возможных реализаций сокращается. Рассмотрим метод с этой сигнатурой, который, как мы знаем, не имеет побочных эффектов:
Каковы возможные реализации этой функции? Очень много. Вы можете очень мало рассказать о том, что делает эта функция. Это могло быть изменение списка ввода. Это может быть объединение целых чисел в пары, их суммирование и возвращение списка вдвое меньшего размера. Есть много других возможностей, которые можно вообразить. Теперь рассмотрим:
Сколько существует реализаций этой функции? Поскольку реализация не может знать тип элементов, теперь можно исключить огромное количество реализаций: элементы нельзя комбинировать, добавлять в список или фильтровать и т. Д. Мы ограничены такими вещами, как: идентификация (без изменений в списке), удаление элементов или реверсирование списка. Об этой функции легче рассуждать, основываясь только на ее сигнатуре.
Кроме… в Java всегда можно обмануть систему типов. Поскольку реализация этого универсального метода может использовать такие вещи, как
instanceof
проверки и / или приведение к произвольным типам, наши рассуждения, основанные на сигнатуре типа, могут быть легко признаны бесполезными. Функция может проверять тип элементов и выполнять любое количество действий в зависимости от результата. Если эти взломы среды выполнения разрешены, сигнатуры параметризованных методов станут для нас гораздо менее полезными.Если бы в Java не было стирания типов (то есть аргументы типа были переопределены во время выполнения), тогда это просто позволило бы больше затруднить рассуждение махинаций такого рода. В приведенном выше примере реализация может нарушить ожидания, установленные сигнатурой типа, только если в списке есть хотя бы один элемент; но если он
T
был реифициро- ван, он мог это сделать, даже если список был пуст. Реифицированные типы просто увеличат (и без того очень многие) возможности препятствовать нашему пониманию кода.Стирание шрифта делает язык менее «мощным». Но некоторые формы «власти» на самом деле вредны.
источник
instanceof
препятствуют нашей способности рассуждать о том, что делает код на основе типов. Если бы Java расширила аргументы типа, это только усугубило бы эту проблему. Стирание типов во время выполнения делает систему типов более полезной.Это не прямой ответ (ОП спросил «каковы преимущества», я отвечаю «каковы недостатки»)
По сравнению с системой типов C # стирание типов Java - настоящая боль для двоих.
Вы не можете реализовать интерфейс дважды
В C # можно реализовать как
IEnumerable<T1>
иIEnumerable<T2>
безопасно, особенно если эти два типа не имеют общего предка (т.е. их предком являетсяObject
).Практический пример: в Spring Framework вы не можете реализовать
ApplicationListener<? extends ApplicationEvent>
несколько раз. Если вам нужно другое поведение на основе того, чтоT
вам нужно проверитьinstanceof
Вы не можете сделать новый T ()
(и для этого вам нужна ссылка на Class)
Как отмечали другие, выполнение эквивалента
new T()
можно выполнить только через отражение, только путем вызова экземпляраClass<T>
, убедившись в параметрах, требуемых конструктором. C # позволяет делать этоnew T()
только в том случае, если вы ограничиваетесьT
конструктором без параметров. ЕслиT
не соблюдает это ограничение, возникает ошибка компиляции .В Java вам часто придется писать методы, которые выглядят следующим образом
Недостатки в приведенном выше коде:
ReflectiveOperationException
выдается во время выполненияЕсли бы я был автором C #, я бы представил возможность указывать одно или несколько ограничений конструктора, которые легко проверить во время компиляции (поэтому мне может потребоваться, например, конструктор с
string,string
параметрами). Но последнее - предположениеисточник
Дополнительный момент, который, похоже, не рассматривался ни в одном из других ответов: если вам действительно нужны дженерики с набором текста во время выполнения, вы можете реализовать его самостоятельно следующим образом:
Затем этот класс может делать все, что было бы достижимо по умолчанию, если бы Java не использовала стирание: он может выделять новые
T
s (при условии, что уT
него есть конструктор, соответствующий шаблону, который он ожидает использовать) или массивыT
s, он может динамически проверять во время выполнения, является ли конкретный объект a,T
и изменять поведение в зависимости от этого и т. д.Например:
источник
избегает раздувания кода, подобного C ++, потому что один и тот же код используется для нескольких типов; однако для стирания типа требуется виртуальная диспетчеризация, тогда как подход с раздутым кодом С ++ может выполнять не виртуально диспетчерские обобщения.
источник
Большинство ответов больше касаются философии программирования, чем реальных технических деталей.
И хотя этому вопросу более 5 лет, вопрос все еще остается: почему стирание шрифтов желательно с технической точки зрения? В конце концов, ответ довольно прост (на более высоком уровне): https://en.wikipedia.org/wiki/Type_erasure
Шаблоны C ++ не существуют во время выполнения. Компилятор генерирует полностью оптимизированную версию для каждого вызова, что означает, что выполнение не зависит от информации о типе. Но как JIT работает с разными версиями одной и той же функции? Не лучше ли иметь одну функцию? Не хотелось бы, чтобы JIT приходилось оптимизировать все его версии. Ну, а как насчет безопасности типов? Угадайте, что надо выйти из окна.
Но подождите секунду: как это делает .NET? Отражение! Таким образом, им нужно оптимизировать только одну функцию, а также получить информацию о типе времени выполнения. Вот почему дженерики .NET раньше работали медленнее (хотя стали намного лучше). Я не утверждаю, что это неудобно! Но это дорого, и его не следует использовать, когда это не является абсолютно необходимым (это не считается дорогостоящим в динамически типизированных языках, потому что компилятор / интерпретатор в любом случае полагается на отражение).
Таким образом, универсальное программирование со стиранием типа близко к нулевым накладным расходам (все еще требуются некоторые проверки / преобразования во время выполнения): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
источник