简体   繁体   English

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

[英]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 type C (§8.1.6) is the entire body of C , 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主体中的名称elementscapacity来引用这些字段。

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 named n shadows , throughout the scope of d , (a) the declarations of any other fields named n that are in scope at the point where d occurs , and (b) the declarations of any other variables named n that are in scope at the point where d occurs but are not declared in the innermost class in which d 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.在您的方法getCapacitygetElements ,您在它们各自的return语句中使用的名称指的是字段,因为它们的声明是程序中特定点范围内的唯一声明。 Since the fields were initialized to 0 and null , those are the values returned.由于字段被初始化为0null ,这些是返回的值。

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];
}

Shadowing with constructor parameters带构造函数参数的阴影

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 named n shadows, throughout the scope of d , the declarations of any other variables named n that are in scope at the point where d 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.

相关问题 我的类必须是不可变的,但我需要一个复制构造函数,并且由于未初始化字段,因此构造函数给我一个错误 - My class needs to be immutable but I need a copy constructor, and since my fields aren't initialized, the constructor gives me an error 为什么在这里初始化我的超类的字段? - Why are the fields of my super class getting initialized here ? 对于int,类的实例变量的Java默认值未初始化为零 - Java default value of instance variables of a class not initialized to zero for int 为什么我的字段没有初始化为我给它的值 - why doesn't my field get initialized to the value I gave it 为什么我的 ArrayList 没有初始化为我指定的容量? - Why is my ArrayList not initialized to the capacity i specify? 为什么初始化实例变量时我会得到零? - why am i getting zero while i have initialized my instance variables? 当我在子类中调用基类方法(此方法初始化字段)时,为什么字段未正确初始化? - Why fields are not initialized correctly when I invoke a base class method (this method initialize the fields) in a subclass? 为什么字段似乎在构造函数之前被初始化? - Why do fields seem to be initialized before constructor? 未在默认构造函数中初始化的变量 - Variable not initialized in default constructor 为什么我设置了变量 null ? - Why is my variable null when I've set it?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM