重写依赖于拥有类的实例。 多态性的要点在于,您可以对一个类进行子类划分,而实现那些子类的对象对于在超类中定义的相同方法(并在子类中重写)将具有不同的行为。 静态方法不与类的任何实例关联,因此该概念不适用。
有两个因素驱动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");
}
}
这段代码不会像您预期的那样工作。 也就是说,特殊员工和普通员工一样可以获得2%的奖金。 但是如果你去掉了“静态”的S,那么专门雇员就会得到3%的奖金。
(诚然,这个例子的编码风格很差,因为在现实生活中,您可能希望奖金乘数位于数据库中的某个位置,而不是硬编码。但这只是因为我不想用大量与主题无关的代码来阻碍这个例子。)
在我看来,你可能想让getBonusMultiplier成为静态的。 也许您希望能够显示所有员工类别的奖金乘数,而不需要在每个类别中都有一个员工实例。 搜索这样的示例实例有什么意义? 如果我们正在创建一个新的员工类别,但还没有为其分配任何员工,该怎么办? 这在逻辑上是一个静态函数。
但不管用。
而且是的,是的,我可以想出任何数量的方法来重写上面的代码,让它工作。 我的观点并不是说它创造了一个无法解决的问题,而是说它为粗心的程序员创造了一个陷阱,因为这种语言的行为并不像我认为一个通情达理的人所期望的那样。
如果我尝试为OOP语言编写一个编译器,我可能很快就会明白为什么实现它以便可以重写静态函数是困难的或者是不可能的。
或者,Java这样做或许有一些很好的原因。 有人能指出这种行为的优点吗?某种类型的问题会因此变得更容易吗? 我的意思是,不要只是指给我看Java语言规范,然后说“看,这是记录了它的行为”。 我知道这点。 但是,有没有一个充分的理由说明它应该这样做呢? (除了明显的“让它正常工作太难了”……)
更新
@Vickirk:如果你的意思是说这是“糟糕的设计”,因为它不适合Java处理静力学的方式,我的回复是,“嗯,嗯,当然。” 就像我在我原来的帖子里说的那样,它不起作用。 但是,如果你的意思是这是一个糟糕的设计,在这个设计工作的语言中会有一些根本性的错误,例如,静态数据可以像虚拟函数一样被重写,这会以某种方式引入歧义,或者不可能有效地实现,或者诸如此类,我会回答,“为什么?这个概念有什么问题?”
我觉得我举的例子是想做的一件很自然的事情。 我有一个类,它有一个不依赖于任何实例数据的函数,我可能非常合理地希望独立于实例调用它,也希望从实例方法中调用它。 为什么这不起作用呢? 这些年来我遇到过很多次这种情况。 在实践中,我通过将函数设为虚函数,然后创建一个静态方法来解决这个问题,该方法的唯一目的就是成为一个静态方法,它将调用传递给虚拟方法,并带有一个虚拟实例。 那似乎是一条很迂回的路。
简短的回答是:完全有可能,但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()
调用所取代(通常带有警告)。 [注意:objectclass
是obj
的编译时类型。] 这些将是以这种方式重写的良好候选者,采用obj
的运行时类型。
如果我们这样做,将使方法体更难读取:父类中的静态调用可能会被动态地“重新路由”。 为了避免这种情况,我们必须使用类名调用静态方法--这使得调用更明显地通过编译时类型层次结构来解析(就像现在一样)。
调用静态方法的其他方法更加棘手:this.staticMethod()
的含义应该与obj.staticMethod()
相同,其运行时类型为this
。 但是,这可能会给现有程序带来一些麻烦,这些程序调用(显然是本地的)静态方法而不加修饰(这可以说相当于this.method()
)。
那么,不加修饰的调用staticmethod()
又如何呢? 我建议他们像今天这样做,使用本地类上下文来决定要做什么。 否则就会引起很大的混乱。 当然,如果method
是非静态方法,则method()
将意味着this.method()
,如果method
是静态方法,则thisclass.method()
将意味着this.method()
。 这是另一个混乱的根源。
如果我们改变了这种行为(并使静态调用潜在地动态地非本地),我们可能需要重新审视final
,private
和protected
作为类的statice
方法的限定符的含义。 然后,我们都必须习惯这样一个事实,即private static
和public final
方法不会被重写,因此可以在编译时安全地解析,并且可以“安全”地作为本地引用读取。