调用类型变量的基类和派生类静态方法

17

我有以下例子:

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() 方法的签名完全相同。我知道静态方法不会被继承,但在我的情况下不应该抛出一个编译时错误吗?

有人可以解释一下这种行为吗?

7个回答

10
这是因为编译器不知道在运行时将替换 T 的确切子类型是什么。这就是为什么它只会将方法调用 T.handle() 绑定到 AbstractErrorHandler.handle() 方法的原因。
问题在于您正在将继承与 Java 类的静态特性混合使用。
为了使其正常工作,您必须取消 .handle() 方法的 static 修饰符,并在 A 类中保留 T 的实例。在运行时,这个 T 实例将是 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();
        }
    }
}

但是我在编译时提供了确切的类型。A<ConcreteErrorHandler> a = new A<ConcreteErrorHandler>(); 类型在编译时已知。 - St.Antario
您提供的类型超出了m方法的范围。 - MadConan
3
@St.Antario Java的泛型不像C++中的模板。类声明并不知道你在其他地方给它的参数化。 - Radiodef
@Radiodef 实际上,这来自Alexandrescu的策略。但我的代码有什么问题吗?我想了解那种行为。 - St.Antario
Java编译器执行一种名为类型擦除的操作,其中泛型的类型从代码中移除。请参见 https://docs.oracle.com/javase/tutorial/java/generics/genMethods.html - Slimu

6

4.4. 类型变量 告诉我们:

带有边界 T & I1 & ... & In 的类型变量 X 的成员是交集类型 T & I1 & ... & In类型变量声明的位置出现的成员。

因此,T extends AbstractErrorHandler 的成员是 AbstractErrorHandler 的成员。 T.handle(); 指的是 AbstractErrorHandler.handle();


4
有界类型参数的擦除是指它的边界(对于边界交集,是边界中的第一个类型)。因此,在您的情况下,T extends AbstractErrorHandler 被擦除为 AbstractErrorHandler,您的方法被有效地替换为:
 public void m() { AbstractErrorHandler.handle(); }

例如,参见JLS 4.6

类型变量(§4.4)的擦除是其最左边界的擦除。


3

基本上,您的方法m将被编译

public void m(){
    AbstractErrorHandler.handle();
}

2
为什么会这样,我在编译时已经提供了类型? - St.Antario
不,你没有。你提供了 T。虽然编译器可能会按照你的想法执行,但显然它没有这样做。 - MadConan
1
虽然正确,但你的答案已经从问题中显而易见了。OP实际上是在问为什么会发生这种情况。 - Radiodef

2
我认为这是因为static是类级别的,而您使用T extends AbstractErrorHandler隐含地告诉编译器使用AbstractErrorHandler。

运行时会假定最高的类级别,因为类型擦除发生在运行时。 m的实现仅使用T,即使您在main方法中将其声明为具体类型,该方法不在m方法的范围内。

看不出它解释了什么。老实说,我本来期望能够查看JLS参考文献或其他一些资料。 - St.Antario

2

2
原因是因为您使用了泛型和Java静态方法,这些方法被隐藏而不是覆盖。在编译时,唯一已知的信息是AbstractErrorHandler类(泛型在Java中在编译时工作,没有带有泛型信息的字节码),并且调用的方法是该类的方法。 如果将方法句柄从静态更改为"实例",则调用的实现是"正确的"(因为该方法被覆盖而不是隐藏),如下面的示例所示。
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();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接