为啥实例变量在java中有默认值? [复制]

Posted

技术标签:

【中文标题】为啥实例变量在java中有默认值? [复制]【英文标题】:Why do instance variables have default values in java? [duplicate]为什么实例变量在java中有默认值? [复制] 【发布时间】:2013-08-16 02:11:13 【问题描述】:

为什么在类中声明的变量有默认值,而在方法中声明的变量,称为“局部变量”,在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 给出了它可能尚未初始化的错误。

【问题讨论】:

int a 实际上有一个默认值0 对象的整个内存块总是用零填充,这就是为什么对象中的所有变量都默认为 0,b 是一个局部变量,根本没有在该部分初始化。它在赋值时被初始化 但是 y b 变量没有默认值? b 在堆栈中分配,与a 不同,并且出于性能原因不会归零。 (或更正确地说:实现不需要清除它,因为它必然会限制实现的性能而几乎没有收益) 请使用大写——我再也不会编辑你的个帖子了 【参考方案1】:

局部变量初始化

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

示例:

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)


字段初始化

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

【讨论】:

但问题仍未得到解答。即使局部变量存储在堆栈上,为什么不给它们分配默认值。 请参阅 Java 语言规范:docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5 引用:“局部变量(第 14.4 节,第 14.14 节)必须在使用之前显式地赋值,通过初始化(第 14.4 节)或赋值( §15.26),以一种可以使用明确分配规则(§16)进行验证的方式。” @Ankur 看最后一部分---字段初始化【参考方案2】:

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

【讨论】:

局部变量加载到堆中?您是否注意到 OP 正在谈论 int,它是一种原始类型? o.O 他只是以int为例。 不过,如果你有一个局部变量 Object oreference 仍然存在于堆栈中,而不是堆中。如果你不初始化o,使用它是错误的——问题是使用reference没有初始化。如果你只是简单地做o = null,那么你可以使用它:你已经初始化了引用,它再次存在于堆栈中(而不是堆中),并且在这个初始化之后没有任何东西“加载到堆中”。如果你做o = new Object(),那么你已经触及了堆,但这与你不能使用未初始化的局部变量这一事实无关。 我认为这里有一个巨大的误解。我不是专门谈论 Java 7 或逃逸分析。随便吧。我放弃了,希望人们不会感到困惑。 '在 Java 7 之前使用之前加载到堆栈'是没有意义的,'所以我们需要显式初始化它们'是一个不合逻辑的。这里没有什么可以真正回答这个问题。前两句只是重述了它,其余的只是很多混乱的华夫饼。最后一句无关紧要。 @BrunoReis 我完全同意。【参考方案3】:

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

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

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

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

【讨论】:

编译器不可能知道在赋值之前何时使用了实例成员。编译器不知道将首先调用该类的哪个方法。另一方面,对方法进行相同的分析当然是可能的,因为只有一个入口点。 @EJP Kotlin 似乎做得相当不错...... Java 不需要那么聪明。【参考方案4】:

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

举个简单的例子

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; 赋值发生在隐式初始化函数 Abc() 中!

       int a = -111;

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

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

如果你看看doSomething()

        int a = 10;
        0: bipush        10

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

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

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

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

【讨论】:

下定决心。局部变量的“内存块”在分配值时分配'在进入方法时分配。不是同时两个。 ipush 语句将紧随其后的 'istore' 指令存储的值 10 压入 a 的堆栈槽中。推送不会“创建”a

以上是关于为啥实例变量在java中有默认值? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

无法在 main() 方法中实例化字段(实例变量)。为啥??爪哇

为啥我能够从在同一对象的另一个实例上调用的方法访问一个实例的私有实例变量? [复制]

为啥在 Student(Student s) 中可以使用实例变量? [复制]

java中,在实例化一个类时,这个类中没有初始值的int类型成员变量i,i的值是否0?

在Java程序设计中实例变量和类变量有啥区别?

为啥通过 python 默认变量初始化变量会在对象实例化过程中保持状态?