ChatGPT解决这个技术问题 Extra ChatGPT

为什么Java不允许覆盖静态方法?

为什么不能覆盖静态方法?

如果可能,请举个例子。

大多数 OOP 语言不允许这样做。
@jmucchiello:看我的回答。我和你的想法一样,但后来了解了 Ruby/Smalltalk 的“类”方法,所以还有其他真正的 OOP 语言可以做到这一点。
@jmucchiello 大多数 OOP 语言都不是真正的 OOP 语言(我想到了 Smalltalk)
可能是因为 Java 在编译时解析了对静态方法的调用。因此,即使您先编写了 Parent p = new Child(),然后编写了 p.childOverriddenStaticMethod(),编译器也会通过查看引用类型将其解析为 Parent.childOverriddenStaticMethod()

N
Nathan Hughes

覆盖取决于拥有一个类的实例。多态性的要点是您可以子类化一个类,并且实现这些子类的对象对于超类中定义的相同方法(并在子类中被覆盖)具有不同的行为。静态方法不与类的任何实例相关联,因此该概念不适用。

推动 Java 设计的有两个考虑因素影响了这一点。一个是对性能的担忧:有很多批评 Smalltalk 太慢(垃圾收集和多态调用是其中的一部分),Java 的创建者决心避免这种情况。另一个决定是 Java 的目标受众是 C++ 开发人员。使静态方法以它们的方式工作有利于 C++ 程序员熟悉并且速度也非常快,因为无需等到运行时才能确定调用哪个方法。


...但仅在 Java 中“正确”。例如,Scala 的“静态类”(称为 objects)等价物允许方法重载。
Objective-C 还允许覆盖类方法。
有一个编译时类型层次结构和一个运行时类型层次结构。询问为什么静态方法调用在存在运行时类型层次结构的情况下不能利用自身是非常有意义的。在 Java 中,当从对象 (obj.staticMethod()) 调用静态方法时会发生这种情况——这是允许的并使用编译时类型。当静态调用在类的非静态方法中时,“当前”对象可能是该类的派生类型——但不考虑在派生类型上定义的静态方法(它们属于运行时类型等级制度)。
我应该说清楚:这个概念不适用是不正确的。
这个答案虽然正确,但更类似于“它是如何”,而不是它应该如何或更准确地满足 OP 的期望,因此在这里,我和其他人的期望。除了“就是这样”之外,没有具体的理由禁止覆盖静态方法。我个人认为这是一个缺陷。
J
Jay

个人认为这是Java设计的一个缺陷。是的,是的,我知道非静态方法附加到实例,而静态方法附加到类等。不过,请考虑以下代码:

public class RegularEmployee {
    private BigDecimal salary;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }

    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }

    public BigDecimal calculateBonus() {
        return salary.multiply(getBonusMultiplier());
    }

    /* ... presumably lots of other code ... */
}

public class SpecialEmployee extends RegularEmployee {
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".03");
    }
}

此代码不会像您预期的那样工作。即,SpecialEmployee 与普通员工一样获得 2% 的奖金。但是,如果您删除“静态”,则 SpecialEmployee 将获得 3% 的奖金。

(诚然,这个例子的编码风格很差,在现实生活中,你可能希望奖金乘数在某个数据库中而不是硬编码。但这只是因为我不想让这个例子陷入困境。与该点无关的代码。)

对我来说,您可能希望将 getBonusMultiplier 设为静态似乎很合理。也许您希望能够显示所有员工类别的奖金乘数,而无需在每个类别中都有员工的实例。搜索这样的示例实例有什么意义?如果我们正在创建一个新的员工类别并且还没有任何员工分配给它怎么办?这在逻辑上是一个静态函数。

但它不起作用。

是的,是的,我可以想出很多方法来重写上面的代码以使其工作。我的观点不是它创造了一个无法解决的问题,而是它为粗心的程序员创造了一个陷阱,因为这种语言的行为并不像我认为一个理性的人所期望的那样。

也许如果我尝试为 OOP 语言编写编译器,我很快就会明白为什么实现它以便可以覆盖静态函数是困难的或不可能的。

或者也许有一些很好的理由说明 Java 的这种行为方式。任何人都可以指出这种行为的一个优势,某种类型的问题会因此变得更容易吗?我的意思是,不要只指向 Java 语言规范并说“看,这是记录它的行为方式”。我知道。但是有充分的理由为什么它应该这样做吗? (除了明显的“让它正常工作太难了”......)

更新

@VicKirk:如果你的意思是这是“糟糕的设计”,因为它不适合 Java 处理静态的方式,我的回答是,“嗯,当然。”正如我在原始帖子中所说,它不起作用。但是,如果您的意思是从某种意义上说这是一种糟糕的设计,那么这种工作的语言会存在根本性的问题,即静态可以像虚函数一样被覆盖,那么这会以某种方式引入歧义,或者不可能有效地实施或类似的,我回答,“为什么?这个概念有什么问题?”

我认为我给出的例子是一件很自然的事情。我有一个类,它的函数不依赖于任何实例数据,我可能非常合理地希望独立于实例调用它,并且希望从实例方法中调用它。为什么这不起作用?这些年来,我已经多次遇到这种情况。在实践中,我通过将函数设为虚拟来解决它,然后创建一个静态方法,其生命中的唯一目的是成为一个静态方法,将调用传递给具有虚拟实例的虚拟方法。这似乎是一种非常迂回的到达那里的方式。


@Bemrose:但这就是我的观点:为什么不应该允许我这样做?也许我对“静态”应该做什么的直观概念与您的不同,但基本上我认为静态是一种可以是静态的方法,因为它不使用任何实例数据,并且应该是静态的,因为您可能想要独立于实例调用它。一个静态显然与一个类相关联:我希望 Integer.valueOf 与 Integers 相关联,而 Double.valueOf 与 Doubles 相关联。
@ewernli & Bemrose:是的,是的,就是这样。我不是在争论这个。由于我的示例中的代码不起作用,我当然不会尝试编写它。我的问题是为什么会这样。 (我担心这会变成我们只是没有交流的谈话之一。“对不起,推销员先生,我可以买一个红色的吗?” “不,它要花 5 美元。” “是的,我知道它很贵5 美元,但是我可以买一个红色的吗?” “先生,我刚刚告诉过你,它需要 5 美元。” “好的,我知道价格,但我问的是颜色。” “我已经告诉过你价格了!” ETC。)
我认为最终这段代码令人困惑。考虑是否将实例作为参数传递。然后你说运行时实例应该指示调用哪个静态方法。这基本上使整个单独的层次结构与现有实例平行。现在,如果子类定义了与非静态相同的方法签名怎么办?我认为规则会使事情变得相当复杂。 Java 试图避免的正是这些语言复杂性。
@Yishai:RE“运行时实例指示调用哪个静态方法”:完全正确。我不明白为什么你不能用静态做任何你可以用虚拟做的事情。 “单独的层次结构”:我会说,使其成为同一层次结构的一部分。为什么静态不包含在同一层次结构中? “子类定义了相同的非静态签名”:我认为这是非法的,就像让子类覆盖具有相同签名但返回类型不同的函数或不抛出所有异常一样是非法的父母抛出,或有一个更窄的范围。
我认为 Jay 说得有道理——当我发现静态不能被覆盖时,我也很惊讶。部分原因是如果我有 A 和方法 someStatic() 并且 B 扩展了 A,那么 B.someMethod() 绑定 到 A 中的方法。如果我随后将 someStatic() 添加到 B,调用代码仍然调用A.someStatic() 直到我重新编译调用代码。同样令我惊讶的是,bInstance.someStatic() 使用了 bInstance 的 declared 类型,而不是运行时类型,因为它在编译时绑定而不是链接,因此 A bInstance; ... bInstance.someStatic() 如果 B.someStatic() 则调用 A.someStatic() ) 存在。
S
Steve Powell

简短的回答是:完全有可能,但 Java 没有这样做。

下面是一些说明 Java 现状的代码:

文件 Base.java

package sp.trial;
public class Base {
  static void printValue() {
    System.out.println("  Called static Base method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Base method.");
  }
  void nonLocalIndirectStatMethod() {
    System.out.println("  Non-static calls overridden(?) static:");
    System.out.print("  ");
    this.printValue();
  }
}

文件 Child.java

package sp.trial;
public class Child extends Base {
  static void printValue() {
    System.out.println("  Called static Child method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Child method.");
  }
  void localIndirectStatMethod() {
    System.out.println("  Non-static calls own static:");
    System.out.print("  ");
    printValue();
  }
  public static void main(String[] args) {
    System.out.println("Object: static type Base; runtime type Child:");
    Base base = new Child();
    base.printValue();
    base.nonStatPrintValue();
    System.out.println("Object: static type Child; runtime type Child:");
    Child child = new Child();
    child.printValue();
    child.nonStatPrintValue();
    System.out.println("Class: Child static call:");
    Child.printValue();
    System.out.println("Class: Base static call:");
    Base.printValue();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Child:");
    child.localIndirectStatMethod();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Base:");
    child.nonLocalIndirectStatMethod();
  }
}

如果你运行它(我是在 Mac 上,从 Eclipse,使用 Java 1.6 运行的)你会得到:

Object: static type Base; runtime type Child.
  Called static Base method.
  Called non-static Child method.
Object: static type Child; runtime type Child.
  Called static Child method.
  Called non-static Child method.
Class: Child static call.
  Called static Child method.
Class: Base static call.
  Called static Base method.
Object: static/runtime type Child -- call static from non-static method of Child.
  Non-static calls own static.
    Called static Child method.
Object: static/runtime type Child -- call static from non-static method of Base.
  Non-static calls overridden(?) static.
    Called static Base method.

在这里,唯一可能令人惊讶的案例(以及问题所在)似乎是第一个案例:

“运行时类型不用于确定调用哪些静态方法,即使使用对象实例 (obj.staticMethod()) 调用也是如此。”

最后一种情况:

“从类的对象方法中调用静态方法时,选择的静态方法是类本身可访问的方法,而不是定义对象运行时类型的类。”

使用对象实例调用

静态调用在编译时解析,而非静态方法调用在运行时解析。请注意,尽管静态方法是(从父级)继承的,但它们不会(被子级)覆盖。如果您有其他预期,这可能是一个惊喜。

从对象方法中调用

对象方法调用使用运行时类型解析,但静态(类)方法调用使用编译时(声明)类型解析。

改变规则

要更改这些规则,以便示例中的最后一个调用称为 Child.printValue(),静态调用必须在运行时提供类型,而不是编译器在编译时使用声明的类来解析调用对象(或上下文)。然后,静态调用可以使用(动态)类型层次结构来解析调用,就像今天的对象方法调用一样。

这很容易实现(如果我们更改 Java :-O),并且一点也不不合理,但是,它有一些有趣的考虑。

主要考虑的是我们需要决定哪些静态方法调用应该这样做。

目前,Java 在语言中有这种“怪癖”,即 obj.staticMethod() 调用被 ObjectClass.staticMethod() 调用替换(通常带有警告)。 [注意: ObjectClassobj 的编译时类型。] 这些将是以这种方式覆盖的良好候选者,采用 obj 的运行时类型。

如果我们这样做了,它会使方法体更难阅读:父类中的静态调用可能会被动态地“重新路由”。为了避免这种情况,我们必须使用类名调用静态方法——这使得调用更明显地通过编译时类型层次结构解决(就像现在一样)。

调用静态方法的其他方式更加棘手:this.staticMethod() 应该与 obj.staticMethod() 的含义相同,采用 this 的运行时类型。但是,这可能会给现有程序带来一些麻烦,这些程序调用(显然是本地的)静态方法而没有修饰(可以说等同于 this.method())。

那么朴素的调用 staticMethod() 呢?我建议他们像今天一样做,并使用本地类上下文来决定做什么。否则会产生很大的混乱。当然,这意味着如果 method 是非静态方法,则 method() 将表示 this.method(),如果 method 是静态方法,则 ThisClass.method()。这是另一个混乱的来源。

其他注意事项

如果我们改变这种行为(并且使静态调用可能动态地非本地调用),我们可能想要重新审视 finalprivateprotected 作为类的 static 方法的限定符的含义。然后,我们都必须习惯 private staticpublic final 方法没有被覆盖,因此可以在编译时安全地解析,并且可以“安全”地作为本地引用读取。


“如果我们这样做了,它会使方法体更难阅读:父类中的静态调用可能会被动态地“重新路由”。”没错,但这正是现在普通的非静态函数调用所发生的事情。这通常被吹捧为普通虚拟功能的积极特征,而不是问题。
M
Mr_and_Mrs_D

其实我们错了。尽管默认情况下 Java 不允许您覆盖静态方法,但如果您仔细阅读 Java 中的 Class 和 Method 类的文档,您仍然可以通过以下解决方法找到模拟静态方法覆盖的方法:

import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;

class RegularEmployee {

    private BigDecimal salary = BigDecimal.ONE;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }
    public BigDecimal calculateBonus() {
        return salary.multiply(this.getBonusMultiplier());
    }
    public BigDecimal calculateOverridenBonus() {
        try {
            // System.out.println(this.getClass().getDeclaredMethod(
            // "getBonusMultiplier").toString());
            try {
                return salary.multiply((BigDecimal) this.getClass()
                    .getDeclaredMethod("getBonusMultiplier").invoke(this));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    // ... presumably lots of other code ...
}

final class SpecialEmployee extends RegularEmployee {

    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".03");
    }
}

public class StaticTestCoolMain {

    static public void main(String[] args) {
        RegularEmployee Alan = new RegularEmployee();
        System.out.println(Alan.calculateBonus());
        System.out.println(Alan.calculateOverridenBonus());
        SpecialEmployee Bob = new SpecialEmployee();
        System.out.println(Bob.calculateBonus());
        System.out.println(Bob.calculateOverridenBonus());
    }
}

结果输出:

0.02
0.02
0.02
0.03

我们想要达到的目标:)

即使我们将第三个变量 Carl 声明为 RegularEmployee 并将其分配给 SpecialEmployee 的实例,我们仍然会在第一种情况下调用 RegularEmployee 方法,在第二种情况下调用 SpecialEmployee 方法

RegularEmployee Carl = new SpecialEmployee();

System.out.println(Carl.calculateBonus());
System.out.println(Carl.calculateOverridenBonus());

看看输出控制台:

0.02
0.03

;)


是的,反射几乎是人们唯一能做的事情——但问题不完全是这个——尽管在这里有它很有用
这个答案是迄今为止我在所有 Java 主题中看到的最大的 hack。读起来很有趣:)
L
Lawrence Dol

静态方法被 JVM 视为全局方法,根本没有绑定到对象实例。

如果您可以从类对象调用静态方法(例如在 Smalltalk 之类的语言中),这在概念上是可能的,但在 Java 中并非如此。

编辑

你可以重载静态方法,没关系。但是你不能重写一个静态方法,因为类没有一流的对象。您可以在运行时使用反射来获取对象的类,但您获取的对象与类层次结构不平行。

class MyClass { ... }
class MySubClass extends MyClass { ... }

MyClass obj1 = new MyClass();
MySubClass obj2 = new MySubClass();

ob2 instanceof MyClass --> true

Class clazz1 = obj1.getClass();
Class clazz2 = obj2.getClass();

clazz2 instanceof clazz1 --> false

您可以对课程进行反思,但它止步于此。您不使用 clazz1.staticMethod() 调用静态方法,而是使用 MyClass.staticMethod()。静态方法不绑定到对象,因此在静态方法中没有 thissuper 的概念。静态方法是全局函数;因此,也没有多态性的概念,因此,方法覆盖没有意义。

但是,如果 MyClass 是运行时调用方法的对象,就像在 Smalltalk 中那样(或者可能是一条评论建议的 JRuby,但我对 JRuby 一无所知),这可能是可能的。

哦,是的……还有一件事。您可以通过对象 obj1.staticMethod() 调用静态方法,但实际上是 MyClass.staticMethod() 的语法糖,应该避免。它通常会在现代 IDE 中引发警告。我不知道他们为什么允许这条捷径。


甚至许多像 Ruby 这样的现代语言都有类方法并允许覆盖它们。
类在 Java 中确实作为对象存在。参见“类”类。我可以说 myObject.getClass() ,它会返回一个适当的类对象的实例。
你只得到类的“描述”——而不是类本身。但差异是微妙的。
您仍然有类,但它隐藏在 VM 中(靠近类加载器),用户几乎无法访问它。
要正确使用 clazz2 instanceof clazz1,您可以改用 class2.isAssignableFrom(clazz1),我相信这会在您的示例中返回 true。
J
Jayanga Kaushalya

dynamic dispatching 使方法覆盖成为可能,这意味着对象的声明类型并不决定其行为,而是决定其运行时类型:

Animal lassie = new Dog();
lassie.speak(); // outputs "woof!"
Animal kermit = new Frog();
kermit.speak(); // outputs "ribbit!"

尽管 lassiekermit 都被声明为 Animal 类型的对象,但它们的行为(方法 .speak())会有所不同,因为动态调度只会在运行时将 bind 方法调用 .speak() 调用到实现 -不是在编译时。

现在,static 关键字开始变得有意义了:“静态”一词是“动态”的反义词。所以您不能覆盖静态方法的原因是因为没有静态成员的动态调度 - 因为静态字面意思是“非动态”。如果他们动态调度(因此可以被覆盖),static 关键字就不再有意义了。


我不认为,人们应该通过其内部实现来争论“静态”与“动态”。那是一种语言的 API 关键字。程序员在期待什么?静态方法在运行时返回常量结果(对于特定类)=> 静态。成员函数返回结果,取决于实例的变量(可以在运行时更改)=> 动态的。
Y
Yoon5oo

是的。实际上Java允许覆盖静态方法,理论上如果你在Java中覆盖静态方法,它会编译和运行顺利,但它会失去Java的基本属性多态性。您将在各处阅读到无法尝试自己编译和运行。你会得到你的答案。例如,如果您有类 Animal 和一个静态方法 eat(),并且您在其子类中重写该静态方法,则将其称为 Dog。然后,当您将 Dog 对象分配给动物引用并根据 Java Dog 的eat() 调用eat() 时,应该调用但在静态覆盖动物的eat() 中将被调用。

class Animal {
    public static void eat() {
        System.out.println("Animal Eating");
    }
}

class Dog extends Animal{
    public static void eat() {
        System.out.println("Dog Eating");
    }
}

class Test {
    public static void main(String args[]) {
       Animal obj= new Dog();//Dog object in animal
       obj.eat(); //should call dog's eat but it didn't
    }
}


Output Animal Eating

根据Java的多态原理,输出应该是Dog Eating
但是结果不同,因为支持多态Java使用后期绑定,这意味着方法只在运行时被调用,而不是在静态方法的情况下.在静态方法中,编译器在编译时而不是运行时调用方法,所以我们根据引用而不是根据对象获取方法,引用 a 包含这就是为什么你可以说实际上它支持静态覆盖,但理论上,它不不。


从对象调用静态方法是不好的做法。
K
Kevin Brock

在 Java(和许多 OOP 语言,但我不能代表所有人;有些根本没有静态)中,所有方法都有一个固定的签名 - 参数和类型。在虚方法中,第一个参数是隐含的:对对象本身的引用,当从对象内部调用时,编译器会自动添加 this

静态方法没有区别——它们仍然有一个固定的签名。但是,通过声明静态方法,您已明确声明编译器不得在该签名的开头包含隐含的对象参数。因此,调用 this 的任何其他代码都不得尝试将对对象的引用放在堆栈上。如果它确实这样做了,那么方法执行将不起作用,因为参数将位于错误的位置 - 移位了 - 在堆栈上。

由于两者之间的这种差异;虚拟方法总是引用上下文对象(即this),因此可以引用堆内属于该对象实例的任何内容。但是对于静态方法,由于没有传递引用,因此该方法无法访问任何对象变量和方法,因为上下文是未知的。

如果您希望 Java 更改定义以便为每个方法(静态或虚拟)传递对象上下文,那么您本质上将只有虚拟方法。

正如有人在对该操作的评论中所问的那样 - 您想要此功能的原因和目的是什么?

我不太了解Ruby,正如OP提到的那样,我做了一些研究。我看到在 Ruby 中,类确实是一种特殊的对象,可以创建(甚至是动态地)新方法。类是 Ruby 中的完整类对象,它们不在 Java 中。这只是您在使用 Java(或 C#)时必须接受的。这些不是动态语言,尽管 C# 正在添加某些形式的动态语言。实际上,Ruby 没有我能找到的“静态”方法——在这种情况下,这些是单例类对象上的方法。然后,您可以使用新类覆盖此单例,并且前一个类对象中的方法将调用新类中定义的方法(对吗?)。因此,如果您在原始类的上下文中调用一个方法,它仍然只会执行原始静态,但在派生类中调用一个方法,将调用来自父类或子类的方法。有趣,我可以从中看到一些价值。它需要不同的思维模式。

由于您使用的是 Java,因此您需要适应这种做事方式。他们为什么这样做?好吧,可能是为了根据可用的技术和理解来提高当时的性能。计算机语言在不断发展。回到足够远的地方,没有像OOP这样的东西。未来,还会有其他新的想法。

编辑:另一条评论。现在我看到了差异,并且作为我自己的 Java/C# 开发人员,我可以理解为什么如果您来自像 Ruby 这样的语言,您从 Java 开发人员那里得到的答案可能会令人困惑。 Java static 方法与 Ruby class 方法不同。 Java 开发人员将很难理解这一点,相反,那些主要使用 Ruby/Smalltalk 之类的语言工作的人也一样。 Java 也使用“类方法”作为谈论静态方法的另一种方式,但 Ruby 对同一术语的使用不同,我可以看到这也会造成极大的困惑。 Java 没有 Ruby 风格的类方法(抱歉); Ruby 没有 Java 风格的静态方法,它们实际上只是旧的过程风格的函数,就像在 C 中发现的那样。

顺便说一句 - 谢谢你的问题!今天我学到了一些关于类方法(Ruby 风格)的新知识。


当然,Java 没有理由不能将“类”对象作为隐藏参数传递给静态方法。它只是不是为了做到这一点而设计的。
R
Rupesh Yadav

嗯……如果你从 Java 中被覆盖的方法应该如何表现的角度来思考,答案是否定的。但是,如果您尝试覆盖静态方法,则不会出现任何编译器错误。这意味着,如果您尝试覆盖,Java 不会阻止您这样做;但你肯定不会得到与非静态方法相同的效果。 Java 中的覆盖仅仅意味着特定方法将根据对象的运行时类型而不是编译时类型来调用(这是覆盖静态方法的情况)。好吧......任何猜测他们为什么表现奇怪的原因?因为它们是类方法,因此在编译时总是只使用编译时类型信息来解决对它们的访问。使用对象引用访问它们只是 Java 设计者给予的额外自由,我们当然不应该只在他们限制它时才考虑停止这种做法:-)

示例:让我们尝试看看如果我们尝试覆盖静态方法会发生什么:-

class SuperClass {
// ......
public static void staticMethod() {
    System.out.println("SuperClass: inside staticMethod");
}
// ......
}

public class SubClass extends SuperClass {
// ......
// overriding the static method
public static void staticMethod() {
    System.out.println("SubClass: inside staticMethod");
}

// ......
public static void main(String[] args) {
    // ......
    SuperClass superClassWithSuperCons = new SuperClass();
    SuperClass superClassWithSubCons = new SubClass();
    SubClass subClassWithSubCons = new SubClass();

    superClassWithSuperCons.staticMethod();
    superClassWithSubCons.staticMethod();
    subClassWithSubCons.staticMethod();
    // ...
}
}

输出:-
SuperClass: inside staticMethod
SuperClass: inside staticMethod
SubClass: inside staticMethod

注意输出的第二行。如果 staticMethod 被覆盖,这一行应该与第三行相同,因为我们将运行时类型的对象上的“staticMethod()”调用为“SubClass”而不是“SuperClass”。这证实了静态方法总是只使用它们的编译时类型信息来解析。


P
Patlatus

我对 Jay 的评论 (https://stackoverflow.com/a/2223803/1517187) 赞不绝口。
我同意这是 Java 的糟糕设计。
正如我们在之前的评论中看到的,许多其他语言都支持覆盖静态方法。我觉得 Jay 也和我一样从 Delphi 来到 Java。
Delphi(Object Pascal)是在 Java 之前实现 OOP 的语言之一,也是最早用于商业应用程序开发的语言之一。
很明显,许多人们有使用这种语言的经验,因为过去它是编写商业 GUI 产品的唯一语言。而且 - 是的,我们可以在 Delphi 中覆盖静态方法。实际上,Delphi 中的静态方法称为“类方法”,而 Delphi 有不同概念的“Delphi 静态方法”,它们是具有早期绑定的方法。要覆盖必须使用后期绑定的方法,请声明“虚拟”指令。所以它非常方便和直观,我希望在 Java 中做到这一点。


Delphi(Object Pascal)是第一种实现 OOP 的语言。 Simula、SmallTalk 和其他支持 OOP 的公司都出现在 Delphi 之前。
@WJS 我从未听说过 Simula 或其他支持 OOP 的语言。我认为 Delphi 是第一种实现 OOP 的语言,用于商业应用程序开发并且广为人知。看来我需要改写这句话
L
Lars

一般来说,允许“覆盖”静态方法是没有意义的,因为没有好的方法来确定在运行时调用哪个方法。以 Employee 为例,如果我们调用 RegularEmployee.getBonusMultiplier() - 应该执行哪个方法?

在 Java 的情况下,可以想象一种语言定义,只要通过对象实例调用静态方法,就可以“覆盖”它们。然而,这一切只会重新实现常规的类方法,为语言增加冗余,而不会真正增加任何好处。


直觉上,我认为它应该像虚函数一样工作。如果 B 扩展 A 并且 A 和 B 都有名为 doStuff 的虚函数,则编译器知道 A 的实例应该使用 A.doStuff 而 B 的实例应该使用 B.doStuff。为什么它不能对静态函数做同样的事情?毕竟,编译器知道每个对象是哪个类的实例。
Erm ...杰伊,不需要(通常不)在实例上调用静态方法...
@meriton,但这样就更容易了,不是吗?如果使用类名调用静态方法,您将使用适合该类的方法。
但是,什么是压倒一切的为你做。如果您调用 A.doStuff() 它应该使用在“B 扩展 A”中被覆盖的版本还是在“C 扩展 A”中被覆盖的版本。如果您有 C 或 B,那么无论如何您都在调用这些版本……无需覆盖。
@meriton:静态方法通常不会被实例调用,但我认为这是因为考虑到 Java 的当前设计,这样的调用没有任何用处!我建议替代设计可能是一个更好的主意。顺便说一句,在非常真实的意义上,静态函数是通过一个实例非常常规地调用的:当您从虚拟函数中调用静态函数时。然后你隐式地得到this.function(),即当前实例。
A
Athens Holloway

为实例成员保留覆盖以支持多态行为。静态类成员不属于特定实例。相反,静态成员属于该类,因此不支持覆盖,因为子类仅继承受保护和公共实例成员,而不是静态成员。您可能希望定义接口和研究工厂和/或策略设计模式来评估替代方法。


您是否没有阅读已经涵盖此问题的任何其他答案,并清楚地表明这些不足以在概念级别上忽略压倒一切的静态因素。我们知道它不起作用。期望覆盖静态方法是完全“干净的,而且在许多其他语言中确实是可能的。
理查德,让我们假设一下,当我 4 年前回答这个问题时,大多数这些答案都没有发布,所以别傻了!没有必要断言我没有仔细阅读。此外,您是否没有读到我们只是在讨论 Java 的覆盖。谁在乎其他语言的可能性。这无关紧要。去其他地方巨魔。您的评论不会为该线程增加任何价值。
A
Arnab

通过覆盖,我们可以根据对象类型创建多态性。静态方法与对象无关。所以java不能支持静态方法覆盖。


Y
Yoon5oo

通过覆盖,您可以实现动态多态性。当您说覆盖静态方法时,您尝试使用的词是矛盾的。

静态说 - 编译时,覆盖用于动态多态性。两者性质相反,因此不能一起使用。

当程序员使用对象并访问实例方法时,就会出现动态多态行为。 JRE 将根据您使用的对象类型映射不同类的不同实例方法。

当你说覆盖静态方法时,我们会通过类名来访问静态方法,类名会在编译时链接,所以没有运行时链接方法与静态方法的概念。所以术语“覆盖”静态方法本身没有任何意义。

注意:即使你使用一个对象访问一个类方法,java 编译器仍然有足够的智能找到它,并且会进行静态链接。


这在很多情况下是不正确的,在运行时静态方法实际上是从包含类的实例调用的,因此确定要调用函数的哪些实例是完全可行的。
static 并不意味着编译时间, static 意味着它绑定到类而不是任何特定对象。它比创建类工厂更有意义,除了静态 Box.createBoxBoxFactory.createBox 更有意义,并且在需要错误检查构造而不抛出异常时是一种不可避免的模式(构造函数不能失败,它们可以只杀死进程/抛出异常),而静态方法可以在失败时返回 null,甚至接受成功/错误回调以编写类似 hastebin.com/codajahati.java 的内容。
x
xuesheng

Java 中的覆盖仅仅意味着特定方法将基于对象的运行时类型而不是它的编译时类型被调用(这是覆盖静态方法的情况)。由于静态方法是类方法,它们不是实例方法,因此它们与引用指向哪个对象或实例这一事实无关,因为由于静态方法的性质,它属于特定类。您可以在子类中重新声明它,但该子类对父类的静态方法一无所知,因为正如我所说,它仅特定于声明它的那个类。使用对象引用访问它们只是 Java 设计者给予的额外自由,我们当然不应该只在他们限制更多细节和示例 http://faisalbhagat.blogspot.com/2014/09/method-overriding-and-method-hiding.html 时才考虑停止这种做法


S
Sam Harwell

重写静态方法有什么好处。您不能通过实例调用静态方法。

MyClass.static1()
MySubClass.static1()   // If you overrode, you have to call it through MySubClass anyway.

编辑:似乎通过语言设计中的不幸疏忽,您可以通过实例调用静态方法。一般没有人这样做。我的错。


“你不能通过实例调用静态方法” 实际上,Java 的一个怪癖是你可以通过实例调用静态方法,尽管这是一个非常糟糕的主意。
事实上,Java 确实允许通过实例访问静态成员:请参阅 Static variable in Java
但是,当您这样做时,适当的现代 IDE 会生成警告,因此至少您可以捕获它,而 Oracle 可以保持向后兼容性。
能够通过实例调用静态方法在概念上没有任何问题。这是为了狡辩而狡辩。为什么像 Date 实例这样的东西不应该调用它自己的静态方法,通过调用接口将实例数据传递给函数?
@RichieHH 这不是他们在争论的问题。问题是为什么允许调用 variable.staticMethod() 而不是 Class.staticMethod(),其中 variable 是声明类型为 Class 的变量。我同意这是糟糕的语言设计。
g
g1ji

这个问题的答案很简单,标记为静态的方法或变量只属于类,因此静态方法不能在子类中继承,因为它们只属于超类。


嗨 G4uKu3_Gaurav。感谢您决定做出贡献。但是,我们通常期望得到比这更长、更详细的答案。
@DJClayworth 您应该点击此链接获取详细答案geeksforgeeks.org/…
感谢您的链接。实际上,我来这里是为了帮助网站的新手,向不习惯的人解释该网站是如何工作的,而不是因为我需要对这个问题的答案。
M
MT4000

简单的解决方案:使用单例实例。它将允许覆盖和继承。

在我的系统中,我有 SingletonsRegistry 类,它返回传递的类的实例。如果未找到实例,则创建它。

Haxe语言类:

package rflib.common.utils;
import haxe.ds.ObjectMap;



class SingletonsRegistry
{
  public static var instances:Map<Class<Dynamic>, Dynamic>;

  static function __init__()
  {
    StaticsInitializer.addCallback(SingletonsRegistry, function()
    {
      instances = null;
    });

  } 

  public static function getInstance(cls:Class<Dynamic>, ?args:Array<Dynamic>)
  {
    if (instances == null) {
      instances = untyped new ObjectMap<Dynamic, Dynamic>();      
    }

    if (!instances.exists(cls)) 
    {
      if (args == null) args = [];
      instances.set(cls, Type.createInstance(cls, args));
    }

    return instances.get(cls);
  }


  public static function validate(inst:Dynamic, cls:Class<Dynamic>)
  {
    if (instances == null) return;

    var inst2 = instances[cls];
    if (inst2 != null && inst != inst2) throw "Can\'t create multiple instances of " + Type.getClassName(cls) + " - it's singleton!";
  }

}

非常酷,这是我第一次听说 Haxe 编程语言 :)
这在 Java 中作为类本身的静态方法更好地实现; Singleton.get()。注册表只是样板开销,它排除了类上的 GC。
你是对的,这是一个经典的解决方案。我不完全记得我为什么选择注册表,可能有一些思想框架导致了这个结果。
R
Rohit Gaikwad

静态方法、变量、块或嵌套类属于整个类而不是对象。

Java中的方法用于公开对象/类的行为。在这里,由于方法是静态的(即,静态方法仅用于表示类的行为。)更改/覆盖整个类的行为将违反面向对象编程的基本支柱之一的现象,即高内聚. (请记住,构造函数是 Java 中的一种特殊方法。)

高凝聚力——一个班级应该只有一个角色。例如:一个汽车类应该只产生汽车对象而不是自行车、卡车、飞机等。但是汽车类可能有一些只属于它自己的特征(行为)。

因此,在设计java编程语言时。语言设计者认为允许开发人员仅通过使方法本质上为静态来将类的某些行为保留给自身。

下面的代码尝试覆盖静态方法,但不会遇到任何编译错误。

public class Vehicle {
static int VIN;

public static int getVehileNumber() {
    return VIN;
}}

class Car extends Vehicle {
static int carNumber;

public static int getVehileNumber() {
    return carNumber;
}}

这是因为,这里我们没有覆盖一个方法,而只是重新声明它。 Java 允许重新声明方法(静态/非静态)。

从 Car 类的 getVehileNumber() 方法中删除 static 关键字将导致编译错误,因为我们试图更改仅属于 Vehicle 类的静态方法的功能。

此外,如果 getVehileNumber() 被声明为 final,那么代码将无法编译,因为 final 关键字限制了程序员重新声明该方法。

public static final int getVehileNumber() {
return VIN;     }

总的来说,这取决于软件设计人员在哪里使用静态方法。我个人更喜欢使用静态方法来执行一些操作,而无需创建任何类的实例。其次,向外界隐藏一个类的行为。


Y
Yoon5oo

这是一个简单的解释。静态方法与类相关联,而实例方法与特定对象相关联。覆盖允许调用与特定对象关联的覆盖方法的不同实现。因此,重写静态方法是违反直觉的,该方法甚至与对象无关,而是首先与类本身相关联。因此,静态方法不能根据调用它的对象而被覆盖,它将始终与创建它的类相关联。


在 IBox 界面中有一个 public abstract IBox createBox(); 有什么反直觉的? Box 可以实现 IBox 以覆盖 createBox,并让对象的创建导致有效的 IBox,否则返回 null。构造函数不能返回“null”,因此你不得不(1)在任何地方使用异常(我们现在做的),或者(2)创建工厂类来做我之前说的,但对新手和专家都没有意义Java的(我们现在也这样做)。静态未实现的方法解决了这个问题。
a
amandeep1991

现在看到上面的回答大家都知道我们不能覆盖静态方法,但是不要误解了从子类访问静态方法的概念。

如果这个静态方法没有被子类中定义的新静态方法隐藏,我们可以使用子类引用访问超类的静态方法。

例如,请参见下面的代码:-

public class StaticMethodsHiding {
    public static void main(String[] args) {
        SubClass.hello();
    }
}


class SuperClass {
    static void hello(){
        System.out.println("SuperClass saying Hello");
    }
}


class SubClass extends SuperClass {
    // static void hello() {
    // System.out.println("SubClass Hello");
    // }
}

输出:-

SuperClass saying Hello

有关在子类中隐藏静态方法的详细信息,请参阅 Java oracle 文档并搜索您可以在子类中做什么。

谢谢


p
pop stack

以下代码表明这是可能的:

class OverridenStaticMeth {   

static void printValue() {   
System.out.println("Overriden Meth");   
}   

}   

public class OverrideStaticMeth extends OverridenStaticMeth {   

static void printValue() {   
System.out.println("Overriding Meth");   
}   

public static void main(String[] args) {   
OverridenStaticMeth osm = new OverrideStaticMeth();   
osm.printValue();   

System.out.println("now, from main");
printValue();

}   

} 

不,它没有; osm 的静态声明类型是 OverridenStaticMeth 而不是 OverrideStaticMeth
另外,我会尽量避免在编程 时使用这么多的冰毒;