Java单例模式下x和y为什么输出不同的值?

问题描述 投票:0回答:1

下面的代码中,为什么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);
}
java java-8 singleton
1个回答
3
投票

不要写这种代码。如您所知,这非常令人困惑。然而,根据规格,它是。虽然很奇怪。让我们通过相当多的步骤来解释这一点。这是关于复杂且通常不相关的细节的个别细节,这些细节结合起来解释了这种行为。

CTCs和初始化器的区别

想象一下这段代码:

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

© www.soinside.com 2019 - 2024. All rights reserved.