当我在我的类的构造函数中声明并初始化它们时,为什么我的字段初始化为null或默认值为零?

这是一个典型的问题和答案,类似的问题,这个问题是阴影的结果。


我在我的类中定义了两个字段,一个是引用types,另一个是基本types。 在类的构造函数中,我尝试将它们初始化为一些自定义值。

当我稍后查询这些字段的值时,它们会返回Java的缺省值,对于引用types为null ,对于基本types为0。 为什么发生这种情况?

这是一个可重现的例子:

 public class Sample { public static void main(String[] args) throws Exception { StringArray array = new StringArray(); System.out.println(array.getCapacity()); // prints 0 System.out.println(array.getElements()); // prints null } } class StringArray { private String[] elements; private int capacity; public StringArray() { int capacity = 10; String[] elements; elements = new String[capacity]; } public int getCapacity() { return capacity; } public String[] getElements() { return elements; } } 

我期望getCapacity()返回值10和getElements()返回一个正确初始化的数组实例。

Java程序中定义的实体(包,types,方法,variables等)具有名称 。 这些用来指代程序其他部分的那些实体。

Java语言为每个名称定义一个范围

声明的范围是声明中声明的实体可以使用一个简单的名称来引用的程序的区域,只要它是可见的(§6.4.1)。

换句话说, 范围是一个编译时间的概念,它决定了一个名字可以用来引用某个程序实体的位置。

你发布的程序有多个声明。 我们开始吧

 private String[] elements; private int capacity; 

这些是字段声明,也称为实例variables ,即。 在类体中声明的一种types的成员。 Java语言规范说明

在类typesC (§8.1.6)中声明或inheritance的成员m声明的范围是C的整个主体,包括任何嵌套types声明。

这意味着你可以使用StringArray体内的名字elementscapacity来引用这些字段。

你的构造函数体中的两个第一个语句

 public StringArray() { int capacity = 10; String[] elements; elements = new String[capacity]; } 

实际上是局部variables声明语句

局部variables声明语句声明一个或多个局部variables名称。

这两条语句在程序中引入了两个新名字。 恰恰相反,这些名字和你的字段是一样的。 在你的例子中, capacity的局部variables声明还包含初始化该初始化该局部variables ,而不是相同名称的字段。 您的字段命名capacity被初始化为其types的默认值 ,即。 值为0

elements的情况有点不同。 局部variables声明语句引入了一个新的名字,但是赋值expression式呢?

 elements = new String[capacity]; 

elements指的是什么实体?

范围规则的状态

块(14.4)中局部variables声明的范围是声明出现的块的其余部分,从其自身的初始化程序开始,并在局部variables声明语句右侧包含任何其他声明程序。

在这种情况下,块是构造函数体。 但构造函数体是StringArray体的一部分,这意味着字段名也在范围内。 那么Java如何确定你指的是什么呢?

Java引入了Shadowing的概念来消除歧义。

有些声明可能会在另一个同名声明的部分范围内被隐藏,在这种情况下,一个简单的名称不能用于引用声明的实体。

(一个简单的名称是一个单一的标识符,例如elements

该文件还指出

d的整个范围内,一个名为n局部variables或exception参数的声明 d (a) 任何其他在d出现的地方处于范围内的字段n的声明,以及(b)任何其他名为nvariables,它们在d出现的位置上,但是没有在声明d的最里面的类中声明。

这意味着名为elements的本地variables优先于名为elements的字段。 expression方式

 elements = new String[capacity]; 

因此初始化本地variables,而不是字段。 该字段被初始化为其types的默认值 ,即。 值为null

在你的方法getCapacitygetElements ,你在它们各自的return语句中使用的名称指向这些字段,因为它们的声明是该程序中特定位置的唯一的声明。 由于字段初始化为0null ,这些值是返回的值。

解决的办法是完全摆脱局部variables声明,因此有名称引用实例variables,如你最初想要的。 例如

 public StringArray() { capacity = 10; elements = new String[capacity]; } 

用构造函数参数进行遮蔽

类似于上面描述的情况,您可能有相同名称的forms(构造函数或方法)参数阴影字段。 例如

 public StringArray(int capacity) { capacity = 10; } 

影子规则状态

d的整个范围内,名为n的字段或forms参数的声明dd出现的位置处的范围内的任何其他名为nvariables的声明。

在上面的示例中,构造函数参数capacity的声明会隐藏也称为capacity的实例variables的声明。 因此不可能用简单的名字来引用实例variables。 在这种情况下,我们需要用它的合格名称来引用它。

一个合格的名字由一个名字,一个“。” 令牌和标识符。

在这种情况下,我们可以使用主expression式作为字段访问expression式的一部分来引用实例variables。 例如

 public StringArray(int capacity) { this.capacity = 10; // to initialize the field with the value 10 // or this.capacity = capacity; // to initialize the field with the value of the constructor argument } 

每种types的variables ,方法和types都有阴影规则。

我的build议是,尽可能使用唯一的名称,以避免这种行为。

int capacity = 10; 在你的构造函数中声明了一个局部variablescapacity ,它影响了类的字段。

补救措施是放弃int

capacity = 10;

这将改变字段值。 同样在class上的其他领域。

你的IDE没有提醒你这个阴影吗?

另一个被广泛接受的约定是为类别成员添加一些前缀(或后缀 – 无论你喜欢什么),以区别于局部variables。

例如具有m_前缀的类成员:

 class StringArray { private String[] m_elements; private int m_capacity; public StringArray(int capacity) { m_capacity = capacity; m_elements = new String[capacity]; } public int getCapacity() { return m_capacity; } public String[] getElements() { return m_elements; } } 

大多数IDE已经支持这个符号,下面是Eclipse

在这里输入图像描述

在java / c / c ++中使用variables有两个部分。 一个是声明variables,另一个是使用variables(无论是分配一个值还是在计算中使用它)。

当你声明一个variables时,你必须声明它的types。 所以你会用

 int x; // to declare the variable x = 7; // to set its value 

使用时不必重新声明variables:

 int x; int x = 7; 

如果variables在相同的范围内,你会得到一个编译器错误; 然而,正如你所发现的,如果variables在不同的范围内,你将掩盖第一个声明。