140

我一直在用静态方法玩修饰符,遇到了一种奇怪的行为。

众所周知,静态方法不能被覆盖,因为它们与类而不是实例相关联。

所以如果我有下面的代码片段,它编译得很好

//Snippet 1 - Compiles fine
public class A {
    static void ts() {
    }
}

class B extends A {
    static void ts() {
    }
}

但是,如果我在 A 类的静态方法中包含 final 修饰符,则编译失败 B 中的 ts() 无法覆盖 A 中的 ts();被覆盖的方法是 static final

为什么在根本无法覆盖静态方法时会发生这种情况?

4

8 回答 8

179

静态方法不能被覆盖,但可以隐藏。B的ts()方法不是覆盖(不受多态性影响)的ts()A 但它会隐藏它。如果您调用ts()B(NOTA.ts()B.ts()... just ts()),则将调用 B 而不是 A。由于这不受多态性的影响,因此ts()A 中的调用将永远不会重定向到 B 中的调用。

该关键字final将禁止隐藏该方法。所以它们不能被隐藏,尝试这样做会导致编译器错误。

希望这可以帮助。

于 2009-11-16T17:47:38.133 回答
15

静态方法不能被覆盖

这并不完全正确。示例代码实际上意味着 B 中的方法 ts 隐藏了 A 中的方法 ts。因此它并不完全覆盖。在Javaranch上有一个很好的解释。

于 2009-11-16T17:48:02.373 回答
11

静态方法属于类,而不是实例。

A.ts()并且B.ts()总是将是单独的方法。

真正的问题是 Java 允许您在实例对象上调用静态方法。当从子类的实例调用时,与父类具有相同签名的静态方法会被隐藏。但是,您不能覆盖/隐藏最终方法

你会认为错误消息会使用隐藏这个词而不是覆盖......

于 2009-11-16T17:55:26.760 回答
8

考虑到以下几点,您可能会发现自己可以考虑将静态方法设为 final:

具有以下课程:

class A {
    static void ts() {
        System.out.print("A");
    }
}
class B extends A {
    static void ts() {
        System.out.print("B");
    }
}

现在调用这些方法的“正确”方法是

A.ts();
B.ts();

这将导致AB但您也可以在实例上调用方法:

A a = new A();
a.ts();
B b = new B();
b.ts();

这也会导致AB

现在考虑以下几点:

A a = new B();
a.ts();

那会打印A。这可能会让您感到惊讶,因为您实际上有一个 class 对象B。但是由于您是从 type 的引用中调用它A,所以它会调用A.ts(). B您可以使用以下代码打印:

A a = new B();
((B)a).ts();

在这两种情况下,您拥有的对象实际上都来自 class B。但是根据指向对象的指针,您将调用 fromA或 from方法B

现在假设您是类的开发人员,A并且您希望允许子类化。但是你真的想要 method ts(),无论何时调用,即使是从子类调用,也就是你想要它做的事情,而不是被子类版本隐藏。然后你可以制作它final并防止它被隐藏在子类中。您可以确定以下代码将从您的类中调用该方法A

B b = new B();
b.ts();

好的,诚然这是以某种方式构建的,但在某些情况下可能是有意义的。

您不应该在实例上调用静态方法,而应直接在类上调用 - 这样您就不会遇到这个问题。例如,如果您在实例上调用静态方法以及将静态方法设为 final,IntelliJ IDEA 也会向您显示警告。

于 2017-09-12T11:53:51.297 回答
0

我认为编译错误在这里非常具有误导性。它不应该说“被覆盖的方法是静态的。”,而应该说“被覆盖的方法是最终的。”。静态修饰符在这里无关紧要。

于 2009-11-16T18:10:54.047 回答
0

B 中的 ts() 方法并没有覆盖 A 中的 ts() 方法,它只是另一种方法。B 类看不到 A 中的 ts() 方法,因为它是静态的,因此它可以声明自己的称为 ts() 的方法。

但是,如果该方法是最终的,那么编译器会发现 A 中有一个不应在 B 中覆盖的 ts() 方法。

于 2009-11-16T17:49:20.640 回答
0

静态方法“隐藏”在继承的类中,不能“非静态”覆盖,即它们不能在“多态”意义上被覆盖。但它们可以“静态地”覆盖。

即使是“静态地”,也不允许重写静态最终方法。

以下示例说明了这一点 -

App.java-

public class App {
    public static void main(String[] args) {
        Base.fun1();
        Base.fun2();
        Derived1.fun1();
        Derived1.fun2();
        Derived2.fun1();
        Derived2.fun2();
    }
}

Base.java-

public abstract class Base {

    protected static void fun1() {
        System.out.println("Static fun1() called in Base");
    }

    protected static final void fun2() {
        System.out.println("Static final fun2() called in Base");
    }

}

Derived1.java-

public class Derived1 extends Base {

    protected static void fun1() {
        System.out.println("Static fun1() called in Derived1");
    }
    
    //Not allowed to override final method
    /*
    protected static final void fun2() {
        System.out.println("Static final fun2() called in Derived1");
    }
    */
}

Derived2.java-

public class Derived2 extends Base {

    protected static void fun1() {
        System.out.println("Static fun1() called in Derived2");
    }
    
    //Not allowed to override final method
    /*
    protected static final void fun2() {
        System.out.println("Static final fun2() called in Derived2");
    }
    */
}

输出 -

Static fun1() called in Base
Static final fun2() called in Base
Static fun1() called in Derived1
Static final fun2() called in Base
Static fun1() called in Derived2
Static final fun2() called in Base
于 2022-02-07T18:29:32.330 回答
0

与非静态方法不同,Java 中不能覆盖静态方法。但是它们像静态和非静态数据成员一样被继承。这就是为什么不能在父类中创建同名的非静态方法的原因

class Writer { 
    public static void doo(){
        System.out.println("sth");
    } 
}
class Author extends Writer{ 
    public void doo(){
        System.out.println("ok"); // error overridden method is static
    }
}

final关键字确保每次调用该方法时都运行特定的方法主体。现在,如果在子类中创建了具有相同名称的静态方法并调用该方法,则子类中的方法将被执行,如果 final 在父类中的静态方法名称之前添加前缀,则不应该是这种情况. 因此 final 关键字限制了在子类中创建具有相同名称的方法。

于 2018-11-26T10:21:55.040 回答