flo*_*wer 1 java singleton java-8
在下面的代码中,为什么x和y分别输出0和1?为什么当我把这段代码\xef\xbc\x9aprivate static Singleton instance = new Singleton();从位置\xe2\x91\xa0放到位置\xe2\x91\xa1时,输出是1, 1?
private static Singleton instance = new Singleton();\n//\xe2\x91\xa0\nprivate static int x = 0;\n\nprivate static int y;\n//\xe2\x91\xa1\nprivate Singleton()\n{\n System.out.println("Starting add");\n x++;\n y++;\n}\n\npublic static Singleton getInstance()\n{\n return instance;\n}\n\npublic static void main(String[] args)\n{\n System.out.println("Starting Singleton");\n Singleton singleton = Singleton.getInstance();\n //Singleton singleton = new Singleton();\n System.out.println(singleton.x);\n System.out.println(singleton.y);\n}\nRun Code Online (Sandbox Code Playgroud)\n
不要写这种代码。正如您所发现的,这非常令人困惑。然而,根据规范。确实很奇怪。让我们通过相当多的步骤来解释这一点。这是关于复杂且通常不相关的细节的个别细节,这些细节结合起来解释了这种行为。
\n想象一下这段代码:
\npublic class Foo {\n private static final long MARK = System.currentTimeMillis();\n}\nRun Code Online (Sandbox Code Playgroud)\n显然, 的值MARK无法在编译时确定 - 这被解释为“时间戳,就像该字段初始化时一样”,归结为“加载此类时”。这意味着第一次任何地方的任何代码(如引用)都Foo必须执行某些代码。在此执行期间,上下文必须存在 - 这样的代码理论上可以引用MARK.
因此,MARK 从 开始,0该代码编译为:
public class Foo {\n private static final long MARK;\n\n static {\n // Until MARK is set, MARK is 0.\n MARK = System.currentTimeMillis();\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n那是合法的java代码(尝试一下;编译它)——那是一个静态初始化程序。跑去javap -c -v Foo看这个东西。
这:
\npublic class Foo {\n private static final int FOO = 5;\n}\nRun Code Online (Sandbox Code Playgroud)\n是非常不同的。如果你运行javap -c -v Foo它,你会发现根本没有静态初始化程序。相反,5 是所谓的 CTC:编译时间常数。编译器本身会解析它,并将该值直接放入类文件中。在第一种情况下,MARKpofed 存在包含 0,然后在初始化期间设置其值。但是,立刻就突然FOO出现了价值5 。
完全取决于您的代码中 CTC 的定义方式,x并且y 不符合条件;具体来说x 没有恒定值,这是因为要符合条件,该字段必须是static and final。显然,两者都不是singleton。因此,您的代码与以下内容相同:
private static Singleton instance;\n//\xe2\x91\xa0\nprivate static int x;\n\nprivate static int y;\n//\xe2\x91\xa1\n\nstatic {\n instance = new Singleton();\n x = 0;\n}\n\nprivate Singleton()\n{\n System.out.println("Starting add");\n x++;\n y++;\n}\n\npublic static Singleton getInstance()\n{\n return instance;\n}\n\n// ....\n}\nRun Code Online (Sandbox Code Playgroud)\n现在应该很明显为什么你得到 0/1 而不是预期的 1/1。首先,构造函数运行,并且x和y都为零。构造函数将它们设置为 1/1。然后x被设置为0。
真正的问题是,这段代码很愚蠢。你不创建单例和静态字段,这根本没有意义。构造函数不应该充当初始化器static。构造函数从根本上来说与实例相关。因此,永远不应该编写此代码,它与自身不一致,并且您需要从前到后了解 JLS 才能了解正在发生的情况。
单例的一般方法是拥有所有非静态内容,除了包含单例及其访问器的字段:
\nclass Foo {\n private static final Foo INSTANCE = new Foo();\n private int x = 0;\n private int y;\n\n private Foo() {\n x++;\n y++;\n }\n\n public static Foo getInstance() { return INSTANCE; }\n}\nRun Code Online (Sandbox Code Playgroud)\n上面的效果很好。
\n或者,如果您有需要初始化的静态内容,请编写一个实际的静态初始化程序。如果你有这个,单例通常没有意义——只需将所有内容都静态化,此时单例将毫无意义:
\npublic class Foo {\n private static int x = 0;\n private static int y = 0;\n\n static {\n x++;\n y++;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n也可以正常工作(这两个片段中的 x 和 y 都是 1 和 1)。
\n