在SO回答一个问题时,我想到了一个解决方案,如果可以扩展Class
类,那就太好了:
这个解决方案包括尝试装饰Class
类,以便只允许包含某些值,在这种情况下,扩展具体类C
的类。
public class CextenderClass extends Class
{
public CextenderClass (Class c) throws Exception
{
if(!C.class.isAssignableFrom(c)) //Check whether is `C` sub-class
throw new Exception("The given class is not extending C");
value = c;
}
private Class value;
... Here, methods delegation ...
}
我知道这个代码不工作,因为Class
是最终的,我想知道为什么Class
是最终的。我理解它必须与安全有关,但我无法想象扩展Class
是危险的情况。你能举几个例子吗?
顺便说一下,我能达到的最接近预期行为的解是:
public class CextenderClass
{
public CextenderClass(Class c) throws Exception
{
if(!C.class.isAssignableFrom(c)) //Check whether is `C` sub-class
throw new Exception("The given class is not extending C");
value = c;
}
public Class getValue() {
return value;
}
private Class value;
}
但缺乏透明装饰之美。
根据类类的注释,只有Java虚拟机创建class对象。.
如果允许扩展类,那么问题就出现了,应该允许创建自定义类对象吗?如果是,那么它将打破上面的规则,即只有JVM应该创建类对象。所以,你没有这种灵活性。
PS:getClass()
只是返回已经创建的类对象。它不返回一个new类对象。
这是一个非常好的问题,但只是一个更普遍的问题的实例,虽然@ theelostmind 4年前刚刚回应(考虑实现的观点)在JVM规则/限制方面的问题仍然存在:为什么JVM提出规则?必须有一个合理的理由。我们必须从更抽象的层次(观点)来考察它
简短的回答:
似乎所有这些都与类型安全有关众所周知,Java是一种强类型语言不允许任何人改变这一事实。
详细的回答(有一些上下文,以便每个人都能理解):所有的故事都是从静态和动态绑定开始的。子类型多态所提供的灵活性使得对象的声明(静态)类型通常不同于其运行时(动态)类型。运行时类型通常是静态类型的子类型。e, g。
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在类车辆。这是一个继承的重新定义方法调用重写.注意,根据函数子类型规则,继承的方法参数的签名必须在子类中保持相同。这造成了一个尴尬的情况,因为在类Vehicle中,我们想引用参数的VIN字段,而Object没有这样的字段。这就是为什么类型转换(Vehicle)x指定意图将x视为Vehicle的原因。没有办法静态地验证这个强制转换,因此要进行动态检查由编译器生成。这是一个动态类型检查的实例。
为了使重写正确工作,要调用的方法由接收方对象的动态类型决定(也称为方法的动态分派(选择),是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();
....
}
class在Java中是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#)允许选择静态方法调度还是动态方法调度。尽管这样做是出于效率的考虑,但事实证明,当在程序中同时使用这两种分派机制时,可能会模糊它们之间的关系程序的含义。