Java Generics - метод переопределения
У меня есть пара классов ClassA и ClassB, как показано ниже.
случай 1:
class ClassA<T extends Number>{
void method(T t){}
}
class ClassB extends ClassA<Integer>{
@Override
void method(Integer i){}
}
случай 2:
class ClassA{
void method(Number t){}
}
class ClassB extends ClassA{
@Override
void method(Integer i){}
}
У меня здесь два вопроса.
[Q1]
Правильно ли, если я скажу, что case2 является представлением среды выполнения case1 (после стирания типа)?
[д2]
Если я прав [q1], то почему case1 принимается как действительное переопределение? (Я знаю, почему case2 не является допустимым переопределением, поскольку параметры не совпадают.)
Кто-то, пожалуйста, пролил свет на это.
Спасибо заранее.
Ответы
Ответ 1
Ответ на [q1] - нет.
Компилятор будет генерировать мостовой метод в ClassB
, который будет фактически превышать method(Number)
.
class ClassB extends ClassA{
// bridge method
void method(Number i){
method((Integer)i);
}
void method(Integer i){}
}
У вас будет полный ответ в java doc по стиранию типа.
Ответ 2
В Java (начиная с версии 5) возвращаемые типы переопределенных методов должны быть ковариантными, а параметры переопределенных методов должны быть контравариантными.
Это означает, что класс переопределения может быть более конкретным в том, что он возвращает, и принимать больше за то, что он получает.
Во втором примере представьте себе переменную типа ClassA
с экземпляром ClassB
в качестве значения.
ClassA a = new ClassB(); // This is legal, since ClassB is a subclass of ClassA
a.method(1.0); // This is legal, since ClassA.method accepts Number
Другой способ, однако, был бы в порядке:
public class ClassC { public Number method(Integer i) {...} }
public class ClassD extends ClassC {
@Override
public Integer method(Number n) {...}
}
действителен, поскольку ClassD
все еще выполняет контракт, определенный ClassC
.
Ответ 3
Фактический суперкласс ClassB
равен ClassA<Integer>
. Поэтому его функция-член method
имеет подпись времени компиляции:
void method(Integer t){}
Вы можете убедить себя, вызвав что-то вроде
ClassA a = new ClassB();
a.method(1.0);
Вы должны увидеть ошибку времени выполнения. На самом деле это можно скомпилировать, потому что я использовал стертую версию ClassA
. Фактически каждое присвоение
общий тип, отличный от ClassA<Integer>
(например, ClassA<Number
> ), потерпит неудачу из-за несовместимых типов.