java—当我在类的构造函数中声明并初始化字段时,为什么要将字段初始化为null或默认值0?

50pmv0ei  于 2021-07-09  发布在  Java
关注(0)|答案(4)|浏览(533)

这是一个典型的问题和类似问题的答案,问题是一个阴影的结果。
我在类中定义了两个字段,一个是引用类型,一个是基元类型。在类的构造函数中,我尝试将它们初始化为一些自定义值。
当我稍后查询这些字段的值时,它们返回java的默认值, null 引用类型为0,基元类型为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() 返回正确初始化的数组示例。

j5fpnvbx

j5fpnvbx1#

int capacity = 10; 在构造函数中声明一个局部变量 capacity 阴影笼罩着整个班级。
补救的办法是放下武器 int : capacity = 10; 这将更改字段值。类中的另一个字段也是如此。
你的ide没有警告你这个阴影吗?

pu82cl6c

pu82cl6c2#

另一个被广泛接受的约定是在类成员中添加一些前缀(或后缀,随您的喜好而定),以区别于局部变量。
例如,类成员 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

2exbekwf

2exbekwf3#

在java/c/c++中使用变量有两个部分。一个是声明变量,另一个是使用变量(无论是赋值还是在计算中使用)。
声明变量时,必须声明其类型。所以你会用

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

使用变量时不必重新声明它:

int x;
int x = 7;

如果变量在同一范围内,则会出现编译器错误;但是,正如您所发现的,如果变量在不同的范围内,您将屏蔽第一个声明。

2jcobegt

2jcobegt4#

java程序中定义的实体(包、类型、方法、变量等)都有名称。它们用于指代程序其他部分中的实体。
java语言为每个名称定义了一个作用域
声明的范围是程序的一个区域,在该区域内,声明声明的实体可以使用一个简单的名称来引用,只要它是可见的(§6.4.1).
换句话说,作用域是一个编译时概念,它决定了在哪里可以使用一个名称来引用某个程序实体。
你发布的程序有多个声明。让我们从

private String[] elements;
private int capacity;

这些是字段声明,也称为示例变量,即在类主体中声明的成员类型。java语言规范说明
成员声明的范围 m 在类类型中声明或由类类型继承 C (§8.1.6)是 C ,包括任何嵌套类型声明。
这意味着您可以使用这些名称 elements 以及 capacity 在…体内 StringArray 引用这些字段。
构造函数体中的前两个语句

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

实际上是局部变量声明语句
局部变量声明语句声明一个或多个局部变量名。
这两个语句在程序中引入了两个新名称。碰巧那些名字和你的字段是一样的。在您的示例中,的局部变量声明 capacity 还包含一个初始化器,用于初始化该局部变量,而不是相同名称的字段。您的字段名为 capacity 初始化为其类型的默认值,即 0 .
关于 elements 有点不同。局部变量声明语句引入了一个新名称,但是赋值表达式呢?

elements = new String[capacity];

什么是实体 elements 指的是什么?
范围状态规则
块中局部变量声明的作用域(§14.4)是出现声明的块的其余部分,从它自己的初始值设定项开始,并在局部变量声明语句的右侧包括任何其他声明符。
在本例中,块是构造函数体。但是构造函数体是 StringArray ,这意味着字段名也在作用域中。那么java如何确定您所指的内容呢?
java引入了阴影的概念来消除歧义。
某些声明可能在其部分范围内被另一个同名声明所掩盖,在这种情况下,不能使用简单名称来引用声明的实体。
(作为单一标识符的简单名称,例如。 elements .)
文件还指出
声明 d 局部变量或异常参数的 n 阴影,在整个 d ,(a)任何其他指定字段的声明 n 在范围内 d 和(b)任何其他名为 n 在范围内 d 发生但未在其中的最内层类中声明 d 已声明。
这意味着名为 elements 优先于名为 elements . 表达式

elements = new String[capacity];

因此正在初始化局部变量,而不是字段。字段初始化为其类型的默认值,即 null .
在你的方法里 getCapacity 以及 getElements ,您在中使用的名称在各自的 return 语句引用这些字段,因为它们的声明是程序中特定点作用域中唯一的声明。因为字段被初始化为 0 以及 null ,这些是返回的值。
解决方案是完全去掉局部变量声明,因此让名称引用示例变量,就像您最初想要的那样。例如

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

带构造函数参数的阴影

与上面描述的情况类似,您可能有形式(构造函数或方法)参数来隐藏具有相同名称的字段。例如

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

阴影规则状态
声明 d 一个名为 n 阴影,在整个 d ,任何其他名为 n 在范围内 d 发生。
在上面的示例中,构造函数参数的声明 capacity 隐藏示例变量(也称为 capacity . 因此,不可能用简单的名称引用示例变量。在这种情况下,我们需要用它的限定名来引用它。
限定名由名称、“.”标记和标识符组成。
在这种情况下,我们可以使用主表达式 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
}

每种变量、方法和类型都有阴影规则。
我的建议是尽可能使用唯一的名字,以避免这种行为。

相关问题