为什么Java不允许重写静态方法?
为什么不可能覆盖静态方法?
如果可能的话,请使用一个例子。
重写取决于有一个类的实例。 多态性的要点是,你可以继承一个类的子类,并且实现这些子类的对象对于在超类中定义的相同方法(并在子类中重写)将具有不同的行为。 静态方法不与任何类的任何实例关联,因此这个概念不适用。
推动Java设计的两个考虑因素影响了这一点。 其中一个问题是性能问题:Smalltalk对它的速度太慢了(垃圾回收和多态调用是其中的一部分),Java的创建者决心避免这种情况。 另一个决定是Java的目标受众是C ++开发人员。 使得静态方法的工作方式具有熟悉C ++程序员的好处,而且速度也非常快,因为不需要等到运行时才能确定要调用的方法。
我个人认为这是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处理静态的问题,我的回答是,“呃,当然。” 正如我在我原来的帖子中所说的那样,它不起作用。 但是,如果你的意思是说这是一种糟糕的设计,从某种意义上讲,这种语言在某种程度上会出现根本性的错误,也就是说静态可以像虚拟功能一样被覆盖,这会以某种方式引入歧义,或者不可能有效地实施或者这样做,我回答说:“为什么?这个概念有什么问题?”
我想我给出的例子是一件很自然的事情,想要做。 我有一个具有不依赖于任何实例数据的函数的类,并且我可能非常合理地想独立于实例调用该函数,并且希望从实例方法中调用。 为什么这不适用? 多年来,我遇到过这种情况,相当多次。 在实践中,我通过使函数变成虚拟来解决它,然后创建一个静态方法,其唯一目的是将静态方法用虚拟实例传递给虚拟方法。 这似乎是一个非常迂回的方式去那里。
简短的答案是:完全可能,但Java不这样做。
以下是一些说明Java中当前状态的代码:
File 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();
}
}
如果你运行这个(我使用Java 1.6在Mac上完成,使用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()
调用取代(通常带有警告)的语言中有这种“怪癖”。 [注意: ObjectClass
是obj
的编译时类型]这些将是以这种方式重写的好候选对象,采用运行时类型的obj
。
如果我们这样做了,它会使方法体更难阅读:父类中的静态调用可能会动态“重新路由”。 为了避免这种情况,我们必须使用类名调用静态方法 - 这使得调用更明显地通过编译时类型层次结构解决(如现在)。
调用静态方法的其他方面都比较棘手: this.staticMethod()
应该意味着相同obj.staticMethod()
取的运行时类型this
。 然而,这可能会导致现有程序的一些头痛,这些程序调用(显然是本地的)静态方法而没有装饰(这可以等价于this.method()
)。
那么staticMethod()
调用staticMethod()
呢? 我建议他们和今天一样,并使用当地的上下文来决定做什么。 否则会产生很大的混乱。 当然,这意味着如果method
是非静态方法,则method()
将意味着this.method()
,而ThisClass.method()
if method
是静态方法。 这是混淆的另一个来源。
其他考虑
如果我们改变了这种行为(并且使得静态调用可能是动态的非本地的),我们可能希望重新审视final
, private
和protected
的含义作为类的static
方法的限定符。 然后,我们都必须习惯这样一个事实: private static
和public final
方法不会被覆盖,因此可以在编译时安全地解决,并且作为本地引用来读取是“安全的”。
上一篇: Why doesn't Java allow overriding of static methods?
下一篇: What is method hiding in Java? Even the JavaDoc explanation is confusing