總結的Java知識點集合

韩续贤發表於2024-03-04

這是我讀大學時的Java知識點總結,還不全面,後續會逐漸增加完善。

知識點集合

例項變數

例項變數是指在類中宣告的變數,其值是針對類的每個例項而獨立儲存的。每個類的例項都有自己的一組例項變數,它們的值可以在物件建立時初始化,並在整個物件的生命週期中保持不變或者隨著物件的狀態而改變。

例項變數也被稱為物件變數,因為它們是在類的物件例項化時建立的,並且每個物件都有自己的一組例項變數。

例項變數的特點包括:

  • 它們屬於物件,而不是類本身。
  • 每個物件都有自己的一組例項變數,每個物件的例項變數值可以是不同的。
  • 它們在物件的整個生命週期中存在,並且可以被物件的方法訪問和修改。
  • 例項變數不能使用 static 關鍵字進行修飾,而是透過例項化物件來訪問。

以下是一個示例,演示瞭如何在Java中定義和使用例項變數:

public class Person {
    // 例項變數
    String name;
    int age;
    
    public void displayInfo() {
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
    }
}

// 建立物件並設定例項變數的值
Person person = new Person();
person.name = "John";
person.age = 25;

// 呼叫方法訪問和使用例項變數
person.displayInfo();

在這個例子中,Person 類有兩個例項變數 nameage,它們屬於每個 Person 物件的一部分。在建立 Person 物件後,可以透過訪問物件的例項變數來設定和獲取它們的值。在 displayInfo() 方法中,可以使用例項變數來展示物件的資訊。

因此,例項變數是定義在類中的變數,每個物件都有自己獨立的一組例項變數,用於儲存物件的狀態和屬性。

例項化物件

例項化物件是根據類的定義建立一個具體的物件,也可以說是將類例項化為物件的過程。

在物件導向程式設計中,類是物件的模板,描述了物件應該具有的屬性和行為。透過例項化物件,我們可以根據類的定義建立一個具體的實體,該實體具有類中定義的屬性和行為。

在Java中,透過使用 new 關鍵字和類的構造方法來例項化物件。構造方法是一種特殊的方法,用於建立物件並初始化其屬性。

以下是一個示例:

// 定義一個類
class Person {
    private String name;
    private int age;
    
    // 構造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 方法
    public void sayHello() {
        System.out.println("Hello, my name is " + name + " and I'm " + age + " years old.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 例項化物件
        Person person = new Person("John", 25);
        
        // 呼叫物件的方法
        person.sayHello(); // 輸出 "Hello, my name is John and I'm 25 years old."
    }
}

在上面的示例中,我們定義了一個 Person 類,具有 nameage 屬性,以及 sayHello() 方法。透過 new 關鍵字和 Person 類的構造方法,我們例項化了一個 Person 物件,並將其賦值給 person 變數。

透過 person 物件,我們可以呼叫 sayHello() 方法,該方法會輸出物件的屬性值。

例項化物件是物件導向程式設計的基本概念之一,它允許我們建立具體的物件例項,並根據類定義的屬性和行為進行操作。

抽象類

Java 中的抽象類是不能被例項化的,只能被繼承。抽象類是用來作為其他類的父類或基類,它本身不能被例項化為物件。

抽象類是透過在類定義中使用關鍵字 abstract 來標識的。抽象類可以包含抽象方法,這些方法沒有具體的實現,需要在子類中進行重寫實現。抽象類可以有普通方法和成員變數,可以提供一些共享的實現邏輯給子類使用。

以下是一個示例:

// 抽象類
abstract class Animal {
    // 抽象方法
    public abstract void makeSound();
    
    // 普通方法
    public void sleep() {
        System.out.println("Animal is sleeping");
    }
}

// 繼承抽象類
class Cat extends Animal {
    // 實現抽象方法
    public void makeSound() {
        System.out.println("Meow");
    }
}

public class Main {
    public static void main(String[] args) {
        // 錯誤示例,不能例項化抽象類
        // Animal animal = new Animal();
        
        // 建立子類物件
        Animal cat = new Cat();
        cat.makeSound(); // 輸出 "Meow"
        cat.sleep(); // 輸出 "Animal is sleeping"
    }
}

在上面的示例中,Animal 類是一個抽象類,其中包含了一個抽象方法 makeSound() 和一個普通方法 sleep()。不能直接例項化 Animal 類,但可以透過繼承它的子類 Cat 來建立物件。

多型性

Java 中的多型性(polymorphism)是指在一個類層次結構中,子類可以以自己的形式重寫父類的方法,並且可以使用父類的引用來引用子類的物件。這種特性允許我們透過父類的引用來呼叫子類特定的方法,從而實現不同型別的物件以相同的方式進行操作。

多型性是物件導向程式設計的重要特性之一,它提高了程式碼的靈活性和可擴充套件性。透過多型性,我們可以編寫通用的程式碼,而不需要針對每個具體的子類編寫獨立的程式碼。

以下是一個示例:

// 父類
class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

// 子類1
class Dog extends Animal {
    public void makeSound() {
        System.out.println("Dog barks");
    }
}

// 子類2
class Cat extends Animal {
    public void makeSound() {
        System.out.println("Cat meows");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog(); // 子類物件賦值給父類引用
        Animal animal2 = new Cat(); // 子類物件賦值給父類引用
        
        animal1.makeSound(); // 呼叫子類的方法,輸出 "Dog barks"
        animal2.makeSound(); // 呼叫子類的方法,輸出 "Cat meows"
    }
}

在上面的示例中,我們定義了一個 Animal 父類和兩個子類 DogCat。透過將子類物件賦值給父類引用,我們可以使用父類的引用來呼叫子類的方法。在 main 方法中,我們建立了一個 Dog 物件,並將其賦值給 Animal 型別的引用 animal1,以及建立了一個 Cat 物件,並將其賦值給 Animal 型別的引用 animal2。然後,我們透過這兩個引用呼叫了 makeSound() 方法,分別輸出了相應的子類特定的聲音。

子類不能覆蓋父類的私有(private)方法

在 Java 中,子類不能覆蓋父類的私有(private)方法。私有方法是指只能在宣告它的類內部訪問的方法,無法被其他類或子類所訪問。

子類繼承父類的方法有以下幾種情況:

  1. 如果父類的方法是公共(public)或受保護(protected)的,子類可以重寫(覆蓋)該方法。
  2. 如果父類的方法是預設訪問修飾符(即沒有修飾符)的,子類可以重寫該方法,前提是子類與父類在同一個包中。
  3. 如果父類的方法是私有的,子類無法訪問該方法,因此也無法重寫它。

以下是一個示例:

class Parent {
    public void publicMethod() {
        System.out.println("Parent's public method");
    }
    
    protected void protectedMethod() {
        System.out.println("Parent's protected method");
    }
    
    private void privateMethod() {
        System.out.println("Parent's private method");
    }
}

class Child extends Parent {
    // 重寫父類的公共方法
    public void publicMethod() {
        System.out.println("Child's public method");
    }
    
    // 重寫父類的受保護方法
    protected void protectedMethod() {
        System.out.println("Child's protected method");
    }
    
    // 無法重寫父類的私有方法,編譯報錯
    // private void privateMethod() {
    //     System.out.println("Child's private method");
    // }
}

public class Main {
    public static void main(String[] args) {
        Parent parent = new Parent();
        parent.publicMethod(); // 輸出 "Parent's public method"
        parent.protectedMethod(); // 輸出 "Parent's protected method"
        // parent.privateMethod(); // 編譯報錯,私有方法無法訪問
        
        Child child = new Child();
        child.publicMethod(); // 輸出 "Child's public method"
        child.protectedMethod(); // 輸出 "Child's protected method"
    }
}

在上面的示例中,我們定義了一個 Parent 父類和一個 Child 子類。父類中有三個方法,分別是公共方法、受保護方法和私有方法。子類繼承了父類,並重寫了父類的公共方法和受保護方法。然而,子類無法重寫父類的私有方法,因為私有方法只能在父類內部訪問。

Runnable 介面並實現 run() 方法

Runnable 是一個介面,其中定義了一個抽象方法 run(),該方法包含執行緒的執行邏輯。實現了 Runnable 介面的類需要實現 run() 方法,並在該方法中編寫執行緒的具體執行邏輯。

建立執行緒的步驟如下:

  1. 建立一個類,實現 Runnable 介面。例如:
public class MyRunnable implements Runnable {
    public void run() {
        // 執行緒的執行邏輯
        System.out.println("Thread is running.");
    }
}
  1. 在實現了 Runnable 介面的類中,編寫執行緒的具體執行邏輯,即在 run() 方法中定義執行緒的行為。
  2. 在主執行緒或其他執行緒中,建立 Thread 物件,並將實現了 Runnable 介面的類的例項作為引數傳遞給 Thread 的建構函式。例如:
public class Main {
    public static void main(String[] args) {
        // 建立實現了 Runnable 介面的類的例項
        MyRunnable myRunnable = new MyRunnable();

        // 建立 Thread 物件,並將實現了 Runnable 介面的類的例項作為引數傳遞
        Thread thread = new Thread(myRunnable);

        // 啟動執行緒
        thread.start();
    }
}

透過將實現了 Runnable 介面的類的例項傳遞給 Thread 的建構函式,可以建立一個新的執行緒,並在該執行緒中執行實現了 run() 方法的程式碼邏輯。

這種方式的優勢是可以實現多重繼承,因為 Java 不支援多重繼承,但可以實現多個介面。同時,它也更加靈活,因為同一個 Runnable 物件可以被多個執行緒共享,從而實現執行緒的資源共享。

總之,透過實現 Runnable 介面並實現 run() 方法,可以建立一個新的類作為執行緒的執行體,並在建立執行緒時將該類的例項傳遞給 Thread 物件來建立新執行緒。

先進後出(LIFO)

先進後出(Last-In-First-Out,LIFO)是一種資料結構,其中最後插入的元素首先被訪問或刪除。以下是實現先進後出的一些常見資料結構:

  1. 堆疊(Stack):堆疊是一種基於 LIFO 原則的資料結構,它使用 push() 方法將元素新增到棧的頂部,並使用 pop() 方法從棧的頂部刪除和訪問元素。
  2. 遞迴呼叫:在程式設計中,遞迴呼叫也可以看作是一種先進後出的行為。每次進行遞迴呼叫時,當前的函式呼叫被暫停並推入呼叫棧,直到遞迴結束開始逐個彈出並執行。

需要注意的是,先進後出是與先進先出(FIFO)相對的概念。在先進後出的資料結構中,最後插入的元素首先被訪問或刪除;而在先進先出的資料結構中,最先插入的元素首先被訪問或刪除。

先進先出(FIFO)

JAVA異常

在 Java 中,異常分為兩種型別:編譯時異常(Checked Exception)和執行時異常(Runtime Exception)。
編譯時異常(Checked Exception)是在編譯階段檢測到的異常,需要在程式碼中進行處理或宣告丟擲。它們通常表示程式在執行過程中可能出現的外部因素引起的錯誤或異常情況。IOException 是編譯時異常的一個常見例子,表示輸入輸出操作可能出現的錯誤,例如檔案不存在或讀寫錯誤等。
執行時異常(Runtime Exception)是在程式執行時發生的異常,不需要在程式碼中進行強制處理或宣告丟擲。它們通常表示程式邏輯錯誤或程式設計錯誤,例如除以零、陣列越界等。RuntimeException 是執行時異常的一個常見例子。

靜態方法

靜態方法是在類級別上定義的方法,與特定的物件例項無關。它屬於類本身,而不是類的例項。可以透過類名直接呼叫靜態方法,而無需建立類的物件。

public class MyClass {
    private static int count; // 靜態變數

    public static void staticMethod() {
        System.out.println("這是一個靜態方法");
    }

    public void instanceMethod() {
        System.out.println("這是一個例項方法");
    }

    public static int getCount() {
        return count;
    }

    public static void setCount(int value) {
        count = value;
    }
}

在上面的程式碼中,我們定義了一個名為MyClass的類。其中包含一個靜態方法staticMethod()和一個例項方法instanceMethod()。還有一個靜態變數count,並提供了靜態的getter和setter方法。

可以透過以下方式呼叫靜態方法和訪問靜態變數:

MyClass.staticMethod(); // 呼叫靜態方法

MyClass myObject = new MyClass();
myObject.instanceMethod(); // 呼叫例項方法

int currentCount = MyClass.getCount(); // 獲取靜態變數的值
MyClass.setCount(10); // 設定靜態變數的值

請注意,靜態方法可以直接透過類名呼叫,而例項方法需要透過類的物件呼叫。靜態方法可以在沒有類的例項的情況下使用,因為它們屬於類本身。而例項方法需要透過類的物件來呼叫,因為它們與特定的物件例項相關聯。

靜態變數

public class StaticVariableExample {
    // 靜態變數
    static int staticVariable = 10;
    // 例項變數
    int instanceVariable = 20;

    public static void main(String[] args) {
        // 直接透過類名訪問靜態變數
        System.out.println("靜態變數的值:" + StaticVariableExample.staticVariable);

        // 建立類的例項物件
        StaticVariableExample obj = new StaticVariableExample();
        // 透過例項物件訪問例項變數
        System.out.println("例項變數的值:" + obj.instanceVariable);

        // 修改靜態變數的值
        StaticVariableExample.staticVariable = 30;
        // 修改例項變數的值
        obj.instanceVariable = 40;

        // 再次訪問靜態變數和例項變數的值
        System.out.println("修改後的靜態變數的值:" + StaticVariableExample.staticVariable);
        System.out.println("修改後的例項變數的值:" + obj.instanceVariable);
    }
}
//輸出結果:
//靜態變數的值:10
//例項變數的值:20
//修改後的靜態變數的值:30
//修改後的例項變數的值:40

成員變數

成員變數(Member Variables)是定義在類中的變數,也稱為例項變數或物件屬性。它們是類的組成部分,用於儲存物件的狀態和資料。

成員變數在類的內部宣告,但在方法之外。它們可以有不同的訪問修飾符(如public、private、protected等),用於控制其可見性和訪問許可權。

每個類的例項(物件)都有自己的一組成員變數,它們獨立於其他物件的成員變數。當建立一個類的例項時,會為該例項分配一塊記憶體來儲存其成員變數的值。

成員變數可以是任何合法的Java資料型別,包括基本資料型別(如int、double、boolean等)和引用資料型別(如String、陣列等)。每個物件的成員變數都有預設的初始值,例如,數值型別的預設值是0,布林型別的預設值是false,引用型別的預設值是null。

透過使用物件引用和點運算子(.)可以訪問和修改物件的成員變數。每個物件都有自己獨立的一組成員變數,可以透過物件引用來訪問和操作屬於該物件的成員變數。

總結來說,成員變數是定義在類中的變數,用於儲存物件的狀態和資料。每個物件都有自己獨立的一組成員變數,透過物件引用和點運算子可以訪問和操作這些成員變數。

當我們定義一個類時,可以在類的內部宣告成員變數。下面是一個示例程式碼,解釋了成員變數的用法:

public class MyClass {
    // 成員變數
    private int myNumber; // 整數型別的成員變數
    private String myString; // 字串型別的成員變數

    // 構造方法,用於建立物件時初始化成員變數
    public MyClass(int number, String str) {
        myNumber = number;
        myString = str;
    }

    // 成員方法,用於訪問成員變數和進行操作
    public void printDetails() {
        System.out.println("Number: " + myNumber);
        System.out.println("String: " + myString);
    }

    // 主方法,用於執行程式
    public static void main(String[] args) {
        // 建立一個物件並傳入初始化引數
        MyClass obj = new MyClass(10, "Hello");
        // 呼叫成員方法來訪問和操作成員變數
        obj.printDetails();
    }
}

在上述程式碼中,我們定義了一個名為MyClass的類,並宣告瞭兩個成員變數myNumbermyString。在構造方法中,我們初始化這兩個成員變數。然後,在成員方法printDetails()中,我們訪問並列印了這兩個成員變數的值。

在主方法中,我們建立了一個MyClass物件obj,並傳入初始化引數10和"Hello"。然後,我們呼叫obj的成員方法printDetails(),它會輸出成員變數myNumbermyString的值。

透過這個例子,我們可以看到成員變數的用法。每個物件都有自己獨立的一組成員變數,我們可以透過物件引用來訪問和操作這些成員變數。

ArrayList和LinkedList

ArrayList和LinkedList是Java中兩種常見的集合類,它們都實現了List介面,但在內部實現和使用方式上有一些區別。

內部實現方式:
ArrayList:基於陣列實現,可以利用索引進行快速訪問和修改元素。插入和刪除元素時需要移動其他元素的位置,效率較低。
LinkedList:基於雙向連結串列實現,每個元素都包含前後指標,插入和刪除元素時只需要修改相鄰元素的指標,效率較高。

訪問效率:
ArrayList:由於基於陣列實現,可以透過索引進行快速訪問和修改元素。時間複雜度為O(1)。
LinkedList:由於基於連結串列實現,訪問和修改元素需要遍歷連結串列,時間複雜度為O(n),其中n為連結串列的長度。

插入和刪除效率:
ArrayList:插入和刪除元素時,需要將後續元素向後或向前移動,時間複雜度為O(n)。
LinkedList:由於基於連結串列實現,插入和刪除元素只需要修改相鄰元素的指標,時間複雜度為O(1)。但是在具體的位置插入和刪除元素時,需要先遍歷到對應位置。

記憶體佔用:
ArrayList:由於是基於陣列實現,需要預先分配一定大小的連續記憶體空間。如果元素數量超過陣列容量,需要進行擴容操作,會佔用更多的記憶體空間。
LinkedList:由於是基於連結串列實現,每個元素都包含前後指標,會消耗更多的記憶體空間。
根據上述區別,我們可以根據具體的應用場景選擇使用ArrayList或LinkedList。如果需要頻繁訪問和修改元素,並且對插入和刪除操作要求不高,可以選擇ArrayList。如果需要頻繁進行插入和刪除操作,並且訪問和修改元素的需求較少,可以選擇LinkedList。

Override

"覆蓋"(Override)和重寫(override)是物件導向程式設計中的一個概念,指的是在子類中重新定義父類中已有的方法。

當一個子類繼承自父類時,它可以使用父類中的方法。然而,有時子類需要對父類的方法進行修改或者定製化操作,這就是方法的覆蓋。

要進行方法的覆蓋,子類需要滿足以下條件:

  1. 子類的方法名、引數列表和返回型別必須與父類中被覆蓋的方法相同。
  2. 子類中的覆蓋方法不能擁有比父類中被覆蓋方法更為嚴格的訪問修飾符。
  3. 子類中覆蓋方法的返回型別可以是父類方法返回型別的子類。

當我們在子類中定義一個與父類中具有相同方法簽名(方法名、引數列表和返回型別)的方法時,就可以認為我們在覆蓋父類的方法。在執行時,當透過子類物件呼叫該方法時,將會執行子類中的方法而不是父類中的方法。

覆蓋方法的目的通常是為了在子類中實現特定的行為,使得子類能夠按照自己的邏輯來執行相同的方法名。

需要注意的是,在Java中,靜態方法不能被覆蓋(Override)。靜態方法屬於類而不是物件,並且在編譯時就確定了呼叫的版本。所以無論如何在子類中定義相同的靜態方法,都不會對父類中的靜態方法產生影響。

方法過載(Method Overloading)

是指在一個類中可以存在多個同名的方法,但這些方法的引數列表不同。當呼叫這個方法時,編譯器會根據傳入的引數的型別和數量來選擇匹配的方法進行呼叫。

方法過載的特點如下:

  1. 方法名相同:過載的方法必須使用相同的方法名。
  2. 引數列表不同:過載的方法必須有不同的引數列表,可以包括引數的型別、數量和順序。
  3. 返回型別可以相同或不同:過載的方法可以具有相同的返回型別,也可以具有不同的返回型別,但返回型別不是方法過載的標準。
  4. 方法過載與訪問修飾符和返回型別無關:過載的方法可以有不同的訪問修飾符(如publicprivateprotected)和返回型別(除了void)。

方法過載的目的是提供一種更靈活的方式來處理不同型別的輸入資料,使程式碼更簡潔、易讀和易於維護。透過方法過載,可以使用相同的方法名來表示一組相關的操作,而不需要在方法名中使用不同的字尾或字首來區分。

例如,以下是一個簡單的示例,演示了方法過載的使用:

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
    
    public int add(int a, int b, int c) {
        return a + b + c;
    }
    
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        
        System.out.println(calculator.add(2, 3));               // 呼叫int add(int a, int b)
        System.out.println(calculator.add(2.5, 3.7));           // 呼叫double add(double a, double b)
        System.out.println(calculator.add(2, 3, 5));            // 呼叫int add(int a, int b, int c)
    }
}

在上述示例中,Calculator類中定義了三個同名的方法add,它們的引數列表不同。透過傳入不同型別和數量的引數,可以呼叫不同的過載方法。執行程式會輸出以下結果:

5
6.2
10

透過方法過載,可以根據不同的引數型別和數量來選擇正確的方法進行呼叫,使程式碼更加靈活和易於理解。

建構函式

建構函式是一種特殊的方法,用於在建立物件時進行初始化操作。它具有與類相同的名稱,沒有返回型別(甚至沒有void),並且在使用new關鍵字建立物件時自動呼叫。

下面是一個簡單的Java程式碼示例,展示了一個名為Person的類和其建構函式的定義:

public class Person {
    private String name;
    private int age;
    
    // 建構函式
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 其他方法
    public void sayHello() {
        System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
    }
    
    public static void main(String[] args) {
        // 使用建構函式建立Person物件
        Person person = new Person("John", 25);
        
        // 呼叫物件的方法
        person.sayHello();
    }
}

在上面的程式碼中,Person類有兩個私有的成員變數name和age,以及一個公共的建構函式和一個sayHello方法。建構函式被定義為public Person(String name, int age),接受兩個引數name和age,並用於初始化物件的成員變數。

在main方法中,使用建構函式new Person("John", 25)來建立一個名為person的Person物件。然後,透過呼叫person物件的sayHello方法,輸出一個簡單的問候語。

建構函式在建立物件時被自動呼叫,用於對物件進行初始化操作,例如給成員變數賦初始值。

深複製和淺複製

淺複製和深複製是在物件複製過程中的兩種不同方式。

淺複製是指建立一個新物件,該物件的欄位值是原始物件欄位值的一份複製。如果欄位是基本型別,複製的就是該欄位的值;如果欄位是引用型別,複製的就是該欄位的引用,兩個物件將共享同一個引用。換句話說,淺複製只複製物件的第一層,不會遞迴地複製物件的引用型別欄位。

深複製是指建立一個新物件,並遞迴地複製原始物件及其所有引用型別欄位所引用的物件,直到所有引用的物件都被複制。深複製會生成一個與原始物件完全獨立的副本,即使對副本物件進行修改也不會影響原始物件。

需要注意的是,深複製可能會導致複雜的物件關聯關係和迴圈引用問題,因此在實現深複製時需要考慮如何解決這些問題。

通常情況下,淺複製可以透過實現Cloneable介面並重寫clone()方法來實現。而深複製可以透過序列化和反序列化、遞迴複製或使用第三方庫等方式來實現。

下面是一個示例程式碼,演示了淺複製和深複製的區別:

class Person implements Cloneable {
    private String name;
    private int age;
    private Address address;

    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Address getAddress() {
        return address;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Address {
    private String city;

    public Address(String city) {
        this.city = city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCity() {
        return city;
    }
}

public class Example {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("Beijing");
        Person person1 = new Person("Alice", 20, address);

        // 淺複製
        Person person2 = (Person) person1.clone();
        System.out.println(person1.getAddress() == person2.getAddress()); // 輸出:true,引用型別欄位共享同一個引用

        // 深複製
        Address newAddress = new Address(person1.getAddress().getCity());
        Person person3 = new Person(person1.getName(), person1.getAge(), newAddress);
        System.out.println(person1.getAddress() == person3.getAddress()); // 輸出:false,引用型別欄位使用新的引用
    }
}

在上面的例子中,Person類包含一個引用型別欄位address,表示人的地址。我們建立了一個person1物件,並透過淺複製和深複製分別建立了person2person3物件。

在淺複製中,person2address欄位和person1共享同一個引用,因此它們指向的是同一個Address物件。而在深複製中,我們手動建立了一個新的Address物件,並將其賦給person3address欄位,使得person3person1擁有不同的地址物件。

因此,淺複製只複製了引用型別欄位的引用,而深複製複製了引用型別欄位的整個物件。

相關文章