Sot*_*lis 6 java constructor field
这是针对类似问题的规范性问题和答案,其中问题是由阴影引起的.
我在我的类中定义了两个字段,一个是引用类型,另一个是基本类型.在类的构造函数中,我尝试将它们初始化为一些自定义值.
当我稍后查询这些字段的值时,它们返回Java的默认值,null对于引用类型,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;
}
}
Run Code Online (Sandbox Code Playgroud)
我希望getCapacity()返回值10并getElements()返回正确初始化的数组实例.
Sot*_*lis 13
Java程序中定义的实体(包,类型,方法,变量等)具有名称.这些用于指代程序其他部分中的那些实体.
Java语言为每个名称定义范围
声明的范围是程序的区域,声明声明的实体可以使用简单的名称引用,只要它是可见的(第6.4.1节).
换句话说,范围是编译时概念,它确定名称可用于引用某个程序实体的位置.
您发布的程序有多个声明.让我们开始吧
private String[] elements;
private int capacity;
Run Code Online (Sandbox Code Playgroud)
这些是字段声明,也称为实例变量,即.在类体中声明的成员类型.Java语言规范说明
m在类类型C(第8.1.6节)中声明或继承的成员声明的范围是整个主体C,包括任何嵌套类型声明.
这意味着您可以使用名称elements和capacity正文中的名称StringArray来引用这些字段.
构造函数体中的两个第一个语句
public StringArray() {
int capacity = 10;
String[] elements;
elements = new String[capacity];
}
Run Code Online (Sandbox Code Playgroud)
实际上是局部变量声明语句
一个局部变量的声明语句声明一个或多个局部变量名.
这两个语句在您的程序中引入了两个新名称.碰巧这些名字与你的字段相同.在您的示例中,局部变量声明capacity还包含初始化器,该初始化器初始化该局部变量,而不是同名字段.您命名的字段capacity初始化为其类型的默认值,即.价值0.
案例elements有点不同.局部变量声明语句引入了一个新名称,但是赋值表达式呢?
elements = new String[capacity];
Run Code Online (Sandbox Code Playgroud)
什么实体elements指的是什么?
规则范围状态
块(第14.4节)中局部变量声明的范围是声明出现的块的其余部分,从其自己的初始化器开始,并包括局部变量声明语句中右侧的任何其他声明符.
在这种情况下,块是构造函数体.但是构造函数体是body的一部分StringArray,这意味着字段名也在范围内.那么Java如何确定你所指的是什么?
Java引入了Shadowing的概念来消除歧义.
某些声明可能会在其作用域的一部分中被另一个同名声明所遮蔽,在这种情况下,简单名称不能用于引用声明的实体.
(简单的名称是单个标识符,例如elements.)
文档还说明
的声明
d一个的局部变量或异常参数命名n阴影,在整个范围d中,(a)指定的任何其他字段的声明n是在范围在其中点d发生,和(b)任何其它变量的命名的声明n是在范围内d发生但未在声明的最内层类中d声明.
这意味着名为的局部变量elements优先于名为的字段elements.表达方式
elements = new String[capacity];
Run Code Online (Sandbox Code Playgroud)
因此,正在初始化局部变量,而不是字段.该字段初始化为其类型的默认值,即.价值null.
里面你的方法getCapacity和getElements你在在各自使用的名称return语句引用字段,因为他们的声明是在范围上在程序中的特定点唯一的.由于字段已初始化为0和null,因此返回的值.
解决方案是完全删除局部变量声明,因此名称引用实际变量,就像您最初想要的那样.例如
public StringArray() {
capacity = 10;
elements = new String[capacity];
}
Run Code Online (Sandbox Code Playgroud)
与上述情况类似,您可能有正式(构造函数或方法)参数遮蔽具有相同名称的字段.例如
public StringArray(int capacity) {
capacity = 10;
}
Run Code Online (Sandbox Code Playgroud)
暗影规则陈述
在整个范围内声明
d一个名为nshadows 的字段或形式参数d的声明,这些变量的声明n是在d发生的范围内的范围内.
在上面的示例中,构造函数参数capacity的声明会影响也命名的实例变量的声明capacity.因此,不可能用简单的名称来引用实例变量.在这种情况下,我们需要使用其限定名称来引用它.
限定名称由名称"a"组成.令牌和标识符.
在这种情况下,我们可以使用主表达式this作为字段访问表达式的一部分来引用实例变量.例如
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
}
Run Code Online (Sandbox Code Playgroud)
每种变量,方法和类型都有阴影规则.
我的建议是尽可能使用唯一的名称,以避免这种行为.