【问题标题】:Why is `Class` class final?为什么`Class`类是final的?
【发布时间】:2014-09-19 15:49:54
【问题描述】:

在 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;

}

但缺少透明装饰的美感。

【问题讨论】:

  • 可能无法改变,:/
  • 虽然我认为公认的答案是绝对正确的,但我可能还要提一下,JVM可以更好地优化final类,所以很多很常见的类都是final的。我认为这是 String 成为 final 背后的部分原因(以及可变字符串也允许更多的脚射,而 Java 就是为了节省脚)

标签: java security reflection decorator


【解决方案1】:

根据Class类中的cmets 只有Java虚拟机创建Class 对象。

如果允许您扩展类,那么就会出现问题,是否应该允许您创建自定义 Class 对象?。如果是,那么它将打破上述只有 JVM 才能创建类对象的规则。所以,你没有那种灵活性。

PS : getClass() 只是返回已经创建的类对象。它不返回 new 类对象。

【讨论】:

  • 听起来很合逻辑,但如果你从字面上理解这条规则,如果你能够创建扩展 Class 的类的对象,这条规则仍然适用。
  • @PabloFranciscoPérezHidalgo - 完全正确.. 你会用 custom 类做什么?您会尝试实例化它们吗? (删除 final 关键字将允许您这样做..)。想象一下在那种情况下会发生什么。同样适用于 String 类(它也是 final)。如果我们被允许扩展这些类,那么我们将 肯定尝试改变 JVM 内部的工作方式。
【解决方案2】:

这是一个非常好的问题,但只是一个更普遍问题的实例,尽管 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#)允许选择静态和动态方法分派。尽管这样做是为了提高效率,但已经表明,当在程序中同时使用这两种调度机制时,可能会掩盖 节目的意义。

【讨论】:

    猜你喜欢
    • 2020-01-27
    • 1970-01-01
    • 1970-01-01
    • 2021-09-24
    • 2019-01-11
    • 1970-01-01
    • 2017-05-20
    • 2017-05-08
    • 1970-01-01
    相关资源
    最近更新 更多