# 3.1 使用Java運算符
運算符以一個或多個參數為基礎,可生成一個新值。參數采用與原始方法調用不同的一種形式,但效果是相同的。根據以前寫程序的經驗,運算符的常規概念應該不難理解。
加號(`+`)、減號和負號(`-`)、乘號(`*`)、除號(`/`)以及等號(`=`)的用法與其他所有編程語言都是類似的。
所有運算符都能根據自己的運算對象生成一個值。除此以外,一個運算符可改變運算對象的值,這叫作“副作用”(Side Effect)。運算符最常見的用途就是修改自己的運算對象,從而產生副作用。但要注意生成的值亦可由沒有副作用的運算符生成。
幾乎所有運算符都只能操作“基本類型”(Primitives)。唯一的例外是`=`、`==`和`!=`,它們能操作所有對象(也是對象易令人混淆的一個地方)。除此以外,`String`類支持`+`和`+=`。
## 3.1.1 優先級
運算符的優先級決定了存在多個運算符時一個表達式各部分的計算順序。Java對計算順序作出了特別的規定。其中,最簡單的規則就是乘法和除法在加法和減法之前完成。程序員經常都會忘記其他優先級規則,所以應該用括號明確規定計算順序。例如:
```
A = X + Y - 2/2 + Z;
```
為上述表達式加上括號后,就有了一個不同的含義。
```
A = X + (Y - 2)/(2 + Z);
```
## 3.1.2 賦值
賦值是用等號運算符(`=`)進行的。它的意思是“取得右邊的值,把它復制到左邊”。右邊的值可以是任何常數、變量或者表達式,只要能產生一個值就行。但左邊的值必須是一個明確的、已命名的變量。也就是說,它必須有一個物理性的空間來保存右邊的值。舉個例子來說,可將一個常數賦給一個變量(`A=4;`),但不可將任何東西賦給一個常數(比如不能`4=A`)。
對主數據類型的賦值是非常直接的。由于基本類型容納了實際的值,而且并非指向一個對象的引用,所以在為其賦值的時候,可將來自一個地方的內容復制到另一個地方。例如,假設為基本類型使用`A=B`,那么`B`處的內容就復制到`A`。若接著又修改了`A`,那么`B`根本不會受這種修改的影響。作為一名程序員,這應成為自己的常識。
但在為對象“賦值”的時候,情況卻發生了變化。對一個對象進行操作時,我們真正操作的是它的引用。所以倘若“從一個對象到另一個對象”賦值,實際就是將引用從一個地方復制到另一個地方。這意味著假若為對象使用`C=D`,那么`C`和`D`最終都會指向最初只有`D`才指向的那個對象。下面這個例子將向大家闡示這一點。
這里有一些題外話。在后面,大家在代碼示例里看到的第一個語句將是`package 03`使用的`package`語句,它代表本書第3章。本書每一章的第一個代碼清單都會包含象這樣的一個`package`(封裝、打包、包裹)語句,它的作用是為那一章剩余的代碼建立章節編號。在第17章,大家會看到第3章的所有代碼清單(除那些有不同封裝名稱的以外)都會自動置入一個名為`c0`3的子目錄里;第4章的代碼置入`c04`;以此類推。所有這些都是通過第17章展示的`CodePackage.java`程序實現的;“封裝”的基本概念會在第5章進行詳盡的解釋。就目前來說,大家只需記住象`package 03`這樣的形式只是用于為某一章的代碼清單建立相應的子目錄。
為運行程序,必須保證在`classpath`里包含了我們安裝本書源碼文件的根目錄(那個目錄里包含了`c02`,`c03c`,`c04`等等子目錄)。
對于Java后續的版本(1.1.4和更高版本),如果您的`main()`用`package`語句封裝到一個文件里,那么必須在程序名前面指定完整的包裹名稱,否則不能運行程序。在這種情況下,命令行是:
```
java c03.Assignment
```
運行位于一個“包裹”里的程序時,隨時都要注意這方面的問題。
下面是例子:
```
//: 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`類非常簡單,它的兩個實例(`n1`和`n2`)是在`main()`里創建的。每個`Number`中的`i`值都賦予了一個不同的值。隨后,將`n2`賦給`n1`,而且`n1`發生改變。在許多程序設計語言中,我們都希望`n1`和`n2`任何時候都相互獨立。但由于我們已賦予了一個引用,所以下面才是真實的輸出:
```
1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27
```
看來改變`n1`的同時也改變了`n2`!這是由于無論`n1`還是`n2`都包含了相同的引用,它指向相同的對象(最初的引用位于`n1`內部,指向容納了值9的一個對象。在賦值過程中,那個引用實際已經丟失;它的對象會由“垃圾收集器”自動清除)。
這種特殊的現象通常也叫作“別名”,是Java操作對象的一種基本方式。但假若不愿意在這種情況下出現別名,又該怎么操作呢?可放棄賦值,并寫入下述代碼:
```
n1.i = n2.i;
```
這樣便可保留兩個獨立的對象,而不是將`n1`和`n2`綁定到相同的對象。但您很快就會意識到,這樣做會使對象內部的字段處理發生混亂,并與標準的面向對象設計準則相悖。由于這并非一個簡單的話題,所以留待第12章詳細論述,那一章是專門討論別名的。其時,大家也會注意到對象的賦值會產生一些令人震驚的效果。
**1. 方法調用中的別名處理**
將一個對象傳遞到方法內部時,也會產生別名現象。
```
//: 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);
}
} ///:~
```
在許多程序設計語言中,`f()`方法表面上似乎要在方法的作用域內制作自己的參數`Letter y`的一個副本。但同樣地,實際傳遞的是一個引用。所以下面這個程序行:
```
y.c = 'z';
````
實際改變的是`f()`之外的對象。輸出結果如下:
```
1: x.c: a
2: x.c: z
```
別名和它的對策是非常復雜的一個問題。盡管必須等至第12章才可獲得所有答案,但從現在開始就應加以重視,以便提早發現它的缺點。
**3.1.3 算術運算符**
Java的基本算術運算符與其他大多數程序設計語言是相同的。其中包括加號(`+`)、減號(`-`)、除號(`/`)、乘號(`*`)以及模數(`%`,從整數除法中獲得余數)。整數除法會直接砍掉小數,而不是進位。
Java也用一種簡寫形式進行運算,并同時進行賦值操作。這是由等號前的一個運算符標記的,而且對于語言中的所有運算符都是固定的。例如,為了將4加到變量`x`,并將結果賦給`x`,可用:`x+=4`。
下面這個例子展示了算術運算符的各種用法:
```
//: 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()`方法打印一個`String`;`pInt()`先打印一個`String`,再打印一個`int`;而`pFlt()`先打印一個`String`,再打印一個`float`。當然,它們最終都要用`System.out.println()`結尾。
為生成數字,程序首先會創建一個`Random`(隨機)對象。由于參數是在創建過程中傳遞的,所以Java將當前時間作為一個“種子值”,由隨機數生成器利用。通過`Random`對象,程序可生成許多不同類型的隨機數字。做法很簡單,只需調用不同的方法即可:`nextInt()`,`nextLong()`,`nextFloat()`或者`nextDouble()`。
若隨同隨機數生成器的結果使用,模數運算符(`%`)可將結果限制到運算對象減1的上限(本例是99)之下。
**1. 一元加、減運算符**
一元減號(`-`)和一元加號(`+`)與二元加號和減號都是相同的運算符。根據表達式的書寫形式,編譯器會自動判斷使用哪一種。例如下述語句:
```
x = -a;
```
它的含義是顯然的。編譯器能正確識別下述語句:
```
x = a * -b;
```
但讀者會被搞糊涂,所以最好更明確地寫成:
```
x = a * (-b);
```
一元減號得到的運算對象的負值。一元加號的含義與一元減號相反,雖然它實際并不做任何事情。
## 3.1.4 自動遞增和遞減
和C類似,Java提供了豐富的快捷運算方式。這些快捷運算可使代碼更清爽,更易錄入,也更易讀者辨讀。
兩種很不錯的快捷運算方式是遞增和遞減運算符(常稱作“自動遞增”和“自動遞減”運算符)。其中,遞減運算符是`--`,意為“減少一個單位”;遞增運算符是`++`,意為“增加一個單位”。舉個例子來說,假設A是一個`int`(整數)值,則表達式`++A`就等價于(`A = A + 1`)。遞增和遞減運算符結果生成的是變量的值。
對每種類型的運算符,都有兩個版本可供選用;通常將其稱為“前綴版”和“后綴版”。“前遞增”表示`++`運算符位于變量或表達式的前面;而“后遞增”表示`++`運算符位于變量或表達式的后面。類似地,“前遞減”意味著`--`運算符位于變量或表達式的前面;而“后遞減”意味著`--`運算符位于變量或表達式的后面。對于前遞增和前遞減(如`++A`或`--A`),會先執行運算,復用成值。而對于后遞增和后遞減(如`A++`或`A--`),會先生成值,再執行運算。下面是一個例子:
```
//: 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
```
從中可以看到,對于前綴形式,我們在執行完運算后才得到值。但對于后綴形式,則是在運算執行之前就得到值。它們是唯一具有“副作用”的運算符(除那些涉及賦值的以外)。也就是說,它們會改變運算對象,而不僅僅是使用自己的值。
遞增運算符正是對“C++”這個名字的一種解釋,暗示著“超載C的一步”。在早期的一次Java演講中,Bill Joy(始創人之一)聲稱“Java=C++--”(C加加減減),意味著Java已去除了C++一些沒來由折磨人的地方,形成一種更精簡的語言。正如大家會在這本書中學到的那樣,Java的許多地方都得到了簡化,所以Java的學習比C++更容易。
## 3.1.5 關系運算符
關系運算符生成的是一個“布爾”(`Boolean`)結果。它們評價的是運算對象值之間的關系。若關系是真實的,關系表達式會生成`true`(真);若關系不真實,則生成`false`(假)。關系運算符包括小于(`<`)、大于(`>`)、小于或等于(`<=`)、大于或等于(`>=`)、等于(`==`)以及不等于(`!=`)。等于和不等于適用于所有內建的數據類型,但其他比較不適用于`boolean`類型。
**1. 檢查對象是否相等**
關系運算符`==`和`!=`也適用于所有對象,但它們的含義通常會使初涉Java領域的人找不到北。下面是一個例子:
```
//: 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);
}
} ///:~
```
其中,表達式`System.out.println(n1 == n2)`可打印出內部的布爾比較結果。一般人都會認為輸出結果肯定先是`true`,再是`false`,因為兩個`Integer`對象都是相同的。但盡管對象的內容相同,引用卻是不同的,而`==`和`!=`比較的正好就是對象引用。所以輸出結果實際上先是`false`,再是`true`。這自然會使第一次接觸的人感到驚奇。
若想對比兩個對象的實際內容是否相同,又該如何操作呢?此時,必須使用所有對象都適用的特殊方法`equals()`。但這個方法不適用于“基本類型”,那些類型直接使用`==`和`!=`即可。下面舉例說明如何使用:
```
//: 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));
}
} ///:~
```
正如我們預計的那樣,此時得到的結果是`true`。但事情并未到此結束!假設您創建了自己的類,就象下面這樣:
```
//: 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));
}
} ///:~
```
此時的結果又變回了`false`!這是由于`equals()`的默認行為是比較引用。所以除非在自己的新類中改變了`equals()`,否則不可能表現出我們希望的行為。不幸的是,要到第7章才會學習如何改變行為。但要注意`equals()`的這種行為方式同時或許能夠避免一些“災難”性的事件。
大多數Java類庫都實現了`equals()`,所以它實際比較的是對象的內容,而非它們的引用。
## 3.1.6 邏輯運算符
邏輯運算符AND(`&&`)、OR(`||`)以及NOT(`!`)能生成一個布爾值(`true`或`false`)——以參數的邏輯關系為基礎。下面這個例子向大家展示了如何使用關系和邏輯運算符。
```
//: 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應用于布爾值。與在C及C++中不同,不可將一個非布爾值當作布爾值在邏輯表達式中使用。若這樣做,就會發現嘗試失敗,并用一個`//!`標出。然而,后續的表達式利用關系比較生成布爾值,然后對結果進行邏輯運算。
輸出列表看起來象下面這個樣子:
```
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
```
注意若在預計為`String`值的地方使用,布爾值會自動轉換成適當的文本形式。
在上述程序中,可將對`int`的定義替換成除`boolean`以外的其他任何主數據類型。但要注意,對浮點數字的比較是非常嚴格的。即使一個數字僅在小數部分與另一個數字存在極微小的差異,仍然認為它們是“不相等”的。即使一個數字只比零大一點點(例如2不停地開平方根),它仍然屬于“非零”值。
**1. 短路**
操作邏輯運算符時,我們會遇到一種名為“短路”的情況。這意味著只有明確得出整個表達式真或假的結論,才會對表達式進行邏輯求值。因此,一個邏輯表達式的所有部分都有可能不進行求值:
```
//: 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");
}
} ///:~
```
每次測試都會比較參數,并返回真或假。它不會顯示與準備調用什么有關的資料。測試在下面這個表達式中進行:
```
if(test1(0)) && test2(2) && test3(2))
```
很自然地,你也許認為所有這三個測試都會得以執行。但希望輸出結果不至于使你大吃一驚:
```
if(test1(0) && test2(2) && test3(2))
```
第一個測試生成一個`true`結果,所以表達式求值會繼續下去。然而,第二個測試產生了一個`false`結果。由于這意味著整個表達式肯定為`false`,所以為什么還要繼續剩余的表達式呢?這樣做只會徒勞無益。事實上,“短路”一詞的由來正種因于此。如果一個邏輯表達式的所有部分都不必執行下去,那么潛在的性能提升將是相當可觀的。
## 3.1.7 按位運算符
按位運算符允許我們操作一個整數主數據類型中的單個“比特”,即二進制位。按位運算符會對兩個參數中對應的位執行布爾代數,并最終生成一個結果。
按位運算來源于C語言的低級操作。我們經常都要直接操縱硬件,需要頻繁設置硬件寄存器內的二進制位。Java的設計初衷是嵌入電視頂置盒內,所以這種低級操作仍被保留下來了。然而,由于操作系統的進步,現在也許不必過于頻繁地進行按位運算。
若兩個輸入位都是1,則按位AND運算符(`&`)在輸出位里生成一個1;否則生成0。若兩個輸入位里至少有一個是1,則按位OR運算符(`|`)在輸出位里生成一個1;只有在兩個輸入位都是0的情況下,它才會生成一個0。若兩個輸入位的某一個是1,但不全都是1,那么按位XOR(`^`,異或)在輸出位里生成一個1。按位NOT(`~`,也叫作“非”運算符)屬于一元運算符;它只對一個參數進行操作(其他所有運算符都是二元運算符)。按位NOT生成與輸入位的相反的值——若輸入0,則輸出1;輸入1,則輸出0。
按位運算符和邏輯運算符都使用了同樣的字符,只是數量不同。因此,我們能方便地記憶各自的含義:由于“位”是非常“小”的,所以按位運算符僅使用了一個字符。
按位運算符可與等號(`=`)聯合使用,以便合并運算及賦值:`&=`,`|=`和`^=`都是合法的(由于`~`是一元運算符,所以不可與`=`聯合使用)。
我們將`boolean`(布爾)類型當作一種“單位”或“單比特”值對待,所以它多少有些獨特的地方。我們可執行按位AND,OR和XOR,但不能執行按位NOT(大概是為了避免與邏輯NOT混淆)。對于布爾值,按位運算符具有與邏輯運算符相同的效果,只是它們不會中途“短路”。此外,針對布爾值進行的按位運算為我們新增了一個XOR邏輯運算符,它并未包括在“邏輯”運算符的列表中。在移位表達式中,我們被禁止使用布爾運算,原因將在下面解釋。
## 3.1.8 移位運算符
移位運算符面向的運算對象也是二進制的“位”。可單獨用它們處理整數類型(基本類型的一種)。左移位運算符(`<<`)能將運算符左邊的運算對象向左移動運算符右側指定的位數(在低位補0)。“有符號”右移位運算符(`>>`)則將運算符左邊的運算對象向右移動運算符右側指定的位數。“有符號”右移位運算符使用了“符號擴展”:若值為正,則在高位插入0;若值為負,則在高位插入1。Java也添加了一種“無符號”右移位運算符(`>>>`),它使用了“零擴展”:無論正負,都在高位插入0。這一運算符是C或C++沒有的。
若對`char`,`byte`或者`short`進行移位處理,那么在移位進行之前,它們會自動轉換成一個`int`。只有右側的5個低位才會用到。這樣可防止我們在一個`int`數里移動不切實際的位數。若對一個`long`值進行處理,最后得到的結果也是`long`。此時只會用到右側的6個低位,防止移動超過`long`值里現成的位數。但在進行“無符號”右移位時,也可能遇到一個問題。若對`byte`或`short`值進行右移位運算,得到的可能不是正確的結果(Java 1.0和Java 1.1特別突出)。它們會自動轉換成`int`類型,并進行右移位。但“零擴展”不會發生,所以在那些情況下會得到-1的結果。可用下面這個例子檢測自己的實現方案:
```
//: 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);
}
} ///:~
```
移位可與等號(`<<=`或`>>=`或`>>>=`)組合使用。此時,運算符左邊的值會移動由右邊的值指定的位數,再將得到的結果賦回左邊的值。
下面這個例子向大家闡示了如何應用涉及“按位”操作的所有運算符,以及它們的效果:
```
//: 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();
}
} ///:~
```
程序末尾調用了兩個方法:`pBinInt()`和`pBinLong()`。它們分別操作一個`int`和`long`值,并用一種二進制格式輸出,同時附有簡要的說明文字。目前,可暫時忽略它們具體的實現方案。
大家要注意的是`System.out.print()`的使用,而不是`System.out.println()`。`print()`方法不會產生一個新行,以便在同一行里羅列多種信息。
除展示所有按位運算符針對`int`和`long`的效果之外,本例也展示了`int`和`long`的最小值、最大值、+1和-1值,使大家能體會它們的情況。注意高位代表正負號:0為正,1為負。下面列出`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
```
數字的二進制形式表現為“有符號2的補值”。
## 3.1.9 三元`if-else`運算符
這種運算符比較罕見,因為它有三個運算對象。但它確實屬于運算符的一種,因為它最終也會生成一個值。這與本章后一節要講述的普通`if-else`語句是不同的。表達式采取下述形式:
```
布爾表達式 ? 值0:值1
```
若“布爾表達式”的結果為`true`,就計算“值0”,而且它的結果成為最終由運算符產生的值。但若“布爾表達式”的結果為`false`,計算的就是“值1”,而且它的結果成為最終由運算符產生的值。
當然,也可以換用普通的`if-else`語句(在后面介紹),但三元運算符更加簡潔。盡管C引以為傲的就是它是一種簡練的語言,而且三元運算符的引入多半就是為了體現這種高效率的編程,但假若您打算頻繁用它,還是要先多作一些思量——它很容易就會產生可讀性極差的代碼。
可將條件運算符用于自己的“副作用”,或用于它生成的值。但通常都應將其用于值,因為那樣做可將運算符與`if-else`明確區別開。下面便是一個例子:
```
static int ternary(int i) {
return i < 10 ? i * 100 : i * 10;
}
```
可以看出,假設用普通的`if-else`結構寫上述代碼,代碼量會比上面多出許多。如下所示:
```
static int alternative(int i) {
if (i < 10)
return i * 100;
return i * 10;
}
```
但第二種形式更易理解,而且不要求更多的錄入。所以在挑選三元運算符時,請務必權衡一下利弊。
## 3.1.10 逗號運算符
在C和C++里,逗號不僅作為函數參數列表的分隔符使用,也作為進行后續計算的一個運算符使用。在Java里需要用到逗號的唯一場所就是`for`循環,本章稍后會對此詳加解釋。
## 3.1.11 字符串運算符`+`
這個運算符在Java里有一項特殊用途:連接不同的字符串。這一點已在前面的例子中展示過了。盡管與`+`的傳統意義不符,但用`+`來做這件事情仍然是非常自然的。在C++里,這一功能看起來非常不錯,所以引入了一項“運算符重載”機制,以便C++程序員為幾乎所有運算符增加特殊的含義。但非常不幸,與C++的另外一些限制結合,運算符重載成為一種非常復雜的特性,程序員在設計自己的類時必須對此有周到的考慮。與C++相比,盡管運算符重載在Java里更易實現,但迄今為止仍然認為這一特性過于復雜。所以Java程序員不能象C++程序員那樣設計自己的重載運算符。
我們注意到運用`String +`時一些有趣的現象。若表達式以一個`String`起頭,那么后續所有運算對象都必須是字符串。如下所示:
```
int x = 0, y = 1, z = 2;
String sString = "x, y, z ";
System.out.println(sString + x + y + z);
```
在這里,Java編譯程序會將`x`,`y`和`z`轉換成它們的字符串形式,而不是先把它們加到一起。然而,如果使用下述語句:
```
System.out.println(x + sString);
```
那么早期版本的Java就會提示出錯(以后的版本能將`x`轉換成一個字符串)。因此,如果想通過“加號”連接字符串(使用Java的早期版本),請務必保證第一個元素是字符串(或加上引號的一系列字符,編譯能將其識別成一個字符串)。
## 3.1.12 運算符常規操作規則
使用運算符的一個缺點是括號的運用經常容易搞錯。即使對一個表達式如何計算有絲毫不確定的因素,都容易混淆括號的用法。這個問題在Java里仍然存在。
在C和C++中,一個特別常見的錯誤如下:
```
while(x = y) {
//...
}
```
程序的意圖是測試是否“相等”(`==`),而不是進行賦值操作。在C和C++中,若`y`是一個非零值,那么這種賦值的結果肯定是`true`。這樣使可能得到一個無限循環。在Java里,這個表達式的結果并不是布爾值,而編譯器期望的是一個布爾值,而且不會從一個`int`數值中轉換得來。所以在編譯時,系統就會提示出現錯誤,有效地阻止我們進一步運行程序。所以這個缺點在Java里永遠不會造成更嚴重的后果。唯一不會得到編譯錯誤的時候是`x`和`y`都為布爾值。在這種情況下,`x = y`屬于合法表達式。而在上述情況下,則可能是一個錯誤。
在C和C++里,類似的一個問題是使用按位AND和OR,而不是邏輯AND和OR。按位AND和OR使用兩個字符之一(`&`或`|`),而邏輯AND和OR使用兩個相同的字符(`&&`或`||`)。就象`=`和`==`一樣,鍵入一個字符當然要比鍵入兩個簡單。在Java里,編譯器同樣可防止這一點,因為它不允許我們強行使用一種并不屬于的類型。
## 3.1.13 轉換運算符
“轉換”(Cast)的作用是“與一個模型匹配”。在適當的時候,Java會將一種數據類型自動轉換成另一種。例如,假設我們為浮點變量分配一個整數值,計算機會將`int`自動轉換成`float`。通過轉換,我們可明確設置這種類型的轉換,或者在一般沒有可能進行的時候強迫它進行。
為進行一次轉換,要將括號中希望的數據類型(包括所有修改符)置于其他任何值的左側。下面是一個例子:
```
void casts() {
int i = 200;
long l = (long)i;
long l2 = (long)200;
}
```
正如您看到的那樣,既可對一個數值進行轉換處理,亦可對一個變量進行轉換處理。但在這兒展示的兩種情況下,轉換均是多余的,因為編譯器在必要的時候會自動進行`int`值到`long`值的轉換。當然,仍然可以設置一個轉換,提醒自己留意,也使程序更清楚。在其他情況下,轉換只有在代碼編譯時才顯出重要性。
在C和C++中,轉換有時會讓人頭痛。在Java里,轉換則是一種比較安全的操作。但是,若進行一種名為“縮小轉換”(Narrowing Conversion)的操作(也就是說,腳本是能容納更多信息的數據類型,將其轉換成容量較小的類型),此時就可能面臨信息丟失的危險。此時,編譯器會強迫我們進行轉換,就好象說:“這可能是一件危險的事情——如果您想讓我不顧一切地做,那么對不起,請明確轉換。”而對于“放大轉換”(Widening conversion),則不必進行明確轉換,因為新類型肯定能容納原來類型的信息,不會造成任何信息的丟失。
Java允許我們將任何基本類型“轉換”為其他任何一種基本類型,但布爾值(`bollean`)要除外,后者根本不允許進行任何轉換處理。“類”不允許進行轉換。為了將一種類轉換成另一種,必須采用特殊的方法(字符串是一種特殊的情況,本書后面會講到將對象轉換到一個類型“家族”里;例如,“橡樹”可轉換為“樹”;反之亦然。但對于其他外來類型,如“巖石”,則不能轉換為“樹”)。
**1. 字面值**
最開始的時候,若在一個程序里插入“字面值”(Literal),編譯器通常能準確知道要生成什么樣的類型。但在有些時候,對于類型卻是曖昧不清的。若發生這種情況,必須對編譯器加以適當的“指導”。方法是用與字面值關聯的字符形式加入一些額外的信息。下面這段代碼向大家展示了這些字符。
```
//: 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
} ///:~
```
十六進制(Base 16)——它適用于所有整數數據類型——用一個前置的`0x`或`0X`指示。并在后面跟隨采用大寫或小寫形式的`0-9`以及`a-f`。若試圖將一個變量初始化成超出自身能力的一個值(無論這個值的數值形式如何),編譯器就會向我們報告一條出錯消息。注意在上述代碼中,最大的十六進制值只會在`char`,`byte`以及`short`身上出現。若超出這一限制,編譯器會將值自動變成一個`int`,并告訴我們需要對這一次賦值進行“縮小轉換”。這樣一來,我們就可清楚獲知自己已超載了邊界。
八進制(Base 8)是用數字中的一個前置`0`以及`0-7`的數位指示的。在C,C++或者Java中,對二進制數字沒有相應的“字面”表示方法。
字面值后的尾隨字符標志著它的類型。若為大寫或小寫的`L`,代表`long`;大寫或小寫的`F`,代表`float`;大寫或小寫的`D`,則代表`double`。
指數總是采用一種我們認為很不直觀的記號方法:`1.39e-47f`。在科學與工程學領域,`e`代表自然對數的基數,約等于`2.718`(Java一種更精確的`double`值采用`Math.E`的形式)。它在象“`1.39×e`的-47次方”這樣的指數表達式中使用,意味著“`1.39×2.718`的-47次方”。然而,自FORTRAN語言發明后,人們自然而然地覺得`e`代表“10多少次冪”。這種做法顯得頗為古怪,因為FORTRAN最初面向的是科學與工程設計領域。理所當然,它的設計者應對這樣的混淆概念持謹慎態度(注釋①)。但不管怎樣,這種特別的表達方法在C,C++以及現在的Java中頑固地保留下來了。所以倘若您習慣將`e`作為自然對數的基數使用,那么在Java中看到象`1.39e-47f`這樣的表達式時,請轉換您的思維,從程序設計的角度思考它;它真正的含義是“`1.39×10`的-47次方”。
①:John Kirkham這樣寫道:“我最早于1962年在一部IBM 1620機器上使用FORTRAN II。那時——包括60年代以及70年代的早期,FORTRAN一直都是使用大寫字母。之所以會出現這一情況,可能是由于早期的輸入設備大多是老式電傳打字機,使用5位Baudot碼,那種碼并不具備小寫能力。乘冪表達式中的‘E’也肯定是大寫的,所以不會與自然對數的基數`e`發生沖突,后者必然是小寫的。`E`這個字母的含義其實很簡單,就是‘Exponential’的意思,即‘指數’或‘冪數’,代表計算系統的基數——一般都是10。當時,八進制也在程序員中廣泛使用。盡管我自己未看到它的使用,但假若我在乘冪表達式中看到一個八進制數字,就會把它認作Base 8。我記得第一次看到用小寫`e`表示指數是在70年代末期。我當時也覺得它極易產生混淆。所以說,這個問題完全是自己‘潛入’FORTRAN里去的,并非一開始就有。如果你真的想使用自然對數的基數,實際有現成的函數可供利用,但它們都是大寫的。”
注意如果編譯器能夠正確地識別類型,就不必使用尾隨字符。對于下述語句:
```
long n3 = 200;
```
它并不存在含混不清的地方,所以200后面的一個`L`大可省去。然而,對于下述語句:
```
float f4 = 1e-47f; //10的冪數
```
編譯器通常會將指數作為雙精度數(`double`)處理,所以假如沒有這個尾隨的`f`,就會收到一條出錯提示,告訴我們須用一個“轉換”將`double`轉換成`float`。
**2. 轉型**
大家會發現假若對主數據類型執行任何算術或按位運算,只要它們“比`int`小”(即`char`,`byte`或者`short`),那么在正式執行運算之前,那些值會自動轉換成`int`。這樣一來,最終生成的值就是`int`類型。所以只要把一個值賦回較小的類型,就必須使用“轉換”。此外,由于是將值賦回給較小的類型,所以可能出現信息丟失的情況)。通常,表達式中最大的數據類型是決定了表達式最終結果大小的那個類型。若將一個`float`值與一個`double`值相乘,結果就是`double`;如將一個`int`和一個`long`值相加,則結果為`long`。
## 3.1.14 Java沒有`sizeof`
在C和C++中,`sizeof()`運算符能滿足我們的一項特殊需要:獲知為數據項目分配的字符數量。在C和C++中,`size()`最常見的一種應用就是“移植”。不同的數據在不同的機器上可能有不同的大小,所以在進行一些對大小敏感的運算時,程序員必須對那些類型有多大做到心中有數。例如,一臺計算機可用32位來保存整數,而另一臺只用16位保存。顯然,在第一臺機器中,程序可保存更大的值。正如您可能已經想到的那樣,移植是令C和C++程序員頗為頭痛的一個問題。
Java不需要`sizeof()`運算符來滿足這方面的需要,因為所有數據類型在所有機器的大小都是相同的。我們不必考慮移植問題——Java本身就是一種“與平臺無關”的語言。
## 3.1.15 復習計算順序
在我舉辦的一次培訓班中,有人抱怨運算符的優先順序太難記了。一名學生推薦用一句話來幫助記憶:“Ulcer Addicts Really Like C A lot”,即“潰瘍患者特別喜歡(維生素)C”。
| 助記詞 | 運算符類型 | 運算符 |
|---------|:----------------------:|------------------------------------:|
| 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 *=)` |
當然,對于移位和按位運算符,上表并不是完美的助記方法;但對于其他運算來說,它確實很管用。
## 3.1.16 運算符總結
下面這個例子向大家展示了如何隨同特定的運算符使用主數據類型。從根本上說,它是同一個例子反迭代復地執行,只是使用了不同的主數據類型。文件編譯時不會報錯,因為那些會導致錯誤的行已用`//!`變成了注釋內容。
```
//: 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`值。而且可測試它為真還是為假,但不可為它們再添加布爾值,或進行其他其他任何類型運算。
在`char`,`byte`和`short`中,我們可看到算術運算符的“轉型”效果。對這些類型的任何一個進行算術運算,都會獲得一個`int`結果。必須將其明確“轉換”回原來的類型(縮小轉換會造成信息的丟失),以便將值賦回那個類型。但對于`int`值,卻不必進行轉換處理,因為所有數據都已經屬于`int`類型。然而,不要放松警惕,認為一切事情都是安全的。如果對兩個足夠大的int值執行乘法運算,結果值就會溢出。下面這個例子向大家展示了這一點:
```
//: 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);
}
} ///:~
```
輸出結果如下:
```
big = 2147483647
bigger = -4
```
而且不會從編譯器那里收到出錯提示,運行時也不會出現異常反應。爪哇咖啡(Java)確實是很好的東西,但卻沒有“那么”好!
對于`char`,`byte`或者`short`,混合賦值并不需要轉換。即使它們執行轉型操作,也會獲得與直接算術運算相同的結果。而在另一方面,將轉換略去可使代碼顯得更加簡練。
大家可以看到,除`boolean`以外,任何一種基本類型都可通過轉換變為其他基本類型。同樣地,當轉換成一種較小的類型時,必須留意“縮小轉換”的后果。否則會在轉換過程中不知不覺地丟失信息。
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物