[英]Why are my fields initialized to null or to the default value of zero when I've declared and initialized them in my class' constructor?
This is meant to be a canonical question and answer for similar questions where the issue is a result of shadowing .这是一个规范的问题和类似问题的答案,其中问题是阴影的结果。
I've defined two fields in my class, one of a reference type and one of a primitive type.我在 class 中定义了两个字段,一个是引用类型,一个是原始类型。 In the class' constructor, I try to initialize them to some custom values.
在类的构造函数中,我尝试将它们初始化为一些自定义值。
When I later query for those fields' values, they come back with Java's default values for them, null
for the reference type and 0 for the primitive type.当我稍后查询这些字段的值时,它们会返回 Java 的默认值,
null
用于引用类型,0 用于原始类型。 Why is this happening?为什么会这样?
Here's a reproducible example:这是一个可重现的示例:
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;
}
}
I expected getCapacity()
to return the value 10 and getElements()
to return a properly initialized array instance.我希望
getCapacity()
返回值 10 和getElements()
返回正确初始化的数组实例。
Entities (packages, types, methods, variables, etc.) defined in a Java program have names . Java 程序中定义的实体(包、类型、方法、变量等)都有名称。 These are used to refer to those entities in other parts of a program.
这些用于引用程序其他部分中的那些实体。
The Java language defines a scope for each name Java 语言为每个名称定义了一个范围
The scope of a declaration is the region of the program within which the entity declared by the declaration can be referred to using a simple name, provided it is visible (§6.4.1).
声明的范围是程序的区域,在该区域内可以使用简单名称引用声明所声明的实体,前提是它是可见的(第 6.4.1 节)。
In other words, scope is a compile time concept that determines where a name can be used to refer to some program entity.换句话说,作用域是一个编译时概念,它决定了一个名称可以用来指代某个程序实体的位置。
The program you've posted has multiple declarations.您发布的程序有多个声明。 Let's start with
让我们开始
private String[] elements;
private int capacity;
These are field declarations, also called instance variables , ie.这些是字段声明,也称为实例变量,即。 a type of member declared in a class body .
在类体中声明的一种成员。 The Java Language Specification states
Java 语言规范指出
The scope of a declaration of a member
m
declared in or inherited by a class typeC
(§8.1.6) is the entire body ofC
, including any nested type declarations.的成员的声明的范围
m
中声明或由类继承的类型C
(§8.1.6)是整个身体C
,包括任何嵌套类型声明。
This means you can use the names elements
and capacity
within the body of StringArray
to refer to those fields.这意味着您可以使用
StringArray
主体中的名称elements
和capacity
来引用这些字段。
The two first statements in your constructor body构造函数体中的前两个语句
public StringArray() {
int capacity = 10;
String[] elements;
elements = new String[capacity];
}
are actually local variable declaration statements实际上是局部变量声明语句
A local variable declaration statement declares one or more local variable names.
局部变量声明语句声明一个或多个局部变量名称。
Those two statements introduce two new names in your program.这两个语句在您的程序中引入了两个新名称。 It just so happens that those names are the same as your fields'.
碰巧这些名称与您的字段相同。 In your example, the local variable declaration for
capacity
also contains an initializer which initializes that local variable , not the field of the same name.在您的示例中,
capacity
的局部变量声明还包含初始化该局部变量的初始化程序,而不是同名的字段。 Your field named capacity
is initialized to the default value for its type, ie.您的名为
capacity
字段被初始化为其类型的默认值,即。 the value 0
.值
0
。
The case for elements
is a little different. elements
的情况略有不同。 The local variable declaration statement introduces a new name, but what about the assignment expression ?局部变量声明语句引入了一个新名称,但是赋值表达式呢?
elements = new String[capacity];
What entity is elements
referring to? elements
指的是什么实体?
The rules of scope state范围状态规则
The scope of a local variable declaration in a block (§14.4) is the rest of the block in which the declaration appears, starting with its own initializer and including any further declarators to the right in the local variable declaration statement.
块中局部变量声明的范围(第 14.4 节)是该声明出现的块的其余部分,从它自己的初始值设定项开始,并包括局部变量声明语句右侧的任何其他声明符。
The block, in this case, is the constructor body.在这种情况下,块是构造函数体。 But the constructor body is part of the body of
StringArray
, which means field names are also in scope.但是构造函数体是
StringArray
体的一部分,这意味着字段名称也在范围内。 So how does Java determine what you're referring to?那么 Java 如何确定您所指的是什么?
Java introduces the concept of Shadowing to disambiguate. Java 引入了Shadowing的概念来消除歧义。
Some declarations may be shadowed in part of their scope by another declaration of the same name, in which case a simple name cannot be used to refer to the declared entity.
某些声明可能在其部分范围内被另一个同名声明遮蔽,在这种情况下,不能使用简单名称来引用声明的实体。
(a simple name being a single identifier, eg. elements
.) (一个简单的名字是一个单一的标识符,例如。
elements
。)
The documentation also states该文件还指出
A declaration
d
of a local variable or exception parameter namedn
shadows , throughout the scope ofd
, (a) the declarations of any other fields namedn
that are in scope at the point whered
occurs , and (b) the declarations of any other variables namedn
that are in scope at the point whered
occurs but are not declared in the innermost class in whichd
is declared.的声明
d
名为局部变量或异常参数的n
阴影,整个的范围d
中,(a)指定的任何其他字段的声明n
在范围上在其中点d
发生,和(b)所述的任何声明其他名为n
变量在d
出现的点的范围内,但未在声明d
的最里面的类中声明。
This means that the local variable named elements
takes priority over the field named elements
.这意味着名为
elements
的局部变量优先于名为elements
的字段。 The expression表达方式
elements = new String[capacity];
is therefore initializing the local variable, not the field.因此正在初始化局部变量,而不是字段。 The field is initialized to the default value for its type, ie.
该字段被初始化为其类型的默认值,即。 the value
null
.值
null
。
Inside your methods getCapacity
and getElements
, the names you use in the in their respective return
statements refer to the fields since their declarations are the only ones in scope at that particular point in the program.在您的方法
getCapacity
和getElements
,您在它们各自的return
语句中使用的名称指的是字段,因为它们的声明是程序中特定点范围内的唯一声明。 Since the fields were initialized to 0
and null
, those are the values returned.由于字段被初始化为
0
和null
,这些是返回的值。
The solution is to get rid of the local variable declarations altogether and therefore have the names refer to the instance variables, as you originally wanted.解决方案是完全摆脱局部变量声明,因此让名称引用实例变量,正如您最初想要的那样。 For example
例如
public StringArray() {
capacity = 10;
elements = new String[capacity];
}
Similar to the situation described above, you may have formal (constructor or method) parameters shadowing fields with the same name.与上述情况类似,您可能有形式(构造函数或方法)参数隐藏具有相同名称的字段。 For example
例如
public StringArray(int capacity) {
capacity = 10;
}
Shadowing rules state阴影规则状态
A declaration
d
of a field or formal parameter namedn
shadows, throughout the scope ofd
, the declarations of any other variables namedn
that are in scope at the point whered
occurs.一个声明
d
字段或命名的形式参数的n
阴影,整个范围d
,任何其他变量的声明命名n
在范围上的点在哪里d
发生。
In the example above, the declaration of the constructor parameter capacity
shadows the declaration of the instance variable also named capacity
.在上面的例子中,构造函数参数
capacity
的声明影响了同样命名为capacity
的实例变量的声明。 It's therefore impossible to refer to the instance variable with its simple name.因此不可能用简单的名称来引用实例变量。 In such cases, we need to refer to it with its qualified name .
在这种情况下,我们需要用它的限定名来引用它。
A qualified name consists of a name, a "."
限定名称由名称、“.”组成。 token, and an identifier.
令牌和标识符。
In this case, we can use the primary expression this
as part of a field access expression to refer to the instance variable.在这种情况下,我们可以使用主表达式
this
作为字段访问表达式的一部分来引用实例变量。 For example例如
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
}
There are Shadowing rules for every kind of variable , method, and type.每种变量、方法和类型都有阴影规则。
My recommendation is that you use unique names wherever possible so as to avoid the behavior altogether.我的建议是尽可能使用唯一的名称,以完全避免这种行为。
int capacity = 10;
in your constructor is declaring an local variable capacity
which shadows the field of the class.在您的构造函数中声明了一个局部变量
capacity
,它隐藏了类的字段。
The remedy is to drop the int
:补救措施是删除
int
:
capacity = 10;
This will change the field value.这将更改字段值。 Ditto for the other field in the class.
班级中的其他领域也是如此。
Didn't your IDE warn you of this shadowing?您的 IDE 没有警告您这种阴影吗?
Another widely accepted convention is to have some prefix (or suffix - whatever you prefer) added to class members to distinguish them from local variables.另一个被广泛接受的约定是在类成员中添加一些前缀(或后缀 - 无论您喜欢什么),以将它们与局部变量区分开来。
For example class members with m_
prefix:例如带有
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;
}
}
Most IDEs have already available support for this notation, below is for Eclipse大多数 IDE 已经支持这种表示法,下面是针对 Eclipse 的
There are two parts to using variables in java/c/c++.在 java/c/c++ 中使用变量有两个部分。 One is to declare the variable and the other is to use the variable (whether assigning a value or using it in a calculation).
一种是声明变量,另一种是使用变量(无论是赋值还是在计算中使用)。
When you declare a variable you must declare its type.当你声明一个变量时,你必须声明它的类型。 So you would use
所以你会使用
int x; // to declare the variable
x = 7; // to set its value
You do not have to re-declare a variable when using it:使用变量时不必重新声明变量:
int x;
int x = 7;
if the variable is in the same scope you will get a compiler error;如果变量在同一范围内,您将收到编译器错误; however, as you are finding out, if the variable is in a different scope you will mask the first declaration.
但是,正如您所发现的,如果变量在不同的范围内,您将屏蔽第一个声明。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.