为什么不能覆盖静态方法?
如果可以的话,请举一个例子。
覆盖取决于类的实例。多态的重点在于您可以对类进行子类化,而实现这些子类的对象将为超类中定义的相同方法具有不同的行为(并在子类中覆盖)。静态方法与类的任何实例都没有关联,因此该概念不适用。
Java设计时考虑了两个方面对此产生了影响。一方面是性能方面的担忧:Smalltalk受到了太慢的批评(垃圾收集和多态调用是其中的一部分),而Java的创建者则决心避免这种情况。另一个考虑因素是Java的目标受众是C++开发人员。使静态方法按照现有方式工作的好处是对于C++程序员来说非常熟悉,并且非常快速,因为无需等待运行时来确定要调用哪个方法。
objects
)的东西允许方法重载。 - user166390obj.staticMethod()
)调用静态方法时,这种情况就会发生——这是允许的并且使用编译时类型。当静态调用位于类的非静态方法中时,“当前”对象可能是该类的派生类型,但是派生类型上定义的静态方法不予考虑(它们在运行时类型层级中)。 - Steve Powell我个人认为这是 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%的奖金。但是如果您删除"static",则SpecialEmployee将获得3%的奖金。
(诚然,这个例子的编码风格很差,因为在现实生活中,您可能希望奖金乘数在某个数据库中而不是硬编码。但这只是因为我不想用许多与重点无关的代码降低示例质量。)
我认为,使getBonusMultiplier静态化是很有道理的。也许您希望能够显示所有类别员工的奖金乘数,而无需在每个类别中都拥有员工实例。寻找此类示例实例的目的是什么?如果我们正在创建新的员工类别,并且尚未分配任何员工怎么办?这种情况下,它就变成了一个静态函数。
但它没有起作用。
是的,我可以想到许多重写上述代码以使其正常工作的方法。我的观点不是它会导致一个无法解决的问题,而是它会给不谨慎的程序员设置陷阱,因为该语言的行为并不像我认为合理的人所期望的那样。
也许,如果我尝试编写面向对象语言的编译器,我很快就会看到为什么实现静态函数可以被覆盖会很困难或不可能。
或者也许Java之所以表现出这种方式,有一些很好的原因。有人能指出这种行为的优点吗?是否有某些问题类别因此变得更容易了?我的意思是,不要只是指向Java语言规范并说"看,这是它的行为文档。" 我知道这个。但是有没有一个好的理由它应该表现出这种方式?(除了明显的"使它正常工作过于困难"...)
更新
@VicKirk:如果您的意思是这是“糟糕的设计”,因为它不符合Java处理静态的方式,我的回答是:“嗯,当然。”正如我在原始帖子中所说,它不起作用。但是如果您的意思是,在这种情况下存在某些根本性问题,即静态可以像虚函数一样被覆盖,这将引入歧义或无法有效实现等问题时,我会回答:“为什么?这个概念有什么问题吗?”
我认为,我给出的例子是一个非常自然的想法。我有一个类,它有一个函数,该函数不依赖于任何实例数据,并且我可能非常合理地希望在没有实例的情况下调用它,同时还希望从实例方法中调用它。为什么不能这样做?多年来,我遇到过这种情况。实际上,我通过使函数虚拟并创建一个唯一目的是成为静态方法的静态方法,然后使用虚拟方法传递呼叫及其虚拟方法,其中包含一个虚拟实例。这似乎是一个非常迂回的方法。
someStatic()
的类A,并且B继承自A,则B.someMethod()
将绑定到A中的该方法。如果随后向B中添加someStatic()
方法,则调用代码仍将调用A.someStatic()
,直到重新编译调用代码。同时,令人惊讶的是,bInstance.someStatic()
使用bInstance
声明的类型而不是运行时类型,因为它在编译时而不是链接时进行绑定,所以如果存在B.someStatic()
,则A bInstance; ... bInstance.someStatic()
将调用A.someStatic()。 - Lawrence Dol简短回答是:它完全可行,但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
是静
事实上我们错了。
尽管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
;)
静态方法在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()
。静态方法不绑定到对象,因此在静态方法中没有this
或super
的概念。静态方法是全局函数;因此也没有多态的概念,因此方法覆盖没有意义。
但是如果MyClass
在运行时是一个对象,你可以像Smalltalk那样调用它的方法(或者可能是JRuby,正如一条评论所提到的,但我对JRuby一无所知)。
哦对了...还有一件事。你可以通过对象obj1.staticMethod()
调用静态方法,但这实际上是MyClass.staticMethod()
的语法糖,应该避免使用。在现代IDE中,通常会引发警告。我不知道为什么他们曾经允许这种快捷方式。
clazz2 instanceof clazz1
,您可以改用 class2.isAssignableFrom(clazz1)
,我相信这在您的示例中会返回 true。 - Simon Forsberg方法重写是通过动态分派实现的,这意味着对象的声明类型并不决定其行为,而是由其运行时类型决定:
Animal lassie = new Dog();
lassie.speak(); // outputs "woof!"
Animal kermit = new Frog();
kermit.speak(); // outputs "ribbit!"
尽管lassie
和kermit
都被声明为Animal
类型的对象,但它们的行为(方法.speak()
)不同,因为动态分派仅会在运行时bind方法调用.speak()
到一个实现 - 而不是在编译时。
现在,这里就是static
关键字开始有意义的地方:单词“static”是“dynamic”的反义词。因此,您无法覆盖静态方法的原因是因为静态成员上没有动态分派 - 因为静态字面上意味着“不动态”。如果它们是动态分派的(因此可以被覆盖),那么static
关键字就不再有意义了。
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
this
。this
)的引用,因此可以引用属于该对象实例的堆中的任何内容。但是对于静态方法,由于未传递引用,该方法无法访问任何对象变量和方法,因为上下文是未知的。static
方法与Ruby的class
方法不同。对于Java开发人员来说,理解这一点将很困难,反之亦然,对于大多使用Ruby/Smalltalk等语言的人也是如此。我可以看出,这也会极大地混淆事实,即Java还使用“类方法”作为另一种谈论静态方法的方式,但是Ruby会以不同的方式使用相同的术语。Java没有Ruby风格的类方法(很抱歉); Ruby没有Java风格的静态方法,这实际上只是旧的过程式风格函数,就像C中找到的那样。
顺便说一下-感谢您的问题!今天我学到了关于类方法(Ruby风格)的新知识。
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已被覆盖,这一行应与第三行相同,因为我们正在使用“SubClass”作为运行时类型的对象调用“staticMethod()”,而不是“SuperClass”。这证实了静态方法始终仅使用其编译时类型信息进行解析。
我喜欢并赞同Jay的评论(https://dev59.com/r3E95IYBdhLWcg3wn_f2#2223803)。
我认为这是Java的糟糕设计。许多其他编程语言都支持重写静态方法,正如我们在之前的评论中所看到的。
我感觉Jay和我一样,也是从Delphi转向了Java。
Delphi(Object Pascal)是在Java之前实现面向对象编程的语言之一,也是最早用于商业应用程序开发的语言之一。
很明显,许多人都有使用过这种语言的经验,因为过去它是唯一用于编写商业GUI产品的语言。是的,在Delphi中,我们可以重写静态方法。实际上,Delphi中的静态方法称为“类方法”,而Delphi具有不同的“Delphi静态方法”概念,这些方法采用早期绑定方式。要重写方法,您必须使用后期绑定,并声明“虚拟”指令。因此,这非常方便和直观,我希望Java也能实现这一点。
Parent p = new Child()
,然后编写了p.childOverriddenStaticMethod()
,编译器也会通过查看引用类型将其解析为Parent.childOverriddenStaticMethod()
。 - Manoj