3.1 使用Java運(yùn)算符

3.1 使用Java運(yùn)算符

運(yùn)算符以一個(gè)或多個(gè)自變量為基礎(chǔ),可生成一個(gè)新值。自變量采用與原始方法調(diào)用不同的一種形式,但效果是相同的。根據(jù)以前寫程序的經(jīng)驗(yàn),運(yùn)算符的常規(guī)概念應(yīng)該不難理解。

加號(hào)(+)、減號(hào)和負(fù)號(hào)(-)、乘號(hào)(*)、除號(hào)(/)以及等號(hào)(=)的用法與其他所有編程語(yǔ)言都是類似的。

所有運(yùn)算符都能根據(jù)自己的運(yùn)算對(duì)象生成一個(gè)值。除此以外,一個(gè)運(yùn)算符可改變運(yùn)算對(duì)象的值,這叫作“副作用”(Side Effect)。運(yùn)算符最常見的用途就是修改自己的運(yùn)算對(duì)象,從而產(chǎn)生副作用。但要注意生成的值亦可由沒有副作用的運(yùn)算符生成。
幾乎所有運(yùn)算符都只能操作“主類型”(Primitives)。唯一的例外是“=”、“==”和“!=”,它們能操作所有對(duì)象(也是對(duì)象易令人混淆的一個(gè)地方)。除此以外,String類支持“+”和“+=”。

3.1.1 優(yōu)先級(jí)

運(yùn)算符的優(yōu)先級(jí)決定了存在多個(gè)運(yùn)算符時(shí)一個(gè)表達(dá)式各部分的計(jì)算順序。Java對(duì)計(jì)算順序作出了特別的規(guī)定。其中,最簡(jiǎn)單的規(guī)則就是乘法和除法在加法和減法之前完成。程序員經(jīng)常都會(huì)忘記其他優(yōu)先級(jí)規(guī)則,所以應(yīng)該用括號(hào)明確規(guī)定計(jì)算順序。例如:

A = X + Y - 2/2 + Z;

為上述表達(dá)式加上括號(hào)后,就有了一個(gè)不同的含義。

A = X + (Y - 2)/(2 + Z);

3.1.2 賦值

賦值是用等號(hào)運(yùn)算符(=)進(jìn)行的。它的意思是“取得右邊的值,把它復(fù)制到左邊”。右邊的值可以是任何常數(shù)、變量或者表達(dá)式,只要能產(chǎn)生一個(gè)值就行。但左邊的值必須是一個(gè)明確的、已命名的變量。也就是說(shuō),它必須有一個(gè)物理性的空間來(lái)保存右邊的值。舉個(gè)例子來(lái)說(shuō),可將一個(gè)常數(shù)賦給一個(gè)變量(A=4;),但不可將任何東西賦給一個(gè)常數(shù)(比如不能4=A)。

對(duì)主數(shù)據(jù)類型的賦值是非常直接的。由于主類型容納了實(shí)際的值,而且并非指向一個(gè)對(duì)象的句柄,所以在為其賦值的時(shí)候,可將來(lái)自一個(gè)地方的內(nèi)容復(fù)制到另一個(gè)地方。例如,假設(shè)為主類型使用“A=B”,那么B處的內(nèi)容就復(fù)制到A。若接著又修改了A,那么B根本不會(huì)受這種修改的影響。作為一名程序員,這應(yīng)成為自己的常識(shí)。

但在為對(duì)象“賦值”的時(shí)候,情況卻發(fā)生了變化。對(duì)一個(gè)對(duì)象進(jìn)行操作時(shí),我們真正操作的是它的句柄。所以倘若“從一個(gè)對(duì)象到另一個(gè)對(duì)象”賦值,實(shí)際就是將句柄從一個(gè)地方復(fù)制到另一個(gè)地方。這意味著假若為對(duì)象使用“C=D”,那么C和D最終都會(huì)指向最初只有D才指向的那個(gè)對(duì)象。下面這個(gè)例子將向大家闡示這一點(diǎn)。

這里有一些題外話。在后面,大家在代碼示例里看到的第一個(gè)語(yǔ)句將是“package 03”使用的“package”語(yǔ)句,它代表本書第3章。本書每一章的第一個(gè)代碼清單都會(huì)包含象這樣的一個(gè)“package”(封裝、打包、包裹)語(yǔ)句,它的作用是為那一章剩余的代碼建立章節(jié)編號(hào)。在第17章,大家會(huì)看到第3章的所有代碼清單(除那些有不同封裝名稱的以外)都會(huì)自動(dòng)置入一個(gè)名為c03的子目錄里;第4章的代碼置入c04;以此類推。所有這些都是通過(guò)第17章展示的CodePackage.java程序?qū)崿F(xiàn)的;“封裝”的基本概念會(huì)在第5章進(jìn)行詳盡的解釋。就目前來(lái)說(shuō),大家只需記住象“package 03”這樣的形式只是用于為某一章的代碼清單建立相應(yīng)的子目錄。

為運(yùn)行程序,必須保證在classpath里包含了我們安裝本書源碼文件的根目錄(那個(gè)目錄里包含了c02,c03c,c04等等子目錄)。
對(duì)于Java后續(xù)的版本(1.1.4和更高版本),如果您的main()用package語(yǔ)句封裝到一個(gè)文件里,那么必須在程序名前面指定完整的包裹名稱,否則不能運(yùn)行程序。在這種情況下,命令行是:

java c03.Assignment

運(yùn)行位于一個(gè)“包裹”里的程序時(shí),隨時(shí)都要注意這方面的問題。
下面是例子:

//: Assignment.java
// Assignment with objects is a bit tricky
package c03;

class Number {
  int i;
}

public class Assignment {
  public static void main(String[] args) {
    Number n1 = new Number();
    Number n2 = new Number();
    n1.i = 9;
    n2.i = 47;
    System.out.println("1: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
    n1 = n2;
    System.out.println("2: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
    n1.i = 27;
    System.out.println("3: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
  }
} ///:~

Number類非常簡(jiǎn)單,它的兩個(gè)實(shí)例(n1和n2)是在main()里創(chuàng)建的。每個(gè)Number中的i值都賦予了一個(gè)不同的值。隨后,將n2賦給n1,而且n1發(fā)生改變。在許多程序設(shè)計(jì)語(yǔ)言中,我們都希望n1和n2任何時(shí)候都相互獨(dú)立。但由于我們已賦予了一個(gè)句柄,所以下面才是真實(shí)的輸出:

1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27

看來(lái)改變n1的同時(shí)也改變了n2!這是由于無(wú)論n1還是n2都包含了相同的句柄,它指向相同的對(duì)象(最初的句柄位于n1內(nèi)部,指向容納了值9的一個(gè)對(duì)象。在賦值過(guò)程中,那個(gè)句柄實(shí)際已經(jīng)丟失;它的對(duì)象會(huì)由“垃圾收集器”自動(dòng)清除)。
這種特殊的現(xiàn)象通常也叫作“別名”,是Java操作對(duì)象的一種基本方式。但假若不愿意在這種情況下出現(xiàn)別名,又該怎么操作呢?可放棄賦值,并寫入下述代碼:

n1.i = n2.i;

這樣便可保留兩個(gè)獨(dú)立的對(duì)象,而不是將n1和n2綁定到相同的對(duì)象。但您很快就會(huì)意識(shí)到,這樣做會(huì)使對(duì)象內(nèi)部的字段處理發(fā)生混亂,并與標(biāo)準(zhǔn)的面向?qū)ο笤O(shè)計(jì)準(zhǔn)則相悖。由于這并非一個(gè)簡(jiǎn)單的話題,所以留待第12章詳細(xì)論述,那一章是專門討論別名的。其時(shí),大家也會(huì)注意到對(duì)象的賦值會(huì)產(chǎn)生一些令人震驚的效果。

1. 方法調(diào)用中的別名處理

將一個(gè)對(duì)象傳遞到方法內(nèi)部時(shí),也會(huì)產(chǎn)生別名現(xiàn)象。

//: PassObject.java
// Passing objects to methods can be a bit tricky

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);
  }
} ///:~

在許多程序設(shè)計(jì)語(yǔ)言中,f()方法表面上似乎要在方法的作用域內(nèi)制作自己的自變量Letter y的一個(gè)副本。但同樣地,實(shí)際傳遞的是一個(gè)句柄。所以下面這個(gè)程序行:

y.c = 'z';

實(shí)際改變的是f()之外的對(duì)象。輸出結(jié)果如下:

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

別名和它的對(duì)策是非常復(fù)雜的一個(gè)問題。盡管必須等至第12章才可獲得所有答案,但從現(xiàn)在開始就應(yīng)加以重視,以便提早發(fā)現(xiàn)它的缺點(diǎn)。

3.1.3 算術(shù)運(yùn)算符

Java的基本算術(shù)運(yùn)算符與其他大多數(shù)程序設(shè)計(jì)語(yǔ)言是相同的。其中包括加號(hào)(+)、減號(hào)(-)、除號(hào)(/)、乘號(hào)(*)以及模數(shù)(%,從整數(shù)除法中獲得余數(shù))。整數(shù)除法會(huì)直接砍掉小數(shù),而不是進(jìn)位。

Java也用一種簡(jiǎn)寫形式進(jìn)行運(yùn)算,并同時(shí)進(jìn)行賦值操作。這是由等號(hào)前的一個(gè)運(yùn)算符標(biāo)記的,而且對(duì)于語(yǔ)言中的所有運(yùn)算符都是固定的。例如,為了將4加到變量x,并將結(jié)果賦給x,可用:x+=4。

下面這個(gè)例子展示了算術(shù)運(yùn)算符的各種用法:

//: MathOps.java
// Demonstrates the mathematical operators
import java.util.*;

public class MathOps {
  // Create a shorthand to save typing:
  static void prt(String s) {
    System.out.println(s);
  }
  // shorthand to print a string and an int:
  static void pInt(String s, int i) {
    prt(s + " = " + i);
  }
  // shorthand to print a string and a float:
  static void pFlt(String s, float f) {
    prt(s + " = " + f);
  }
  public static void main(String[] args) {
    // Create a random number generator,
    // seeds with current time by default:
    Random rand = new Random();
    int i, j, k;
    // '%' limits maximum value to 99:
    j = rand.nextInt() % 100;
    k = rand.nextInt() % 100;
    pInt("j",j);  pInt("k",k);
    i = j + k; pInt("j + k", i);
    i = j - k; pInt("j - k", i);
    i = k / j; pInt("k / j", i);
    i = k * j; pInt("k * j", i);
    i = k % j; pInt("k % j", i);
    j %= k; pInt("j %= k", j);
    // Floating-point number tests:
    float u,v,w;  // applies to doubles, too
    v = rand.nextFloat();
    w = rand.nextFloat();
    pFlt("v", v); pFlt("w", w);
    u = v + w; pFlt("v + w", u);
    u = v - w; pFlt("v - w", u);
    u = v * w; pFlt("v * w", u);
    u = v / w; pFlt("v / w", u);
    // the following also works for
    // char, byte, short, int, long,
    // and double:
    u += v; pFlt("u += v", u);
    u -= v; pFlt("u -= v", u);
    u *= v; pFlt("u *= v", u);
    u /= v; pFlt("u /= v", u);
  }
} ///:~

我們注意到的第一件事情就是用于打印(顯示)的一些快捷方法:prt()方法打印一個(gè)String;pInt()先打印一個(gè)String,再打印一個(gè)int;而pFlt()先打印一個(gè)String,再打印一個(gè)float。當(dāng)然,它們最終都要用System.out.println()結(jié)尾。

為生成數(shù)字,程序首先會(huì)創(chuàng)建一個(gè)Random(隨機(jī))對(duì)象。由于自變量是在創(chuàng)建過(guò)程中傳遞的,所以Java將當(dāng)前時(shí)間作為一個(gè)“種子值”,由隨機(jī)數(shù)生成器利用。通過(guò)Random對(duì)象,程序可生成許多不同類型的隨機(jī)數(shù)字。做法很簡(jiǎn)單,只需調(diào)用不同的方法即可:nextInt(),nextLong(),nextFloat()或者nextDouble()。

若隨同隨機(jī)數(shù)生成器的結(jié)果使用,模數(shù)運(yùn)算符(%)可將結(jié)果限制到運(yùn)算對(duì)象減1的上限(本例是99)之下。

1. 一元加、減運(yùn)算符

一元減號(hào)(-)和一元加號(hào)(+)與二元加號(hào)和減號(hào)都是相同的運(yùn)算符。根據(jù)表達(dá)式的書寫形式,編譯器會(huì)自動(dòng)判斷使用哪一種。例如下述語(yǔ)句:

x = -a;

它的含義是顯然的。編譯器能正確識(shí)別下述語(yǔ)句:

x = a * -b;

但讀者會(huì)被搞糊涂,所以最好更明確地寫成:

x = a * (-b);

一元減號(hào)得到的運(yùn)算對(duì)象的負(fù)值。一元加號(hào)的含義與一元減號(hào)相反,雖然它實(shí)際并不做任何事情。

3.1.4 自動(dòng)遞增和遞減

和C類似,Java提供了豐富的快捷運(yùn)算方式。這些快捷運(yùn)算可使代碼更清爽,更易錄入,也更易讀者辨讀。

兩種很不錯(cuò)的快捷運(yùn)算方式是遞增和遞減運(yùn)算符(常稱作“自動(dòng)遞增”和“自動(dòng)遞減”運(yùn)算符)。其中,遞減運(yùn)算符是“--”,意為“減少一個(gè)單位”;遞增運(yùn)算符是“++”,意為“增加一個(gè)單位”。舉個(gè)例子來(lái)說(shuō),假設(shè)A是一個(gè)int(整數(shù))值,則表達(dá)式++A就等價(jià)于(A = A + 1)。遞增和遞減運(yùn)算符結(jié)果生成的是變量的值。

對(duì)每種類型的運(yùn)算符,都有兩個(gè)版本可供選用;通常將其稱為“前綴版”和“后綴版”。“前遞增”表示++運(yùn)算符位于變量或表達(dá)式的前面;而“后遞增”表示++運(yùn)算符位于變量或表達(dá)式的后面。類似地,“前遞減”意味著--運(yùn)算符位于變量或表達(dá)式的前面;而“后遞減”意味著--運(yùn)算符位于變量或表達(dá)式的后面。對(duì)于前遞增和前遞減(如++A或--A),會(huì)先執(zhí)行運(yùn)算,再生成值。而對(duì)于后遞增和后遞減(如A++或A--),會(huì)先生成值,再執(zhí)行運(yùn)算。下面是一個(gè)例子:

//: AutoInc.java
// Demonstrates the ++ and -- operators

public class AutoInc {
  public static void main(String[] args) {
    int i = 1;
    prt("i : " + i);
    prt("++i : " + ++i); // Pre-increment
    prt("i++ : " + i++); // Post-increment
    prt("i : " + i);
    prt("--i : " + --i); // Pre-decrement
    prt("i-- : " + i--); // Post-decrement
    prt("i : " + i);
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~

該程序的輸出如下:

i : 1
++i : 2
i++ : 2
i : 3
--i : 2
i-- : 2
i : 1

從中可以看到,對(duì)于前綴形式,我們?cè)趫?zhí)行完運(yùn)算后才得到值。但對(duì)于后綴形式,則是在運(yùn)算執(zhí)行之前就得到值。它們是唯一具有“副作用”的運(yùn)算符(除那些涉及賦值的以外)。也就是說(shuō),它們會(huì)改變運(yùn)算對(duì)象,而不僅僅是使用自己的值。
遞增運(yùn)算符正是對(duì)“C++”這個(gè)名字的一種解釋,暗示著“超載C的一步”。在早期的一次Java演講中,Bill Joy(始創(chuàng)人之一)聲稱“Java=C++--”(C加加減減),意味著Java已去除了C++一些沒來(lái)由折磨人的地方,形成一種更精簡(jiǎn)的語(yǔ)言。正如大家會(huì)在這本書中學(xué)到的那樣,Java的許多地方都得到了簡(jiǎn)化,所以Java的學(xué)習(xí)比C++更容易。

3.1.5 關(guān)系運(yùn)算符

關(guān)系運(yùn)算符生成的是一個(gè)“布爾”(Boolean)結(jié)果。它們?cè)u(píng)價(jià)的是運(yùn)算對(duì)象值之間的關(guān)系。若關(guān)系是真實(shí)的,關(guān)系表達(dá)式會(huì)生成true(真);若關(guān)系不真實(shí),則生成false(假)。關(guān)系運(yùn)算符包括小于(<)、大于(>)、小于或等于(<=)、大于或等于(>=)、等于(==)以及不等于(!=)。等于和不等于適用于所有內(nèi)建的數(shù)據(jù)類型,但其他比較不適用于boolean類型。

1. 檢查對(duì)象是否相等

關(guān)系運(yùn)算符==和!=也適用于所有對(duì)象,但它們的含義通常會(huì)使初涉Java領(lǐng)域的人找不到北。下面是一個(gè)例子:

//: Equivalence.java

public class Equivalence {
  public static void main(String[] args) {
    Integer n1 = new Integer(47);
    Integer n2 = new Integer(47);
    System.out.println(n1 == n2);
    System.out.println(n1 != n2);
  }
} ///:~

其中,表達(dá)式System.out.println(n1 == n2)可打印出內(nèi)部的布爾比較結(jié)果。一般人都會(huì)認(rèn)為輸出結(jié)果肯定先是true,再是false,因?yàn)閮蓚€(gè)Integer對(duì)象都是相同的。但盡管對(duì)象的內(nèi)容相同,句柄卻是不同的,而==和!=比較的正好就是對(duì)象句柄。所以輸出結(jié)果實(shí)際上先是false,再是true。這自然會(huì)使第一次接觸的人感到驚奇。

若想對(duì)比兩個(gè)對(duì)象的實(shí)際內(nèi)容是否相同,又該如何操作呢?此時(shí),必須使用所有對(duì)象都適用的特殊方法equals()。但這個(gè)方法不適用于“主類型”,那些類型直接使用==和!=即可。下面舉例說(shuō)明如何使用:

//: EqualsMethod.java

public class EqualsMethod {
  public static void main(String[] args) {
    Integer n1 = new Integer(47);
    Integer n2 = new Integer(47);
    System.out.println(n1.equals(n2));
  }
} ///:~

正如我們預(yù)計(jì)的那樣,此時(shí)得到的結(jié)果是true。但事情并未到此結(jié)束!假設(shè)您創(chuàng)建了自己的類,就象下面這樣:

//: EqualsMethod2.java

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));
  }
} ///:~

此時(shí)的結(jié)果又變回了false!這是由于equals()的默認(rèn)行為是比較句柄。所以除非在自己的新類中改變了equals(),否則不可能表現(xiàn)出我們希望的行為。不幸的是,要到第7章才會(huì)學(xué)習(xí)如何改變行為。但要注意equals()的這種行為方式同時(shí)或許能夠避免一些“災(zāi)難”性的事件。

大多數(shù)Java類庫(kù)都實(shí)現(xiàn)了equals(),所以它實(shí)際比較的是對(duì)象的內(nèi)容,而非它們的句柄。

3.1.6 邏輯運(yùn)算符

邏輯運(yùn)算符AND(&&)、OR(||)以及NOT(!)能生成一個(gè)布爾值(true或false)——以自變量的邏輯關(guān)系為基礎(chǔ)。下面這個(gè)例子向大家展示了如何使用關(guān)系和邏輯運(yùn)算符。

//: Bool.java
// Relational and logical operators
import java.util.*;

public class Bool {
  public static void main(String[] args) {
    Random rand = new Random();
    int i = rand.nextInt() % 100;
    int j = rand.nextInt() % 100;
    prt("i = " + i);
    prt("j = " + j);
    prt("i > j is " + (i > j));
    prt("i < j is " + (i < j));
    prt("i >= j is " + (i >= j));
    prt("i <= j is " + (i <= j));
    prt("i == j is " + (i == j));
    prt("i != j is " + (i != j));

    // Treating an int as a boolean is 
    // not legal Java
//! prt("i && j is " + (i && j));
//! prt("i || j is " + (i || j));
//! prt("!i is " + !i);

    prt("(i < 10) && (j < 10) is "
       + ((i < 10) && (j < 10)) );
    prt("(i < 10) || (j < 10) is "
       + ((i < 10) || (j < 10)) );
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~

只可將AND,OR或NOT應(yīng)用于布爾值。與在C及C++中不同,不可將一個(gè)非布爾值當(dāng)作布爾值在邏輯表達(dá)式中使用。若這樣做,就會(huì)發(fā)現(xiàn)嘗試失敗,并用一個(gè)“//!”標(biāo)出。然而,后續(xù)的表達(dá)式利用關(guān)系比較生成布爾值,然后對(duì)結(jié)果進(jìn)行邏輯運(yùn)算。
輸出列表看起來(lái)象下面這個(gè)樣子:

i = 85
j = 4
i > j is true
i < j is false
i >= j is true
i <= j is false
i == j is false
i != j is true
(i < 10) && (j < 10) is false
(i < 10) || (j < 10) is true

注意若在預(yù)計(jì)為String值的地方使用,布爾值會(huì)自動(dòng)轉(zhuǎn)換成適當(dāng)?shù)奈谋拘问健?/p>

在上述程序中,可將對(duì)int的定義替換成除boolean以外的其他任何主數(shù)據(jù)類型。但要注意,對(duì)浮點(diǎn)數(shù)字的比較是非常嚴(yán)格的。即使一個(gè)數(shù)字僅在小數(shù)部分與另一個(gè)數(shù)字存在極微小的差異,仍然認(rèn)為它們是“不相等”的。即使一個(gè)數(shù)字只比零大一點(diǎn)點(diǎn)(例如2不停地開平方根),它仍然屬于“非零”值。

1. 短路

操作邏輯運(yùn)算符時(shí),我們會(huì)遇到一種名為“短路”的情況。這意味著只有明確得出整個(gè)表達(dá)式真或假的結(jié)論,才會(huì)對(duì)表達(dá)式進(jìn)行邏輯求值。因此,一個(gè)邏輯表達(dá)式的所有部分都有可能不進(jìn)行求值:

//: ShortCircuit.java
// Demonstrates short-circuiting behavior
// with logical operators.

public class ShortCircuit {
  static boolean test1(int val) {
    System.out.println("test1(" + val + ")");
    System.out.println("result: " + (val < 1));
    return val < 1;
  }
  static boolean test2(int val) {
    System.out.println("test2(" + val + ")");
    System.out.println("result: " + (val < 2));
    return val < 2;
  }
  static boolean test3(int val) {
    System.out.println("test3(" + val + ")");
    System.out.println("result: " + (val < 3));
    return val < 3;
  }
  public static void main(String[] args) {
    if(test1(0) && test2(2) && test3(2))
      System.out.println("expression is true");
    else
      System.out.println("expression is false");
  }
} ///:~

每次測(cè)試都會(huì)比較自變量,并返回真或假。它不會(huì)顯示與準(zhǔn)備調(diào)用什么有關(guān)的資料。測(cè)試在下面這個(gè)表達(dá)式中進(jìn)行:

if(test1(0)) && test2(2) && test3(2))

很自然地,你也許認(rèn)為所有這三個(gè)測(cè)試都會(huì)得以執(zhí)行。但希望輸出結(jié)果不至于使你大吃一驚:

if(test1(0) && test2(2) && test3(2))

第一個(gè)測(cè)試生成一個(gè)true結(jié)果,所以表達(dá)式求值會(huì)繼續(xù)下去。然而,第二個(gè)測(cè)試產(chǎn)生了一個(gè)false結(jié)果。由于這意味著整個(gè)表達(dá)式肯定為false,所以為什么還要繼續(xù)剩余的表達(dá)式呢?這樣做只會(huì)徒勞無(wú)益。事實(shí)上,“短路”一詞的由來(lái)正種因于此。如果一個(gè)邏輯表達(dá)式的所有部分都不必執(zhí)行下去,那么潛在的性能提升將是相當(dāng)可觀的。

3.1.7 按位運(yùn)算符

按位運(yùn)算符允許我們操作一個(gè)整數(shù)主數(shù)據(jù)類型中的單個(gè)“比特”,即二進(jìn)制位。按位運(yùn)算符會(huì)對(duì)兩個(gè)自變量中對(duì)應(yīng)的位執(zhí)行布爾代數(shù),并最終生成一個(gè)結(jié)果。

按位運(yùn)算來(lái)源于C語(yǔ)言的低級(jí)操作。我們經(jīng)常都要直接操縱硬件,需要頻繁設(shè)置硬件寄存器內(nèi)的二進(jìn)制位。Java的設(shè)計(jì)初衷是嵌入電視頂置盒內(nèi),所以這種低級(jí)操作仍被保留下來(lái)了。然而,由于操作系統(tǒng)的進(jìn)步,現(xiàn)在也許不必過(guò)于頻繁地進(jìn)行按位運(yùn)算。

若兩個(gè)輸入位都是1,則按位AND運(yùn)算符(&)在輸出位里生成一個(gè)1;否則生成0。若兩個(gè)輸入位里至少有一個(gè)是1,則按位OR運(yùn)算符(|)在輸出位里生成一個(gè)1;只有在兩個(gè)輸入位都是0的情況下,它才會(huì)生成一個(gè)0。若兩個(gè)輸入位的某一個(gè)是1,但不全都是1,那么按位XOR(^,異或)在輸出位里生成一個(gè)1。按位NOT(~,也叫作“非”運(yùn)算符)屬于一元運(yùn)算符;它只對(duì)一個(gè)自變量進(jìn)行操作(其他所有運(yùn)算符都是二元運(yùn)算符)。按位NOT生成與輸入位的相反的值——若輸入0,則輸出1;輸入1,則輸出0。

按位運(yùn)算符和邏輯運(yùn)算符都使用了同樣的字符,只是數(shù)量不同。因此,我們能方便地記憶各自的含義:由于“位”是非?!靶 钡?,所以按位運(yùn)算符僅使用了一個(gè)字符。

按位運(yùn)算符可與等號(hào)(=)聯(lián)合使用,以便合并運(yùn)算及賦值:&=,|=和^=都是合法的(由于~是一元運(yùn)算符,所以不可與=聯(lián)合使用)。

我們將boolean(布爾)類型當(dāng)作一種“單位”或“單比特”值對(duì)待,所以它多少有些獨(dú)特的地方。我們可執(zhí)行按位AND,OR和XOR,但不能執(zhí)行按位NOT(大概是為了避免與邏輯NOT混淆)。對(duì)于布爾值,按位運(yùn)算符具有與邏輯運(yùn)算符相同的效果,只是它們不會(huì)中途“短路”。此外,針對(duì)布爾值進(jìn)行的按位運(yùn)算為我們新增了一個(gè)XOR邏輯運(yùn)算符,它并未包括在“邏輯”運(yùn)算符的列表中。在移位表達(dá)式中,我們被禁止使用布爾運(yùn)算,原因?qū)⒃谙旅娼忉尅?/p>

3.1.8 移位運(yùn)算符

移位運(yùn)算符面向的運(yùn)算對(duì)象也是二進(jìn)制的“位”??蓡为?dú)用它們處理整數(shù)類型(主類型的一種)。左移位運(yùn)算符(<<)能將運(yùn)算符左邊的運(yùn)算對(duì)象向左移動(dòng)運(yùn)算符右側(cè)指定的位數(shù)(在低位補(bǔ)0)。“有符號(hào)”右移位運(yùn)算符(>>)則將運(yùn)算符左邊的運(yùn)算對(duì)象向右移動(dòng)運(yùn)算符右側(cè)指定的位數(shù)。“有符號(hào)”右移位運(yùn)算符使用了“符號(hào)擴(kuò)展”:若值為正,則在高位插入0;若值為負(fù),則在高位插入1。Java也添加了一種“無(wú)符號(hào)”右移位運(yùn)算符(>>>),它使用了“零擴(kuò)展”:無(wú)論正負(fù),都在高位插入0。這一運(yùn)算符是C或C++沒有的。

若對(duì)char,byte或者short進(jìn)行移位處理,那么在移位進(jìn)行之前,它們會(huì)自動(dòng)轉(zhuǎn)換成一個(gè)int。只有右側(cè)的5個(gè)低位才會(huì)用到。這樣可防止我們?cè)谝粋€(gè)int數(shù)里移動(dòng)不切實(shí)際的位數(shù)。若對(duì)一個(gè)long值進(jìn)行處理,最后得到的結(jié)果也是long。此時(shí)只會(huì)用到右側(cè)的6個(gè)低位,防止移動(dòng)超過(guò)long值里現(xiàn)成的位數(shù)。但在進(jìn)行“無(wú)符號(hào)”右移位時(shí),也可能遇到一個(gè)問題。若對(duì)byte或short值進(jìn)行右移位運(yùn)算,得到的可能不是正確的結(jié)果(Java 1.0和Java 1.1特別突出)。它們會(huì)自動(dòng)轉(zhuǎn)換成int類型,并進(jìn)行右移位。但“零擴(kuò)展”不會(huì)發(fā)生,所以在那些情況下會(huì)得到-1的結(jié)果??捎孟旅孢@個(gè)例子檢測(cè)自己的實(shí)現(xiàn)方案:

//: URShift.java
// Test of unsigned right shift

public class URShift {
  public static void main(String[] args) {
    int i = -1;
    i >>>= 10;
    System.out.println(i);
    long l = -1;
    l >>>= 10;
    System.out.println(l);
    short s = -1;
    s >>>= 10;
    System.out.println(s);
    byte b = -1;
    b >>>= 10;
    System.out.println(b);
  }
} ///:~

移位可與等號(hào)(<<=或>>=或>>>=)組合使用。此時(shí),運(yùn)算符左邊的值會(huì)移動(dòng)由右邊的值指定的位數(shù),再將得到的結(jié)果賦回左邊的值。

下面這個(gè)例子向大家闡示了如何應(yīng)用涉及“按位”操作的所有運(yùn)算符,以及它們的效果:

//: BitManipulation.java
// Using the bitwise operators
import java.util.*;

public class BitManipulation {
  public static void main(String[] args) {
    Random rand = new Random();
    int i = rand.nextInt();
    int j = rand.nextInt();
    pBinInt("-1", -1);
    pBinInt("+1", +1);
    int maxpos = 2147483647;
    pBinInt("maxpos", maxpos);
    int maxneg = -2147483648;
    pBinInt("maxneg", maxneg);
    pBinInt("i", i);
    pBinInt("~i", ~i);
    pBinInt("-i", -i);
    pBinInt("j", j);
    pBinInt("i & j", i & j);
    pBinInt("i | j", i | j);
    pBinInt("i ^ j", i ^ j);
    pBinInt("i << 5", i << 5);
    pBinInt("i >> 5", i >> 5);
    pBinInt("(~i) >> 5", (~i) >> 5);
    pBinInt("i >>> 5", i >>> 5);
    pBinInt("(~i) >>> 5", (~i) >>> 5);

    long l = rand.nextLong();
    long m = rand.nextLong();
    pBinLong("-1L", -1L);
    pBinLong("+1L", +1L);
    long ll = 9223372036854775807L;
    pBinLong("maxpos", ll);
    long lln = -9223372036854775808L;
    pBinLong("maxneg", lln);
    pBinLong("l", l);
    pBinLong("~l", ~l);
    pBinLong("-l", -l);
    pBinLong("m", m);
    pBinLong("l & m", l & m);
    pBinLong("l | m", l | m);
    pBinLong("l ^ m", l ^ m);
    pBinLong("l << 5", l << 5);
    pBinLong("l >> 5", l >> 5);
    pBinLong("(~l) >> 5", (~l) >> 5);
    pBinLong("l >>> 5", l >>> 5);
    pBinLong("(~l) >>> 5", (~l) >>> 5);
  }
  static void pBinInt(String s, int i) {
    System.out.println(
      s + ", int: " + i + ", binary: ");
    System.out.print("   ");
    for(int j = 31; j >=0; j--)
      if(((1 << j) &  i) != 0)
        System.out.print("1");
      else
        System.out.print("0");
    System.out.println();
  }
  static void pBinLong(String s, long l) {
    System.out.println(
      s + ", long: " + l + ", binary: ");
    System.out.print("   ");
    for(int i = 63; i >=0; i--)
      if(((1L << i) & l) != 0)
        System.out.print("1");
      else
        System.out.print("0");
    System.out.println();
  }
} ///:~

程序末尾調(diào)用了兩個(gè)方法:pBinInt()和pBinLong()。它們分別操作一個(gè)int和long值,并用一種二進(jìn)制格式輸出,同時(shí)附有簡(jiǎn)要的說(shuō)明文字。目前,可暫時(shí)忽略它們具體的實(shí)現(xiàn)方案。

大家要注意的是System.out.print()的使用,而不是System.out.println()。print()方法不會(huì)產(chǎn)生一個(gè)新行,以便在同一行里羅列多種信息。

除展示所有按位運(yùn)算符針對(duì)int和long的效果之外,本例也展示了int和long的最小值、最大值、+1和-1值,使大家能體會(huì)它們的情況。注意高位代表正負(fù)號(hào):0為正,1為負(fù)。下面列出int部分的輸出:

-1, int: -1, binary: 
   11111111111111111111111111111111
+1, int: 1, binary: 
   00000000000000000000000000000001
maxpos, int: 2147483647, binary: 
   01111111111111111111111111111111
maxneg, int: -2147483648, binary: 
   10000000000000000000000000000000
i, int: 59081716, binary: 
   00000011100001011000001111110100
~i, int: -59081717, binary: 
   11111100011110100111110000001011
-i, int: -59081716, binary: 
   11111100011110100111110000001100
j, int: 198850956, binary: 
   00001011110110100011100110001100
i & j, int: 58720644, binary: 
   00000011100000000000000110000100
i | j, int: 199212028, binary: 
   00001011110111111011101111111100
i ^ j, int: 140491384, binary: 
   00001000010111111011101001111000
i << 5, int: 1890614912, binary: 
   01110000101100000111111010000000
i >> 5, int: 1846303, binary: 
   00000000000111000010110000011111
(~i) >> 5, int: -1846304, binary: 
   11111111111000111101001111100000
i >>> 5, int: 1846303, binary: 
   00000000000111000010110000011111
(~i) >>> 5, int: 132371424, binary: 
   00000111111000111101001111100000

數(shù)字的二進(jìn)制形式表現(xiàn)為“有符號(hào)2的補(bǔ)值”。

3.1.9 三元if-else運(yùn)算符

這種運(yùn)算符比較罕見,因?yàn)樗腥齻€(gè)運(yùn)算對(duì)象。但它確實(shí)屬于運(yùn)算符的一種,因?yàn)樗罱K也會(huì)生成一個(gè)值。這與本章后一節(jié)要講述的普通if-else語(yǔ)句是不同的。表達(dá)式采取下述形式:

布爾表達(dá)式 ? 值0:值1

若“布爾表達(dá)式”的結(jié)果為true,就計(jì)算“值0”,而且它的結(jié)果成為最終由運(yùn)算符產(chǎn)生的值。但若“布爾表達(dá)式”的結(jié)果為false,計(jì)算的就是“值1”,而且它的結(jié)果成為最終由運(yùn)算符產(chǎn)生的值。

當(dāng)然,也可以換用普通的if-else語(yǔ)句(在后面介紹),但三元運(yùn)算符更加簡(jiǎn)潔。盡管C引以為傲的就是它是一種簡(jiǎn)練的語(yǔ)言,而且三元運(yùn)算符的引入多半就是為了體現(xiàn)這種高效率的編程,但假若您打算頻繁用它,還是要先多作一些思量——它很容易就會(huì)產(chǎn)生可讀性極差的代碼。

可將條件運(yùn)算符用于自己的“副作用”,或用于它生成的值。但通常都應(yīng)將其用于值,因?yàn)槟菢幼隹蓪⑦\(yùn)算符與if-else明確區(qū)別開。下面便是一個(gè)例子:

static int ternary(int i) {
return i < 10 ? i * 100 : i * 10;
}

可以看出,假設(shè)用普通的if-else結(jié)構(gòu)寫上述代碼,代碼量會(huì)比上面多出許多。如下所示:

static int alternative(int i) {
if (i < 10)
return i * 100;
return i * 10;
}

但第二種形式更易理解,而且不要求更多的錄入。所以在挑選三元運(yùn)算符時(shí),請(qǐng)務(wù)必權(quán)衡一下利弊。

3.1.10 逗號(hào)運(yùn)算符

在C和C++里,逗號(hào)不僅作為函數(shù)自變量列表的分隔符使用,也作為進(jìn)行后續(xù)計(jì)算的一個(gè)運(yùn)算符使用。在Java里需要用到逗號(hào)的唯一場(chǎng)所就是for循環(huán),本章稍后會(huì)對(duì)此詳加解釋。

3.1.11 字串運(yùn)算符+

這個(gè)運(yùn)算符在Java里有一項(xiàng)特殊用途:連接不同的字串。這一點(diǎn)已在前面的例子中展示過(guò)了。盡管與+的傳統(tǒng)意義不符,但用+來(lái)做這件事情仍然是非常自然的。在C++里,這一功能看起來(lái)非常不錯(cuò),所以引入了一項(xiàng)“運(yùn)算符過(guò)載”機(jī)制,以便C++程序員為幾乎所有運(yùn)算符增加特殊的含義。但非常不幸,與C++的另外一些限制結(jié)合,運(yùn)算符過(guò)載成為一種非常復(fù)雜的特性,程序員在設(shè)計(jì)自己的類時(shí)必須對(duì)此有周到的考慮。與C++相比,盡管運(yùn)算符過(guò)載在Java里更易實(shí)現(xiàn),但迄今為止仍然認(rèn)為這一特性過(guò)于復(fù)雜。所以Java程序員不能象C++程序員那樣設(shè)計(jì)自己的過(guò)載運(yùn)算符。

我們注意到運(yùn)用“String +”時(shí)一些有趣的現(xiàn)象。若表達(dá)式以一個(gè)String起頭,那么后續(xù)所有運(yùn)算對(duì)象都必須是字串。如下所示:

int x = 0, y = 1, z = 2;
String sString = "x, y, z ";
System.out.println(sString + x + y + z);

在這里,Java編譯程序會(huì)將x,y和z轉(zhuǎn)換成它們的字串形式,而不是先把它們加到一起。然而,如果使用下述語(yǔ)句:

System.out.println(x + sString);

那么早期版本的Java就會(huì)提示出錯(cuò)(以后的版本能將x轉(zhuǎn)換成一個(gè)字串)。因此,如果想通過(guò)“加號(hào)”連接字串(使用Java的早期版本),請(qǐng)務(wù)必保證第一個(gè)元素是字串(或加上引號(hào)的一系列字符,編譯能將其識(shí)別成一個(gè)字串)。

3.1.12 運(yùn)算符常規(guī)操作規(guī)則

使用運(yùn)算符的一個(gè)缺點(diǎn)是括號(hào)的運(yùn)用經(jīng)常容易搞錯(cuò)。即使對(duì)一個(gè)表達(dá)式如何計(jì)算有絲毫不確定的因素,都容易混淆括號(hào)的用法。這個(gè)問題在Java里仍然存在。
在C和C++中,一個(gè)特別常見的錯(cuò)誤如下:

while(x = y) {
//...
}

程序的意圖是測(cè)試是否“相等”(==),而不是進(jìn)行賦值操作。在C和C++中,若y是一個(gè)非零值,那么這種賦值的結(jié)果肯定是true。這樣使可能得到一個(gè)無(wú)限循環(huán)。在Java里,這個(gè)表達(dá)式的結(jié)果并不是布爾值,而編譯器期望的是一個(gè)布爾值,而且不會(huì)從一個(gè)int數(shù)值中轉(zhuǎn)換得來(lái)。所以在編譯時(shí),系統(tǒng)就會(huì)提示出現(xiàn)錯(cuò)誤,有效地阻止我們進(jìn)一步運(yùn)行程序。所以這個(gè)缺點(diǎn)在Java里永遠(yuǎn)不會(huì)造成更嚴(yán)重的后果。唯一不會(huì)得到編譯錯(cuò)誤的時(shí)候是x和y都為布爾值。在這種情況下,x = y屬于合法表達(dá)式。而在上述情況下,則可能是一個(gè)錯(cuò)誤。

在C和C++里,類似的一個(gè)問題是使用按位AND和OR,而不是邏輯AND和OR。按位AND和OR使用兩個(gè)字符之一(&或|),而邏輯AND和OR使用兩個(gè)相同的字符(&&或||)。就象“=”和“==”一樣,鍵入一個(gè)字符當(dāng)然要比鍵入兩個(gè)簡(jiǎn)單。在Java里,編譯器同樣可防止這一點(diǎn),因?yàn)樗辉试S我們強(qiáng)行使用一種并不屬于的類型。

3.1.13 造型運(yùn)算符

“造型”(Cast)的作用是“與一個(gè)模型匹配”。在適當(dāng)?shù)臅r(shí)候,Java會(huì)將一種數(shù)據(jù)類型自動(dòng)轉(zhuǎn)換成另一種。例如,假設(shè)我們?yōu)楦↑c(diǎn)變量分配一個(gè)整數(shù)值,計(jì)算機(jī)會(huì)將int自動(dòng)轉(zhuǎn)換成float。通過(guò)造型,我們可明確設(shè)置這種類型的轉(zhuǎn)換,或者在一般沒有可能進(jìn)行的時(shí)候強(qiáng)迫它進(jìn)行。

為進(jìn)行一次造型,要將括號(hào)中希望的數(shù)據(jù)類型(包括所有修改符)置于其他任何值的左側(cè)。下面是一個(gè)例子:

void casts() {
int i = 200;
long l = (long)i;
long l2 = (long)200;
}

正如您看到的那樣,既可對(duì)一個(gè)數(shù)值進(jìn)行造型處理,亦可對(duì)一個(gè)變量進(jìn)行造型處理。但在這兒展示的兩種情況下,造型均是多余的,因?yàn)榫幾g器在必要的時(shí)候會(huì)自動(dòng)進(jìn)行int值到long值的轉(zhuǎn)換。當(dāng)然,仍然可以設(shè)置一個(gè)造型,提醒自己留意,也使程序更清楚。在其他情況下,造型只有在代碼編譯時(shí)才顯出重要性。

在C和C++中,造型有時(shí)會(huì)讓人頭痛。在Java里,造型則是一種比較安全的操作。但是,若進(jìn)行一種名為“縮小轉(zhuǎn)換”(Narrowing Conversion)的操作(也就是說(shuō),腳本是能容納更多信息的數(shù)據(jù)類型,將其轉(zhuǎn)換成容量較小的類型),此時(shí)就可能面臨信息丟失的危險(xiǎn)。此時(shí),編譯器會(huì)強(qiáng)迫我們進(jìn)行造型,就好象說(shuō):“這可能是一件危險(xiǎn)的事情——如果您想讓我不顧一切地做,那么對(duì)不起,請(qǐng)明確造型?!倍鴮?duì)于“放大轉(zhuǎn)換”(Widening conversion),則不必進(jìn)行明確造型,因?yàn)樾骂愋涂隙苋菁{原來(lái)類型的信息,不會(huì)造成任何信息的丟失。

Java允許我們將任何主類型“造型”為其他任何一種主類型,但布爾值(bollean)要除外,后者根本不允許進(jìn)行任何造型處理?!邦悺辈辉试S進(jìn)行造型。為了將一種類轉(zhuǎn)換成另一種,必須采用特殊的方法(字串是一種特殊的情況,本書后面會(huì)講到將對(duì)象造型到一個(gè)類型“家族”里;例如,“橡樹”可造型為“樹”;反之亦然。但對(duì)于其他外來(lái)類型,如“巖石”,則不能造型為“樹”)。

1. 字面值

最開始的時(shí)候,若在一個(gè)程序里插入“字面值”(Literal),編譯器通常能準(zhǔn)確知道要生成什么樣的類型。但在有些時(shí)候,對(duì)于類型卻是曖昧不清的。若發(fā)生這種情況,必須對(duì)編譯器加以適當(dāng)?shù)摹爸笇?dǎo)”。方法是用與字面值關(guān)聯(lián)的字符形式加入一些額外的信息。下面這段代碼向大家展示了這些字符。

//: Literals.java

class Literals {
  char c = 0xffff; // max char hex value
  byte b = 0x7f; // max byte hex value
  short s = 0x7fff; // max short hex value
  int i1 = 0x2f; // Hexadecimal (lowercase)
  int i2 = 0X2F; // Hexadecimal (uppercase)
  int i3 = 0177; // Octal (leading zero)
  // Hex and Oct also work with long.
  long n1 = 200L; // long suffix
  long n2 = 200l; // long suffix
  long n3 = 200;
  //! long l6(200); // not allowed
  float f1 = 1;
  float f2 = 1F; // float suffix
  float f3 = 1f; // float suffix
  float f4 = 1e-45f; // 10 to the power
  float f5 = 1e+9f; // float suffix
  double d1 = 1d; // double suffix
  double d2 = 1D; // double suffix
  double d3 = 47e47d; // 10 to the power
} ///:~

十六進(jìn)制(Base 16)——它適用于所有整數(shù)數(shù)據(jù)類型——用一個(gè)前置的0x或0X指示。并在后面跟隨采用大寫或小寫形式的0-9以及a-f。若試圖將一個(gè)變量初始化成超出自身能力的一個(gè)值(無(wú)論這個(gè)值的數(shù)值形式如何),編譯器就會(huì)向我們報(bào)告一條出錯(cuò)消息。注意在上述代碼中,最大的十六進(jìn)制值只會(huì)在char,byte以及short身上出現(xiàn)。若超出這一限制,編譯器會(huì)將值自動(dòng)變成一個(gè)int,并告訴我們需要對(duì)這一次賦值進(jìn)行“縮小造型”。這樣一來(lái),我們就可清楚獲知自己已超載了邊界。

八進(jìn)制(Base 8)是用數(shù)字中的一個(gè)前置0以及0-7的數(shù)位指示的。在C,C++或者Java中,對(duì)二進(jìn)制數(shù)字沒有相應(yīng)的“字面”表示方法。

字面值后的尾隨字符標(biāo)志著它的類型。若為大寫或小寫的L,代表long;大寫或小寫的F,代表float;大寫或小寫的D,則代表double。

指數(shù)總是采用一種我們認(rèn)為很不直觀的記號(hào)方法:1.39e-47f。在科學(xué)與工程學(xué)領(lǐng)域,“e”代表自然對(duì)數(shù)的基數(shù),約等于2.718(Java一種更精確的double值采用Math.E的形式)。它在象“1.39×e的-47次方”這樣的指數(shù)表達(dá)式中使用,意味著“1.39×2.718的-47次方”。然而,自FORTRAN語(yǔ)言發(fā)明后,人們自然而然地覺得e代表“10多少次冪”。這種做法顯得頗為古怪,因?yàn)镕ORTRAN最初面向的是科學(xué)與工程設(shè)計(jì)領(lǐng)域。理所當(dāng)然,它的設(shè)計(jì)者應(yīng)對(duì)這樣的混淆概念持謹(jǐn)慎態(tài)度(注釋①)。但不管怎樣,這種特別的表達(dá)方法在C,C++以及現(xiàn)在的Java中頑固地保留下來(lái)了。所以倘若您習(xí)慣將e作為自然對(duì)數(shù)的基數(shù)使用,那么在Java中看到象“1.39e-47f”這樣的表達(dá)式時(shí),請(qǐng)轉(zhuǎn)換您的思維,從程序設(shè)計(jì)的角度思考它;它真正的含義是“1.39×10的-47次方”。

①:John Kirkham這樣寫道:“我最早于1962年在一部IBM 1620機(jī)器上使用FORTRAN II。那時(shí)——包括60年代以及70年代的早期,F(xiàn)ORTRAN一直都是使用大寫字母。之所以會(huì)出現(xiàn)這一情況,可能是由于早期的輸入設(shè)備大多是老式電傳打字機(jī),使用5位Baudot碼,那種碼并不具備小寫能力。乘冪表達(dá)式中的‘E’也肯定是大寫的,所以不會(huì)與自然對(duì)數(shù)的基數(shù)‘e’發(fā)生沖突,后者必然是小寫的?!瓻’這個(gè)字母的含義其實(shí)很簡(jiǎn)單,就是‘Exponential’的意思,即‘指數(shù)’或‘冪數(shù)’,代表計(jì)算系統(tǒng)的基數(shù)——一般都是10。當(dāng)時(shí),八進(jìn)制也在程序員中廣泛使用。盡管我自己未看到它的使用,但假若我在乘冪表達(dá)式中看到一個(gè)八進(jìn)制數(shù)字,就會(huì)把它認(rèn)作Base 8。我記得第一次看到用小寫‘e’表示指數(shù)是在70年代末期。我當(dāng)時(shí)也覺得它極易產(chǎn)生混淆。所以說(shuō),這個(gè)問題完全是自己‘潛入’FORTRAN里去的,并非一開始就有。如果你真的想使用自然對(duì)數(shù)的基數(shù),實(shí)際有現(xiàn)成的函數(shù)可供利用,但它們都是大寫的。”

注意如果編譯器能夠正確地識(shí)別類型,就不必使用尾隨字符。對(duì)于下述語(yǔ)句:

long n3 = 200;

它并不存在含混不清的地方,所以200后面的一個(gè)L大可省去。然而,對(duì)于下述語(yǔ)句:

float f4 = 1e-47f; //10的冪數(shù)

編譯器通常會(huì)將指數(shù)作為雙精度數(shù)(double)處理,所以假如沒有這個(gè)尾隨的f,就會(huì)收到一條出錯(cuò)提示,告訴我們須用一個(gè)“造型”將double轉(zhuǎn)換成float。

2. 轉(zhuǎn)型

大家會(huì)發(fā)現(xiàn)假若對(duì)主數(shù)據(jù)類型執(zhí)行任何算術(shù)或按位運(yùn)算,只要它們“比int小”(即char,byte或者short),那么在正式執(zhí)行運(yùn)算之前,那些值會(huì)自動(dòng)轉(zhuǎn)換成int。這樣一來(lái),最終生成的值就是int類型。所以只要把一個(gè)值賦回較小的類型,就必須使用“造型”。此外,由于是將值賦回給較小的類型,所以可能出現(xiàn)信息丟失的情況)。通常,表達(dá)式中最大的數(shù)據(jù)類型是決定了表達(dá)式最終結(jié)果大小的那個(gè)類型。若將一個(gè)float值與一個(gè)double值相乘,結(jié)果就是double;如將一個(gè)int和一個(gè)long值相加,則結(jié)果為long。

3.1.14 Java沒有“sizeof”

在C和C++中,sizeof()運(yùn)算符能滿足我們的一項(xiàng)特殊需要:獲知為數(shù)據(jù)項(xiàng)目分配的字符數(shù)量。在C和C++中,size()最常見的一種應(yīng)用就是“移植”。不同的數(shù)據(jù)在不同的機(jī)器上可能有不同的大小,所以在進(jìn)行一些對(duì)大小敏感的運(yùn)算時(shí),程序員必須對(duì)那些類型有多大做到心中有數(shù)。例如,一臺(tái)計(jì)算機(jī)可用32位來(lái)保存整數(shù),而另一臺(tái)只用16位保存。顯然,在第一臺(tái)機(jī)器中,程序可保存更大的值。正如您可能已經(jīng)想到的那樣,移植是令C和C++程序員頗為頭痛的一個(gè)問題。
Java不需要sizeof()運(yùn)算符來(lái)滿足這方面的需要,因?yàn)樗袛?shù)據(jù)類型在所有機(jī)器的大小都是相同的。我們不必考慮移植問題——Java本身就是一種“與平臺(tái)無(wú)關(guān)”的語(yǔ)言。

3.1.15 復(fù)習(xí)計(jì)算順序

在我舉辦的一次培訓(xùn)班中,有人抱怨運(yùn)算符的優(yōu)先順序太難記了。一名學(xué)生推薦用一句話來(lái)幫助記憶:“Ulcer Addicts Really Like C A lot”,即“潰瘍患者特別喜歡(維生素)C”。

助記詞 運(yùn)算符類型 運(yùn)算符
Ulcer Unary + - ++ – [[ rest...]]
Addicts Arithmetic (and shift) * / % + - << >>
Really Relational > < >= <= == !=
Like Logical (and bitwise) ** && & ^ **
C Conditional (ternary) A > B ? X : Y
A Lot Assignment = (and compound assignment like *=)

當(dāng)然,對(duì)于移位和按位運(yùn)算符,上表并不是完美的助記方法;但對(duì)于其他運(yùn)算來(lái)說(shuō),它確實(shí)很管用。

3.1.16 運(yùn)算符總結(jié)
下面這個(gè)例子向大家展示了如何隨同特定的運(yùn)算符使用主數(shù)據(jù)類型。從根本上說(shuō),它是同一個(gè)例子反反復(fù)復(fù)地執(zhí)行,只是使用了不同的主數(shù)據(jù)類型。文件編譯時(shí)不會(huì)報(bào)錯(cuò),因?yàn)槟切?huì)導(dǎo)致錯(cuò)誤的行已用//!變成了注釋內(nèi)容。

//: AllOps.java
// Tests all the operators on all the
// primitive data types to show which
// ones are accepted by the Java compiler.

class AllOps {
  // To accept the results of a boolean test:
  void f(boolean b) {}
  void boolTest(boolean x, boolean y) {
    // Arithmetic operators:
    //! x = x * y;
    //! x = x / y;
    //! x = x % y;
    //! x = x + y;
    //! x = x - y;
    //! x++;
    //! x--;
    //! x = +y;
    //! x = -y;
    // Relational and logical:
    //! f(x > y);
    //! f(x >= y);
    //! f(x < y);
    //! f(x <= y);
    f(x == y);
    f(x != y);
    f(!y);
    x = x && y;
    x = x || y;
    // Bitwise operators:
    //! x = ~y;
    x = x & y;
    x = x | y;
    x = x ^ y;
    //! x = x << 1;
    //! x = x >> 1;
    //! x = x >>> 1;
    // Compound assignment:
    //! x += y;
    //! x -= y;
    //! x *= y;
    //! x /= y;
    //! x %= y;
    //! x <<= 1;
    //! x >>= 1;
    //! x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! char c = (char)x;
    //! byte B = (byte)x;
    //! short s = (short)x;
    //! int i = (int)x;
    //! long l = (long)x;
    //! float f = (float)x;
    //! double d = (double)x;
  }
  void charTest(char x, char y) {
    // Arithmetic operators:
    x = (char)(x * y);
    x = (char)(x / y);
    x = (char)(x % y);
    x = (char)(x + y);
    x = (char)(x - y);
    x++;
    x--;
    x = (char)+y;
    x = (char)-y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x= (char)~y;
    x = (char)(x & y);
    x  = (char)(x | y);
    x = (char)(x ^ y);
    x = (char)(x << 1);
    x = (char)(x >> 1);
    x = (char)(x >>> 1);
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void byteTest(byte x, byte y) {
    // Arithmetic operators:
    x = (byte)(x* y);
    x = (byte)(x / y);
    x = (byte)(x % y);
    x = (byte)(x + y);
    x = (byte)(x - y);
    x++;
    x--;
    x = (byte)+ y;
    x = (byte)- y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x = (byte)~y;
    x = (byte)(x & y);
    x = (byte)(x | y);
    x = (byte)(x ^ y);
    x = (byte)(x << 1);
    x = (byte)(x >> 1);
    x = (byte)(x >>> 1);
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void shortTest(short x, short y) {
    // Arithmetic operators:
    x = (short)(x * y);
    x = (short)(x / y);
    x = (short)(x % y);
    x = (short)(x + y);
    x = (short)(x - y);
    x++;
    x--;
    x = (short)+y;
    x = (short)-y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x = (short)~y;
    x = (short)(x & y);
    x = (short)(x | y);
    x = (short)(x ^ y);
    x = (short)(x << 1);
    x = (short)(x >> 1);
    x = (short)(x >>> 1);
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void intTest(int x, int y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x = ~y;
    x = x & y;
    x = x | y;
    x = x ^ y;
    x = x << 1;
    x = x >> 1;
    x = x >>> 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void longTest(long x, long y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x = ~y;
    x = x & y;
    x = x | y;
    x = x ^ y;
    x = x << 1;
    x = x >> 1;
    x = x >>> 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    float f = (float)x;
    double d = (double)x;
  }
  void floatTest(float x, float y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    //! x = ~y;
    //! x = x & y;
    //! x = x | y;
    //! x = x ^ y;
    //! x = x << 1;
    //! x = x >> 1;
    //! x = x >>> 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    //! x <<= 1;
    //! x >>= 1;
    //! x >>>= 1;
    //! x &= y;
    //! x ^= y;
    //! x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    double d = (double)x;
  }
  void doubleTest(double x, double y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    //! x = ~y;
    //! x = x & y;
    //! x = x | y;
    //! x = x ^ y;
    //! x = x << 1;
    //! x = x >> 1;
    //! x = x >>> 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    //! x <<= 1;
    //! x >>= 1;
    //! x >>>= 1;
    //! x &= y;
    //! x ^= y;
    //! x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
  }
} ///:~

注意布爾值(boolean)的能力非常有限。我們只能為其賦予true和false值。而且可測(cè)試它為真還是為假,但不可為它們?cè)偬砑硬紶栔?,或進(jìn)行其他其他任何類型運(yùn)算。
在char,byte和short中,我們可看到算術(shù)運(yùn)算符的“轉(zhuǎn)型”效果。對(duì)這些類型的任何一個(gè)進(jìn)行算術(shù)運(yùn)算,都會(huì)獲得一個(gè)int結(jié)果。必須將其明確“造型”回原來(lái)的類型(縮小轉(zhuǎn)換會(huì)造成信息的丟失),以便將值賦回那個(gè)類型。但對(duì)于int值,卻不必進(jìn)行造型處理,因?yàn)樗袛?shù)據(jù)都已經(jīng)屬于int類型。然而,不要放松警惕,認(rèn)為一切事情都是安全的。如果對(duì)兩個(gè)足夠大的int值執(zhí)行乘法運(yùn)算,結(jié)果值就會(huì)溢出。下面這個(gè)例子向大家展示了這一點(diǎn):

//: Overflow.java
// Surprise! Java lets you overflow.

public class Overflow {
  public static void main(String[] args) {
    int big = 0x7fffffff; // max int value
    prt("big = " + big);
    int bigger = big * 4;
    prt("bigger = " + bigger);
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~

輸出結(jié)果如下:

big = 2147483647
bigger = -4

而且不會(huì)從編譯器那里收到出錯(cuò)提示,運(yùn)行時(shí)也不會(huì)出現(xiàn)異常反應(yīng)。爪哇咖啡(Java)確實(shí)是很好的東西,但卻沒有“那么”好!
對(duì)于char,byte或者short,混合賦值并不需要造型。即使它們執(zhí)行轉(zhuǎn)型操作,也會(huì)獲得與直接算術(shù)運(yùn)算相同的結(jié)果。而在另一方面,將造型略去可使代碼顯得更加簡(jiǎn)練。

大家可以看到,除boolean以外,任何一種主類型都可通過(guò)造型變?yōu)槠渌黝愋?。同樣地,?dāng)造型成一種較小的類型時(shí),必須留意“縮小轉(zhuǎn)換”的后果。否則會(huì)在造型過(guò)程中不知不覺地丟失信息。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容