是一个Java类实例单例吗?

huangapple go评论79阅读模式
英文:

Is a Java Class instance singleton?

问题

根据《Java 8 语言规范 §15.8.2》(引用):

[...]

类文字表达式会评估为当前实例的类的定义类加载器(§12.2)定义的命名类型(或void)的 Class 对象。

[...]

主要是,'the Class object' 暗示着这应该是一个单例。此外,§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

英文:

According to the Java 8 Language Spec §15.8.2 (quote):

> [...]
>
> A class literal evaluates to the Class object for the named type (or for void) as defined by the defining class loader (§12.2) of the class of the current instance.
>
> [...]

Mainly, 'the Class object' insinuates that this is or should be a singleton. Also §12.2 says:

> [...]
>
> Well-behaved class loaders maintain these properties:
>
> - Given the same name, a good class loader should always return the same class object.
>
> [...]

In fact, using Java 8*, the following** prints true and true:

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());
	}
}

Is the class loader always 'well-behaved' and why (not)? In other words: are Class instances singleton? The other way around: can a Class of the same type be a different instance?

Notes: I do not refer to the singleton pattern here. However, if the Class implementation follows that pattern, that would be interesting. As a side-step, but by no means the main question: because the singleton pattern's legitimate uses are point of debate, would Java's Class be a good candidate to apply the singleton pattern to?

*:

$ 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)

**: my IDE even warns me that the printed expressions are always true.

答案1

得分: 3

你错误地使用了术语“singleton”。

一个“singleton”意味着只存在一个属于其自身类的对象。一个Class对象是java.lang.Class类的一个实例,这个类有多个实例。实际上,只有一个Class对象是不可能的,因为存在一个Class对象已经意味着至少存在两个类,java.lang.Objectjava.lang.Class,所以在运行时必须至少存在两个Class对象。

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

表现良好的类加载器应该维护以下属性:

  • 对于相同的名称,良好的类加载器应该始终返回相同的类对象。
  • 如果类加载器L1委托加载类C给另一个加载器L2,则对于任何作为C的直接超类或直接超接口、C中字段的类型、C中方法或构造函数的形式参数的类型或C中方法的返回类型的类型,L1L2应该返回相同的Class对象。

恶意的类加载器可能会违反这些属性。但是,它不能破坏类型系统的安全性,因为Java虚拟机会防止这种情况发生。

注意最后一句话。JVM会防止违反这些要求的情况发生。在你的示例代码中,由于相同符号引用的重复出现,有两种可能性:

  1. JVM会在同一上下文中记住第一次解析此符号引用的结果,并在下次出现时直接重用它,而不再向类加载器请求。

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

由于这两种方法都涉及到记住结果,因此在解析相同上下文中的相同引用时通常使用第一种方法,因为它更简单和更有效。当涉及到解析使用相同引用的不同符号引用指向的类时,如果违反了类加载器的约束,JVM确实会[在这种情况下抛出错误]。

因此,表达式Main.class == Main.classnew Main().getClass() == new Main().getClass()永远不会评估为false。最有可能的是,对Main的符号引用的解析将采用一种捷径,无论类加载器的行为如何,都会使用相同的运行时类。但即使它不采用捷径,而类加载器的行为不当,在下一次查询时返回不同的类对象,JVM也会检测到并抛出错误,因此该表达式根本不会评估为布尔值结果。在任何情况下,它都不可能结果为false

英文:

You are using the term singleton incorrectly.

A singleton implies the existence of only one object of its own class. A Class object is an instance of the class java.lang.Class and there is more than one instance of this class. It’s actually impossible to have only one Class object, as the existence of a Class object does already imply the existence of at least two classes, java.lang.Object and java.lang.Class, so there must be at least two Class objects in the runtime.

Your example is not capable of spotting whether the class loader is well behaved or not. You already mentioned JLS §12.2

> Well-behaved class loaders maintain these properties:
>
> - Given the same name, a good class loader should always return the same class object.
>
> - If a class loader L1 delegates loading of a class C to another loader L2, then for any type T that occurs as the direct superclass or a direct superinterface of C, or as the type of a field in C, or as the type of a formal parameter of a method or constructor in C, or as a return type of a method in C, L1 and L2 should return the same Class object.
>
> A malicious class loader could violate these properties. However, it could not undermine the security of the type system, because the Java Virtual Machine guards against this.

Mind the last sentence. A JVM will guard against violations of these requirements. With the repeated occurrences of the same symbolic references within your example code, there are two possibilities

  1. The JVM remembers the result of the first resolution of this symbolic reference within this context and just reuses it on the next occurrence without asking a class loader again.

  2. The JVM remembers the result of the first resolution of this symbolic reference within this context and compares it with the result of subsequent resolutions of the same reference and throws an error if they mismatch.

Since both approaches imply remembering the result, the first approach is usually used when it comes to resolving the same reference within the same context, as it is simpler and more efficient. When it comes to different symbolic references that resolve to classes using the same reference, the JVM will indeed throw an error when the class loader constraints are violated.

So expressions like Main.class == Main.class or new Main().getClass() == new Main().getClass() will never evaluate to false. Most likely, the resolution of the symbolic reference to Main will go a short-cut, using the same runtime class regardless of what the class loader would do. But even when it does not take the short-cut and the ClassLoader is misbehaving, returning a different class object for the next query, the JVM would detect it and throw an error, so the expression would not evaluate to a boolean result at all. In neither case could it result in false.

答案2

得分: 0

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

类加载器始终是“行为良好”的吗?为什么(或者为什么不是)?

这实际上取决于实现方式。如果有意使类加载器始终返回一个新的 Class 对象,它就不会是“行为良好”的。至少 OpenJDK 的所有类加载器都是行为良好的。

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

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

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

反过来说,同一类型的 Class 可以是不同的实例吗?

只有在由两个不同的、符合规范、行为良好的类加载器加载时才会是不同的实例。

作为一个侧面问题,但绝不是主要问题:因为单例模式的合法用途存在争议,Java 的 Class 是否是一个适合应用单例模式的良好候选对象?

这相当主观,但我认为,Java 的 Class 不是一个适合应用单例模式的良好候选对象,因为大多数单例模式都是这样实现的:

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

因此,它实际上是一个类的一个对象,只是由一些小细节(例如不同的类加载器)不同而已。

英文:

In a single classloader, the Class object is the same.
>Is the class loader always 'well-behaved' and why (not)?

It really depends on the implementation. If it is done deliberately that always the classloader always returns a new Class-Object, it won't be well-behaved.
At least all classloaders of OpenJDK are well-behaving.

>In other words: are Class instances singleton? The other way around: can a Class of the same type be a different instance?

In one single classloader, every Class instance is a singleton. With multiple classloaders, following will evaluate to false:

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

>The other way around: can a Class of the same type be a different instance?

Only if loaded by two different, conforming,well-behaved classloaders.

>As a side-step, but by no means the main question: because the singleton pattern's legitimate uses are point of debate, would Java's Class be a good candidate to apply the singleton pattern to?

This is quite opinion-based, but I think, it's no good candidate to apply the singleton pattern, as most singletons are implemented like this:

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

So more that it is really ONE object of a Class, many that only differ by some small things like a different Classloader.

huangapple
  • 本文由 发表于 2020年8月10日 15:46:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/63336082.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定