1.初始化變量

正如您已經知道的,您可以在類中聲明多個變量,而且不僅聲明它們,還可以立即用它們的初始值初始化它們。

這些相同的變量也可以在構造函數中初始化。這意味著,理論上,這些變量可以被賦值兩次。例子

代碼 筆記
class Cat
{
   public String name;
   public int age = -1;

   public Cat(String name, int age)
   {
     this.name = name;
     this.age = age;
   }

   public Cat()
   {
     this.name = "Nameless";
   }
}



變量age被賦予初始值




初始值被覆蓋


age 變量存儲它的初始值。
 Cat cat = new Cat("Whiskers", 2);
這是允許的:將調用第一個構造函數
 Cat cat = new Cat();
這是允許的:將調用第二個構造函數

Cat cat = new Cat("Whiskers", 2);這是執行時發生的情況:

  • Cat創建了一個對象
  • 所有實例變量都用它們的初始值初始化
  • 調用構造函數並執行其代碼。

換句話說,變量首先得到它們的初始值,然後才執行構造函數的代碼。


2.類中變量的初始化順序

變量不僅在構造函數運行之前被初始化——它們是按照明確定義的順序初始化的:它們在類中聲明的順序。

讓我們看一些有趣的代碼:

代碼 筆記
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

這段代碼不會編譯,因為在a創建變量時還沒有b 和c 變量。但是你可以像下面這樣編寫你的代碼——這段代碼將編譯並運行得很好。

代碼 筆記
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

但請記住,您的代碼必須對其他開發人員透明。最好不要使用這樣的技術,因為它會損害代碼的可讀性。

這裡我們必須記住,在變量被賦值之前,它們有一個默認值。對於int類型,這是零。

JVM在初始化a變量時,會簡單地為int類型賦默認值:0。

當它到達 時b,a 變量將已知並具有一個值,因此 JVM 將為它分配值 2。

而當它到達c變量時,變量ab變量已經被初始化,所以JVM很容易計算出初始值c:0+2+3。

如果您在方法內部創建了一個變量,您將無法使用它,除非您之前已為其賦值。但這對於類的變量而言並非如此!如果一個類的變量沒有被賦予初始值,那麼它被賦予一個默認值。


3.常量

當我們分析對像是如何創建的時,值得一提的是常量的初始化,即帶有修飾符的變量final

如果變量具有修飾符final,則必須為其分配初始值。你已經知道這一點,這並不奇怪。

但是你不知道的是,如果你在構造函數中賦值,你不必馬上賦初值。這對於 final 變量來說工作得很好。唯一的要求是,如果你有多個構造函數,那麼必須在每個構造函數中為一個最終變量賦值。

例子:

public class Cat
{
   public final int maxAge = 25;
   public final int maxWeight;

   public Cat (int weight)
   {
     this.maxWeight = weight; // Assign an initial value to the constant
   }
}


4.構造函數中的代碼

還有一些關於構造函數的重要說明。之後,隨著你繼續學習Java,你會遇到繼承、序列化、異常等,它們都會不同程度地影響構造函數的工作。現在深入探討這些主題毫無意義,但我們至少有義務觸及它們。

例如,這裡有一條關於構造函數的重要說明。理論上,您可以在構造函數中編寫任何復雜的代碼。但是不要這樣做。例子:

class FilePrinter
{
   public String content;

   public FilePrinter(String filename) throws Exception
   {
      FileInputStream input = new FileInputStream(filename);
      byte[] buffer = input.readAllBytes();
      this.content = new String(buffer);
   }

   public void printFile()
   {
      System.out.println(content);
   }
}






打開文件讀取流
將文件讀入字節數組
將字節數組保存為字符串




在屏幕上顯示文件內容

在 FilePrinter 類構造函數中,我們立即在文件上打開字節流並讀取其內容。這是一種複雜的行為,可能會導致錯誤。

如果沒有這樣的文件怎麼辦?如果讀取文件有問題怎麼辦?如果它太大了怎麼辦?

複雜的邏輯意味著出錯的可能性很高,這意味著代碼必須正確處理異常。

示例 1 — 序列化

在標準 Java 程序中,有很多情況下您不是創建類對象的人。例如,假設您決定通過網絡發送一個對象:在這種情況下,Java 機器本身會將您的對象轉換為一組字節,發送它,然後從該字節集重新創建對象。

但是假設您的文件在另一台計算機上不存在。構造函數中會出現錯誤,沒有人會處理它。這很有可能導致程序終止。

示例 2 — 初始化類的字段

如果您的類構造函數可以拋出已檢查的異常,即用 throws 關鍵字標記,那麼您必須在創建對象的方法中捕獲指示的異常。

但是如果沒有這樣的方法呢?例子:

代碼  筆記
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
這段代碼不會編譯。

FilePrinter構造函數可以拋出檢查異常FilePrinter,這意味著如果不將對象包裝在 try-catch 塊中,則無法創建對象。而一個try-catch塊只能寫在一個方法中



5.基類構造函數

在前面的課程中,我們稍微討論了繼承。不幸的是,我們對繼承和 OOP 的完整討論保留在專門用於 OOP 的級別,構造函數的繼承已經與我們相關。

如果你的類繼承了另一個類,父類的一個對象將被嵌入到你的類的一個對像中。更重要的是,父類有自己的變量和自己的構造函數。

這意味著當你的類有一個父類並且你繼承了它的變量和方法時,了解和理解變量是如何初始化的以及構造函數是如何調用的是非常重要的。

班級

我們如何知道初始化變量和調用構造函數的順序?讓我們從編寫兩個類的代碼開始。一個將繼承另一個:

代碼 筆記
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

class ChildClass extends ParentClass
{
   public String c;
   public String d;

   public ChildClass()
   {
   }
}










ChildClass 繼承ParentClass類。

我們需要確定初始化變量和調用構造函數的順序。日誌記錄將幫助我們做到這一點。

記錄

日誌記錄是通過將程序寫入控制台或文件來記錄程序在運行時執行的操作的過程。

確定構造函數已被調用非常簡單:在構造函數的主體中,向控制台寫入一條消息。但是你怎麼知道一個變量是否已經被初始化了呢?

實際上,這也不是很困難:編寫一個特殊的方法來返回用於初始化變量的值,並記錄初始化。這就是代碼的樣子:

最終代碼

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

   public static String print(String text)
   {
      System.out.println(text);
      return text;
   }
}

class ParentClass
{
   public String a = Main.print("ParentClass.a");
   public String b = Main.print("ParentClass.b");

   public ParentClass()
   {
      Main.print("ParentClass.constructor");
   }
}

class ChildClass extends ParentClass
{
   public String c = Main.print("ChildClass.c");
   public String d = Main.print("ChildClass.d");

   public ChildClass()
   {
      Main.print("ChildClass.constructor");
   }
}




創建ChildClass對象


此方法將傳遞的文本寫入控制台並返回它。





聲明ParentClass

Display text 並用它初始化變量。




寫一條消息,說明構造函數已被調用。忽略返回值。


聲明ChildClass

Display text 並用它初始化變量。




寫一條消息,說明構造函數已被調用。忽略返回值。

如果執行這段代碼,文本將顯示在屏幕上,如下所示:

方法的控制台輸出Main.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

所以你總是可以親自確保在調用構造函數之前初始化一個類的變量。基類在繼承類初始化之前得到完全初始化。