Overview of Content
這篇文章將會省略 Java 基礎運算子介紹、使用,只寫個人認為比較特別、重點的運算子跟修飾符
深入探討Java中常用運算子和修飾符的重要概念與特殊用法。我們將簡潔地介紹 >>
、>>>
運算子、==
運算子與 JVM 多型判別、equals
方法與 ==
運算子、instanceof
運算子與 JVM 多型判斷,以及參考類型的向上、向下轉型。
同時,我們也將討論 abstract
修飾符的作用、static
修飾符在 JVM 中的特性,以及static方法的特性與靜態方法放置的位置。無論您是Java初學者還是有經驗的開發者,這篇指南都將幫助您深入理解Java中這些重要的概念,提升您的程式設計技能。
寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀
Java 運算子
Java 常用運算子的優先等級如下表(數字越小,優先級越高)
優先序 | 分類 | 運算子 |
---|---|---|
1 | 一元 | ! 、++ 、- 、~ |
2 | 二元(數學、位移) | * 、/ 、% 、+ 、- 、>> 、<< 、>>> |
3 | 比較 | > 、>= 、< 、<= 、!= 、== |
4 | 邏輯、位元運算 | && 、` |
5 | 三元 | ?: |
6 | 賦值、複合 | = 、*= 、-= 、+= 、/= 、%= |
>>
、>>>
運算子
● >>
、>>>
兩個運算子都是位移「右移」運算子,其差異負數在 位移後的補位數值
位移運算子 | 正數補位數值 | 負數補位數值 |
---|---|---|
>> | 0 | 1 |
>>> | 0 | 0(特點) |
A. 正數位移:兩者位移後結果相同
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));
}
B. 負數位移:兩者位移後 結果不相同,可以看到 >>>
符號位移後,複數變正數
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);
}
● 浮點數不支援位元運算
==
運算子與 JVM 多型判別
我們用兩種角度來看 ==
運算子
● Java 作為 靜態語言 的角度 來看:
在靜態檢查時,兩個物件必須要有繼承關係(而且是同一繼承分支上)才可以使用 ==
運算子,否則不用編譯也不會通過!
繼承關係如下:
class Fruit { }
class Apple extends Fruit { }
class Banana extends Fruit { }
靜態語言撰寫,測試
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 在判斷 ==
符號時,以「多型」時又可以分為兩個角度來看
A. 參考值 reference:這是最直接的比較方式,即兩個參考是否指向同一個物件,比較的是兩個 物件的參考(ref
)是否相同
public void runtimeCheck() {
Apple greenApple = new Apple();
Apple redApple = new Apple();
System.out.println(greenApple == redApple); // false
}
B. 多型別時的比較:在多型別的情況下,比較兩個物件的是否相同
例如,當一個子類別的物件被賦值給一個父類別的參考時,這兩個引用指向是同一個物件,但它們的類型不同情況下,如果我們只是比較參考的一致性,則結果為真,因為它們指向同一個物件
public static void runtimeCheck() {
Apple greenApple = new Apple();
Fruit redApple = greenApple; // 兩者雖然宣告不同,但是仍是同一個物件
System.out.println(greenApple == redApple); // true
}
● 但是,如果我們想要兩個物體是否屬於同一個類型(包括父類別和子類別),則需要使用
instanceof
運算子或getClass()
方法來進行比較這種比較方式主要用於檢查兩個物件是否屬於同一個類型,並且可以考慮多種類型的情況
equals
方法與 ==
運算子比較
● equals
方法:是 Object
類的方法,而 Java 類預設都是繼承 Object
類,所以所有的類都可以用(除了基礎 8 大基礎類)
它預設是比較當前物件與傳入物件的參考
// Object.java
public boolean equals(Object obj) {
return (this == obj);
}
● JDK
中有幾個類型會覆寫 Object#equals
方法,像是 File
、Date
、String
、包裝類(Integer
,Long
... 等等)
像是 String 類的 equals
方法就是比較字串的內容
// 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;
}
●
equals
方法 &==
運算子以 String 類來說,要注意你要比較的是 String 的內容,還是 String 的參考,如果要 比較的內容是
String
則須使用equals
方法
instanceof
運算子 & JVM 多型判斷
我們用兩種角度來看 instanceof
運算子
● Java 作為 靜態語言 的角度 來看:
在靜態檢查時,兩個物件必須要有繼承關係(而且是同一繼承分支上)才可以使用 instanceof
運算子,否則不用編譯也不會通過
即便是同個父類的 2 個子類,也不在同一個繼承分之上
繼承關係如下:
class Fruit { }
class Apple extends Fruit { }
class Banana extends Fruit { }
靜態語言撰寫,測試
public void staticLangInstanceOfCheck_1() {
Banana banana = new Banana();
// 不合法!直接編譯不過
System.out.println(banana instanceof Animal);
}
● JVM 運行時的角度 來看:
判斷的是 左值是否是右值的派生類(左值是否是右值的繼承類)
public void runtimeInstanceOfCheck() {
Banana banana = new Banana();
System.out.println(banana instanceof Fruit);
}
參考類型 - 向上、向下轉型
● 參考類別的轉型有分為兩種:1. 向上轉型、2. 向下轉型
● 向上轉型:所謂的向上轉型是 子類別直接、間接賦值給父類別;而 Java 的向上轉形式自動的
public void upperCast() {
Banana banana = new Banana();
Fruit fruit = banana;
System.out.println(fruit);
}
● 向下轉型:所謂的向下轉型是 父類別直接、間接賦予給子類型;必須 強制 轉型
public void downCast() {
Fruit fruit = new Fruit();
Banana banana = (Banana) fruit;
System.out.println(banana);
}
● 向下轉形式危險的?
是的,向下轉型通常是一種危險的行為,畢竟父類別不一定有子類別的成員物件
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 需要遵循以下語法規則
● 只有抽象類別可以有抽象方法,一般類別不能有抽象方法
abstract class AbstractClz {
// Valid
abstract void showMsg();
}
● abstract 方法可以有非抽象的建構方法
abstract class AbstractClz {
abstract void showMsg();
// Valid
void sayHello() {
System.out.println("Hello");
}
}
● abstract 不可以用來描述建構函數
class MyClz {
// Invalid
abstract MyClz() {
}
}
● 抽象類、抽象方法不可以使用 fainl
描述
// Invalid
final abstract class AbstractClz {
// Invalid
final abstract void showMsg();
void sayHello() {
System.out.println("Hello");
}
}
static 修飾符:JVM 看 static 變數
● 這裡略過一般的 static
用法,特別提出 跟 JVM 相關的設計概念
A. JVM 載入類別時就會執行 static
程式碼(包括 static 變數、static 區塊)
class MyClz2 {
// static 變數
public static int value = 10;
// static 區塊
static {
Map<String, String> staticMap = new HashMap<>();
List<String> strList = new ArrayList<>();
}
}
static 區塊會按照順序做加載
B. JVM 只會與為靜態變數分配一次記憶體(也就是只會被執行一次)
C. 靜態變數在 JVM 記憶體分配策略中大多 分配在 方法區(有些也會放置在堆區)
一般變數會根據執行序(線程)分配到棧區
static 修飾符:static 方法的特性、靜態方法放置的位置
● 靜態方法可存取的內容:不可存取有關於實例(instance
)的所有數據
● 既然不能存取實例,也就 不能使用 this
、super
關鍵字
static void cannotUseThis() {
System.out.println("instance: " + this);
}
static void cannotUseSuper() {
System.out.println("instance: " + super.toString());
}
● 也就不能 直接存取所屬類別的實體變數、方法
class MyClz3 {
int value = 10;
static void showValue() {
// Invalid
System.out.println("value: " + value);
}
}
● 對於靜態方法 JVM 只會在 方法區 內尋找(不會到堆區尋找)
● 方法(
Method
)的位元組碼都放在方法區!不論實體方法、靜態方法,它們的位元組碼都放置在方法區(跟是否事實例化無關,純粹是方法的原始編譯碼放在方法區)
更多的 Java 語言相關文章
Java 語言深入
● 在這個系列中,我們全方位地探討了 Java 語言的各個核心主題,旨在幫助你徹底掌握這門強大的編程語言。無論你是想深入理解 Java 的基礎類型與變數作用域,還是探索異常處理與運算子的細節,這些文章都將為您提供寶貴的知識
此外,我們還涵蓋了物件創建、函數式編程、註解應用以及泛型的深入分析,幫助您提升在實際開發中的技能和效率
點擊以下連結,開始你的學習之旅~
● 深入探索 Java 基礎類型、編碼、浮點數、參考類型和變數作用域 | 探討細節
● 深入了解 Java 應用與編譯:從原始檔到命令產出 JavaDoc 文件 | JDK 結構
● 深入理解 Java 運算子與修飾符 | 重要概念、細節 | equals 比較
● 深入探索 Java 物件創建與引用細節:Clone 和finalize 特性,以及強、軟、弱、虛引用
● 認識 Java 函數式編程:從 Lambda 表達式到方法引用 | 3 種方法引用
Java IO 相關文章
● 探索 Java IO 的奧秘,了解檔案操作、流處理、NIO等精彩內容!
深入 Java 多執行緒
● 這一系列文章將帶你深入了解 Java 多執行緒技術的各個方面,從基礎知識到進階應用,涵蓋了多執行緒編程的核心概念與實踐
深入 Java 物件導向
● 探索 Java 物件導向的奧妙,掌握介面、抽象類、繼承等重要概念!幫助你針對物件導向設計有更深入的了解!