23

为什么在类中声明的变量具有默认值,但在方法中声明的变量,称为“局部变量”,在 Java 中没有默认值?

例如

class abc
{
   int a;

   public static void main(String ss[])
   {
        int b;

          abc aa=new abc();
          System.out.println(aa.a);
          System.out.println(b);
    }
 }

在上面的示例中,变量a的默认值为 0,但变量b给出了可能尚未初始化的错误。

4

4 回答 4

16

所有成员变量都必须加载到堆中,因此在创建类实例时必须使用默认值进行初始化。在局部变量的情况下,它们不会被加载到堆中,它们会存储在堆栈中,直到它们在 java 7 之前被使用,所以我们需要显式地初始化它们。现在“Java Hotspot Server Compiler”执行“逃逸分析”并决定在堆栈而不是堆上分配一些变量。

于 2013-08-14T08:19:30.160 回答
2

局部变量初始化

在方法和块中声明的变量称为局部变量。局部变量在方法调用时创建时未初始化。因此,局部变量必须在使用前显式初始化。否则,当包含方法或块被执行时,编译器会将其标记为错误。

例子:

public class SomeClassName{

public static void main(String args[]){
int total;
System.out.println("The incremented total is " + total + 3); //(1)
}
}

编译器抱怨在 (1) 处的 println 语句中使用的局部变量 total 可能未初始化。使用前初始化局部变量total可以解决问题:

public class SomeClassName{

public static void main(String args[]){
int total = 45; //Local variable initialized with value 45 System.out.println("The incremented total is " + total+ 3); //(1)
}
}

字段初始化

如果没有为实例或静态变量提供初始化,无论是在声明时还是在初始化程序块中,都会使用其类型的默认值隐式初始化。每次实例化类时,都会使用其类型的默认值初始化实例变量,即针对从该类创建的每个对象。静态变量在首次加载类时使用其类型的默认值进行初始化。

于 2013-08-14T08:18:44.560 回答
1

由于局部变量是在堆栈上分配的,因此局部变量的内存块是在为其分配值时分配的。

举个简单的例子

class Abc {
   int i = -111;
   int e;

   int doSomething() {
        int a = 10;
        int b = a + i;    
        int c = b + 100;

        Abc d = new Abc();

        e = b + c + d.a;

        return e + 1000;
    }
 }

和来自的字节码javap -c Abc

Compiled from "Abc.java"
class Abc {
  int i;
  int e;

  Abc();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        -111
       7: putfield      #2                  // Field i:I
      10: return

  int doSomething();
    Code:
       0: bipush        10
       2: istore_1
       3: iload_1
       4: aload_0
       5: getfield      #2                  // Field i:I
       8: iadd
       9: istore_2
      10: iload_2
      11: bipush        100
      13: iadd
      14: istore_3
      15: new           #3                  // class Abc
      18: dup
      19: invokespecial #4                  // Method "<init>":()V
      22: astore        4
      24: aload_0
      25: iload_2
      26: iload_3
      27: iadd
      28: aload         4
      30: getfield      #2                  // Field i:I
      33: iadd
      34: putfield      #5                  // Field e:I
      37: aload_0
      38: getfield      #5                  // Field e:I
      41: sipush        1000
      44: iadd
      45: ireturn
}

当调用方法时,分配堆栈中称为当前帧的内存空间

如果你仔细看,甚至int a=-111;赋值发生在一个隐式的 init 函数中Abc()

       int a = -111;

       5: bipush        -111
       7: putfield      #2                  // Field a:I

由于没有为字段变量e分配任何值,如果是原始变量,它将为 0,如果是 Object 引用,则为 null

如果你看doSomething()

        int a = 10;
        0: bipush        10

对于要使用的局部变量,在这种情况下需要将初始值推入堆栈 10 。没有这个 'push' [initialization] a 的值不能被后续语句访问(因为该值不在堆栈上)。一旦将值推送到堆栈上,其他操作(如 iadd istore 等)就会在堆栈上执行

下面的语句实际上在堆空间上创建了一个对象并调用了 init 方法。这是像'e'这样的未初始化变量获取默认值的地方

      15: new           #3                  // class Abc
      18: dup

我将进一步的字节码比较留给你;)但我希望它很清楚

于 2013-08-14T09:14:54.063 回答
0

tl;dr:这或多或少是一个任意的选择

如果你问我,Java 有实例变量的默认值是一个错误。编译器应该强制程序员在之前初始化它,就像局部变量的情况一样。

默认值背后的基本原理是安全。当一个对象被实例化时,将为该对象分配一块内存,其中包含实例变量指向的位置等。Java 设计者认为用零和空值擦除这部分内存是一个好主意。这样,您将永远不会读取在分配对象之前碰巧存在的垃圾。他们本可以强制初始化;选择没有任何根本性。它可能使事情易于实现,并且对 Java 的设计者来说足够有意义。

在局部变量的情况下,设计者选择强制初始化(或者更准确地说,当只声明局部变量时,他们选择不进行任何类型的初始化,因此编译器最合乎逻辑的行为是强制初始化使用前的变量)。

于 2013-08-14T09:14:47.790 回答