簡體   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?

這是一個規范的問題和類似問題的答案,其中問題是陰影的結果。


我在 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 程序中定義的實體(包、類型、方法、變量等)都有名稱 這些用於引用程序其他部分中的那些實體。

Java 語言為每個名稱定義了一個范圍

聲明的范圍是程序的區域,在該區域內可以使用簡單名稱引用聲明所聲明的實體,前提是它是可見的(第 6.4.1 節)。

換句話說,作用域是一個編譯時概念,它決定了一個名稱可以用來指代某個程序實體的位置。

您發布的程序有多個聲明。 讓我們開始

private String[] elements;
private int capacity;

這些是字段聲明,也稱為實例變量,即。 類體中聲明的一種成員。 Java 語言規范指出

的成員的聲明的范圍m中聲明或由類繼承的類型C (§8.1.6)是整個身體C ,包括任何嵌套類型聲明。

這意味着您可以使用StringArray主體中的名稱elementscapacity來引用這些字段。

構造函數體中的前兩個語句

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 引入了Shadowing的概念來消除歧義。

某些聲明可能在其部分范圍內被另一個同名聲明遮蔽,在這種情況下,不能使用簡單名稱來引用聲明的實體。

(一個簡單的名字是一個單一的標識符,例如。 elements 。)

該文件還指出

的聲明d名為局部變量或異常參數的n陰影,整個的范圍d中,(a)指定的任何其他字段的聲明n在范圍上在其中點d發生,和(b)所述的任何聲明其他名為n變量在d出現的點的范圍內,但未在聲明d的最里面的類中聲明。

這意味着名為elements的局部變量優先於名為elements的字段。 表達方式

elements = new String[capacity];

因此正在初始化局部變量,而不是字段。 該字段被初始化為其類型的默認值,即。 null

在您的方法getCapacitygetElements ,您在它們各自的return語句中使用的名稱指的是字段,因為它們的聲明是程序中特定點范圍內的唯一聲明。 由於字段被初始化為0null ,這些是返回的值。

解決方案是完全擺脫局部變量聲明,因此讓名稱引用實例變量,正如您最初想要的那樣。 例如

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
}

每種變量、方法和類型都有陰影規則。

我的建議是盡可能使用唯一的名稱,以完全避免這種行為。

int capacity = 10; 在您的構造函數中聲明了一個局部變量capacity ,它隱藏了類的字段。

補救措施是刪除int

capacity = 10;

這將更改字段值。 班級中的其他領域也是如此。

您的 IDE 沒有警告您這種陰影嗎?

另一個被廣泛接受的約定是在類成員中添加一些前綴(或后綴 - 無論您喜歡什么),以將它們與局部變量區分開來。

例如帶有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 的

在此處輸入圖片說明

在 java/c/c++ 中使用變量有兩個部分。 一種是聲明變量,另一種是使用變量(無論是賦值還是在計算中使用)。

當你聲明一個變量時,你必須聲明它的類型。 所以你會使用

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

使用變量時不必重新聲明變量:

int x;
int x = 7;   

如果變量在同一范圍內,您將收到編譯器錯誤; 但是,正如您所發現的,如果變量在不同的范圍內,您將屏蔽第一個聲明。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM