三、運算

所有運算符都能根據自己的運算對象生成一個值。除此以外,一些運算符可改變運算對象的值,這叫作“副作用”(Side Effect)。

幾乎所有運算符都只能操作基本類型(Primitives)。唯一的例外是 =、== 和 !=,它們能操作所有對象(這也是令人混淆的一個地方)。除此以外,String 類支持 + 和 +=。

1、賦值

右邊可以是任何常量、變量或者可產生一個返回值的表達式。但左邊必須是一個明確的、已命名的變量。也就是說,必須要有一個物理的空間來存放右邊的值。

基本類型的賦值都是直接的,而不像對象,賦予的只是其內存的引用。

// operators/Assignment.java
// Assignment with objects is a bit tricky
class Tank {
    int level;
}

public class Assignment {

    public static void main(String[] args) {
        Tank t1 = new Tank();
        Tank t2 = new Tank();
        t1.level = 9;
        t2.level = 47;
        System.out.println("1: t1.level: " + t1.level +
            ", t2.level: " + t2.level);
        t1 = t2;
        System.out.println("2: t1.level: " + t1.level +
            ", t2.level: " + t2.level);
        t1.level = 27;
        System.out.println("3: t1.level: " + t1.level +
            ", t2.level: " + t2.level);
    }
}

在 Java 中,由於賦予的只是對象的引用,改變 t1 也就改變了 t2。 這是因爲 t1 和 t2 此時指向的是堆中同一個對象。(t1 原始對象的引用在 t2 賦值給其時丟失,它引用的對象會在垃圾回收時被清理)。

2、方法調用中的別名

當我們把對象傳遞給方法時,會發生別名現象。

// operators/PassObject.java
// 正在傳遞的對象可能不是你之前使用的
class Letter {
    char c;
}

public class PassObject {
    static void f(Letter y) {
        y.c = 'z';
    }

    public static void main(String[] args) {
        Letter x = new Letter();
        x.c = 'a';
        System.out.println("1: x.c: " + x.c);
        f(x);
        System.out.println("2: x.c: " + x.c);
     }
}

結果:

1: x.c: a
2: x.c: z

一旦傳遞了一個引用。那麼實際上 y.c =‘z’; 是在方法 f() 之外改變對象。

3、遞增和遞減

前遞增和前遞減(如 ++a 或 --a),會先執行遞增/減運算,再返回值。而對於後遞增和後遞減(如 a++ 或 a–),會先返回值,再執行遞增/減運算。

4、測試等價對象

// operators/Equivalence.java
public class Equivalence {
    public static void main(String[] args) {
        Integer n1 = 128;
        Integer n2 = 128;
        System.out.println(n1 == n2);
        System.out.println(n1 != n2);
    }
}

結果:

false
true

儘管對象的內容一樣,對象的引用卻不一樣。== 和 != 比較的是對象引用,所以輸出實際上應該是先輸出 false,再輸出 true.
Integer 內部維護着一個 IntegerCache 的緩存,默認緩存範圍是 [-128, 127],所以 [-128, 127] 之間的值用== 和 != 比較也能能到正確的結果。
必須使用所有對象(不包括基本類型)中都存在的 equals() 方法.

// operators/EqualsMethod2.java
// 默認的 equals() 方法沒有比較內容
class Value {
    int i;
}

public class EqualsMethod2 {
    public static void main(String[] args) {
        Value v1 = new Value();
        Value v2 = new Value();
        v1.i = v2.i = 100;
        System.out.println(v1.equals(v2));
    }
}

結果:

false

原因: equals() 的默認行爲是比較對象的引用而非具體內容。因此,除非你在新類中覆寫 equals() 方法,否則我們將獲取不到想要的結果。
大多數 Java 庫類通過覆寫 equals() 方法比較對象的內容而不是其引用。

5、邏輯運算

在 Java 邏輯運算中,我們不能像 C/C++ 那樣使用非布爾值, 而僅能使用 AND、 OR、 NOT。
邏輯運算符支持一種稱爲“短路”(short-circuiting)的現象。整個表達式會在運算到可以明確結果時就停止並返回結果,這意味着該邏輯表達式的後半部分不會被執行到。

6、字面值常量

通常,當我們向程序中插入一個字面值常量(Literal)時,編譯器會確切地識別它的類型。

在文本值的後面添加字符可以讓編譯器識別該文本值的類型。

  • 前綴

Long :大寫 L 或小寫 l (不推薦使用 l,因爲容易與阿拉伯數值 1 混淆)。 float:大寫 F 或小寫 f 。
double :大寫 D 或小寫 d。

  • 後綴

16進制: 0x 或 0X
8進制: 0
2進制: 0b 或 0B

我們可以在數字字面量中包含下劃線 _,以使結果更清晰。這對於大數值的分組特別有用。

// operators/Underscores.java
public class Underscores {
    public static void main(String[] args) {
        double d = 341_435_936.445_667;
        System.out.println(d);
        int bin = 0b0010_1111_1010_1111_1010_1111_1010_1111;
        System.out.println(Integer.toBinaryString(bin));
        System.out.printf("%x%n", bin); // [1]
        long hex = 0x7f_e9_b7_aa;
        System.out.printf("%x%n", hex);
    }
}

結果

3.41435936445667E8
101111101011111010111110101111
2fafafaf
7fe9b7aa

下面是合理使用的規則:

  1. 僅限單 _,不能多條相連。
  2. 數值開頭和結尾不允許出現 _。
  3. F、D 和 L的前後禁止出現 _
  4. 二進制前導 b 和 十六進制 x 前後禁止出現 _。

7、位運算

位運算符允許我們操作一個整型數字中的單個二進制位。
源自 C 語言的底層操作。我們經常要直接操縱硬件,頻繁設置硬件寄存器內的二進制位。

我們可以對 boolean 型變量執行與、或、異或運算,但不能執行非運算(大概是爲了避免與邏輯“非”混淆)。對於布爾值,位運算符具有與邏輯運算符相同的效果,只是它們不會中途“短路”

右移位運算符有“正”、“負”值:若值爲正,則在高位插入 0;若值爲負,則在高位插入 1。Java 也添加了一種“不分正負”的右移位運算符(>>>),它使用了“零擴展”(zero extension):無論正負,都在高位插入 0。

如果移動 char、byte 或 short,則會在移動發生之前將其提升爲 int,結果爲 int。若對一個 long 值進行處理,最後得到的結果也是 long。

移位可以與等號 <<= 或 >>= 或 >>>= 組合使用。左值被替換爲其移位運算後的值。左值被替換爲其移位運算後的值。但是,問題來了,當無符號右移與賦值相結合時,若將其與 byte 或 short 一起使用的話,則結果錯誤。取而代之的是,它們被提升爲 int 型並右移,但在重新賦值時被截斷。在這種情況下,結果爲 -1。

// operators/URShift.java
// 測試無符號右移
public class URShift {
    public static void main(String[] args) {
        int i = -1;
        System.out.println(Integer.toBinaryString(i));
        i >>>= 10;
        System.out.println(Integer.toBinaryString(i));
        long l = -1;
        System.out.println(Long.toBinaryString(l));
        l >>>= 10;
        System.out.println(Long.toBinaryString(l));
        short s = -1;
        System.out.println(Integer.toBinaryString(s));
        s >>>= 10;
        System.out.println(Integer.toBinaryString(s));
        byte b = -1;
        System.out.println(Integer.toBinaryString(b));
        b >>>= 10;
        System.out.println(Integer.toBinaryString(b));
        b = -1;
        System.out.println(Integer.toBinaryString(b));
        System.out.println(Integer.toBinaryString(b>>>10));
    }
}
11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111

8、類型轉換

在 Java 裏,類型轉換則是一種比較安全的操作。但是,若將數據類型進行“向下轉換”(Narrowing Conversion)的操作(將容量較大的數據類型轉換成容量較小的類型),可能會發生信息丟失的危險。此時,編譯器會強迫我們進行轉型,好比在提醒我們:該操作可能危險,若你堅持讓我這麼做,那麼對不起,請明確需要轉換的類型。 對於“向上轉換”(Widening conversion),則不必進行顯式的類型轉換,因爲較大類型的數據肯定能容納較小類型的數據,不會造成任何信息的丟失。

從 float 和 double 轉換爲整數值時,小數位將被截斷。若你想對結果進行四捨五入,可以使用 java.lang.Math 的 round() 方法。

兩個大的 int 型整數相乘時,結果有可能超出 int 型的範圍。
【參考】:On java 8中文翻譯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章