这是一个非常好的问题,但只是一个更普遍问题的实例,尽管 4 年前@TheLostMind 刚刚就 JVM 规则/限制做出了回应(从实现的角度考虑),但问题仍然存在:为什么 JVM提出那个规则?必须有一个合理的理由。我们必须从更抽象的层面(观点)来研究它
简答:
这似乎与类型安全有关,而且我们都知道Java 是一种强类型语言,并且不允许任何人改变这一事实。
详尽的答案(带有一些上下文,以便每个人都能理解):
所有的故事都从static and dynamic binding开始。
subtype polymorphism 提供的灵活性使得对象的声明(静态)类型通常不同于其运行时(动态)类型。
运行时类型通常是静态类型的子类型。
例如。
AClass a = new AClass();
AsubClass b = new AsubClass(); // AsubClass is derived from AClass
a=b;
a的静态类型是AClass,赋值后a=b; 它的运行时类型是AsubClass。这会影响在执行消息时选择最合适的方法。
现在考虑给定的类 Vehicle
以下。
public class Vehicle {
private int VIN; // Vehicle Identification Number
private String make;
public boolean equals(Object x) {
return (VIN == (Vehicle)x.VIN);
}
// other methods
}
根类java.lang.Object中的equals方法被定义为对对象身份的测试。
这是定义一般对象相等性的唯一有意义的方法。
也就是说,如果两个对象具有相同的标识,则它们是相等的。
在特定的类中,更合适的相等含义可能更合适。在上述类别中,如果它们的 VIN(车辆识别号)相等,则认为两辆车是相等的。
所以方法 equals 在
类车辆。这种对继承的重新定义
方法称为覆盖。
请注意,根据function subtyping rule,继承方法参数的签名需要在子类中保持相同。
这造成了一个尴尬的情况,因为在类 Vehicle 中我们想引用参数的 VIN 字段,而 Object 没有这样的字段。这就是类型转换 (Vehicle)x 指定意图是将 x 视为车辆的原因。没有办法
静态验证这个转换,因此编译器会生成一个动态检查。这是dynamic type checking.的一个实例
为了使覆盖正确工作,要调用的方法由接收者对象的动态类型决定(也称为dynamic dispatch (selection) of methods,是 OO 语言中动态绑定的最重要情况。)
例如
Object a = new Object();
Object b = new Object();
Vehicle aV = new Vehicle();
Vehicle bV = new Vehicle();
a=aV;
b=bV;
. . .
a.equals(b)
. . .
响应消息 a.equals(b) 被调用的方法将是类 Vehicle 中重写的方法 equals,因为 a 的运行时类型是 Vehicle。
在某些情况下,重写方法可能会出现问题并且不应被允许。一个很好的例子是 Java.lang.Object 的 getClass() 。该方法在底层虚拟平台中有特定的实现,保证调用该方法确实会返回方法接收者的类对象。
允许覆盖将对该方法的预期语义产生严重影响,从而在动态类型检查中产生重大问题。这可能就是为什么 getClass() 被声明为 final 的原因。
例如
public class Object {
public final Class getClass();
....
}
Java 中的类 Class 是 final 的,即不能扩展,因此它的任何方法都不能被覆盖。 由于类Class只有自省方法,这保证了类型系统在运行时的安全性,即类型信息不能在运行时发生变化。
进一步扩展这个概念......
基于接收对象类型的方法的动态分派(选择)是面向对象语言的基本技术。
它带来了使整个面向对象范式工作的灵活性。
通过继承向已编译和运行的应用程序添加新类型只需要编译和链接新引入的类型,而无需重新编译现有应用程序。然而,这种灵活性会带来一些效率损失,因为关于方法选择的决定被推迟到运行时。现代的
语言具有动态分配方法的有效技术,但某些语言(如 C++ 和 C#)试图通过提供静态绑定(方法选择)选项来避免相关成本。在 C# 中,方法是静态绑定的,除非它们被显式声明为 virtual。
例如
public class Object {
public virtual boolean equals(Object x);
// other methods
}
在 C# 中重写此方法将由显式关键字 override 指示。
例如
public class Vehicle {
private int VIN;
private String make;
public override boolean equals(Object x) {
return (VIN == (Vehicle)x.VIN);
}
// other methods
}
接收者是类对象的方法总是静态绑定的。
原因是该类的所有对象只有一个类对象。由于接收者在编译时是已知的,因此无需将方法选择推迟到运行时。因此,这些方法被声明为静态的,以表明它们属于类本身。
一个例子是类 Vehicle 的方法 numberOfVehicles 车辆的数量不是单个车辆对象的属性。它是
Vehicle 类的所有对象的属性,因此它属于类本身。
例如
public class Vehicle {
// fields;
public static int numberOfVehicles();
// other methods
}
我们可以将以上所有讨论总结如下:
– 选择执行消息的方法的基本机制(方法
dispatch) 在面向对象语言中是动态的。它基于接收器对象的运行时类型。
——静态(即类)方法的接收者是类对象。由于只有
给定类型的一个类对象,静态方法的选择是静态的。
– 某些语言(C++ 和 C#)允许选择静态和动态方法分派。尽管这样做是为了提高效率,但已经表明,当在程序中同时使用这两种调度机制时,可能会掩盖
节目的意义。