--- 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)); } ``` >  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); } ``` >  :::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); } ``` >  * **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); } ``` >  * **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() { } } ``` >  * **抽象類、抽象方法不可以使用 `fainl` 描述** ```java= // Invalid final abstract class AbstractClz { // Invalid final abstract void showMsg(); void sayHello() { System.out.println("Hello"); } } ``` >  ### 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 記憶體分配策略中大多 **分配在 ++方法區++(有些也會放置在堆區)** > 一般變數會根據執行序(線程)分配到棧區 > >  ### static 修飾符:static 方法的特性、JVM 角度看待 static 方法 * **靜態方法可存取的內容**:不可存取有關於實例(`instance`)的所有數據 >  * 既然不能存取實例,也就 **不能使用 `this`、`super` 關鍵字** ```java= static void cannotUseThis() { System.out.println("instance: " + this); } static void cannotUseSuper() { System.out.println("instance: " + super.toString()); } ``` >  * 也就不能 **直接存取所屬類別的實體變數、方法** ```java= class MyClz3 { int value = 10; static void showValue() { // Invalid System.out.println("value: " + value); } } ``` >  * **對於靜態方法 JVM 只會在 ++方法區++ 內尋找**(不會到堆區尋找) :::success * **方法(`Method`)的位元組碼都放在方法區!** **不論實體方法、靜態方法**,它們的位元組碼都放置在方法區(跟是否事實例化無關,純粹是方法的原始編譯碼放在方法區) >  ::: ## 更多的 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 基礎`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up