---
# System prepended metadata

title: Java 特殊運算子 | 修飾符與 JVM 的關係
tags: [Java 基礎]

---

---
title: 'Java 特殊運算子 | 修飾符與 JVM 的關係'
disqus: kyleAlien
---

Java 特殊運算子 | 修飾符與 JVM 的關係
===

## Overview of Content


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

該節會省略基礎運算子，只寫個人認為比較特別、重點的運算子

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**深入理解 Java 運算子與修飾符 | 重要概念 | equals 比較**](https://devtechascendancy.com/java-operators-modifiers-key-concepts_equals/)
:::

[TOC]

## Java 常用、特殊運算子

Java 常用運算子的優先等級如下表（**數字越小，優先級越高**）

| 優先序 | 分類 | 運算子 |
| - | - | - |
| 1 | 一元 | `!`、`++`、`-`、`~` |
| 2 | 二元（數學、位移） | `*`、`/`、`%`、`+`、`-`、`>>`、`<<`、`>>>` |
| 3 | 比較 | `>`、`>=`、`<`、`<=`、`!=`、`==` |
| 4 | 邏輯、位元運算 | `&&`、`||`、`&`、`|`、`^`、 |
| 5 | 三元 | `?:` |
| 6 | 賦值、複合 | `=`、`*=`、`-=`、`+=`、`/=`、`%=` |

### `>>`、`>>>` 運算子

* `>>`、`>>>` 兩個運算子都是位移「**右移**」運算子，其差異負數在 **位移後的補位數值**

    | 位移運算子 | 正數補位數值 | 負數補位數值 |
    | - | - | - |
    | `>>` | 0 | 1 |
    | `>>>` | 0 | **0（特點）** |

    1. **正數位移**：兩者位移後結果相同

        ```java=
        public void movePos_1() {
            int value = 15 >> 1;

            System.out.println("15 >> 1:" + Integer.toBinaryString(value));
        }

        public void movePos_2() {
            int value = 15 >>> 1;

            System.out.println("15 >>> 1:" + Integer.toBinaryString(value));
        }
        ```
        > ![](https://hackmd.io/_uploads/ByesczN5n.png)

    2. **負數位移**：兩者位移後 **結果不相同**，可以看到 **`>>>` 符號位移後，複數變正數**

        ```java=
        public void moveNeg_1() {
            int value = -1 >> 1;

            System.out.println("-1 >> 1:" + Integer.toBinaryString(value) + ", decimal:" + value);
        

        public void moveNeg_2() {
            int value = -1 >>> 1;

            System.out.println("-1 >>> 1:" + Integer.toBinaryString(value) + ", decimal:" + value);
        }
        ```
        > ![](https://hackmd.io/_uploads/rJvSofV92.png)


        :::warning
        * **浮點數不支援位元運算**
        :::

### `==` 運算子 & JVM 多型判別

我們用兩種角度來看 `==` 運算子

* **Java 作為 ++靜態語言++ 的角度** 來看：

    **在靜態檢查時，兩個物件必須要有繼承關係（而且是同一繼承分支上）才可以使用 `==` 運算子**，**否則不用編譯也不會通過！**

    繼承關係如下：
    ```java=
    class Fruit { }

    class Apple extends Fruit { }

    class Banana extends Fruit { }
    ```

    靜態語言撰寫，測試

    ```java=
    public void staticLangCheck_1() {
        Fruit fruit = new Fruit();
        Banana banana = new Banana();

        System.out.println(fruit == banana);
    }


    public void staticLangCheck_2() {
        Apple apple = new Apple();                // 兩個類之間沒有直向關係！
        Banana banana = new Banana();

        // 不合法！直接編譯不過
        System.out.println(apple == banana);
    }
    ```

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

* **JVM 運行時的角度** 來看：

    JVM 在判斷 `==` 符號時，以「**多型**」時又可以分為兩個角度來看
    
    1. **參考值 reference**：這是最直接的比較方式，即兩個參考是否指向同一個物件，比較的是兩個 **物件的參考(`ref`)是否相同**

        ```java=
        public void runtimeCheck() {
            Apple greenApple = new Apple();
            Apple redApple = new Apple();

            System.out.println(greenApple == redApple);        // false
        }
        ```

    2. **多型別時的比較**：在多型別的情況下，比較兩個物件的是否相同

        例如，當一個子類別的物件被賦值給一個父類別的參考時，這兩個引用指向是同一個物件，但它們的類型不同情況下，如果我們只是比較參考的一致性，則結果為真，因為它們指向同一個物件
        
        ```java=
        public static void runtimeCheck() {
            Apple greenApple = new Apple();
            Fruit redApple = greenApple;        // 兩者雖然宣告不同，但是仍是同一個物件

            System.out.println(greenApple == redApple);    // true
        }
        ```
        
        
        :::info
        * 但是，如果我們想要兩個物體是否屬於同一個類型（包括父類別和子類別），則需要使用 `instanceof` 運算子或 `getClass() `方法來進行比較
            
            > 這種比較方式主要用於檢查兩個物件是否屬於同一個類型，並且可以考慮多種類型的情況
        :::

### `equals` 方法 & `==` 運算子

* **`equals` 方法**：是 `Object` 類的方法，而 Java 類預設都是繼承 `Object` 類，所以所有的類都可以用（除了基礎 8 大基礎類）

    **它預設是比較當前物件與傳入物件的參考**
    
    ```java=
    // Object.java

    public boolean equals(Object obj) {
        return (this == obj);
    }
    ```
    
* **`JDK` 中有幾個類型會覆寫 `Object#equals` 方法，像是 `File`、`Date`、`String`、包裝類**（`Integer`，`Long`... 等等）
    
    像是 String 類的 `equals` 方法就是比較字串的內容
    
    ```java=
    // String.java

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (!COMPACT_STRINGS || this.coder == aString.coder) {
                // 查看 StringLatin1#equals 方法
                return StringLatin1.equals(value, aString.value);
            }
        }
        return false;
    }

    
    // StringLatin1.java
    public static boolean equals(byte[] value, byte[] other) {
        if (value.length == other.length) {
            for (int i = 0; i < value.length; i++) {
                if (value[i] != other[i]) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
    ```
    
    :::warning
    * **`equals` 方法 & `==` 運算子**
        
        以 String 類來說，要注意你要比較的是 String 的內容，還是 String 的參考，如果要 **比較的內容是 `String` 則須使用 `equals` 方法**
    :::


### `instanceof` 運算子 & JVM 多型判斷

我們用兩種角度來看 `instanceof` 運算子

* **Java 作為 ++靜態語言++ 的角度** 來看：

    **在靜態檢查時，兩個物件必須要有繼承關係（而且是同一繼承分支上）才可以使用 `instanceof` 運算子**，否則不用編譯也不會通過
    
    > 即便是同個父類的 2 個子類，也不在同一個繼承分之上
    
    繼承關係如下：
    ```java=
    class Fruit { }

    class Apple extends Fruit { }

    class Banana extends Fruit { }
    ```

    靜態語言撰寫，測試

    ```java=
    public void staticLangInstanceOfCheck_1() {
        Banana banana = new Banana();

        // 不合法！直接編譯不過
        System.out.println(banana instanceof Animal);
    }
    ```

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

* **JVM 運行時的角度** 來看：

    判斷的是 **左值是否是右值的派生類**（左值是否是右值的繼承類）

    ```java=
    public void runtimeInstanceOfCheck() {
        Banana banana = new Banana();

        System.out.println(banana instanceof Fruit);
    }
    ```

### 參考類型 - 向上、向下轉型

* 參考類別的轉型有分為兩種：^1.^ 向上轉型、^2.^ 向下轉型
    
    * **向上轉型**：所謂的向上轉型是 **子類別直接、間接賦值給父類別**；而 **Java 的向上轉形式自動的**

        ```java=
        public void upperCast() {
            Banana banana = new Banana();
            Fruit fruit = banana;
    
            System.out.println(fruit);
        }
        ```

    * **向下轉型**：所謂的向下轉型是 **父類別直接、間接賦予給子類型**；**必須 ++強制++ 轉型**

        ```java=
        public void downCast() {
            Fruit fruit = new Fruit();
            Banana banana = (Banana) fruit;

            System.out.println(banana);
        }
        ```
        
        :::warning
        * **向下轉形式危險的**？
            
            是的，向下轉型通常是一種危險的行為，畢竟父類別不一定有子類別的成員物件
        :::


## Java 修飾符

Java 會提供一些修飾符，來修飾類別、變數、方法，使用正確的修飾符可以 **有助於提高軟體系統的可重用性、維護性、拓展性、執行效能... 等等**；下表示修飾符比較

| 修飾符         | 類別 | 方法 | 建構函數 | 成員變數 | 區域變數 |
| -------------- | ---- | ---- | -------- | -------- | -------- |
| `abstract`     | v    | v    | -        | -        | -        |
| `static`       | -    | v    | -        | v        | -        |
| `public`       | v    | v    | v        | v        | -        |
| `protected`    | -    | v    | v        | v        | -        |
| `private`      | -    | v    | v        | v        | -        |
| `synchronized` | -    | v    | -        | -        | -        |
| `native`       | -    | v    | -        | -        | -        |
| `transient`    | -    | -    | -        | v        | -        |
| `volatile`     | -    | -    | -        | v        | -        |
| `final`        | v    | v    | -        | v        | v        |

### abstract 修飾符

* abstract 可用來描述抽象類（從一些具體類別抽象出來的類型）、抽象方法（具體類別共同操作的方法）；使用 abstract 需要遵循以下語法規則

    * **只有抽象類別可以有抽象方法**，一般類別不能有抽象方法

        ```java=
        abstract class AbstractClz {

            // Valid
            abstract void showMsg();

        }
        ```
        
    * **abstract 方法可以有非抽象的建構方法**

        ```java=
        abstract class AbstractClz {

            abstract void showMsg();

            // Valid
            void sayHello() {
                System.out.println("Hello");
            }

        }
        ```
    
    * **abstract 不可以用來描述建構函數**
        
        ```java=
        class MyClz {

            // Invalid
            abstract MyClz() {

            }

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

    * **抽象類、抽象方法不可以使用 `fainl` 描述**

        ```java=
        // Invalid

        final abstract class AbstractClz {

            // Invalid
            final abstract void showMsg();

            void sayHello() {
                System.out.println("Hello");
            }

        }
        ```

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

### static 修飾符：JVM 看 static 變數

* 這裡略過一般的 `static` 用法，特別提出 **跟 JVM 相關的設計概念**

    1. **JVM 載入類別時就會執行 `static` 程式碼（包括 static 變數、static 區塊）**
        
        > static 區塊會按照順序做加載
            
        ```java=
        class MyClz2 {

            // static 變數
            public static int value = 10;

            // static 區塊
            static {
                Map<String, String> staticMap = new HashMap<>();
                List<String> strList = new ArrayList<>();
            }

        }
        ```
    
    2. **JVM 只會與為靜態變數分配一次記憶體（也就是只會被執行一次）**
    
    3. 靜態變數在 JVM 記憶體分配策略中大多 **分配在 ++方法區++（有些也會放置在堆區）**
        
        > 一般變數會根據執行序（線程）分配到棧區
        > 
        > ![](https://hackmd.io/_uploads/r1aMsESi3.png)


### static 修飾符：static 方法的特性、JVM 角度看待 static 方法

* **靜態方法可存取的內容**：不可存取有關於實例（`instance`）的所有數據
    
    > ![](https://hackmd.io/_uploads/By72OESi2.png)

    * 既然不能存取實例，也就 **不能使用 `this`、`super` 關鍵字**
    
        ```java=
        static void cannotUseThis() {
            System.out.println("instance: " + this);
        }

        static void cannotUseSuper() {
            System.out.println("instance: " + super.toString());
        }
        ```

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

    * 也就不能 **直接存取所屬類別的實體變數、方法**

        ```java=
        class MyClz3 {

            int value = 10;

            static void showValue() {
                // Invalid
                System.out.println("value: " + value);
            }

        }
        ```

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

* **對於靜態方法 JVM 只會在 ++方法區++ 內尋找**（不會到堆區尋找）

    :::success
    * **方法（`Method`）的位元組碼都放在方法區！**
        
        **不論實體方法、靜態方法**，它們的位元組碼都放置在方法區（跟是否事實例化無關，純粹是方法的原始編譯碼放在方法區）
        
        > ![](https://hackmd.io/_uploads/By8bi4Bin.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 基礎`
