---
# System prepended metadata

title: Java 創建物件、JVM 引用細節
tags: [Java 基礎]

---

---
title: 'Java 創建物件、JVM 引用細節'
disqus: kyleAlien
---

Java 創建物件、JVM 引用細節
===

## Overview of Content


如有引用參考請詳註出處，感謝 :smile_cat: 

在 JVM 中，物件的通常都會建立在堆區，而堆區也是 GC 最常處理的區塊；以物件的生命週期來說，在堆區建立後就是”生“，被 GC 回收後就是 “死”

但這個章節主要說明的是 Java 如何創建物件（顯示、隱式）、引用關係

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**深入探索 Java 物件創建與引用細節：Clone 和finalize 特性，以及強、軟、弱、虛引用**](https://devtechascendancy.com/java-object-creation_jvm-reference-details/)
:::

[TOC]

## Java 建立物件的方式

在 Java 中有多種建立物件的方式，而不同的物件建立方式又會有不同的特性；分別有以下方法

1. **使用 `new` 關鍵字**：最常見的方案

2. **使用 JVM Bytecode 反射創建**：在設計、使用框架時常見；通常會使用到 `java.lang.Class`、`java.lang.Constructor` 這兩個類

3. **使用 Clone 機制**：實作 Cloneable 這個標示界面

4. **反序列化**：這個方案較少見；通常會使用到 `java.io.ObjectInputStream` 類來將物件實體化

    :::info
    * **物件的建立前提是類已經被 ClassLoader 加載進方法區，並初始化完成**
    :::

### Clone 特性

* Object#**`clone` 方法**：它是 Object 中的方法，它的源碼實現如下（以下我們看的是 `Java 15` 的源碼）

    ```java=
    // Object.java    

    public class Object {
        ... 省略部份

        protected native Object clone() throws CloneNotSupportedException;

    }
    ```
    
    
    我們可以看到 ^1.^ **`clone` 是一個 Native 方法**，代表他是在 Native 層被實作的，^2.^ 它是 `protected` 方法，並且 ^3.^ 可能會拋出 `CloneNotSupportedException`
    
    * 關於第二點與 **`Cloneable` 標示介面** 有關係
        
        ```java=
        // java.lang.Cloneable

        public interface Cloneable {
        }
        ```

        :::success
        * **標示界面** 是甚麽？
            
            標示介面通常是拿來判斷的，**標示界面並不具有任何需要實作的方法**
        
        * **`Protected` 方法**
            
            介於它是 Protected 方法，所以如果要讓外部呼叫，我們可以使用 `overload` 的技巧，對外開放
        :::
        
        
        * 如果我們呼叫 `clone` 方法，但並沒有實作 `Cloneable` 介面，那就會拋出 `CloneNotSupportedException` 異常

            ```java=
            class NoCloneable {

                @Override
                public NoCloneable clone() {
                    try {
                        return (NoCloneable) super.clone();
                    } catch (CloneNotSupportedException e) {
                        // 例外轉譯
                        throw new AssertionError();
                    }
                }

            }
            ```
            > ![](https://hackmd.io/_uploads/HJs3NYFp2.png)
        
        * 只要我們標示該介面是 `Cloneable` 就不會拋出異常

            ```java=
            class WithCloneable implements Cloneable {

                @Override
                public WithCloneable clone() {
                    try {
                        return (WithCloneable) super.clone();
                    } catch (CloneNotSupportedException e) {
                        // 例外轉譯
                        throw new AssertionError();
                    }
                }

            }
            ```
            
* **Clone 預設是 ==淺拷貝==**！當它拷貝物件時會 **針對類型的不同而有不同的操作**，我們接著來看看這兩種不同類型 clone 會如何操作

    * **基礎類型**：在呼叫 clone 方法，JVM 會對基礎類型（`int, long, double`... 等等）創建一個全新的基礎類型物件，並賦予 clone 物件

        ``` mermaid
        graph TD;

        物件-->| JVM clone |Clone_物件
        Clone_物件-->新的基礎類型
        ```
    
    * **參考類型**：參考類型（`ref`）像是物件、陣列；**JVM 會直接複製該參考引用**，而不會自動幫你創建一個新的參考物件！**也就是說 clone 出來的物件，會與原先的物件使用相同的引用！**
        
        > **深拷貝**：如果有需拷貝全新參考類型，那要則需要自己手動做參考類型的 clone 方法

        ``` mermaid
        graph TD;

        物件-->| JVM clone |Clone_物件;
        物件-->參考物件;

        Clone_物件-->參考物件
        ```

        ```java=
        class WithCloneable implements Cloneable {

            public String myStr = "Hello";

            public final int[] intArray = new int[1000];

            @Override
            public WithCloneable clone() {
                try {
                    return (WithCloneable) super.clone();
                } catch (CloneNotSupportedException e) {
                    throw new AssertionError();
                }
            }

            public static void main(String[] args) {

                WithCloneable wc = new WithCloneable();

                WithCloneable c1 = wc.clone();

                System.out.println("Is clone object hashcode same: "
                        + (wc.hashCode() == c1.hashCode()));

                System.out.println("Before set clone object's array(hashcode): "
                        + (Arrays.hashCode(wc.intArray) == Arrays.hashCode(c1.intArray)));

                System.out.println("Before set clone object's string(hashcode): "
                        + (wc.myStr.hashCode() == c1.hashCode()));

                System.out.println("Before set clone object's string(value): "
                        + (wc.myStr.equals(c1.myStr)));

                System.out.println();

                c1.intArray[0] = 1000;
                System.out.println("After set clone object's array(hashcode): "
                        + (Arrays.hashCode(wc.intArray) == Arrays.hashCode(c1.intArray)));

                c1.myStr = "World";
                System.out.println("After set clone object's string(value): "
                        + (wc.myStr.equals(c1.myStr)));

                System.out.println("After set clone object's string(value): "
                        + (wc.myStr.equals(c1.myStr)));
            }

        }
        ```
        > ![](https://hackmd.io/_uploads/ByTxFYKT3.png)


### Clone 注意事項：不經建構函數

* 對於 **使用 `clone` 方法來創過的物件，是 ++完全不會經過建構函數++**，它會直接拷貝記憶體內容！

    ```java=
    class CloneAndConstructor implements Cloneable {

        private final String msg;

        CloneAndConstructor() {
            this.msg = "No message";

            System.out.println("Constructor without params");
        }

        CloneAndConstructor(String msg) {
            this.msg = msg;

            System.out.println("Constructor with params");
        }

        @Override
        public CloneAndConstructor clone() {
            try {
                return (CloneAndConstructor) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new AssertionError(e);
            }
        }

        public static void main(String[] args) {
            CloneAndConstructor cc = new CloneAndConstructor("Hello Clone");
            System.out.println("Original object: " + cc.msg + "\n");

            CloneAndConstructor cloneObj = cc.clone();
            System.out.println("Clone object: " + cloneObj.msg);

        }

    }
    ```
    > ![](https://hackmd.io/_uploads/SyBwoKYTh.png)


### 建構函數 - 反序列化

* 序列化/反序列化物件是一種比較少見的行為，但仍是一種創建物件的方式；它的特性如下


    * 序列化物件 **必須實作 `Serializable` 界面**，該介面同樣也是一個標示介面

        ```java=
        // java.io.Serializable

        public interface Serializable {
        }
        ```


    * 反序列化會拷貝原先物件的參考引用物件 ref，但在改變參考引用物件 ref 的數值後，JVM 會自動幫我們創建一個新的物件
        
        > 這類似一種 Copy on write 的技術

        * 示意圖如下，在更改物件之前，指向同一個參考物件

            ``` mermaid
            graph TD;

            物件-->| JVM clone |Clone_物件;
            物件-->參考物件;

            Clone_物件-->參考物件
            ```


        * 示意圖如下，在更改物件之後，指向全新的參考物件

            ``` mermaid
            graph TD;

            物件-->|JVM Clone|Clone_物件;
            物件-->參考類型;

            Clone_物件-->改動參考物件
            改動參考物件-->新的參考類型
            ```


    * 反序列化後，**會創建一個全新的物件**，**有新的記憶體位置**（以引用來對照是不同的物件，但內容值相同），這種用法請特別注意只能用在「**值物件**」

        ```java=
        class AccountInfo implements Serializable {
            public final String name;
            public final long id;
            public final String password;

            public final String[] otherMessages = new String[1000];

            public AccountInfo() {
                this.name = "";
                this.id = -1;
                this.password = "-";


                System.out.println("Construct without params");
            }

            public AccountInfo(String name, long id, String password) {
                this.name = name;
                this.id = id;
                this.password = password;

                System.out.println("Construct with params");
            }

            @Override
            public String toString() {
                return "Name: " + name + "\n" +
                        "id: " + id + "\n" +
                        "password: " + password + "\n" +
                        "otherMessages: " + Arrays.toString(otherMessages);
            }

            public static void main(String[] args) {
                String filename = "DemoAccount";

                // 序列化
                AccountInfo accountInfo = new AccountInfo("Alien", 9527, "HelloWorld");

                try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))){
                    oos.writeObject(accountInfo);
                    oos.flush();

                    System.out.println("寫入對象成功，物件原本 hashcode: " + accountInfo.hashCode() + "\n");
                } catch (IOException e) {
                    e.printStackTrace();
                }

                // 反序列化
                try (ObjectInputStream oos = new ObjectInputStream(new FileInputStream(filename))){
                    AccountInfo deserializeObj = (AccountInfo) oos.readObject();

                    System.out.println("讀取對象成功，反序列化後 hashcode: " + deserializeObj.hashCode());
                    System.out.println(("Is same content: " + accountInfo).equals(deserializeObj.toString()));

                    System.out.println("clone's array hashcode: " + Arrays.hashCode(deserializeObj.otherMessages));
                    System.out.println("original's array hashcode: " + Arrays.hashCode(accountInfo.otherMessages) + "\n");

                    accountInfo.otherMessages[0] = "123";

                    System.out.println("clone's array index 0: " + deserializeObj.otherMessages[0]);
                    System.out.println("clone's array index 0: " + Arrays.hashCode(deserializeObj.otherMessages) + "\n");

                    System.out.println("original's array index 0: " + accountInfo.otherMessages[0]);
                    System.out.println("original's array index 0: " + Arrays.hashCode(accountInfo.otherMessages) + "\n");
                } catch (IOException | ClassNotFoundException e) {
                    e.printStackTrace();
                }

            }
        }
        ```
        > ![](https://hackmd.io/_uploads/rJPnl5K6h.png)

## 觸發 JVM GC：觀察物件回收

:::success
* Java GC 是否回收物件，這會有關到 JVM 判斷物件的 **可達性分析** 機制，當物件不可達時，就會被認定該物件不再使用，這時就可以回收
    
    > **JVM 又會因為 Java 引用的差異而會有不同的處理方式**
:::

:::warning
* 有關於 **手動觸發 GC**
    
    當我們使用 `System.gc()`、`Runtime.getRuntime().gc()` **並非是直接驅動立即開始 GC**
    
    > GC 是另外一個低優先度的線程去處理的行為，所以通常我們測試時會使用 Thread#sleep 讓主線程休眠，等待 GC 回收
:::

### finalize 方法、特性

* 該方法的原形在 `Object.java` 中，**可以用來拯救被回收的對象**，有以下特點
    
    1. 是只能拯救回收的對象一次 (**一次 new 會有一次 finalize 機會，拯救後如果再次被加入回收列表，就不會再通知！**)

    2. **運行 `finalize` 的方法跑在另一個線程**，該線程優先權較低


        ```java=
        package GC;

        public class testFinalize {

            public static testFinalize intance = null;

            @Override
            public void finalize() {
                System.out.println("finalize work");
                // 牽引回 GC Root
                intance = this;
            }

            public static void main(String[] args) throws InterruptedException {
                intance = new testFinalize();
                intance = null;

                // First：手動 GC
                System.gc();		

                // 由於 finally Thread 優先權太低，所以延遲主線程
                Thread.sleep(1000);		
                if(intance == null) {
                    System.out.println("First intance == null");
                } else {
                    System.out.println("First intance != null");
                }

                intance = null;

                // Second：手動 GC
                System.gc();		
                Thread.sleep(1000);
                if(intance == null) {
                    System.out.println("Second intance == null");
                } else {
                    System.out.println("Second intance != null");
                }
            }

        }
        ```

        **--實做結果--**
        > ![](https://i.imgur.com/wmISS8I.png)
        :::warning
        * **`Object#finalize` 方法也被遺棄**，不再推薦被使用

        * 為何需要休眠一秒？ 

            **因為 finalize 線程是運行在別的線程**，優先權沒有主線程高，不一定會被先執行到，**==fialize 可以算是歷史的產物==**

            如果沒有休眠可以看到下方的 `finalize` 根本來不及拯救

            > ![](https://i.imgur.com/yo2ealp.png)

        :::


    3. **在 `finalize` 方法中拋出異常，GC 也不會有問題**（GC 會吞下這個異常）

        ```java=
        public class GCTest {

            private static GCTest gcTest;

            @Override
            protected void finalize() throws Throwable {
                System.out.println("GC - finalize");

                gcTest = this;

                // 並不會產生異常，GC 會吞下這筆異常
                throw new Exception("Test exception");
            }

            public static void main(String[] args) throws InterruptedException {
                gcTest = new GCTest();

                System.out.println("Before GC, GCTest: " + gcTest);
                gcTest = null;

                System.gc();
                Thread.sleep(1000);

                System.out.println("After GC, GCTest: " + gcTest);

            }

        }
        ```
        > ![](https://hackmd.io/_uploads/HJ-VMPs6n.png)




## Java 物件引用差異

JDK 1.2 後就有出現不同的引用方式，**強引用、軟引用、弱引用、虛引用**，以下會使用　`System.gc()` 強制會收並觀察其引用狀況

> 這些引用物件都在 `java.lang.ref` 套件

### 強引用：GC 不回收

* 如果對象具有強引用 (new 物件)，**GC 就絕對不會回收它，寧可拋出 OOM (`Out of Memory`) 異常也不會回收該對象** 來解決內存不足的問題

    > 可以拿引用指針指向 null，促進 GC 回收（讓該對象不可達）
    
    ```java=
    Object o = new Object();

    o = null; // 將引用指針指向 null
    ```
    
### 參考佇列 ReferenceQueue 使用：追蹤回收

* 參考佇列 (`ReferenceQueue`) 可以使用在 **軟引用、弱引用、虛引用** 之上，**可以用來追蹤 JVM 回收所參考的物件的活動**

    範例如下

    ```java=
    void useWeakWithQueue() throws InterruptedException {

        String msg = new String("Apple");

        ReferenceQueue<String> rq = new ReferenceQueue<>();
        WeakReference<String> wf = new WeakReference<>(msg, rq);

        System.out.println("Before GC - WeakReference: " + wf.get());
        System.out.println("Before GC - ReferenceQueue: " + rq.poll());

        // 移除強參考（必須）
        msg = null;
        System.gc();
        Thread.sleep(1000);

        System.out.println("Before GC - WeakReference: " + wf.get());
        // 被回收後 wf 才加入 ReferenceQueue
        System.out.println("Before GC - ReferenceQueue: " + rq.poll());

    }
    ```
    
    藉由結果我們可以看到，當物件被 GC 回收後，才會出現在 `ReferenceQueue` 列表中，如果物件尚未被回收則不會出現在列表
    
    > ![](https://hackmd.io/_uploads/ryatowoT3.png)


### 軟引用 SoftReference

* 當 **++內存不夠++ (OOM) 時就會 GC 軟引用對象**，並回收他們來解決內存不足的問題

    :::info
    * 但如果 GC 回收後發現內存仍不足就會再往外拋出
    :::

    ```java=
    // 設定 VM Options: -Xmx20m -Xms20m

    public class testSoftRef {

        public static void main(String[] args) {
            String str = new String("Alien");

            SoftReference<String> soft = new SoftReference<>(str);

            str = null;	// 移除強引用
            System.out.println("soft String: " + soft.get());
            System.gc();

            System.out.println("After GC, soft String: " + soft.get());

            List<byte[]> list = new LinkedList<>();
            try {
                // 100 個 1M 對象
                for(int i = 0; i < 100; i++) {
                    list.add(new byte[1024*1024]);
                }
                System.gc();
            } catch (Throwable e) {
                System.out.println("OOM-----------: " + soft.get());
            }

        }

    }
    ```
    
    1. **將強引用標記為軟引用**
    
        > 使用 `new SoftReference<>(str)` 
        
    2. **促進回收，將引用指向於 null，讓它離開 GC 判定的可達性區塊**
        
        > `str = null`
        
        :::success
        * 如何觸發 OOM
            
            1. 由於 JVM 通常在 **內存足夠的狀況下不會回收 `SoftReference` 物件**，所以在這邊設定堆區的 `最大 -Xmx`、`-Xms 最小值`
            
            2. 手動創建超出堆區大小的強引用（`list.add(new byte[1024*1024])`），就會發生 OOM
                
                > 這時 GC 就會被喚起去檢查 `SoftReference` 物件
        :::
    
        > ![](https://i.imgur.com/7haLsmB.png)



### 弱引用 WeakReference
* 生命週期比軟引用更短，**每次 ++GC 發現後立刻回收++**，**不管內存是否足夠** (就算內存足夠也回收)

    > GC 回收的前提：該物件是不可達物件

    ```java=
    import java.lang.ref.WeakReference;

    public class ReferenceTest {

        public static void main(String[] args) {
            String str = new String("Alien");
            //"1. "
            WeakReference<String> weak = new WeakReference<>(str);

            System.out.println("weak Integer: " + weak.get());

            System.gc();

            System.out.println("After GC, weak Integer: " + weak.get());
            //"2. "
            str = null;
            System.gc();

            System.out.println("After GC & ptr to null, weak Integer: " + weak.get());
        }

    }
    ```
    1. 將強引用標記為弱引用
        
        > `new WeakReference<>(str)`
    
    2. 促進回收，**將引用指向於 null (至其為不可打狀態) 並且再次回收才可回收掉**

        > 不管記憶體是否足夠，**只要觸發 GC 後，被發現就會直接回收**
        > 
        > ![](https://i.imgur.com/vSewKWz.png)


### 虛引用 PhantomReference

* **任何時候都可能被回收**，其目的是，**當被回收時會收到系統通知，功能是確認 GC 有正常運作**
    
    > 有可能你剛創建出 `PhantomReference` 引用物件後，立即就無法透過 `PhantomReference` 取得物件了

    ```java=
    public class ReferenceTest {

        public static void main(String[] args) {
            String str = new String("Alien");

            ReferenceQueue<String> queue = new ReferenceQueue<>();

            PhantomReference<String> phantom = new PhantomReference<>(str, queue);

            System.out.println("phantom Integer: " + phantom.get());

            System.gc();

            System.out.println("After GC, phantom Integer: " + phantom.get());

            str = null;
            System.gc();

            System.out.println("After GC & ptr to null, phantom Integer: " + phantom.get());
        }
    }
    ```
    **--實做結果--**
    > ![](https://i.imgur.com/MJ1Yuhu.png)


## 其他知識點

### 清除物件引用

* 通常當我們不需要一個物件時，最好有習慣去手動清理物件，這可以幫助 GC 對於物件的可達性分析判斷；這裡我們可以針對常見的兩種狀況做分析

    1. **方法中創建的物件**：
        
        這種物件的引用在 JVM 中的 Java 棧，而物件本身則在 Java 堆中；在離開該方法後，引用就不會再可達！這種就會自動回收
        
        ```java=
        void showMessage(String... strings) {
            StringBuffer stringBuffer = new StringBuffer();

            for (String string : strings) {
                stringBuffer.append(string).append("\t");
            }

            System.out.println(stringBuffer.toString());

            // 沒必要！
            // 因為離開該方法後該 StringBuffer 物件就會被回收（非即時）
            stringBuffer = null;
        }
        ```
        
    2. **方法中創建，但同時引用到類中（全局）**：
        
        ```java=
        class MessageHandler {

            private List<StringBuffer> history = new ArrayList<>();

            void collectMessage(String... strings) {
                StringBuffer stringBuffer = new StringBuffer();

                for (String string : strings) {
                    stringBuffer.append(string).append("\t");
                }

                // 將區域變量中的物件 加入到 全局變量，該物件就不會被回收（因為可達）
                history.add(stringBuffer);
            }

            String popMessage(int index) throws IllegalAccessException {
                if (index >= history.size()) {
                    throw new IllegalAccessException();
                }

                StringBuffer tmp = history.get(index);
                String result = tmp.toString();

                // 移除全局變量對物件的引用
                history.remove(tmp);
                // 手動解除引用
                tmp = null;

                return result;
            }

        }
        ```


### 不可變類別、可變類別：執行序的保護性複製

* **不可變類別**：通常是一個 Data Class，它負責攜帶資料，其重點是
    
    1. 不可變類別成員皆為 `final` (Kotlin 中就是 `val`) **不可改變**
    
    2. 由於它的不可變性，也就代表他是 **read only 資料**
    
    3. 又由於它是 read only 資料，所以它 **執行序（Thread, 線程）安全**

        ```java=
        class ImmutableData {

            public final String message;
            public final String name;

            ImmutableData(String message, String name) {
                this.message = message;
                this.name = name;
            }
        }
        ```

* **可變類別**：與其相反的就是可變類別，它與不可變類別的特性就剛好相反，所以在多執行序（Thread, 線程）使用中要小心

    ```java=
    class MutableData {
        public String message;
        public String name;
    }
    ```
    
    :::success
    * **保護性複製**：
        
        在某些情況之下，雖然我們的類別是不可變，但其實其成員內是可變的，所以我們要用保護性複製來保護不可變資料；範例如下
        
        * 非安全性使用範例：

            ```java=
            public class CopyFinalObj {

                public static void main(String[] args) {
                    unsafeOperation();
                }

                public static void unsafeOperation() {

                    LinkedList<String> myMessages = new LinkedList<>() {
                        {
                            push("HelloWorld");
                        }
                    };

                    UnsafeMessageBox box = new UnsafeMessageBox(myMessages);
                    System.out.println(box);

                    myMessages.push("Yoyo123");
                    System.out.println(box);

                }

            }

            class UnsafeMessageBox {

                private final LinkedList<String> messages;

                public UnsafeMessageBox(LinkedList<String> message) {
                    this.messages = message;
                }

                @Override
                public String toString() {
                    return messages.toString();
                }

            }
            ```
            外部操縱影響到內部資料
            
            > ![](https://hackmd.io/_uploads/r1vFQx96n.png)

        * **保護性複製範例**：

            ```java=
            public class CopyFinalObj {

                public static void main(String[] args) {
                    safeOperation();
                }

                public static void safeOperation() {

                    LinkedList<String> myMessages = new LinkedList<>() {
                        {
                            push("HelloWorld");
                        }
                    };

                    SafeMessageBox box = new SafeMessageBox(myMessages);
                    System.out.println(box);

                    myMessages.push("Yoyo123");
                    System.out.println(box);

                }

            }

            class SafeMessageBox {

                private final LinkedList<String> messages;

                public SafeMessageBox(LinkedList<String> message) {
                    // 保護性複製！
                    this.messages = new LinkedList<>(message);
                }

                @Override
                public String toString() {
                    return messages.toString();
                }

            }
            ```
            **外部操縱不會影響到內部資料**
            
            > ![](https://hackmd.io/_uploads/BkAB4g56n.png)

    :::


## 更多的 Java 語言相關文章


### Java 語言深入

* 在這個系列中，我們深入探討了 Java 語言的各個方面，從基礎類型到異常處理，從運算子到物件創建與引用細節。點擊連結了解更多！

    :::info

    * [**深入探索 Java 基礎類型、編碼、浮點數、參考類型和變數作用域 | 探討細節**](https://devtechascendancy.com/basic-types_encoding_reference_variables-scopes/)
    * [**深入了解 Java 應用與編譯：從原始檔到命令產出 JavaDoc 文件 | JDK 結構**](https://devtechascendancy.com/java-compilation_jdk_javadoc_jar_guide/)

    * [**深入理解 Java 異常處理：從基礎概念到最佳實踐指南**](https://devtechascendancy.com/java-jvm-exception-handling-guide/)

    * [**深入理解 Java 運算子與修飾符 | 重要概念、細節 | equals 比較**](https://devtechascendancy.com/java-operators-modifiers-key-concepts_equals/)

    * [**深入探索 Java 物件創建與引用細節：Clone 和finalize 特性，以及強、軟、弱、虛引用**](https://devtechascendancy.com/java-object-creation_jvm-reference-details/)

    :::

### Java IO 相關文章

* 探索 Java IO 的奧秘，了解檔案操作、流處理、NIO等精彩內容！

    :::warning
    * [**Java File 操作指南：基礎屬性判斷、資料夾和檔案的創建、以及簡單示範**](https://devtechascendancy.com/java-file-operations-guide/)

    * [**深入探索 Java 編碼知識**](https://devtechascendancy.com/basic-types_encoding_reference_variables-scopes/)

    * [**深入理解 Java IO 操作：徹底了解流、讀寫、序列化與技巧**](https://devtechascendancy.com/deep-in-java-io-operations_stream-io/)

    * [**深入理解 Java NIO：緩衝、通道與編碼 | Buffer、Channel、Charset**](https://devtechascendancy.com/deep-dive-into-java-nio_buf-channel-charset/)
    :::

### 深入 Java 物件導向
* 探索 Java 物件導向的奧妙，掌握介面、抽象類、繼承等重要概念！

    :::danger
    * [**深入比較介面與抽象類：從多個角度剖析**](https://devtechascendancy.com/comparing-interfaces-abstract-classes/)

    * [**深度探究物件導向：繼承的利與弊 | Java、Kotlin 為例 | 最佳實踐 | 內部類細節**](https://devtechascendancy.com/deep-dive-into-oop-inheritance/)

    * [**「類」的生命週期、ClassLoader 加載 | JVM 與 Class | Java 為例**](https://devtechascendancy.com/class-lifecycle_classloader-exploration_jvm/)
    :::


## Appendix & FAQ

:::info
:::

###### tags: `Java 基礎`
