---
# System prepended metadata

title: 資料類型、編碼、變數、參考類型
tags: [Java 基礎]

---

---
title: '資料類型、編碼、變數、參考類型'
disqus: kyleAlien
---

資料類型、編碼、變數、參考類型
===

## Overview of Content

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

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**深入探索 Java 基礎類型、編碼、浮點數、參考類型和變數作用域 | 探討細節**](https://devtechascendancy.com/basic-types_encoding_reference_variables-scopes/)
:::

[TOC]

## Java 基礎類型

Java 語言將資料類型分為 **基本類型**、**參考類型**，如下表展示… 並本篇會依據幾個特別、大家容易稿混的類型進行說明

1. **基本類型** 如下表

    | 基本類型 | 補充 |
    | - | - |
    | boolean | - |
    | float | 數值類型（浮點數） |
    | double | 數值類型（浮點數） |
    | byte | 數值類型（整數類） |
    | short | 數值類型（整數類） |
    | int | 數值類型（整數類） |
    | long | 數值類型（整數類） |
    | char | 數值類型（字符類） |
    
    :::info
    * Java 編譯器會將 Java 編譯為 Bytecode 時，會用 **int、byte 來表示 boolean**
    :::

2. **參考類型** 如下表
    
    | 參考類型 | 補充 |
    | - | - |
    | class | 類別 |
    | interface | 界面 |
    | **array** | 陣列 |

## Java 編碼

### Java 字元編碼、常見的編碼：Unicode 產生

* char 是字元類型，**Java 大多採用 `Unicode` 字元編碼**
    
    > `Unicde` 編碼也就是 **`UTF-16BE`**

    :::success
    * **字元編碼** 是什麼概念？
        
        用一串特定的二進位碼來標示特定得字元，就像是一個 Mapping 表單一樣

    :::

* 常見的 **字元編碼** 如下表

    | 字元編碼 | 字元、字節數 | 簡單介紹 |
    | - | - |- |
    | `ASCII` | 7 bit | **當今最通用單字節碼**，美國資訊互換標準程式碼，是為 **羅馬字母編制的編碼**，主要用於表達現代英語、西歐語言 |
    | `ISO-8859-1`（`Latin-1`） | 8 bit | 國際標準化組織 (IOS) 為 **西歐語言** 的字元碼制定的編碼，與 ASCII 相容 |
    | `GB2312` | 2 byte | 簡中編碼，與 `ASCII` 相容 |
    | `GBK` | 2 byte | 漢字編碼，與 `GB2312` 相容 |
    | `Unicode`, `UCS` | 2 or 4 byte (後面說明) | 收入全世界所有語言的編碼，`UCS` (`Universl Character Set`) 是指採用 `Unicode` 字元編碼的通用字元集，單位為碼元（`Code point`） |
    | `UTF8`、`UTF16` | 1~16 byte | **將 `Unicode` 轉換為作業系統支援的編碼！** 單位為碼點（`Code unit`） |
    
    :::info
    * **`Unicode` 分為 2 種字元編碼**
        
        * 使用 2byte 編碼：又稱為 `UCS-2`，也是 **Java 採用的位元組編碼方案**

        * 使用 4byte 編碼：又稱為 `UCS-4 `

    * `Java` 語言如果要使用 `Unicode` 則需要 **使用 `\u` 開頭標注**
    :::
    
* Unicode 產生是因為，原本在 1990 年左右有兩個團隊（`ISO/IEC`、`Unicode Consortium`）都打算統一字元集合… 其思想很簡單，單純的使用 Mapping 的方式，將碼點（`Code point`）對應上碼元（`Code unit`）

    > `ISO/IEC` 搶先在 1990 年發布第一套字元集編碼 `UCS-2`，這種編碼的碼元就是兩個 Byte 大小
    
    ```mermaid
    graph LR

    人類文字的字元 <--> cp(碼點, Code point)

    編碼 <--> cu(碼元, Code unit)

    cp <-.->|一一對應| cu
    ```
    
    然而兩個團隊很快意到一個世界不需要兩種統一編碼，最後決定合併，才發佈了 `Unicode 1.0`（也就是 `UCS-2`），然而人類的字元統計過後漸漸超過 2 Byte，後來才出現 `UCS-4` 編碼
    
    :::info
    * UTF-16 可以說是容納 `UCS-2`、`UCS-4` 編碼
        
        UTF-16 會依照字元在哪個碼點（`Code point`）去對應到  `UCS-2`、`UCS-4` 編碼
    
    * 像是 Java、JavaScript 兩種語言，在計算字元長度時就是使用 UTF-16 的「碼元」作為長度計算 
    :::
    


### 手動運算 Unicode 編碼轉 UTF8

* **`UTF8` 就是以 1 個 Byte 為單位對 `UCS`（Unicode） 編碼**，使用遮罩的方式將 `UCS` 編碼資料填入 `UTF8` 編碼；範例如下… 我們將 Unicode 編碼轉 UTF8

    | `USC-2` 編碼範圍（16 進位） | `UTF8` 遮罩（2 進位） |
    | - | - |
    | 0000-007F | `0xxx` `xxxx` |
    | 0080-07FF | `110x` `xxxx` `10xx` `xxxx` |
    | 0800-FFFF | `1110` `xxxx` `10xx` `xxxx` `10xx` `xxxx` |


    * **「漢」`USC-2` 轉「漢」`UTF8`**

        1. `Unicode` 編碼為 16 進位資料 `0x6C49`

        2. `0x6C49` 至於 `USC-2` 編碼範圍 `0800-FFFF`

        3. 將 「漢」的 `0x6C49` 轉為 2 進位 `0110 110001 001001`

        4. 將其套上 `UTF8` 遮罩就變為 `E6 B1 89`

            | 「漢」`Unicode` 編碼 | 「漢」2 進位 | 「漢」`USC-2` 編碼區間 | 「漢」`UTF8` 遮罩 | 套上遮罩後 |
            | - | - | - | - | - |
            | `0x6C49` | `0110 110001 001001` | `0800-FFFF` | `1110` `xxxx` `10xx` `xxxx` `10xx` `xxxx` | `1110` `0110` `1011` `0001` `1000` `1001` |

## Java 浮點數類型

Java 的浮點數類型有兩種，分別是 `float`、`double` 類型，這裡我們就要說明浮點數的計算方式、浮點數誤差、Java 提供的特殊數字


### Java 計算浮點數的方式：浮點數規則

* Java 語言的 `float`、`double` 類型 **遵循 `IEEE754` 標準**，該標準分別為 32、64 位元浮點數規定二進位資料表示形式，主要由 3 個元素組成，如下表

    | 元素 | 說明 | bit 數量 |
    | - | - | - |
    | S | 符號位元，表示正負 | 1 |
    | E | 指數 | 8 |
    | M | 尾數 | 23 |

    ```java=
    // 32 bit 的 float 的二進位表示，透過固定規則就可以轉換為浮點數

    SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMMM
    ```
    
* float 類型的二進位資料 **儲存規則，會牽涉到 `隱含位元`、`階碼`**
    
    * **隱含位元**：尾數 `M` 之前有一個 隱含位元，隱含位元為 `1.` or `0.` 兩種，而使用哪個取決於指數 `M`、`E`

        | 元素 `M` 值 | 元素 `E` 值 | 隱含位元 |  
        | - | - | - |
        | 非 0 | `1 ~ 254` | `1.` |
        | 非 0 | 其他 | `0.` |
        

    * **階碼**：階碼採用移碼表示，對應表如下
    
        | 元素 `M` 值 | 元素 `E` 值 | 階碼 |  
        | - | - | - |
        | 非 0 | `1 ~ 254` | `E-127` |
        | 非 0 | `0` | `-126` |
        
        
* **換算格式**：**(-1)^S^ * 2^階碼^ * (隱含位元)**；範例如下
    
    * **`5.0f` 的轉換**
        
        * 二進位： `0 10000001 01000000000000000000000`
            
            > 查表可對應到：隱含位元 `1.`、階碼為 `E-127`
                
        * 隱含位元：由於 `E` 為 `10000001` 也就是 129，在 `1 ~ 254` 之間，所以隱含位元為 `1.`
        
        * 套上公式計算：(-1)^0^ * 2^129-127^ * `1.01000000000000000000000`，也就是 2^2^ * `1.01000000000000000000000`
        
        透過位元移動 2 位，所以最終就是 `101.000000000000000000000` 也就是 5（解答！）
        
        
### 浮點數誤差

* 由上 Float 浮點數計算規則可知，**尾數數值越多，精度越高**，而 **double 精度又比 float 高**，測試範例如下

    * **float 精度測試**

        ```java=
        public void testFloat() {
            float res = 0, testTime = 26;

            while (testTime > 0) {
                res += 0.1f;
                testTime--;
            }

            System.out.println("float add 26 times: " + res);
        }
        ```
        > ![](https://hackmd.io/_uploads/BJ2gvYTY3.png)

    * **double 精度測試**

        ```java=
        public void testDouble() {
            double res = 0, testTime = 26;

            while (testTime > 0) {
                res += 0.1f;
                testTime--;
            }

            System.out.println("double add 26 times: " + res);
        }
        ```
        > ![](https://hackmd.io/_uploads/By2bPY6Fh.png)

:::success
* 在某些場合會特別注重浮點數的誤差（像是銀行的業務），如果要表示任意精度的資料，可以使用 **`BigDecimal` 類**
:::

### Java 提供的特殊數字

* 在浮點數計算中，如果 **科符記號 `E` 為 255**，那就用來表示一些 **特殊數字**，如下表

    | 特殊數字 | 二進位 |
    | - | - |
    | `Float.NaN` 非數字 | `0111 1111 1100 0000 0000 0000 0000 0000` |
    | `Float.POSITIVE_INFINITY` 正無窮大 | `0111 1111 1000 0000 0000 0000 0000 0000` |
    | `Float.NEGATIVE_INFINITY` 負無窮大 | `1111 1111 1000 0000 0000 0000 0000 0000` |

    ```java=
    public void testSpecialNum() {

        float nan = (float) (0.0 / 0.0);
        System.out.println(nan);

        float pos = (float) (1.0 / 0.0);
        System.out.println(pos);

        float neg = (float) (-1.0 / 0.0);
        System.out.println(neg);

    }
    ```

    > ![](https://hackmd.io/_uploads/rJNTjKTK2.png)

    :::info
    * **計算、程式中，科學記號的差異**
        
        差異在 **底數**，**程式中，科學記號 E 的底數為 10**，這是為了符合一般人的計算標準；而在 **電腦中則是使用 2 進位作為科學記號 E 的底數**（方便電腦識別）

    :::
    
## 參考類型

Java 是物件導向語言，隱藏了物件在 JVM 內的真實地址，使用 **參考類型 （++引用的概念++）**

> 像是 C 語言，就把變數地址暴露給開發者使用

而 Java 的參考類型有 3 個

* **類別**（`Class`）參考
* **界面**（`Interface`）參考
* **陣列參考**（在 Java 中，陣列也被視為物件）

    :::info
    * **類別類型、類別參考類型**
        
        ```java=
        class Hello {
            
            public static void main(String[] args) {
                // Hello hello，中的 Hello 是類別「參考」類型
                // new Hello()，中的 Hello 則是「類別類型」
                Hello hello = new Hello();
            }
            
        }
        ```
    :::
    
### 基本類型、參考類型

* **基本類型**：也就是 Java 的基礎八大類型（布林、整數、浮點數...），**在記憶體中佔有實體位置，數據多大就佔多少空間位置**

* **參考類型**：參考類型包裝多個基礎類型（也可以包裝其他參考類型），**在記憶體中佔固定大小位置（指針），指針指向 JVM 堆區中的實體位置**

    > 參考類型須使用 `new` 關鍵字創建

    ```java=
    // 參考類型
    class Hello {

        // 基本類型
        int foo = 100;
        long bar = 30000L;

        // 參考類型
        int[] fooArray = {10, 20, 30};
        String object = "HelloWorld";
        
        public static void main(String[] args) {
            Hello hello = new Hello();
        }

    }
    ```

    > 下圖中，類別「參考類型」在 JVM 模型中的位置，類別「參考類型」是放在方法區的 對象（物件）類型
    > 
    > ![](https://hackmd.io/_uploads/HkWGg6RYn.png)


### 物件的預設參考：this

> 「物件」說的是實體資料（`instance`），反觀「類」則是物件的模板

* 當物件建立好後，JVM 會分類一個參考自身物件得引用 `this`，透過 `this` 可以傳遞、訪問當前物件（instance）

    :::warning
    * 物件建立完成後才會建立出 `this` 引用，如果在物件尚未建立完成時去使用子類，則很可能導致無法取得當前引用
    :::

    ```java=
    class Hello {

        Hello() {
            // 將自身引用傳入到令一個物件，令一個物件就可以調用該 instance
            new World().startHello(this);
        }

        String getMsg() {
            return "Hello";
        }

    }

    class World {

        public void startHello(Hello hello) {
            System.out.println(hello.getMsg());
        }
    }
    ```

## 變數作用域

* 變數的作用域是指 **它的存在範圍**，不在範圍內部可訪問

* 變數存活的生命週期，不同描述的變數會有不同的生命週期限制，一般變數生命週期都至於花括號 `{ }` 之中

### 一般變數、靜態變數

* **一般變數**：一般變數區域會 **存在 JVM 堆區、棧區**，常見如下表

    | 變數區域 | 區域 | 生命週期 |
    | - | - | - |
    | 全域變數 (堆區) | 這裡的全域，是指 Java 類中的全域，作用域是整個類中 | 與物件同進退 |
    | 區域變數 | 只存在花括號之間的變數，外部不可使用 | 只存在 `{}` 中 |
    | 方法參數 | 作用域是方法開始、到結束 | 只存在 `{}` 中 |
    | 例外處理參數 | `catch` 區塊內的變數 | 只存在 `{}` 中 |

    ```java=
    class Hello {

        // 全域變數 msg
        String msg = "My message";

        void myFunction(int value) {        // 方法參數 value
            // 區域變數 showValue
            int showValue = value * value;

            try {
                // 區域變數 str
                String[] str = msg.split(" ");

                // 區域變數 firstStr
                String firstStr = str[0];

                System.out.println("Show value " + showValue + ", msg: " + firstStr);
                
            } catch (Exception e) {
                // 例外處理參數 e
                e.printStackTrace();
            }

        }

    }
    ```
    
    :::warning
    * **當基礎類型、引用類型傳入方法時，是不同的狀態**！
        
        * 基礎類型傳入方法時，是使用 **複製數值** 的方式來將參數傳入，修改方法內的參數，並不會影響外部變數
        
        * 引用類型傳入方法時，是直接將 **引用傳入**，所以在內部可以直接修改引用的物件的成員
    :::

* **靜態變數**：靜態變數 **存在 JVM 的方法區之中**，**在方法區中的變數，會被所有的類共同使用**，不需要建立物件實體（instance）

    ```java=
    class Hello {

        static String HELLO_WORLD = "Hello World";

        public static void main(String[] args) {
            Hello.HELLO_WORLD = "This is main";
        }

    }
    ```

### 變數的初始化、預設值

* **區域變數是沒有初始化數值的！必須手動初始化**

* 而 Java 會對類的全域變數給予初始化值，如下表

    | 類型 | 預設初始化值 |
    | - | - |
    | `byte`、`short`、`int`、`long` | 0 |
    | `float` | 0.0f |
    | `double` | 0.0d |
    | `char` | `\u0000` |
    | `boolean` | false |
    | 參考類型（物件） | null |
    | 參考陣列（物件） | 預設也為 null，在初始化過後，如果仍沒有給元素賦值，那就使用該元素預設的初始化值 |

## 更多的 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 基礎`
