Nat*_*hes 480
覆盖取决于具有类的实例.多态性的一点是,您可以对类进行子类化,并且实现这些子类的对象将对超类中定义的相同方法具有不同的行为(并在子类中重写).静态方法与类的任何实例都没有关联,因此该概念不适用.
推动Java设计的两个因素影响了这一点.一个是对性能的担忧:有很多人批评Smalltalk关于它太慢(垃圾收集和多态调用是其中的一部分)而Java的创建者决心避免这种情况.另一个决定是Java的目标受众是C++开发人员.使静态方法按照它们的方式工作有利于熟悉C++程序员并且也非常快,因为没有必要等到运行时才能确定要调用哪种方法.
Jay*_*Jay 181
我个人认为这是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");
}
}
Run Code Online (Sandbox Code Playgroud)
此代码无法正常工作.也就是说,SpecialEmployee就像普通员工一样获得2%的奖金.但如果你删除"静态",那么SpecialEmployee就会获得3%的奖励.
(不可否认,这个例子的编码风格很差,因为在现实生活中你可能希望奖金乘数在某个地方而不是硬编码.但那只是因为我不想让这个例子陷入困境与该点无关的代码.)
我觉得你可能想让getBonusMultiplier保持静态,这似乎很合理.也许您希望能够为所有类别的员工显示奖金乘数,而无需在每个类别中都有员工实例.搜索这样的示例实例有什么意义?如果我们正在创建一个新类别的员工并且还没有分配任何员工呢?这在逻辑上是一个静态函数.
但它不起作用.
是的,是的,我可以想到任何方法来重写上面的代码以使其工作.我的观点并不是它创造了一个无法解决的问题,而是它为那些粗心的程序员创造了一个陷阱,因为这种语言并不像我认为合理的人所期望的那样.
也许如果我尝试为OOP语言编写编译器,我很快就会明白为什么实现它以便静态函数可以被覆盖将是困难或不可能的.
或者也许有一些很好的理由说明Java的行为方式.任何人都可以指出这种行为的优势,某些类别的问题由此变得更容易吗?我的意思是,不要只指向Java语言规范并说"看,这是记录它的行为".我知道.但它有一个很好的理由为什么它应该这样做?(除了显而易见的"让它正常工作太难了"......)
更新
@VicKirk:如果你的意思是"糟糕的设计",因为它不适合Java处理静态的方式,我的回答是,"嗯,当然,呃." 正如我在原帖中所说,它不起作用.但是,如果你的意思是设计不好,那么这种语言会有一些根本性的错误,即静态可以像虚函数一样被覆盖,这会以某种方式引入歧义或者不可能我回答说:"为什么?这个概念出了什么问题?"
我认为我给出的例子是一件很自然的事情.我有一个类,它有一个不依赖于任何实例数据的函数,我可能非常合理地想要独立于实例调用,以及想要在实例方法中调用.为什么这不起作用?这些年来,我经常遇到这种情况.在实践中,我通过使函数虚拟化来创建静态方法,然后创建一个静态方法,其生命中唯一的目的是将静态方法传递给具有虚拟实例的虚方法.这似乎是一个非常迂回的方式到达那里.
Ste*_*ell 40
简短的回答是:它完全有可能,但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();
}
}
Run Code Online (Sandbox Code Playgroud)
档案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();
}
}
Run Code Online (Sandbox Code Playgroud)
如果你运行它(我在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.
Run Code Online (Sandbox Code Playgroud)
在这里,唯一可能是惊喜(以及问题所在)的案例似乎是第一种情况:
"运行时类型不用于确定调用哪些静态方法,即使使用对象实例(obj.staticMethod())调用也是如此."
和去年的情况:
"当从类的对象方法中调用静态方法时,所选择的静态方法是可从类本身访问的方法,而不是从定义对象的运行时类型的类."
静态调用在编译时解析,而非静态方法调用在运行时解析.请注意,虽然静态方法是从父级继承的,但它们不会被重写(由子级).如果你不这么想的话,这可能是一个惊喜.
使用运行时类型解析对象方法调用,但使用编译时(声明)类型解析静态(类)方法调用.
要更改这些规则,以便调用示例中的最后一次调用Child.printValue(),静态调用必须在运行时提供类型,而不是编译器在编译时使用对象的声明类来解析调用(或上下文).然后,静态调用可以使用(动态)类型层次结构来解析调用,就像今天的对象方法调用一样.
这很容易实现(如果我们改变了Java:-O),并且根本不合理,但它有一些有趣的考虑因素.
主要考虑因素是我们需要决定哪些静态方法调用应该这样做.
目前,Java在语言中有这种"怪癖",obj.staticMethod()呼叫被呼叫取代ObjectClass.staticMethod()(通常带有警告).[ 注意: ObjectClass是编译时类型obj.]这些是以这种方式覆盖的好候选者,采用运行时类型obj.
如果我们这样做会使方法体难以理解:父类中的静态调用可能会被动态地 "重新路由".为了避免这种情况,我们必须使用类名调用静态方法 - 这使得使用编译时类型层次结构(如现在)更明显地解决了调用.
调用静态方法的其他方法更棘手:this.staticMethod()应该obj.staticMethod()与运行时类型相同this.然而,这可能会导致现有程序出现一些令人头疼的问题,这些程序调用(显然是本地的)静态方法而没有装饰(可以说相当于this.method()).
那么简单的电话staticMethod()怎么样?我建议他们和今天一样,并使用本地类上下文来决定做什么.否则会产生很大的混乱.当然,method()这意味着this.method()如果method是非静态方法,并且ThisClass.method()如果method是静态方法则意味着.这是混乱的另一个原因.
如果我们改变了这种行为(并使静态调用可能是动态非本地的),我们可能想要重新审视它的含义final,private并protected作为static类方法的限定符.那么我们都必须习惯这样的事实private static和public final方法没有被重载,因此可以在编译时安全地解决,是"安全"改为局部引用.
Pat*_*tus 23
其实我们错了.
尽管Java默认情况下不允许您覆盖静态方法,但如果仔细查看Java中的类和方法类的文档,您仍然可以通过以下解决方法找到模拟静态方法覆盖的方法:
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());
}
}
Run Code Online (Sandbox Code Playgroud)
结果输出:
0.02
0.02
0.02
0.03
Run Code Online (Sandbox Code Playgroud)
我们想要实现的目标:)
即使我们将第三个变量Carl声明为RegularEmployee并将其分配给SpecialEmployee的实例,我们仍将在第一种情况下调用RegularEmployee方法并在第二种情况下调用SpecialEmployee方法
RegularEmployee Carl = new SpecialEmployee();
System.out.println(Carl.calculateBonus());
System.out.println(Carl.calculateOverridenBonus());
Run Code Online (Sandbox Code Playgroud)
只看输出控制台:
0.02
0.03
Run Code Online (Sandbox Code Playgroud)
;)
ewe*_*nli 18
静态方法被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
Run Code Online (Sandbox Code Playgroud)
你可以反思课程,但它会停在那里.您不是通过使用clazz1.staticMethod()而是使用来调用静态方法MyClass.staticMethod().静态方法没有绑定到一个对象,并有因此没有的概念this,也没有super在一个静态方法.静态方法是一个全局函数; 因此,也没有多态性的概念,因此,方法覆盖是没有意义的.
但是如果MyClass是一个在运行时调用方法的对象,就像在Smalltalk中那样(或者可能是JRuby作为一个注释建议,但我对JRuby一无所知).
哦,是的......还有一件事.你可以通过一个对象调用静态方法,obj1.staticMethod()但是MyClass.staticMethod()应该避免使用真正的语法糖.它通常会在现代IDE中引发警告.我不知道为什么他们允许这个捷径.
Ric*_*uen 13
通过动态调度可以实现方法覆盖,这意味着对象的声明类型不会确定其行为,而是确定其运行时类型:
Animal lassie = new Dog();
lassie.speak(); // outputs "woof!"
Animal kermit = new Frog();
kermit.speak(); // outputs "ribbit!"
Run Code Online (Sandbox Code Playgroud)
尽管两个lassie和kermit被声明为类型的对象Animal,他们的行为(方法.speak())变化,因为动态调度只能绑定方法调用.speak(),以实现在运行时-而不是在编译时.
现在,static关键字开始有意义:"静态"一词是"动态"的反义词.所以你不能覆盖静态方法的原因是因为静态成员没有动态调度 - 因为静态字面意思是"不动态".如果他们动态调度(因此可以覆盖)static关键字就再也没有意义了.
Shu*_*oni 10
是.实际上Java允许重写静态方法,理论上如果你在Java中覆盖静态方法然后它将编译并运行顺利,但它将失去多态性,这是Java的基本属性.您将在任何地方阅读,无法尝试自己编译和运行.你会得到你的答案.例如,如果你有类动物和静态方法eat(),你覆盖它的子类中的静态方法让它叫做Dog.然后当你将Dog对象分配给Animal Reference并根据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
Run Code Online (Sandbox Code Playgroud)
根据Java的多态原理,输出应该是Dog Eating.
但结果不同,因为支持Polymorphism Java使用Late Binding,这意味着只在运行时调用方法,而在静态方法的情况下不调用.在静态方法中,编译器在编译时调用方法而不是运行时,所以我们根据引用获取方法而不是根据对象引用包含这就是为什么你可以说实际上它支持静态覆盖,但理论上,它没有"T.
保留为实例成员保留以支持多态行为。静态类成员不属于特定实例。相反,静态成员属于该类,因此不支持覆盖,因为子类仅继承受保护的实例和公共实例成员,而不继承静态成员。您可能需要定义一个界面并研究工厂和/或策略设计模式,以评估替代方法。
一般来说,允许“覆盖”静态方法是没有意义的,因为没有好的方法可以确定在运行时调用哪个方法。以 Employee 为例,如果我们调用 RegularEmployee.getBonusMultiplier() - 应该执行哪个方法?
在 Java 的情况下,人们可以想象一种语言定义,只要它们通过对象实例调用,就可以“覆盖”静态方法。然而,所有这些都只是重新实现常规的类方法,为语言添加冗余,而没有真正增加任何好处。
在Java(和许多OOP语言,但我不能说所有;有些根本没有静态)所有方法都有固定的签名 - 参数和类型.在虚方法中,隐含第一个参数:对对象本身的引用,当从对象内部调用时,编译器会自动添加this.
静态方法没有区别 - 它们仍然具有固定的签名.但是,通过声明方法static,您已明确声明编译器不得在该签名的开头包含隐含的object参数.因此,调用它的任何其他代码必须不得尝试在堆栈上引用对象.如果它确实这样做了,那么方法执行将不起作用,因为参数将在堆栈上的错误位置 - 移位1.
因为这两者之间存在差异; 虚方法始终具有对上下文对象的引用(即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中运行的角度思考,答案是否定的.但是,如果尝试覆盖静态方法,则不会出现任何编译器错误.这意味着,如果你试图覆盖,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();
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
输出: -
SuperClass: inside staticMethod
SuperClass: inside staticMethod
SubClass: inside staticMethod
注意输出的第二行.如果staticMethod被覆盖,则该行应该与第三行相同,因为我们在运行时类型的对象上调用'staticMethod()'作为'SubClass'而不是'SuperClass'.这确认了静态方法总是仅使用其编译时类型信息来解析.
我喜欢并加倍 Jay 的评论(/sf/answers/155666241/)。
我同意这是 Java 的糟糕设计。
正如我们在之前的评论中看到的那样,许多其他语言支持覆盖静态方法。我觉得 Jay 和我一样也是从 Delphi 来到 Java 的。
Delphi (Object Pascal) 是 Java 之前实现 OOP 的语言之一,也是最早用于商业应用程序开发的语言之一。
很明显,很多人都使用过这种语言,因为它过去是编写商业 GUI 产品的唯一语言。而且 - 是的,我们可以在 Delphi 中覆盖静态方法。实际上,Delphi 中的静态方法被称为“类方法”,而 Delphi 有“Delphi 静态方法”的不同概念,它们是早期绑定的方法。要覆盖必须使用后期绑定的方法,请声明“虚拟”指令。所以它非常方便和直观,我希望在 Java 中实现这一点。
通过重写,您可以实现动态多态性。当您说重写静态方法时,您尝试使用的词语是矛盾的。
静态表示-编译时,重写用于动态多态性。两者性质相反,不能同时使用。
当程序员使用对象并访问实例方法时,就会出现动态多态行为。JRE将根据您使用的对象类型映射不同类的不同实例方法。
当你说重写静态方法时,静态方法我们会通过类名来访问,类名会在编译时链接,所以不存在运行时与静态方法链接的概念。因此,术语“重写”静态方法本身没有任何意义。
注意:即使你用一个对象访问一个类方法,java编译器仍然有足够的智能来找到它,并且会做静态链接。