【问题标题】:How does the static modifier affect this code?static 修饰符如何影响这段代码?
【发布时间】:2013-12-05 14:38:55
【问题描述】:

这是我的代码:

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,但是看不懂。

谁能给我解释一下?

【问题讨论】:

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

标签: java static


【解决方案1】:

在 Java 中发生两个阶段:1. 识别,2. 执行

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

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

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

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

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

这将抛出一个NullPointerException,因为obj 仍然没有class A 的引用。

【讨论】:

  • 我将扩展:将行 static A obj = new A(); 移到 static int num2=0; 下方,你应该得到 1 和 1。
  • 仍然让我感到困惑的是,即使 num1 没有显式初始化,它也(隐式)初始化为 0。显式初始化和隐式初始化之间应该没有区别...
  • @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() 构造函数时定义的。
【解决方案2】:

java 不会初始化任何静态或非静态数据成员的值,直到它未被调用但它创建它。

这样当 num1 和 num2 将在 main 中调用时,它将被初始化为值

num1 = 0+1;和

num2=0;

【讨论】:

    【解决方案3】:

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

    首先被调用的是A的构造函数,它将num1num2都设置为1:

    static A obj = new A();

    那么,

    static int num2=0;
    

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

    这就是为什么num1 是 1 而num2 是 0。

    附带说明,构造函数不应该修改静态变量,这是非常糟糕的设计。相反,请尝试对implementing a Singleton in Java 使用不同的方法。

    【讨论】:

      【解决方案4】:

      static 修饰符在应用于变量声明时意味着该变量是类变量而不是实例变量。也就是说……num1变量只有一个,num2变量只有一个。

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

      因此,当您引用obj.num1obj.num2 时,您实际上指的是 静态变量,其实际名称是A.num1A.num2。同样,当构造函数递增num1num2 时,它(分别)递增相同的变量。

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

      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.num2。声明完成后,A.num1A.num2都是1A.obj指的是新建的A实例。

      3. 第二个声明 (A.num1) 没有初始化器,所以 A.num1 不会改变。

      4. 第三个声明 (A.num2) 有一个初始化器,将零分配给 A.num2

      因此,在类初始化结束时,A.num11A.num20 ...这就是您的打印语句显示的内容。

      这种令人困惑的行为实际上是因为您在静态初始化完成之前创建了一个实例,并且您正在使用的构造函数依赖于并修改一个尚未完成的静态初始化。这是你应该避免在实际代码中做的事情。

      【讨论】:

        【解决方案5】:

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

        正如上面多次提到的,正在发生的事情是在类 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
        

        【讨论】:

          【解决方案6】:

          也许这样想会有所帮助。

          类是对象的蓝图。

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

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

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

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

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

          read more

          希望这会有所帮助!

          如果你觉得有些琐碎,这个想法是由Plato首先介绍的

          【讨论】:

            【解决方案7】:

            可以在 JLS 中找到一个部分:§12.4.2

            详细初始化过程:

            9.接下来,要么执行类的类变量初始化器和静态初始化器,要么执行接口的字段初始化器, 按文字顺序,就好像它们是一个单独的块,除了 final 类变量和接口的字段,其值为 首先初始化编译时常量

            所以这三个静态变量将按文本顺序一一初始化。

            所以

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

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

            【讨论】:

              【解决方案8】:

              static关键字在java中主要用于内存管理。我们可以将静态关键字应用于变量、方法、块和嵌套类。 static关键字属于类而不是类的实例。关于static关键字的简要说明:

              http://www.javatpoint.com/static-keyword-in-java

              【讨论】:

                【解决方案9】:

                1,0 是正确的。

                当类被加载时,所有静态数据都被初始化,它们被声明。默认情况下 int 为 0。

                • 创建了第一个 A。 num1 和 num2 变成 1 和 1
                • static int num1; 什么都不做
                • static int num2=0;这写0到num2

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2020-12-09
                  • 2011-02-13
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-11-29
                  • 2017-04-22
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多