Why doesn't Java allow overriding of static methods?
为什么不可能重写静态方法?
如果可能,请使用示例。
重写依赖于具有类的实例。多态性的要点是,您可以对一个类进行子类化,实现这些子类的对象对于在超类中定义的相同方法(并在子类中重写)具有不同的行为。静态方法与类的任何实例都没有关联,因此概念不适用。
驱动Java设计的两个因素影响了这一点。一个是对性能的关注:SimalTalk有太多的批评认为它太慢(垃圾收集和多态调用是其中的一部分),Java的创建者决心避免这一点。另一个是Java的目标受众是C++开发人员的决定。使静态方法以它们的方式工作有利于C++程序员的熟悉性,而且速度非常快,因为不必等到运行时才知道要调用哪种方法。
我个人认为这是Java设计中的一个缺陷。是的,是的,我理解非静态方法附加到实例,而静态方法附加到类等。不过,请考虑以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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%的奖金。但是如果你去掉"静态"的,那么特殊员工将得到3%的奖金。
(不可否认,这个例子的编码风格很差,在现实生活中,您可能希望奖金乘数位于某个数据库中,而不是硬编码。但这仅仅是因为我不想用很多与这一点无关的代码来拖住这个例子。)
在我看来,您可能希望使getbonusmultipler静态化是很合理的。也许您希望能够显示所有员工类别的奖金乘数,而不需要在每个类别中都有一个员工实例。搜索此类示例实例的意义是什么?如果我们正在创建一个新的员工类别,但尚未为其分配任何员工呢?这在逻辑上是一个静态函数。
但它不起作用。
是的,是的,我可以想出很多方法来重写上面的代码,使其工作。我的观点不是它会造成一个无法解决的问题,而是它会给粗心大意的程序员制造一个陷阱,因为语言的行为并不像我认为一个理性的人所期望的那样。
也许,如果我试图为OOP语言编写编译器,我会很快明白为什么要实现它以便重写静态函数是困难的或不可能的。
或者也许有一些很好的理由来解释为什么Java会这样做。有人能指出这种行为的好处吗?这种行为使某些问题更容易处理?我的意思是,不要仅仅指向Java语言规范,说"看,这是文件的行为"。我知道。但是有没有一个很好的理由让它这样做呢?(除了明显的"让它正常工作太难"之外…)
更新
@维克尔克:如果你的意思是这是"糟糕的设计",因为它不适合Java如何处理静态,我的回答是,"嗯,当然,DUH。"正如我在我原来的文章中所说的,它是行不通的。但是,如果你的意思是,从某种意义上说,这是一个糟糕的设计,在这种情况下,语言会有一些根本性的错误,即静态可以像虚拟函数一样被重写,这会以某种方式引入歧义,或者不可能有效地实现,或者是这样,我回答说,"为什么?这个概念怎么了?"
我认为我举的例子是一件很自然的事情。我有一个类,它有一个不依赖于任何实例数据的函数,我可能非常合理地希望独立于实例调用它,也希望从实例方法中调用它。为什么这样不行?这些年来,我经常遇到这种情况。在实践中,我通过使函数虚拟化来绕过它,然后创建一个静态方法,它在生活中的唯一目的是成为一个静态方法,通过一个虚拟实例将调用传递给虚拟方法。这似乎是一个非常迂回的方式到达那里。
简短的回答是:这完全是可能的,但Java并没有这么做。
这里有一些代码说明Java中的当前事务状态:
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); } } |
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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)你得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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. |
在这里,唯一可能令人惊讶的案例(以及问题所在)似乎是第一个案例:
运行时类型不用于确定调用哪些静态方法,即使使用对象实例(
最后一个案例是:
从类的对象方法中调用静态方法时,所选的静态方法是从类本身而不是从定义对象运行时类型的类中访问的方法。
使用对象实例调用静态调用在编译时解析,而非静态方法调用在运行时解析。请注意,尽管静态方法是继承自父方法的,但它们不会被子方法覆盖。如果你没有预料到的话,这会是一个惊喜。
从对象方法内调用对象方法调用是使用运行时类型解析的,但是静态(类)方法调用是使用编译时(声明的)类型解析的。
更改规则要更改这些规则,使示例中最后一个调用名为
这很容易实现(如果我们改变了Java:-O),这一点也不完全是不合理的,但是它有一些有趣的考虑。
主要考虑的是,我们需要决定哪些静态方法调用应该这样做。
目前,Java在语言中具有这种"怪癖",EDCOX1调用2调用被EDCOX1调用5调用(通常带有警告)取代。[注:
如果这样做,将使方法体更难读取:父类中的静态调用可能会动态地"重新路由"。为了避免这种情况,我们必须使用类名调用静态方法——这使得调用更明显地通过编译时类型层次结构来解决(现在)。
调用静态方法的其他方法更为复杂:
那么,不加修饰的"EDOCX1"(13)呢?我建议他们像今天这样做,并使用本地类上下文来决定要做什么。否则就会产生巨大的混乱。当然,这意味着如果
如果我们改变了这种行为(并且使静态调用可能是动态的非本地调用),我们可能希望重新访问
实际上我们错了。尽管Java不允许您在默认情况下重写静态方法,但是如果您仔细查看Java中类和方法类的文档,您仍然可以找到一种方法来模拟遵循下列解决方案的静态方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | 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()); } } |
结果输出:
1 2 3 4 | 0.02 0.02 0.02 0.03 |
我们试图达到的目标:)
即使我们将第三个变量Carl声明为RegularEmployee并将其分配给它的SpecialEmployee实例,在第一种情况下我们仍然可以调用RegularEmployee方法,在第二种情况下仍然可以调用SpecialEmployee方法。
1 2 3 4 |
看看输出控制台:
1 2 | 0.02 0.03 |
;)
静态方法被JVM视为全局方法,根本没有绑定到对象实例。
如果你可以从类对象调用静态方法(比如像SimalTalk这样的语言),在概念上是可能的,但Java中不是这样的。
编辑
你可以重载静态方法,没关系。但不能重写静态方法,因为类不是第一类对象。可以在运行时使用反射来获取对象的类,但所获得的对象不与类层次结构平行。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
你可以在课堂上反思,但它就停在那里。您不能通过使用
但如果
哦,是的…还有一件事。您可以通过对象
方法重写通过动态调度实现,这意味着对象的声明类型不确定其行为,而是确定其运行时类型:
1 2 3 4 | Animal lassie = new Dog(); lassie.speak(); // outputs"woof!" Animal kermit = new Frog(); kermit.speak(); // outputs"ribbit!" |
虽然
现在,这里是
对。实际上Java允许重写静态方法,并且在理论上,如果您在Java中重写静态方法,那么它将编译和运行顺利,但它将丢失多态性,这是Java的基本属性。您将在任何地方阅读到您自己编译和运行是不可能的。你会得到你的答案。例如,如果您有一个类animal和一个静态方法eat(),并且您在它的子类中重写了这个静态方法,让它成为dog。然后,当你把狗对象分配给动物参考,并根据Java狗的EAT()调用EAT()时,应该调用,但是在静态覆盖中,动物EAT()将被调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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的多态性原理,输出应为EDCOX1×0。但是结果是不同的,因为支持多态性Java使用后期绑定,这意味着方法只在运行时调用,而不是在静态方法的情况下调用。在静态方法中,编译器在编译时调用方法,而不是在运行时调用方法,因此我们根据引用而不是根据对象获得方法引用A,这就是为什么您可以说它实际上支持静态溢出,但理论上不支持。
在Java中(和许多OOP语言,但我不能全部发言,有些根本没有静态)所有的方法都有固定的签名-参数和类型。在虚拟方法中,第一个参数是隐含的:对对象本身的引用,当从对象内部调用时,编译器会自动添加
静态方法没有区别——它们仍然有固定的签名。但是,通过声明方法static,您已经明确声明编译器不能在该签名的开头包含隐含的对象参数。因此,调用此函数的任何其他代码都不能试图将对堆栈上对象的引用放入堆栈中。如果它这样做了,那么方法执行将不会工作,因为参数将在堆栈上的错误位置(移动了一个)。
由于这两者之间的差异,虚拟方法总是引用上下文对象(即
如果希望Java改变定义,以便在每个方法中传递一个对象上下文,静态或虚拟,那么本质上只有虚拟方法。
正如有人在对OP的评论中所问的,您想要这个特性的原因和目的是什么?
我对Ruby不太了解,正如OP提到的,我做了一些研究。我看到Ruby类实际上是一种特殊的对象,可以创建(甚至动态地)新方法。类是Ruby中的完全类对象,它们不在Java中。这只是在使用Java(或C语言)时必须接受的东西。这些不是动态语言,尽管C正在添加一些动态形式。实际上,据我所知,Ruby没有"静态"方法——在这种情况下,这些方法是单例类对象上的方法。然后您可以用一个新的类来重写这个singleton,上一个类对象中的方法将调用在新类中定义的方法(正确吗?)。因此,如果在原始类的上下文中调用方法,它仍然只执行原始静态,但在派生类中调用方法,则会从父类或子类调用方法。有趣的是,我可以从中看到一些价值。它需要不同的思维模式。
因为你在Java中工作,你需要适应这种做事方式。他们为什么这么做?嗯,可能是为了提高当时的性能,基于现有的技术和理解。计算机语言不断发展。回到足够远的地方,没有OOP这样的东西。在未来,会有其他的新想法。
编辑:另一条评论。现在我看到了这些差异,我自己也是爪哇开发人员,我能理解为什么你从Java开发人员那里得到的答案可能会让你困惑,如果你来自像露比这样的语言。Java EDCOX1的2种方法与Ruby EDCOX1和3种方法不同。Java开发人员很难理解这一点,反过来说,那些主要使用Ruby/SimalTalk这样的语言的人也是如此。我可以看到,Java也使用"类方法"作为静态方法的另一种方式,但这一术语被Ruby不同地使用。Java没有Ruby风格的类方法(抱歉);露比没有Java风格的静态方法,这些方法实际上只是旧的过程样式函数,如C中找到的。
顺便问一下-谢谢你的问题!今天我学到了一些关于类方法(Ruby风格)的新知识。
好。。。如果从Java中如何重写方法的角度来考虑,答案是否定的。但是,如果您试图重写一个静态方法,就不会得到任何编译器错误。这意味着,如果你试图重写,Java不会阻止你这样做,但是你肯定不会得到与非静态方法相同的效果。Java中的重写仅仅意味着基于对象的运行时类型调用特定的方法,而不是在编译时类型调用它(这是重载静态方法的情况)。可以。。。有人猜测他们为什么行为怪异吗?因为它们是类方法,所以在编译时,对它们的访问总是通过使用编译时类型信息来解决的。使用对象引用访问它们只是Java设计者给出的一种额外的自由,我们当然不应该认为只有在限制它时才停止这种做法:
示例:让我们看看如果尝试重写静态方法会发生什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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(); // ... } } |
输出:
注意输出的第二行。如果staticMethod被重写,则此行应该与第三行相同,因为我们将运行时类型的对象上的"staticMethod()"调用为"subclass",而不是"superclass"。这证实了静态方法总是只用编译时类型信息来解析的。
一般来说,允许静态方法的"重写"是没有意义的,因为没有好的方法来确定在运行时调用哪个方法。以员工为例,如果我们调用RegularEmployee.getBonusMultiplier()-应该执行哪个方法?
在Java的情况下,人们可以想象一种语言定义,只要通过对象实例调用静态方法,就可以"重写"静态方法。然而,所有这一切将要做的是重新实现常规类方法,在不真正增加任何好处的情况下为语言添加冗余。
重写是为实例成员保留的,以支持多态行为。静态类成员不属于特定实例。相反,静态成员属于类,因此不支持重写,因为子类只继承受保护的实例成员和公共实例成员,而不是静态成员。您可能需要定义一个接口和研究工厂和/或策略设计模式来评估一个替代方法。
我喜欢并加倍杰伊的评论(https://stackoverflow.com/a/2223803/1517187)。我同意这是Java的糟糕设计。正如我们在前面的注释中看到的,许多其他语言支持重写静态方法。我觉得杰伊也像我一样从Delphi来到Java。Delphi(对象pascal)是实现OOP的第一种语言。很明显,许多人都有使用该语言的经验,因为它过去是编写商业图形用户界面产品的唯一语言。是的,我们可以在Delphi中覆盖静态方法。实际上,Delphi中的静态方法被称为"类方法",而Delphi有不同的"Delphi静态方法"概念,即早期绑定的方法。要重写必须使用后期绑定的方法,请声明"virtual"指令。所以它非常方便和直观,我希望在Java中使用。
通过重写,我们可以根据对象类型创建多态性。静态方法与对象没有关系。因此,Java不能支持静态方法重写。
通过重写,可以实现动态多态性。当您说覆盖静态方法时,您试图使用的单词是矛盾的。
静态表示-编译时,重写用于动态多态性。两者在本质上是相反的,因此不能一起使用。
当程序员使用对象并访问实例方法时,就会出现动态多态行为。JRE将根据您使用的对象类型映射不同类的不同实例方法。
当您说重写静态方法时,我们将使用类名访问静态方法,类名将在编译时链接,因此没有在运行时将方法与静态方法链接的概念。所以术语"重写"静态方法本身没有任何意义。
注意:即使你用一个对象访问一个类方法,Java编译器还是足够聪明的找到它,并将进行静态链接。
在Java overriding意味特别是简单的方法将是基于所谓的"运行时类型 的对象而不是在编译时类型的信息(这是overridden案例与静态方法)。AS是静态方法的类的方法的方法,他们都是不审,所以他们有没有用,这冰的参考对象或指指点点,例如,因为鸽子两部《自然的静态方法,它属于一个特定的类。你可以再明确它的subclass subclass所以你不会知道什么(parent class的静态方法,因为,我说,它是只读的,特异性两类细胞,它已被宣布。使用对象访问他们的证明人是自由的,就是城市设计院给定的Java和我们应该一定不想停止,只有当他们restrict做法的信息 更多的细节和实例 faisalbhagat.blogspot.com http:/ / / / / method-overriding-and-method-hiding.html 2014年09
回答这问题是简单的方法或变量为静态市场属于两个类是只读的,所以静态方法不能被继承的子类,因为他们属于两个超级只读。
简单的解决方案:使用Singleton实例。它将允许overrides和传承。
在我的系统中,有singletonsregistry审级的,它通过对返回舱。例如,如果发现冰槽,它是创造了。
haxe语言类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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!"; } } |
这是一个简单的解释。一个相关的静态方法的类冰与冰在审的相关方法和一个独立的对象。overrides allow呼叫的不同的实施方法与相关overridden特有的对象。所以它是反直觉的两个静态方法的冰覆盖,甚至不相关的类和对象,但它在第一次的地方。所以不能是静态方法-基于对象的overridden冰叫它,它始终是与相关的类,这是创新。
一个静态方法,可变块或嵌套的类属于两个类而不是全部的对象。
一个用Java方法在冰expose行为的两个对象/类。在这里,作为《冰(i.e静态方法,静态方法的冰用来代表一类的行为(只读)。overriding /行为改变的全部会violate现象级的一部基础柱的面向对象编程i.e,高的凝聚力。(我记得一constructor是一种特殊的Java方法。)
高凝聚力的一类应该只有一个角色。例如:一个生产汽车类应该只读数据的自行车而不是汽车,卡车,飞机,汽车等。但这类可能有一些特征(行为),它属于只读。
因此,当设计的Java编程语言。两种语言的设计思想允许开发商保持某一类behaviours只读两市A本身的制作方法,在静态的性质。
下面的代码tries两个覆盖件的静态方法,但将不会遇到任何编译错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; }} |
这是因为,在这里,我们是不是overriding A是我们的方法,但它只是重新申报。Java允许重新声明一个方法(静态和非静态)。
关键词从静态解的getvehilenumber()方法的结果将为汽车类编译错误,因为,我们是想改变的功能性的静态方法,属于汽车类只读。
也,如果getvehilenumber(AA)的冰,然后宣布最终的代码将不会编译,因为最终的限制的关键字从重新申报程序的方法。
1 2 | public static final int getVehileNumber() { return VIN; } |
整体而言,这是upto软件设计为在两个使用的静态方法。 在prefer道贺的两个静态方法使用两个perform一些行为没有产生任何一个审级。secondly,两个隐藏的行为的一类从外面的世界。
重写静态方法有什么好处呢?不能通过实例调用静态方法。
1 2 | MyClass.static1() MySubClass.static1() // If you overrode, you have to call it through MySubClass anyway. |
编辑:通过在语言设计中的一个不幸的疏忽,您可以通过一个实例调用静态方法。一般来说,没有人这样做。我的错。
现在看到上面的答案,每个人都知道我们不能重写静态方法,但是我们不应该误解从子类访问静态方法的概念。
We can access static methods of super class with subclass reference if this static method has not been hidden by new static method defined in sub class.
例如,请参见下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
输出:
1 | SuperClass saying Hello |
See Java oracle docs and search for What You Can Do in a Subclass for details about hiding of static methods in sub class.
< /块引用>
谢谢
下面的代码显示这是可能的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 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();
}
}