Вызывающая база и производные статические методы переменной типа
У меня есть следующий пример:
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
A<ConcreteErrorHandler> a = new A<ConcreteErrorHandler>();
a.m(); //Exception here!
}
public static class AbstractErrorHandler {
public static void handle(){
throw new UnsupportedOperationException("Not implemented");
}
}
public static class ConcreteErrorHandler extends AbstractErrorHandler{
public static void handle(){
System.out.println("Concrete handler");
}
}
public static class A<T extends AbstractErrorHandler>{
public void m(){
T.handle();
}
}
}
IDEONE
Почему метод базового класса вызывается, но не производный? Подписи методов handle()
совершенно одинаковы. Я знаю, что статические методы не наследуют, но не должны ли возникать ошибка компиляции в моем случае?
Может ли кто-нибудь объяснить это поведение?
Ответы
Ответ 1
Причиной этого является то, что компилятор не знает, который точный подтип AbstractErrorHandler
будет заменять T
на Runtime. Поэтому он просто связывает вызов метода T.handle()
с методом AbstractErrorHandler.handle()
.
Проблема заключается в том, что вы смешиваете наследование с static
функциями классов в Java.
Чтобы это работало (правильно), вам нужно избавиться от модификатора static
для методов .handle()
и сохранить экземпляр T
в классе A
. Этот экземпляр T
(в Runtime) будет отдельным подклассом AbstractErrorHandler
, а затем будет выполнен фактический .handle()
метод.
Например:
class Ideone {
public static void main(String[] args) throws java.lang.Exception {
A<ConcreteErrorHandler> a = new A<ConcreteErrorHandler>(new ConcreteErrorHandler());
a.m();
}
public static class AbstractErrorHandler {
public void handle() {
throw new UnsupportedOperationException("Not implemented");
}
}
public static class ConcreteErrorHandler extends AbstractErrorHandler {
public void handle() {
System.out.println("Concrete handler");
}
}
public static class A<T extends AbstractErrorHandler> {
T instance;
A(T instance) {
this.instance = instance;
}
public void m() {
instance.handle();
}
}
}
Ответ 2
4.4. Переменные типа говорят нам, что:
Элементы переменной типа X
с bound T & I1 & ... & In
являются членами типа пересечения T & I1 & ... & In
, появляющимся в точке, где объявлена переменная типа.
Поэтому члены T extends AbstractErrorHandler
являются членами AbstractErrorHandler
. T.handle();
относится к AbstractErrorHandler.handle();
.
Ответ 3
Стирание параметра ограниченного типа является границей (и в случае связанного пересечения - первого типа в границе). Поэтому в вашем случае T extends AbstractErrorHandler
стирается до AbstractErrorHandler
, и ваш метод эффективно заменяется на:
public void m() { AbstractErrorHandler.handle(); }
См. например JLS 4.6
Стирание переменной типа (§4.4) является стиранием ее левой части.
Ответ 4
Потому что в основном ваш метод m
будет скомпилирован в
public void m(){
AbstractErrorHandler.handle();
}
Ответ 5
Я считаю, что это связано с тем, что static
является классом, и вы говорите компилятору неявно использовать AbstractErrorHandler, используя T extends AbstractErrorHandler
.
Время выполнения будет принимать наивысший уровень класса, поскольку стирание стилей происходит во время выполнения.
Реализация m
использует только T
, которая является AbstractErrorHandler, несмотря на то, что вы объявили ее конкретным типом в основном методе, который не входит в объем метода m
.
Ответ 6
Компилятор Java стирает все типовые параметры в общем коде, вы не можете проверить, какой тип параметрирования для универсального типа используется во время выполнения. Поэтому используется верхний граничный тип AbstractErrorHandler
.
см. более подробную информацию: https://docs.oracle.com/javase/tutorial/java/generics/restrictions.html
Ответ 7
Причина в том, что вы используете generics и java static methods, которые не скрыты. Во время компиляции единственной известной информацией является класс AbstractErrorHandler
(generics работает во время компиляции в java, байт-код с информацией генериков отсутствует), а метод называется одним из класса.
Если вы измените форму дескриптора метода static на "экземпляр", реализация называется "правильной" (потому что метод переопределен не скрыт), как в приведенном ниже примере.
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
A<AbstractErrorHandler> a = new A<AbstractErrorHandler>();
a.m(new ConcreteErrorHandler()); //Exception here!
}
public static class AbstractErrorHandler {
public void handle(){
throw new UnsupportedOperationException("Not implemented");
}
}
public static class ConcreteErrorHandler extends AbstractErrorHandler{
public void handle(){
System.out.println("Concrete handler");
}
}
public static class A<T extends AbstractErrorHandler>{
public void m(T t){
t.handle();
}
}
}