为什么Java为重写的静态方法强制执行返回类型兼容性?

时间:2012-02-24 23:12:14

标签: java static override

Per this answerthis answer,Java静态方法不是虚拟的,无法覆盖。因此,直观地说,这应该有效(即使在99%的情况下,这是危险的编程):

class Foo
{
    public static String frob() {
        return "Foo";
    }
}

class Bar extends Foo
{
    public static Number frob() {
        return 123;
    }
}

然而,在实践中,这会让你:

Foo.java:10: frob() in Bar cannot override frob() in Foo; attempting to use incompatible return type
found   : java.lang.Number
required: java.lang.String
    public static Number frob() {
                         ^

天真地看来,Foo.frob()Bar.frob()似乎彼此无关;然而Java坚持认为他们这样做了。为什么呢?

(Nb:我不想听到为什么以这种方式编码是个坏主意,我想听听Java和/或JVM设计中的内容,这使得这种限制成为必要。)


更新以添加:对于那些认为编译器会因为在实例上调用静态方法而感到困惑的人,如果你允许这样做:它不会。在方法签名 兼容的情况下,它已经必须解决这个问题:

class Foo
{
    static String frob() {
        return "Foo";
    }
}

class Bar extends Foo
{
    static String frob() {
        return "Bar";
    }
}

class Qux {
    public static void main(String[] args) {
        Foo f = new Foo();
        Foo b = new Bar();
        Bar b2 = new Bar();

        System.out.println(f.frob());
        System.out.println(b.frob());
        System.out.println(b2.frob());
    }
}

得到你:

Foo
Foo
Bar

问题是,为什么不能轻易地(在不兼容的签名案例中)得到你的具体原因是什么:

Foo
Foo
123

4 个答案:

答案 0 :(得分:7)

请考虑以下事项:

public class Foo {
  static class A {
    public static void doThing() {
      System.out.println("the thing");
    }
  }

  static class B extends A {

  }

  static class C extends B {
    public static void doThing() {
      System.out.println("other thing");
    }
  }

  public static void main(String[] args) {
    A.doThing();
    B.doThing();
    C.doThing();
  }
}

运行它!它编译并打印出来

the thing
the thing
other thing

静态方法是一种继承 - 在B.doThing被转换为对A.doThing的调用的意义上 - 并且可以被覆盖。

这似乎主要是对JLS的判断。但是,JLS似乎解决此问题的最具体方式是section 8.2,这并不是说静态方法不是继承的。

答案 1 :(得分:3)

JLS 8.4.2 Method Signature,简要说明:

  

如果两个方法具有相同的名称和参数类型,则它们具有相同的签名。

没有任何关于静态的说法。可以通过实例(或空引用)调用静态方法 - 如果通过超类声明引用子类,该方法应如何解决?

答案 2 :(得分:2)

这是因为在Java中,基于对象的运行时类型调用特定方法,而不是基于它的编译时类型。但是,静态方法是类方法,因此只能使用编译时类型信息在编译期间解析对它们的访问。也就是说,如果您可以编译上面的内容并使用像这样的代码

会发生什么
Foo bar = new Bar();
bar.frob();

答案 3 :(得分:1)

好吧,JVM可能会允许这样做,但让我们从编译器的角度讨论为什么它是一个非常糟糕的主意。

实例数据(包括实例所在的类型)不是在编译时要解决的简单问题。显然它在运行时是众所周知的。如果我有一个Bar类型的变量条,并且我调用s = bar.frob(),则编译器需要对哪个类型条进行反向工程以查看返回值是否可接受。如果在编译时确定类型是一个超级难题,这会使编译器效率最低。在最坏的情况下,答案是错误的,并且您得到应该在编译时捕获的运行时错误。