深入理解 Java 運算子與修飾符 | 重要概念、細節 | equals 比較

深入理解 Java 運算子與修飾符 | 重要概念、細節 | equals 比較

Overview of Content

這篇文章將會省略 Java 基礎運算子介紹、使用,只寫個人認為比較特別、重點的運算子跟修飾符

深入探討Java中常用運算子和修飾符的重要概念與特殊用法。我們將簡潔地介紹 >>>>> 運算子、== 運算子與 JVM 多型判別、equals 方法與 == 運算子、instanceof 運算子與 JVM 多型判斷,以及參考類型的向上、向下轉型。

同時,我們也將討論 abstract 修飾符的作用、static 修飾符在 JVM 中的特性,以及static方法的特性與靜態方法放置的位置。無論您是Java初學者還是有經驗的開發者,這篇指南都將幫助您深入理解Java中這些重要的概念,提升您的程式設計技能。

寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀


Java 運算子

Java 常用運算子的優先等級如下表(數字越小,優先級越高

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

>>>>> 運算子

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

位移運算子正數補位數值負數補位數值
>>01
>>>00(特點)

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 方法,像是 FileDateString、包裝類IntegerLong... 等等)

像是 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 會提供一些修飾符,來修飾類別、變數、方法,使用正確的修飾符可以 有助於提高軟體系統的可重用性、維護性、拓展性、執行效能... 等等;下表示修飾符比較

修飾符類別方法建構函數成員變數區域變數
abstractvv---
static-v-v-
publicvvvv-
protected-vvv-
private-vvv-
synchronized-v---
native-v---
transient---v-
volatile---v-
finalvv-vv

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)的所有數據

● 既然不能存取實例,也就 不能使用 thissuper 關鍵字


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 物件導向的奧妙,掌握介面、抽象類、繼承等重要概念!幫助你針對物件導向設計有更深入的了解!


Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響