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