java 操作符詳解

stormzhangV發表於2017-11-09

來源:讀者投稿
地址:chaodongyang.com/2017/11/09/…

在最底層,java 中的資料是通過操作符來操作的。Java 是建立在 C++ 基礎之上的,所以 C 和 C++ 程式設計師會非常熟悉 java 的大多數操作符。當然 Java 也做了一些改進和簡化。

使用 java 操作符

操作符接受一個或多個引數,並生成一個新值。引數的形式與普通的方法呼叫不同,但效果是相同的。加號 (+)、減號 (-)、乘號 (*)、除號 (/) 以及賦值號 (=) 的用法和其他變成語言是類似的。

幾乎所有的操作符都只能操作 「基本型別」。但是 = 、 == 、 != ,可以操作所有的物件。除此以外,String 類支援 + 和 += 。

優先順序

當一個表示式中存在多個操作符時,操作符的優先順序就決定了各部分的計算順序。和我們數學中的優先順序是一樣的,先乘除後加減,有括號的先計算括號中的值。

public class JavaOperator {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int x = 1, y = 2 , z =3;
        int a = x + y - 2/2 +z;
        int b = x + (y -2)/(2 +z);

        System.out.println("a = " + a + "b = " +b);

    }

}複製程式碼

輸出結果: a = 5 b = 1

請注意,輸出語句中的 (+) 號操作符。在這種上下文環境中,(+) 號意味著 “字串的連線”,並且如果必要他還執行字串的轉換。規則如下:當編譯器觀察到一個 String 後面緊跟著一個 (+) 號,而這個 (+) 號後面又緊跟著一個非 String 型別的元素時,就會嘗試著將這個非 String 型別的引數轉換為 String 型別。正如輸出中將 a 和 b 從 int 成功的轉為了 String。

賦值

賦值操作符使用 (=) 號。它的意思是取右邊的值把它賦值給左邊。右邊可以是任何常量,變數,或者表示式。但左邊必須是一個明確的變數。也就是說,必須有一個明確的物理空間可以儲存等號右邊的值。舉例: a = 4
但是不能把任何東西賦值給一個常數,也就是說常數 4 不能作為左邊的值。

對於基本資料型別的賦值是很簡單的。基本型別儲存了實際的數值,而並非指向一個物件的引用。所以在為其賦值時是直接將一個地方的內容複製到了另外一個地方。所以兩個值是互相不受影響的。但是在為物件賦值時,情況就不一樣了。對一個物件操作時,我們操作的不是物件本身,而是物件的引用。所以如果將一個物件賦值給另外一個物件,實際上是將這個物件的引用賦值給另外一個物件。此時兩個物件指向的是同一個引用。

class NObj {
    int a;
}

public class JavaOperator {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        NObj n1 = new NObj();
        NObj n2 = new NObj();

        n1.a = 0;
        n2.a = 1;
        System.out.println("n1.a = " +n1.a + "n2.a =" + n2.a);

        //賦值
        n1 = n2;
        System.out.println("n1.a = " +n1.a + "n2.a =" + n2.a);

        n1.a = 20;
        System.out.println("n1.a = " +n1.a + "n2.a =" + n2.a);


    }

}複製程式碼
n1.a = 0   n2.a =1
n1.a = 1   n2.a =1
n1.a = 20  n2.a =1複製程式碼

原本 n1 包含的物件的引用 0 在對 n1 賦值的時候就被覆蓋了,也就丟失了。而這個不在被引用的 0 物件引用就會被垃圾回收期自動的清理。這種特殊的現象被稱為“別名現象”。

方法呼叫中的別名問題

講一個物件傳遞給方法時,也會產生別名問題。

public class JavaOperator {

    static void f(NObj n){
        n.a = 100;
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        NObj n1 = new NObj();
        n1.a = 0;
        System.out.println("n1.a = " +n1.a);

        //賦值
        f(n1);
        System.out.println("n1.a = " +n1.a);        

    }

}複製程式碼
n1.a = 0
n1.a = 100複製程式碼

算數操作符

java 的基本算數操作符和大多數程式設計語言是相同的。但是 java 也使用一種 C 和 C++ 的簡化符號同時進行運算和賦值操作。這種操作符後面緊跟著一個 = 號來表示,它對於 Java 中的所有操作符都適用。只要有其實際意義就可以。例如:要將 x 加 4,並將結果賦值給它,可以這麼寫 x+=4

一元加、減操作符

一元減號 (-) 和 一元加號 (+) 與二元減號和加號都是用相同的符號。根據表示式的書寫格式,編譯器會自動判斷是那種操作符。

x = -a;

x = a * -b;複製程式碼

一元減號用於轉變資料的符號,而一元加號只是為了與一元減號相對應。但是它唯一的動作僅僅是將比較小的型別的運算元提升為 int 。

自動遞增和遞減

Java 提供了大量的快捷運算子。這些快捷運算使得編碼更方便,同時也使得編碼更容易閱讀,但有時也可能讀起來更困難。
遞增和遞減運算子是兩種很不錯的運算子。其中遞減運算子是 「--」,意味著減少一個單位。遞增運算子是 「++」,意味著增加一個單位。這兩個操作符還有兩個不同的操作方式,通常為字首式和字尾式。字首式會先執行運算在生成值,字尾式會先生成值,在執行運算。

public class JavaOperator {


    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int a = 0;
        System.out.println("第一次a等於: " + a);

        System.out.println("++a: " + ++a);
        System.out.println("第二次a等於: " + a);

        System.out.println("a++: " + a++);
        System.out.println("第三次a等於: " + a);

        System.out.println("--a: " + --a);
        System.out.println("第四次a等於: " + a);

        System.out.println("a--: " + a--);
        System.out.println("第五次a等於: " + a);
    }
}複製程式碼
第一次a等於: 0
++a: 1
第二次a等於: 1
a++: 1
第三次a等於: 2
--a: 1
第四次a等於: 1
a--: 1
第五次a等於: 0複製程式碼

關係操作符

關係操作符生成的是一個 boolean 結果,計算的是運算元之間的關係。如果關係是真實的,表示式會生成 true ;如果關係是假的,表示式會生成 false 。關係操作符包括小於 (<) 、大於 (>)、小於或等於 (<=)、大於或等於 (>=)、等於 (==) 以及不等於 (!=)。等於和不等於適合於操作所有的基本資料型別,而其他的比較不適用 Boolean 型別,因為 true 和 false 比較大於小於沒有實際意義。

測試物件的等價性

關係操作符中 (==) 和 (!=) 適用於所有物件,下面看一個例子。

public static void main(String[] args) {

        Integer n1 = new Integer(20);
        Integer n2 = new Integer(20);

        System.out.println(n1 == n2);
        System.out.println(n1 != n2);
    }複製程式碼
false
true複製程式碼

數值是相同的怎麼會輸出這樣的結果呢,是因為 (==) 和 (!=) 對比的是物件的引用地址,而不是物件的實際值。這兩個是 new 出來的不同物件。所以引用地址是不一樣的。如果我們想比較兩個物件的實際值該怎麼辦呢?必須使用特殊的方法 equals()。但這個方法不適用於基本型別,基本型別直接用上邊的兩個就可以。

System.out.println(n1.equals(n2));
true複製程式碼

邏輯操作符

邏輯操作符 “與” (&&) 、“或” (||) 、“非” (!) 能根據引數的邏輯關係,生成一個 boolean 值。

public static void main(String[] args) {

        Random random = new Random(20);
        int i = random.nextInt(100);
        int j = random.nextInt(100);

        System.out.println("i=:" + i);
        System.out.println("j=:" + j);
        System.out.println("i>j=:" + (i>j));
        System.out.println("i<j=:" + (i<j));
        System.out.println("i>=j=:" + (i>=j));
        System.out.println("i<=j=:" + (i<=j));

        System.out.println("i==j=:" + (i==j));
        System.out.println("i!=j=:" + (i!=j));

        System.out.println("i > 10 && j >10 =:" + ((i>10) && (j>10)));
        System.out.println("i < 10 || j <10 =:" + ((i>10) || (j>10)));

    }複製程式碼
i=:53
j=:36
i>j=:true
i<j=:false
i>=j=:true
i<=j=:false
i==j=:false
i!=j=:true
i > 10 && j >10 =:true
i < 10 || j <10 =:true複製程式碼

“與” 、"或" 、“非” 只可用於 boolean 值,不可直接用於基本型別。

短路

當使用邏輯運算子時,我們會遇到一種 “短路” 現象。即一旦能夠明確無誤的計算整個表示式的值,就不在計算表示式餘下的部分。因此整個表示式靠後的部分可能不會被計算到。如果所有的邏輯表示式都有一部分不必計算,那將獲得潛在的效能提升。

public static void main(String[] args) {

        boolean b = test1(0) && test2(5) && test3(2);
        System.out.println(" 結果沒執行3" + b);

    }

    static boolean test1(int i){
        System.out.println("執行1");
        return i<1;
    }

    static boolean test2(int i){
        System.out.println("執行2");
        return i<2;
    }

    static boolean test3(int i){
        System.out.println("執行3");
        return i<3;
    }複製程式碼
執行1
執行2
 結果沒執行3false複製程式碼

直接常量

如果在程式中使用直接常量,編譯器可以準確的知道要生成什麼樣的型別,有時候確實模稜兩可的。如果發生這種情況,必須對編譯器加以指導,用於直接常量相關的字元來額外增加一些資訊。

int i = 0x2f;
int b = 0177;
char s = 0xffff;
long n1 = 200L;
double d1 = 2D;複製程式碼

指數計數法

java 採用了一種很不直觀的計數法來表示指數,例如

float f = 1.39e-43f;
double d = 470e-47;

System.out.println("指數結果" + f);
System.out.println("指數結果" + d);

指數結果1.39E-43
指數結果4.7E-45複製程式碼

注意:e 代表 10 的次方冪。看到 1.39e-43f 代表 1.39 乘以 10 的 -43 次冪。

三元操作符 if-else

三元操作符也稱之為條件操作符,它顯得比較特殊,因為它有三個運算元,但卻是操作符的一種,因為它最終會產生一個值。採取的形式:

boolean exp ? value0 : value1

如果 exp 結果為 true,就計算 value0 ,如果為 false ,就計算 value1。它的結果也會是操作符最終產生的值。和普通的 if else 語句很相似,但三元操作符更簡潔。

public static void main(String[] args) {

        System.out.println(test1(5));
        System.out.println(test2(5));

    }

    static int test1(int x){
        return x < 10 ? x*10 : x/10;
    }

    static int test2(int x){
        if (x <10) {
            return x*10;
        }else {
            return x/10;
        }
    }複製程式碼
50
50複製程式碼

字串操作符 + 和 +=

這兩個操作符用於連線不同的字串。字串的連線有一個規則:如果表示式是以一個字串開頭,那麼後續所有運算元都必須是字串型別。編譯器會把表示式後的序列自動轉換成字串型別。

public static void main(String[] args) {

        int x =0,y =1, z =2;
        String s = "abc";

        System.out.println(s + x + y +z);

        s += "edf";

        System.out.println(s + (x + y +z));

        System.out.println("" + x);


    }複製程式碼
abc012
abcedf3
0複製程式碼

型別轉換操作符

型別轉換。在適當的時候 java 會將一種資料型別自動的轉換為另一種資料型別。假如我們為浮點型別賦值整數型別。編譯器會將 int 自動轉換為 float。型別轉換允許我們進行顯示的型別轉換,或者進行強制的型別轉換。

int i = 200;
long lng = (long)i;
lng = i;

long lng2 = (long)300;
lng2 = 200;
i = (int)lng2;複製程式碼

注意:這裡可能會引入多餘的轉換,例如,編譯器在必要的時候會自動進行 int 值到 long 值的提升。但是你仍然可以做這樣的多餘的事,以提醒自己需要留意。java 中型別轉換是一種比較安全的操作。然而,如果要執行一種名為窄化轉換的操作。更多資訊資料的型別轉換為無法容納那麼多資訊的型別,就有可能面臨資訊丟失的可能。此時,編譯器會強制要求我們進行型別轉換。而對於擴充套件轉換,而不必進行顯示的型別轉換,因為新型別肯定能容納原來型別的資訊,不會造成任何資訊的丟失。java 允許我們把任何的基本資料型別轉換為別的基本資料型別。boolean 型別不允許進行任何型別的轉換。

截尾和舍入

在執行窄化轉換時,必須注意截尾和舍入的問題。例如,將一個浮點型轉換為整型別,java 會如何處理呢?

public static void main(String[] args) {

    double above = 0.8, below = 0.2;

    float fabove = 0.8f, fbelow = 0.2f;

    System.out.println("int above" + (int)above);
    System.out.println("int below" + (int)below);
    System.out.println("int fabove" + (int)fabove);
    System.out.println("int fabove" + (int)fbelow);

    }複製程式碼
int above 0
int below 0
int fabove 0
int fabove 0複製程式碼

答案是轉換為整數時總是對數字截尾。如果想取得四捨五入的結果呢?就需要使用 lang 包中的 Math 類的 round() 方法。

System.out.println("int above" + Math.round(above));
System.out.println("int below" + Math.round(below));
System.out.println("int fabove" + Math.round(fabove));
System.out.println("int fabove" + Math.round(fbelow));複製程式碼
int above1
int below0
int fabove1
int fabove0複製程式碼

操作符小結

如果對基本型別執行算術運算或按位運算,大家會發現,只要是比 int 型別小的型別,比如,char、byte、short。那麼在運算前,這些值都會自動轉換為 int。這樣最終生成的結果就是 int 型別。如果想把結果賦值給較小的型別,就必須進行型別轉換。通常表示式中最大的資料型別決定了結果的資料型別。

java 中的資料型別在所有的機器中的大小都是相同的。所以,我們不必考慮移植的問題。

對於 boolean 值我們只能賦予它 true 或者 false。如果兩個足夠大的 int 型別進行乘法運算,結果會溢位 int 型別的範圍。但是編譯器也不會出現錯誤或者警告資訊,執行時也不會產生異常。

另外 java 中還有專門用於二進位制位操作和位移操作的運算子。