【问题标题】: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?当我在类构造函数中声明并初始化它们时,为什么我的字段被初始化为 null 或默认值零?
【发布时间】:2020-05-01 23:48:12
【问题描述】:

这意味着对于类似问题的规范问题和答案,问题是由于阴影.


我在我的类中定义了两个字段,一个是引用类型,一个是原始类型。在 class\' 构造函数中,我尝试将它们初始化为一些自定义值。

当我稍后查询这些字段的值时,它们会返回 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;
    }
}

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

    标签: java constructor field


    【解决方案1】:

    Java 程序中定义的实体(包、类型、方法、变量等)具有names。这些用于引用程序其他部分中的那些实体。

    Java 语言为每个名称定义了一个scope

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

    换句话说,范围是一个编译时概念,它决定了在哪里可以使用名称来引用某个程序实体。

    您发布的程序有多个声明。让我们从

    private String[] elements;
    private int capacity;
    

    这些是field 声明,也称为instance variables,即。在class body 中声明的成员类型。 Java 语言规范声明

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

    这意味着您可以在StringArray 的正文中使用名称elementscapacity 来引用这些字段。

    构造函数主体中的前两个语句

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

    其实是local variable declaration statements

    一个局部变量声明语句声明一个或多个局部变量名。

    这两个语句在您的程序中引入了两个新名称。碰巧这些名称与您的字段相同。在您的示例中,capacity 的局部变量声明还包含一个初始化程序,该初始化程序初始化该局部变量,而不是同名字段。您名为capacity 的字段被初始化为default value 的类型,即。值0

    elements 的情况有点不同。局部变量声明语句引入了一个新名称,但是assignment expression 呢?

    elements = new String[capacity];
    

    elements 指的是什么实体?

    的规则范围状态

    一的范围局部变量块中的声明(§14.4)是 声明出现的块的其余部分,从它的开始 自己的初始化程序,并在右侧包括任何进一步的声明符 局部变量声明语句。

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

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

    一些声明可能在其部分范围内被另一个声明所遮蔽 相同名称的声明,在这种情况下,不能使用简单名称 用于引用声明的实体。

    (一个简单的名字是一个单一的标识符,例如。 elements.)

    该文件还指出

    声明d局部变量或名为n 的异常参数阴影, 在d 的整个范围内, (a)任何其他人的声明 名为n 的字段在d 出现的位置范围内, 和 (b) 任何其他名为 n 的变量的声明在范围内 d 出现但未在最里面的类中声明的点 其中声明了d

    这意味着名为elements 的局部变量优先于名为elements 的字段。表达方式

    elements = new String[capacity];
    

    因此正在初始化局部变量,而不是字段。该字段被初始化为其类型的default value,即。值null

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

    解决方案是完全摆脱局部变量声明,因此让名称引用实例变量,正如您最初想要的那样。例如

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

    使用构造函数参数遮蔽

    与上述情况类似,您可能有 formal (constructor or method) parameters 具有相同名称的阴影字段。例如

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

    阴影规则状态

    名为 n shadows 的字段或形式参数的声明 d, 在d 的范围内,任何其他变量的声明 命名为 n 的,在 d 出现的位置范围内。

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

    限定名称由名称、“.”组成。令牌和标识符。

    在这种情况下,我们可以使用primary expression this 作为field access expression 的一部分来引用实例变量。例如

    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
    }
    

    阴影每个kind of variable、方法和类型的规则。

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

    【讨论】:

      【解决方案2】:

      int capacity = 10; 在您的构造函数中声明了一个局部变量 capacity阴影类的领域。

      补救措施是删除int

      capacity = 10;

      这将更改字段值。类中的其他领域也是如此。

      您的 IDE 没有警告您这种阴影吗?

      【讨论】:

      • Eclipse 似乎没有。 IntelliJ 或您正在使用的任何东西都可以吗?
      • 使用 setter 或构造函数参数来隐藏类变量是很常见的。如果 IDE 会警告您,我会感到惊讶。
      【解决方案3】:

      另一个被广泛接受的约定是在类成员中添加一些前缀(或后缀 - 无论你喜欢什么),以将它们与局部变量区分开来。

      例如带有 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

      【讨论】:

        【解决方案4】:

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

        当你声明一个变量时,你必须声明它的类型。所以你会使用

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

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

        int x;
        int x = 7;   
        

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

        【讨论】:

          猜你喜欢
          • 2017-06-26
          • 2020-05-01
          • 1970-01-01
          • 2016-02-04
          • 1970-01-01
          • 2016-02-22
          • 1970-01-01
          • 2013-03-05
          • 2014-12-29
          相关资源
          最近更新 更多