【问题标题】:Is a Java Class instance singleton?Java 类实例是单例吗?
【发布时间】:2023-03-08 07:14:01
【问题描述】:

根据Java 8 Language Spec §15.8.2(引用):

[...]

类字面量计算为由当前实例的类的定义类加载器 (§12.2) 定义的命名类型(或 void)的 Class 对象。

[...]

主要是,“Class 对象”暗示这是或应该是单例。 §12.2 还说:

[...]

行为良好的类加载器维护这些属性:

  • 给定相同的名称,一个好的类加载器应该总是返回相同的类对象。

[...]

实际上,使用 Java 8*,以下**打印 truetrue

class Main {
    public static void main(String[] args) {
        Main main1 = new Main();
        Main main2 = new Main();
        System.out.println(main1.getClass().equals(main2.getClass()));
        System.out.println(main1.getClass() == main2.getClass());
    }
}

类加载器总是“表现良好”吗?为什么(不)?换句话说:Class 实例是单例吗?反过来:同一类型的Class 可以是不同的实例吗?

注意:我在这里没有提到单例模式。但是,如果Class 实现遵循该模式,那将会很有趣。作为一个次要步骤,但绝不是主要问题:因为单例模式的合法用途是争论的焦点,Java 的 Class 是否适合将单例模式应用于?

*:

$ java -version
openjdk version "1.8.0_262"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_262-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.262-b10, mixed mode)

**:我的 IDE 甚至警告我打印的表达式总是true

【问题讨论】:

    标签: java class java-8 singleton jls


    【解决方案1】:

    你错误地使用了singleton这个词。

    一个单例意味着它自己的类中只存在一个对象。 Class 对象是 java.lang.Class 类的一个实例,并且该类的实例不止一个。实际上不可能只有一个Class 对象,因为Class 对象的存在确实意味着至少存在两个类java.lang.Objectjava.lang.Class,所以必须至少有两个Class运行时中的对象。

    您的示例无法发现类加载器是否表现良好。你已经提到了JLS §12.2

    行为良好的类加载器维护这些属性:

    • 给定相同的名称,一个好的类加载器应该总是返回相同的类对象。

    • 如果类加载器 L1 将类 C 的加载委托给另一个加载器 L2,则对于任何类型 T,它作为 C 的直接超类或直接超接口出现,或者作为C 中的字段类型,或 C 中方法或构造函数的形式参数类型,或 CL1L2 中方法的返回类型应返回相同的Class 对象。

    恶意类加载器可能会违反这些属性。但是,它不会破坏类型系统的安全性,因为 Java 虚拟机对此进行了防范。

    记住最后一句话。 JVM 将防止违反这些要求。在您的示例代码中重复出现相同的符号引用,有两种可能性

    1. JVM 会记住此上下文中此符号引用的第一次解析的结果,并在下一次出现时重用它,而无需再次询问类加载器。

    2. JVM 会记住此上下文中此符号引用的第一次解析结果,并将其与同一引用的后续解析结果进行比较,如果不匹配则抛出错误。

    由于这两种方法都意味着记住结果,因此当涉及到在相同上下文中解析相同引用时,通常使用第一种方法,因为它更简单且更有效。当涉及到使用相同引用解析为类的不同符号引用时,JVM 确实会throw an error when the class loader constraints are violated

    所以像Main.class == Main.classnew Main().getClass() == new Main().getClass() 这样的表达式永远不会计算为false。最有可能的是,对Main 的符号引用的解析将走捷径,无论类加载器会做什么,都使用相同的运行时类。但即使它不走捷径并且ClassLoader 行为不端,为下一个查询返回不同的类对象,JVM 也会检测到它并抛出错误,因此表达式不会评估为boolean结果。在这两种情况下都不会导致false

    【讨论】:

      【解决方案2】:

      在单个类加载器中,Class 对象是相同的。

      类加载器总是“表现良好”吗?为什么(不)?

      这真的取决于实施。如果故意让类加载器总是返回一个新的Class-Object,它就不会表现得很好。 至少 OpenJDK 的所有类加载器都表现良好。

      换句话说:类实例是单例的吗?反过来:同类型的Class可以是不同的实例吗?

      在一个单一的类加载器中,每个 Class 实例都是一个单例。使用多个类加载器,以下将评估为 false:

      ClassLoaderA.getInstance().loadClass("foo.Bar")==ClassLoaderB.getInstance().loadClass("foo.Bar");
      

      反过来:同类型的类可以是不同的实例吗?

      仅当由两个不同的、一致的、行为良好的类加载器加载时。

      作为一个次要步骤,但绝不是主要问题:因为单例模式的合法用途是争论的焦点,Java 的 Class 是否适合应用单例模式?

      这是相当基于意见的,但我认为,它不是应用单例模式的好选择,因为大多数单例都是这样实现的:

      class Foo{
         public static final Foo INSTANCE=new Foo();
         private Foo(){
            ìf(INSTANCE!=null)
               throw new IllegalAccessException("No Foo instances for you!");
         }
      }
      

      更多的是它实际上是一个类的一个对象,许多只是在一些小事情上有所不同,比如不同的类加载器。

      【讨论】:

      • “如果它是一个糟糕的自定义实现,总是返回一个新的 Class-Object,它不会表现良好。” - 你假设一个 JVM 错误这里。这在理论上是可能的,但在实践中......只有在有人故意这样做的情况下才会发生。
      • @StephenC 我的意思是这样,我只是措辞错误。谢谢指出
      猜你喜欢
      • 2021-04-21
      • 2023-03-11
      • 2023-03-13
      • 2015-02-18
      • 2011-06-10
      • 1970-01-01
      • 1970-01-01
      • 2017-09-23
      • 1970-01-01
      相关资源
      最近更新 更多