有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java初始化字段变量

public class Foo {
    private int var;

    public Foo() {
        var = 10;
    }
}

在这个代码段中,var会先被分配一个默认值,然后再被重新分配到10,还是直接分配到10而不分配默认值

有点小问题,但我很好奇


共 (6) 个答案

  1. # 1 楼答案

    它将首先被赋予默认值。特别是,如果Foo是从Bar派生的,并且Bar的构造函数可以以某种方式获取var的值(例如,通过在Bar中声明并在Foo中重写的虚拟方法),那么即使变量是final,该默认值也是可见的。例如:

    class Parent {
    
        public Parent() {
            showVariables();
        }
    
        public void showVariables() {
        }
    }
    
    class Child extends Parent {
        private final int x;
    
        public Child() {
            x = 10;
        }
    
        @Override
        public void showVariables() {
            System.out.println("x = " + x); // Prints x = 0
        }
    }
    
    public class Test {
    
        public static void main(String[] args) {
            new Child();
        }
    }
    

    请注意,即使在声明点初始化字段,也会发生此仍然

    public class Foo {
        private int var = 10;
    
        public Foo() {
            // Implicit call to super constructor - this occurs *before*
            // var is assigned the value 10
        }
    }
    

    在这方面,Java不同于C#。在C#中,在调用基构造函数之前var将被赋值为10

  2. # 2 楼答案

    指定默认值意味着在指定默认值之前它有一个值。对象在创建时具有默认值,但未指定该值

    如果查看字节码,唯一的代码是分配新值

  3. # 3 楼答案

    如果查看Foo.class的反编译字节码,您将注意到以下内容:

    • 类的构造函数本身只分配值10(bipush和putfield)。该类的构造函数不会先分配0,然后再分配10
    • 无论从哪个代码访问,只要访问VM,该字段的默认值都为0。所以这个默认值不会出现在任何地方——至少不会出现在类的字节码中,或者其他通过反射等方式访问字段的类中。基本默认值被烘焙到VM中
    • 显式设置默认值将产生不同的字节码,请参见第二个示例

    public class Foo {
    
      private int var;
    
      public Foo();
         0  aload_0 [this]
         1  invokespecial java.lang.Object() [10]
         4  aload_0 [this]
         5  bipush 10
         7  putfield Foo.var : int [12]
        10  return
    

    如果您编写以下内容:

    public class Foo {
        private int var = 0;
    
        public Foo() {
            var = 20;
        }
    }
    

    字节码将是:

     0  aload_0 [this]
     1  invokespecial java.lang.Object() [10]
     4  aload_0 [this]
     5  iconst_0
     6  putfield Foo.var : int [12]
     9  aload_0 [this]
    10  bipush 20
    12  putfield Foo.var : int [12]
    15  return
    

    下一个示例显示,访问变量仍然不会导致任何值的赋值

    public class Foo {
        private int var;
    
        public Foo() {
            System.out.println(var);
            var=10;
        }
    }
    

    此代码将打印0,因为操作码8处的getField Foo.var会将“0”推送到操作数堆栈上:

    public Foo();
       0  aload_0 [this]
       1  invokespecial java.lang.Object() [10]
       4  getstatic java.lang.System.out : java.io.PrintStream [12]
       7  aload_0 [this]
       8  getfield Foo.var : int [18]
      11  invokevirtual java.io.PrintStream.println(int) : void [20]
      14  aload_0 [this]
      15  bipush 10
      17  putfield Foo.var : int [18]
      20  return
    
  4. # 5 楼答案

    在调用构造函数之前,未初始化的字段将始终被分配默认值,因为运行时将在调用构造函数之前将对象的内存分配归零。它必须这样做,因为它不知道构造函数可能提前做什么,并且因为派生类可能存在于其他JAR/类路径中,并获取值(如果受保护)或调用使用该字段的方法,然后再由构造函数初始化

    这是独立于编译器执行的,因此这不是编译器可以优化的,编译器甚至不能控制它

  5. # 6 楼答案

    根据spec:(第4.2.15节)

    前0名

    然后是10

    如果您首先在构造函数中读取它,您将得到0

    public class Foo {
        private int var;
    
        public Foo() {
            System.out.println(var); //0
            var = 10;
        }
    }