静态修饰符如何影响此代码?


109

这是我的代码:

class A {
    static A obj = new A();
    static int num1;
    static int num2=0;

    private A() {
        num1++;
        num2++;
    }
    public static A getInstance() {
        return obj;
    }
}

public class Main{
    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

输出为1 0,但我听不懂。

有人可以向我解释吗?


10
好问题!我们应该从中学到什么:不要这样做!;)
isnot2bad

Answers:


116

在Java中,分为两个阶段:1.标识,2.执行

  1. 识别阶段,将检测所有静态变量并使用默认值对其进行初始化。

    所以现在的值是:
    A obj=null
    num1=0
    num2=0

  2. 第二阶段,execution,从上到下开始。在Java中,执行从第一个静态成员开始。
    这里的第一个静态变量是static A obj = new A();,因此首先它将创建该变量的对象并调用构造函数,因此num1and 的值num2变为1
    然后再次static int num2=0;执行,这使得num2 = 0;

现在,假设您的构造函数是这样的:

 private A(){
    num1++;
    num2++;
    System.out.println(obj.toString());
 }

这将抛出一个NullPointerException作为obj仍然还没有得到的参考 class A


11
我将继续:将static A obj = new A();下面的行移动到static int num2=0;1和
Thomas

2
仍然令我感到困惑的是,即使num1没有显式的初始化,它也是(隐式地)用0初始化的。显式和隐式初始化之间应该没有真正的区别……
isnot2bad

@ isnot2bad“隐式初始化”作为声明的一部分发生。无论您以什么顺序提交,声明都会在分配之前发生。A obj = new A(); int num1; int num2 = 0;变成这样:A obj; int num1; int num2; obj = new A(); num2 = 0;。Java这样做num1, num2是由到达new A()构造函数的时间定义的。
汉斯Z

31

static修饰符应用于变量声明时的含义是,该变量是类变量,而不是实例变量。换句话说,...只有一个num1变量,也只有一个num2变量。

(除了:静态变量在某些其他语言中类似于全局变量,不同之处在于它的名称在任何地方都不可见。即使将其声明为a public static,只有在当前类或超类中声明了非限定名称,该变量才可见,或者使用静态导入方式导入。这就是区别。真正的全局可见,无需任何限定。)

因此,当您引用obj.num1and时obj.num2,您实际上是在引用其真实名称为and 静态变量。并且类似地,当构造函数递增和时,它将分别递增相同的变量。A.num1A.num2num1num2

您的示例中令人困惑的皱纹是在类初始化中。通过首先默认初始化所有静态变量,然后按照它们在类中出现的顺序执行声明的静态初始化程序(和静态初始化程序块),来初始化一个类。在这种情况下,您将拥有:

static A obj = new A();
static int num1;
static int num2=0;

事情是这样的:

  1. 静态函数以其默认初始值开始;A.objnullA.num1/ A.num2为零。

  2. 第一个声明(A.obj)创建的实例A(),并为和构造A增量。当声明完成时,并且都为,并引用新构造的实例。A.num1A.num2A.num1A.num21A.objA

  3. 第二个声明(A.num1)没有初始化程序,因此A.num1不会更改。

  4. 第三个声明(A.num2)具有一个初始化器,该初始化器将赋零A.num2

因此,在类初始化的最后,A.num1is 1A.num2is 0...,这就是您的打印语句所显示的内容。

这种令人困惑的行为实际上归因于您在静态初始化完成之前创建实例,并且所使用的构造函数依赖于并修改了尚未初始化的静态对象。您应该避免在实际代码中执行此操作。


16

1,0是正确的。

加载该类时,所有静态数据都将在以后初始化。默认情况下,int为0。

  • 首先创建一个A。num1和num2变成1和1
  • static int num1;什么都没有
  • static int num2=0;这写入0到num2

9

这是由于静态初始化程序的顺序。类中的静态表达式按自上而下的顺序求值。

第一个被称为是的构造A,该套num1num2两个至1:

static A obj = new A();

然后,

static int num2=0;

被调用并再次设置num2 = 0。

这就是为什么num11 num2为0。

附带说明一下,构造函数不应修改静态变量,这是非常糟糕的设计。相反,尝试使用另一种方法在Java中实现Singleton


6

可以在JLS中找到一节:§12.4.2

详细的初始化过程:

9,接下来,按照文本顺序执行类的类变量初始化器和静态初始化器,或者接口的字段初始化器,就好像它们是单个块一样,除了最终的类变量和值可以编译的接口字段外时间常量首先被初始化

因此,这三个静态变量将按文本顺序一一初始化。

所以

static A obj = new A();
//num1 = 1, num2 = 1;
static int num1;
//this is initilized first, see below.
static int num2=0;
//num1 = 1, num2 = 0;

如果我将订单更改为:

static int num1;
static int num2=0;
static A obj = new A();

结果将是1,1

请注意,static int num1;它不是变量初始值设定项,因为(§8.3.2):

如果字段声明器包含变量初始值设定项,则它具有已声明变量的赋值(第15.26节),并且:如果声明器用于类变量(即静态字段),则变量初始值设定项为初始化类后,仅对赋值进行一次评估并执行一次赋值

并且在创建类时初始化该类变量。这首先发生(第4.12.5节)。

程序中的每个变量在使用其值之前都必须具有一个值:每个类变量,实例变量或数组组件在创建时都使用默认值初始化(第15.9节,第15.10节):对于字节类型,默认值为零,即(字节)0的值。对于short类型,默认值为零,即(short)0的值。对于int类型,默认值为零,即0。对于long类型,默认值为零,即0L。对于float类型,默认值为正零,即0.0f。对于double类型,默认值为正零,即0.0d。对于char类型,默认值为空字符,即'\ u0000'。对于布尔类型,默认值为false。对于所有引用类型(第4.3节),默认值为null。


2

也许以这种方式考虑会有所帮助。

类是对象的蓝图。

对象被实例化时可以具有变量。

类也可以具有变量。这些被声明为静态的。因此,它们是在类而不是对象实例上设置的。

在应用程序中,每个人只能拥有任何一个类,因此有点像专门用于该类的全局存储。当然,可以从应用程序中的任何位置访问和修改这些静态变量(假设它们是公共的)。

这是“ Dog”类的示例,该类使用静态变量来跟踪其创建的实例数。

“狗”类是云,而橙色框是“狗”实例。

狗类

阅读更多

希望这可以帮助!

如果您觉得有些琐事,这个想法是由柏拉图首次提出的



0

上面的许多答案都是正确的。但实际上为了说明正在发生的事情,我在下面做了一些小的修改。

如上面多次提到的,正在发生的是在完全加载类A之前创建了类A的实例。因此,没有观察到被认为是正常的“行为”。这与从可以重写的构造方法中调用方法并不太相似。在这种情况下,实例变量可能不会处于直观状态。在此示例中,类变量未处于直观状态。

class A {
    static A obj = new A();
    static int num1;
    static int num2;
    static {
        System.out.println("Setting num2 to 0");
        num2 = 0;
    }

    private A() {
        System.out.println("Constructing singleton instance of A");
        num1++;
        num2++;
    }

    public static A getInstance() {
        return obj;
    }
}

public class Main {

    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

输出为

Constructing singleton instance of A
Setting num2 to 0
1
0

0

Java不会初始化任何静态或非静态数据成员的值,直到它不被调用而是创建它为止。

所以在这里,当在main中调用num1和num2时,它将使用值进行初始化

num1 = 0 + 1; 和

num2 = 0;

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.