Ошибка в переопределении родовых коллекций в Java
Когда я пытаюсь переопределить метод, который принимает List<String>
Я получаю следующую ошибку компиляции.
Несколько маркеров в этой строке:
- Методgetname(List<Integer>)
типаchild
должен переопределить или реализовать метод супертипа
- Столкновение с именем: методgetname(List<Integer>)
типаchild
имеет такое же стирание, что иgetname(List<String>)
типаparent
но не отменяет это
У меня сложилось впечатление, что из-за стирания, метод, который принимает List<String>
и метод подкласса, который принимает List<Integer>
будет считаться переопределенным, потому что подпись обоих методов после стирания одинакова.
Вот определение для сигнатуры метода, которая включает стирание.
Я не понимаю, почему возникает эта ошибка и что именно она означает.
Мой код ниже:
class parent {
public void getname(List<String> num) {
System.out.printf("parent class: %s",num);
}
}
class child extends parent {
@Override // here is the compile error
public void getname(List<Integer> num) {
System.out.printf("child class: %s",num);
}
}
5 ответов
List<String>
а также List<Integer>
разные типы и getname(List<String> num)
а также getname(List<Integer> num)
методы с разными сигнатурами Таким образом, второе не перекрывает первое. Так child
не может распространяться parent
с этим методом.
Сообщение об ошибке довольно ясно: оно имеет такое же стирание, но типы не совпадают, поэтому оно не считается переопределением. List<Integer>
это не List<String>
; он не может трактовать это как переопределение (что потребовало бы точного соответствия типов) или перегрузку (что потребовало бы разных стираний).
В основном ваше впечатление неверно, и это невозможно. JLS считает это специально незаконным.
Из 8.4.2:
Сигнатура метода m1 является подписью метода m2, если либо:
m2 имеет такую же подпись, как m1, или
подпись m1 такая же, как стирание (§4.6) подписи m2.
Две сигнатуры метода m1 и m2 эквивалентны переопределению, если либо m1 является подписью m2, либо m2 является подписью m1.
Ободренный бит важен, потому что он не говорит, что "стирание m1 такое же, как стирание m2". То, что он на самом деле допускает, это (и некоторые более замысловатые примеры, подобные этому):
class A {
void process(List<String> list) {}
}
class B extends A {
@Override
void process(List list) {} // |List| is erasure of List<T>
}
Поскольку метод подписи B.process
стирание A.process
это переопределение.
Согласно 8.4.9, примером, подобным OP, может быть перегрузка, поскольку сигнатуры не эквивалентны переопределению:
Если два метода класса... имеют одинаковое имя, но сигнатуры не эквивалентны переопределению, то имя метода считается перегруженным.
За исключением того, что это конкретно ошибка времени компиляции ( 8.4.8.3):
Это ошибка времени компиляции, если объявление типа T имеет метод-член m1 и существует метод m2, объявленный в T, или супертип T, такой что выполняются все следующие условия:
m1 и m2 имеют одинаковые названия.
м2 доступен с T.
Подпись m1 не является подписью (§8.4.2) подписи m2.
Сигнатура m1 или некоторого переопределения метода m1 (прямо или косвенно) имеет то же самое стирание, что и сигнатура m2 или переопределения некоторого метода m2 (прямо или косвенно).
Эти ограничения необходимы, потому что генерики реализованы посредством стирания. Приведенное выше правило подразумевает, что методы, объявленные в одном и том же классе с одним и тем же именем, должны иметь разные стирания....
Чтобы добавить к ответам уже здесь, я хочу прокомментировать the signature of the methods is the same after erasure
... но компилятор проверяет тип метода перед удалением.
Вы могли бы сделать что-то вроде создания "другого" Parent
класс, такой как
class Parent {
public void getname(List<Integer> num) {
System.out.printf("child class: %s",num);
}
}
, скомпилируйте его, используйте его для компиляции Child
класс, а затем смешайте свой оригинал Parent.class
а также Child.class
в той же JVM без проблем, избегая проверок компилятора и используя стирание типов.
Но пока компилятор замечает, что он делает что-то подобное "в одном и том же режиме", он потерпит неудачу по причинам, объясненным Ашотом и Луи.
Вы указали на ошибку, очевидно, из-за аннотации @Override. При проверке аннотаций (которые, как я полагаю, должно произойти на самой ранней стадии процесса компиляции), стирание типа не произошло. Таким образом, типы List не совпадают с List, и вы получите предупреждение. Но даже без этой аннотации я получаю ошибку
name clash: getname(java.util.List<java.lang.Integer>) in child and getname(java.util.List<java.lang.String>) in parent have the same erasure, yet neither overrides the other
class child extends parent{`.
Эта ошибка ясно говорит о том, что у них такое же стирание, но все равно ни одна из поездок. Так что Java в принципе не позволяет этого. Я могу понять, что это приведет ко многим проблемам / беспорядкам, если позволено. Например, если кто-то звонит new Child().get_name( new List<String> () )
, это не может быть разрешено в методе Child, который нарушит концепцию переезда. Так что это правильно, что это не разрешено.