为什么Java中的singleton模式中x和y输出不同的值?

ovfsdjhp  于 2023-04-28  发布在  Java
关注(0)|答案(1)|浏览(107)

在下面的代码中,为什么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);
}
mmvthczy

mmvthczy1#

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

CTC和初始化器的区别

想象一下这段代码:

public class Foo {
  private static final long MARK = System.currentTimeMillis();
}

显然,MARK的值不能在编译时确定-这被解释为“时间戳,因为它是在该字段初始化时”,这归结为“当这个类被加载时”。这意味着任何地方的代码第一次引用Foo时,必须执行一些代码。在这个执行过程中,上下文必须存在-这样的代码在理论上可以引用MARK
因此,MARK0开始,代码编译如下:

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的定义方式,在您的代码中,xy不符合条件;具体来说,x * 没有 * 一个常量值,这是因为要符合条件,该字段必须是staticfinal。显然,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了。首先,构造函数运行,xy都为零。构造函数将它们设置为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)。

相关问题