JavaRush /Java Blog /Random-TW /Java 中的賦值與初始化
Viacheslav
等級 3

Java 中的賦值與初始化

在 Random-TW 群組發布

介紹

電腦程式的主要目的是資料處理。要處理數據,您需要以某種方式儲存它。我建議了解資料是如何儲存的。
Java 中的賦值與初始化 - 1

變數

變數是儲存任何資料的容器。我們來看看Oracle官方的Tutorial: Declaring Member Variables。根據本教程,有幾種類型的變數:
  • Fields:類別中宣告的變數;
  • 局部變數:方法或程式碼區塊中的變數;
  • 參數:方法宣告中的變數(在簽章中)。
所有變數都必須有變數類型和變數名。
  • 變數的類型指示變數代表什麼資料(即它可以儲存什麼資料)。我們知道,變數的型別可以是原始型別(primitives ,也可以是物件類型,而不是原始型別(Non-primitive)。對於物件變量,它們的類型由特定的類別描述。
  • 變數名必須是小寫,駝峰式。您可以在“變數:命名”中閱讀有關命名的更多資訊。
另外,如果是類別層級變量,即 是一個類別字段,可以為其指定存取修飾符。有關詳細信息,請參閱控制對類別成員的存取

變數聲明

所以,我們記住變數是什麼。為了開始使用變量,您需要聲明它。首先,讓我們來看看局部變數。為了方便起見,我們將使用教程點的線上解決方案,而不是 IDE:Online IDE。讓我們在他們的線上 IDE 中運行這個簡單的程式:
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
因此,如您所見,我們宣告了一個名為 namenumber和 type的局部變數int。我們按下「執行」按鈕,得到錯誤:
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
發生了什麼事?我們聲明了一個變量,但沒有初始化它的值。值得注意的是,這個錯誤不是在執行時(即不是在執行時)發生的,而是在編譯時發生的。智慧編譯器在存取局部變數之前檢查它是否會被初始化。因此,由此得出以下陳述:
  • 局部變數只能在初始化後才能存取;
  • 局部變數沒有預設值;
  • 局部變數的值在編譯時檢查。
因此,我們被告知必須初始化該變數。初始化變數就是給變數賦值。然後讓我們弄清楚它是什麼以及為什麼。

初始化局部變數

初始化變數是 Java 中最棘手的主題之一,因為… 與記憶體、JVM 實作、JVM 規範以及其他同樣可怕和棘手的事情密切相關。但你至少可以在某種程度上嘗試弄清楚它。讓我們從簡單到複雜。為了初始化變量,我們將使用賦值運算子並更改先前程式碼中的行:
int number = 2;
在此選項中,不會出現錯誤,並且值將顯示在螢幕上。在這種情況下會發生什麼?讓我們試著推理一下。如果我們想要給一個變數賦值,那麼我們希望該變數儲存一個值。事實證明,該值必須儲存在某個地方,但是儲存在哪裡呢?在磁碟上?但這非常慢,可能會對我們造成限制。事實證明,我們「此時此地」能夠快速有效地儲存資料的唯一地方是記憶體。這意味著我們需要在記憶體中分配一些空間。這是真實的。當一個變數被初始化時,將在分配給java進程的記憶體中為其分配空間,我們的程式將在該進程中執行。分配給java進程的記憶體被分為幾個區域或區域。其中哪一個將分配空間取決於宣告變數的類型。 記憶體分為以下幾個部分:堆、棧和非堆。讓我們從堆疊記憶體開始。Stack翻譯為堆疊(例如一疊書)。它是一種 LIFO(後進先出)資料結構。也就是說,就像一疊書。當我們在其中添加書籍時,我們將它們放在最上面,當我們將它們拿走時,我們將拿走最上面的一本(即最近添加的那本)。因此,我們啟動了我們的計劃。眾所周知,Java程式是由JVM,也就是Java虛擬機器來執行的。JVM 必須知道程式應該從哪裡開始執行。為此,我們聲明一個 main 方法,稱為「入口點」。為了在 JVM 中執行,會建立一個主執行緒(Thread)。當創建線程時,它會在記憶體中分配自己的堆疊。此堆疊由幀組成。當每個新方法在線程中執行時,都會為其分配一個新幀並添加到堆疊頂部(就像書堆中的一本新書一樣)。該框架將包含對物件和原始類型的引用。是的,是的,我們的int會儲存在堆疊上,因為… int 是原始型別。在分配幀之前,JVM 必須了解要在那裡保存什麼。正是由於這個原因,我們會收到「變數可能尚未初始化」的錯誤,因為如果它沒有初始化,那麼JVM將無法為我們準備堆疊。因此,在編譯程式時,智慧編譯器將幫助我們避免犯錯並破壞一切。 (!)為了清楚起見,我推薦一篇超級文章:「Java 堆疊和堆疊:Java 記憶體分配教學」。它連結到一個同樣酷的影片:
方法執行完成後,為這些方法分配的幀將從線程堆疊中刪除,為此幀分配的記憶體以及所有資料也將被清除。

初始化局部物件變數

讓我們再次將程式碼更改為更棘手一些:
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
這裡會發生什麼事?我們再談談吧。JVM 知道它應該從哪裡執行程序,即 她看到了主要方法。它創建一個線程並為其分配記憶體(畢竟,線程需要在某處儲存執行所需的資料)。在這個線程中,為main方法分配了一個幀。接下來我們建立一個 HelloWorld 物件。這個物件不再是在堆疊上創建的,而是在堆上創建的。因為object不是原始型,而是物件類型。並且堆疊只會儲存對堆中物件的參考(我們必須以某種方式存取該物件)。接下來,在main方法的堆疊中,將分配幀用於執行println方法。執行完main方法後,所有的frame都會被銷毀。如果幀被破壞,所有資料都將被破壞。object物件不會立即被銷毀。首先,對它的引用將被銷毀,因此沒有人會再引用該對象對象,並且不再可能存取記憶體中的該對象。智慧型 JVM 對此有自己的機制-垃圾收集器(簡稱垃圾收集器或 GC)。然後它從記憶體中刪除沒有其他人引用的物件。上面給出的連結再次描述了此過程。甚至還有一個有解釋的影片。

初始化字段

類別中指定的欄位的初始化以特殊方式進行,具體取決於該欄位是否是靜態的。如果一個字段有關鍵字static,那麼這個字段指的是類別本身,如果沒有指定static這個詞,那麼這個字段指的是這個類別的一個實例。讓我們來看一個例子:
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
在此範例中,欄位在不同時間初始化。number 欄位將在 HelloWorld 類別物件建立後初始化。但是count字段會在Java虛擬機器載入類別時被初始化。類別載入是一個單獨的主題,因此我們不會在這裡混合它。值得一提的是,當類別在運行時已知時,靜態變數就會被初始化。這裡還有更重要的事情,你已經注意到了這一點。我們沒有在任何地方指定該值,但它有效。確實如此。作為欄位的變數,如果沒有指定值,則會使用預設值進行初始化。對於數值,浮點數為 0 或 0.0。對於布林值來說這是錯誤的。對於所有物件類型變量,該值將為 null(我們稍後會討論這一點)。看來,為什麼會這樣呢?但因為物件是在Heap(堆中)中創建的。該區域的工作是在運作系統中執行的。而且我們可以在運行時初始化這些變量,不像堆疊,必須在執行之前準備好記憶體。這就是 Java 中記憶體的工作原理。但這裡還有一個特點。這個小片段觸及了記憶的不同角落。我們記得,在堆疊記憶體中為 main 方法分配了一個幀。此幀儲存對堆記憶體中物件的引用。但是 count 儲存在哪裡呢?我們記得,在堆中創建物件之前,立即初始化該變數。這是一個非常棘手的問題。在Java 8之前,有一個稱為PERMGEN的記憶體區域。從 Java 8 開始,這個區域發生了變化,稱為 METASPACE。本質上,靜態變數是類別定義的一部分,即 它的元數據。因此,它儲存在元資料儲存庫 METASPACE 中是合乎邏輯的。MetaSpace屬於同一個Non-Heap記憶體區域,並且是它的一部分。同樣重要的是要考慮變數聲明的順序。例如,這段程式碼有個錯誤:
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

什麼是空

如上所述,物件類型的變量,如果它們是類別的字段,則會初始化為預設值,並且該預設值為 null。但是Java中的null是什麼?首先要記住的是原始類型不能為 null。這都是因為 null 是一個特殊的引用,它不引用任何地方、任何物件。因此,只有物件變數可以為 null。第二件需要理解的重要事情是 null 是一個引用。我參考的也有它們的重量。關於這個主題,你可以閱讀 stackoverflow 上的問題:「Does null variable require space in memory」。

初始化區塊

當考慮變數的初始化時,不考慮初始化塊將是一種罪。它看起來像這樣:
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }

}
輸出順序為:靜態區塊、區塊、建構函數。正如我們所看到的,初始化區塊在建構函數之前執行。有時這可能是一種方便的初始化方法。

結論

我希望這個簡短的概述能夠提供一些關於它的工作原理和原因的見解。#維亞切斯拉夫
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION