這是我讀大學時的Java知識點總結,還不全面,後續會逐漸增加完善。 知識點集合 實例變數 實例變數是指在類中聲明的變數,其值是針對類的每個實例而獨立存儲的。每個類的實例都有自己的一組實例變數,它們的值可以在對象創建時初始化,併在整個對象的生命周期中保持不變或者隨著對象的狀態而改變。 實例變數也被稱為 ...
這是我讀大學時的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
類有兩個實例變數 name
和 age
,它們屬於每個 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
類,具有 name
和 age
屬性,以及 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
父類和兩個子類 Dog
和 Cat
。通過將子類對象賦值給父類引用,我們可以使用父類的引用來調用子類的方法。在 main
方法中,我們創建了一個 Dog
對象,並將其賦值給 Animal
類型的引用 animal1
,以及創建了一個 Cat
對象,並將其賦值給 Animal
類型的引用 animal2
。然後,我們通過這兩個引用調用了 makeSound()
方法,分別輸出了相應的子類特定的聲音。
子類不能覆蓋父類的私有(private)方法
在 Java 中,子類不能覆蓋父類的私有(private)方法。私有方法是指只能在聲明它的類內部訪問的方法,無法被其他類或子類所訪問。
子類繼承父類的方法有以下幾種情況:
- 如果父類的方法是公共(public)或受保護(protected)的,子類可以重寫(覆蓋)該方法。
- 如果父類的方法是預設訪問修飾符(即沒有修飾符)的,子類可以重寫該方法,前提是子類與父類在同一個包中。
- 如果父類的方法是私有的,子類無法訪問該方法,因此也無法重寫它。
以下是一個示例:
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() 方法,併在該方法中編寫線程的具體執行邏輯。
創建線程的步驟如下:
- 創建一個類,實現 Runnable 介面。例如:
public class MyRunnable implements Runnable {
public void run() {
// 線程的執行邏輯
System.out.println("Thread is running.");
}
}
- 在實現了 Runnable 介面的類中,編寫線程的具體執行邏輯,即在 run() 方法中定義線程的行為。
- 在主線程或其他線程中,創建 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)是一種數據結構,其中最後插入的元素首先被訪問或刪除。以下是實現先進後出的一些常見數據結構:
- 堆棧(Stack):堆棧是一種基於 LIFO 原則的數據結構,它使用 push() 方法將元素添加到棧的頂部,並使用 pop() 方法從棧的頂部刪除和訪問元素。
- 遞歸調用:在編程中,遞歸調用也可以看作是一種先進後出的行為。每次進行遞歸調用時,當前的函數調用被暫停並推入調用棧,直到遞歸結束開始逐個彈出並執行。
需要註意的是,先進後出是與先進先出(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
的類,並聲明瞭兩個成員變數myNumber
和myString
。在構造方法中,我們初始化這兩個成員變數。然後,在成員方法printDetails()
中,我們訪問並列印了這兩個成員變數的值。
在主方法中,我們創建了一個MyClass
對象obj
,並傳入初始化參數10和"Hello"。然後,我們調用obj
的成員方法printDetails()
,它會輸出成員變數myNumber
和myString
的值。
通過這個例子,我們可以看到成員變數的用法。每個對象都有自己獨立的一組成員變數,我們可以通過對象引用來訪問和操作這些成員變數。
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)是面向對象編程中的一個概念,指的是在子類中重新定義父類中已有的方法。
當一個子類繼承自父類時,它可以使用父類中的方法。然而,有時子類需要對父類的方法進行修改或者定製化操作,這就是方法的覆蓋。
要進行方法的覆蓋,子類需要滿足以下條件:
- 子類的方法名、參數列表和返回類型必須與父類中被覆蓋的方法相同。
- 子類中的覆蓋方法不能擁有比父類中被覆蓋方法更為嚴格的訪問修飾符。
- 子類中覆蓋方法的返回類型可以是父類方法返回類型的子類。
當我們在子類中定義一個與父類中具有相同方法簽名(方法名、參數列表和返回類型)的方法時,就可以認為我們在覆蓋父類的方法。在運行時,當通過子類對象調用該方法時,將會執行子類中的方法而不是父類中的方法。
覆蓋方法的目的通常是為了在子類中實現特定的行為,使得子類能夠按照自己的邏輯來執行相同的方法名。
需要註意的是,在Java中,靜態方法不能被覆蓋(Override)。靜態方法屬於類而不是對象,並且在編譯時就確定了調用的版本。所以無論如何在子類中定義相同的靜態方法,都不會對父類中的靜態方法產生影響。
方法重載(Method Overloading)
是指在一個類中可以存在多個同名的方法,但這些方法的參數列表不同。當調用這個方法時,編譯器會根據傳入的參數的類型和數量來選擇匹配的方法進行調用。
方法重載的特點如下:
- 方法名相同:重載的方法必須使用相同的方法名。
- 參數列表不同:重載的方法必須有不同的參數列表,可以包括參數的類型、數量和順序。
- 返回類型可以相同或不同:重載的方法可以具有相同的返回類型,也可以具有不同的返回類型,但返回類型不是方法重載的標準。
- 方法重載與訪問修飾符和返回類型無關:重載的方法可以有不同的訪問修飾符(如
public
、private
、protected
)和返回類型(除了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
對象,並通過淺拷貝和深拷貝分別創建了person2
和person3
對象。
在淺拷貝中,person2
的address
欄位和person1
共用同一個引用,因此它們指向的是同一個Address
對象。而在深拷貝中,我們手動創建了一個新的Address
對象,並將其賦給person3
的address
欄位,使得person3
和person1
擁有不同的地址對象。
因此,淺拷貝只複製了引用類型欄位的引用,而深拷貝複製了引用類型欄位的整個對象。