Почему не разрешено использовать следующие два метода в одном классе?
class Test{
void add(Set<Integer> ii){}
void add(Set<String> ss){}
}
Я получаю compilation error
Метод add (Set) имеет то же самое стирание, что и Add (Set), что и другой метод в типе Test.
в то время как я могу обойти это, мне было интересно, почему javac не нравится это.
Я вижу, что во многих случаях логика этих двух методов будет очень похожа и может быть заменена одним
public void add(Set<?> set){}
метод, но это не всегда так.
Это очень раздражает, если вы хотите иметь два, constructors
которые принимают эти аргументы, потому что тогда вы не можете просто изменить имя одного из constructors
.
List
и я не знаю, как справиться с этим.Ответы:
Это правило предназначено для предотвращения конфликтов в устаревшем коде, который все еще использует необработанные типы.
Вот иллюстрация того, почему это было запрещено, взято из JLS. Предположим, что до того, как дженерики были представлены в Java, я написал такой код:
Вы расширяете мой класс, вот так:
После введения дженериков я решил обновить свою библиотеку.
Вы не готовы делать какие-либо обновления, поэтому вы оставляете свой
Overrider
класс в покое. Чтобы правильно переопределитьtoList()
метод, разработчики языка решили, что необработанный тип «эквивалентен переопределению» для любого обобщенного типа. Это означает, что хотя подпись вашего метода формально больше не равна подписи моего суперкласса, ваш метод все равно переопределяет.Сейчас проходит время, и вы решаете, что готовы обновить свой класс. Но вы немного облажались, и вместо редактирования существующего необработанного
toList()
метода вы добавили новый метод, подобный этому:Из-за переопределенной эквивалентности необработанных типов оба метода имеют допустимую форму для переопределения
toList(Collection<T>)
метода. Но, конечно, компилятор должен разрешить один метод. Чтобы устранить эту неоднозначность, классам не разрешается иметь несколько методов, эквивалентных переопределению, то есть несколько методов с одинаковыми типами параметров после стирания.Ключ заключается в том, что это правило языка, разработанное для обеспечения совместимости со старым кодом с использованием необработанных типов. Это не ограничение, требуемое стиранием параметров типа; поскольку разрешение метода происходит во время компиляции, было бы достаточно добавить универсальные типы к идентификатору метода.
источник
javac
.List<?>
и некоторые,enum
которые вы определяете, чтобы указать тип. Специфичная для типа логика может быть в методе перечисления. В качестве альтернативы вы можете создать два разных класса, а не один класс с двумя разными конструкторами. Общая логика будет в суперклассе или вспомогательном объекте, которому делегируются оба типа.В дженериках Java используется стирание типов. Бит в угловых скобках (
<Integer>
и<String>
) удаляется, поэтому вы получите два метода с одинаковой сигнатурой (add(Set)
вы видите в ошибке). Это не разрешено, потому что среда выполнения не знает, какой использовать для каждого случая.Если Java когда-либо получит переработанные дженерики, то вы можете сделать это, но сейчас это вряд ли возможно.
источник
Это потому, что Java Generics реализованы с помощью типа Erasure .
Ваши методы будут переведены во время компиляции в нечто вроде:Разрешение метода происходит во время компиляции и не учитывает параметры типа. ( см. ответ Эриксона )
Оба метода имеют одинаковую сигнатуру без параметров типа, отсюда и ошибка.
источник
Проблема в том, что
Set<Integer>
иSet<String>
на самом деле рассматриваются какSet
от JVM. Выбор типа для Set (String или Integer в вашем случае) является только синтаксическим сахаром, используемым компилятором. JVM не может различитьSet<String>
иSet<Integer>
.источник
Set
, но поскольку разрешение метода происходит во время компиляции, когда доступна необходимая информация, это не имеет значения. Проблема состоит в том, что разрешение этих перегрузок будет конфликтовать с разрешением для необработанных типов, поэтому они были объявлены недопустимыми в синтаксисе Java.(Ljava/util/Collection;)Ljava/util/List;
он не работает. Вы можете использовать(Ljava/util/Collection<String>;)Ljava/util/List<String>;
, но это несовместимое изменение, и вы столкнетесь с неразрешимыми проблемами в местах, где все, что у вас есть, это стертый тип. Возможно, вам придется полностью отказаться от стирания, но это довольно сложно.Определите один метод без типа, как
void add(Set ii){}
Вы можете упомянуть тип при вызове метода на основе вашего выбора. Это будет работать для любого типа набора.
источник
Вполне возможно, что компилятор преобразует Set (Integer) в Set (Object) в байт-коде Java. В этом случае Set (Integer) будет использоваться только на этапе компиляции для проверки синтаксиса.
источник
Set
. Обобщения не существуют в байт-коде, они являются синтаксическим сахаром для приведения и обеспечивают безопасность типов во время компиляции.Я столкнулся с этим, когда попытался написать что-то вроде:
Continuable<T> callAsync(Callable<T> code) {....}
иContinuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}
они стали для компилятора двумя определениямиContinuable<> callAsync(Callable<> veryAsyncCode) {...}
Стирание типа буквально означает стирание информации аргументов типа из обобщений. Это ОЧЕНЬ раздражает, но это ограничение, которое будет с Java некоторое время. Для конструкторов мало что можно сделать, например, 2 новых подкласса, специализирующихся с разными параметрами в конструкторе. Или используйте вместо этого методы инициализации ... (виртуальные конструкторы?) С разными именами ...
для подобных методов работы переименование поможет, как
Или с некоторыми более описательными именами, самодокументирующими для случаев oyu, как
addNames
и томуaddIndexes
подобное.источник