看switch case 語句

下面的程序將打印一個單詞,其第一個字母是由一個隨機數生成器來選擇的。請
描述該程序的行爲:
import java.util.Random;
public class Rhymes {
private static Random rnd = new Random();
public static void main(String[] args) {
StringBuffer word = null;
switch(rnd.nextInt(2)) {
case 1: word = new StringBuffer('P');
case 2: word = new StringBuffer('G');
default: word = new StringBuffer('M');
}
word.append('a');
word.append('i');
word.append('n');
System.out.println(word);
}
}
乍一看,這個程序可能會在一次又一次的運行中,以相等的概率打印出Pain,Gain 或 Main。看起來該程序會根據隨機數生成器所選取的值來選擇單詞的第一
個字母:0 選M,1 選P,2 選G。謎題的題目也許已經給你提供了線索,它實際
上既不會打印Pain,也不會打印Gain。也許更令人喫驚的是,它也不會打印Main,
並且它的行爲不會在一次又一次的運行中發生變化,它總是在打印ain。
有三個bug 湊到一起引發了這種行爲。你完全沒有發現它們嗎?第一個bug 是所
選取的隨機數使得switch 語句只能到達其三種情況中的兩種。

Random.nextInt(int)的規範描述道:“返回一個僞隨機的、均等地分佈在從0
(包括)到指定的數值(不包括)之間的一個int 數值”[Java-API]。這意味着
表達式rnd.nextInt(2)可能的取值只有0和1,Switch語句將永遠也到不了case
2 分支,這表示程序將永遠不會打印Gain。nextInt 的參數應該是3 而不是2。

第二個bug 是在不同的情況(case)中沒有任何break 語句。不論switch 表達
式爲何值,該程序都將執行其相對應的case 以及所有後續的case[JLS 14.11]。
因此,儘管每一個case 都對變量word 賦了一個值,但是總是最後一個賦值勝出,
覆蓋了前面的賦值。最後一個賦值將總是最後一種情況(default),即new
StringBuffer{'M'}。這表明該程序將總是打印Main,而從來不打印Pain或Gain。

在switch的各種情況中缺少break語句是非常常見的錯誤。從5.0版本起,javac
提供了-Xlint:fallthrough 標誌,當你忘記在一個case 與下一個case 之間添
加break 語句是,它可以生成警告信息。不要從一個非空的case 向下進入了另
一個case。這是一種拙劣的風格,因爲它並不常用,因此會誤導讀者。十次中
有九次它都會包含錯誤。如果Java 不是模仿C 建模的,那麼它倒是有可能不需
要break。對語言設計者的教訓是:應該考慮提供一個結構化的switch 語句。

最後一個,也是最微妙的一個bug 是表達式new StringBuffer('M')可能沒有做
哪些你希望它做的事情。你可能對StringBuffer(char)構造器並不熟悉,這很
容易解釋:它壓根就不存在。StringBuffer 有一個無參數的構造器,一個接受
一個String 作爲字符串緩衝區初始內容的構造器,以及一個接受一個int 作爲
緩衝區初始容量的構造器。在本例中,編譯器會選擇接受int 的構造器,通過拓
寬原始類型轉換把字符數值'M'轉換爲一個int 數值77[JLS 5.1.2]。換句話說,
new StringBuffer('M')返回的是一個具有初始容量77 的空的字符串緩衝區。該
程序餘下的部分將字符a、i 和n 添加到了這個空字符串緩衝區中,並打印出該
字符串緩衝區那總是ain 的內容。

爲了避免這類問題,不管在什麼時候,都要儘可能使用熟悉的慣用法和API。如
果你必須使用不熟悉的API,那麼請仔細閱讀其文檔。在本例中,程序應該使用
常用的接受一個String 的StringBuffer 構造器。
下面是該程序訂正了這三個bug 之後的正確版本,它將以均等的概率打印Pain、
Gain 和Main:
import java.util.Random;
public class Rhymes1 {

private static Random rnd = new Random();
public static void main(String[] args) {
StringBuffer word = null;
switch(rnd.nextInt(3)) {
case 1:
word = new StringBuffer("P");
break;
case 2:
word = new StringBuffer("G");
break;
default:
word = new StringBuffer("M");
break;
}
word.append('a');
word.append('i');
word.append('n');
System.out.println(word);
}
}
儘管這個程序訂正了所有的bug,它還是顯得過於冗長了。下面是一個更優雅的

版本:
import java.util.Random;
public class Rhymes2 {
private static Random rnd = new Random();
public static void main(String[] args) {
System.out.println("PGM".charAt(rnd.nextInt(3)) + "ain");
}
}
下面是一個更好的版本。儘管它稍微長了一點,但是它更加通用。它不依賴於所
有可能的輸出只是在它們的第一個字符上有所不同的這個事實:
import java.util.Random;
public class Rhymes3 {
public static void main(String[] args) {
String a[] = {"Main","Pain","Gain"};
System.out.println(randomElement(a));
}
private static Random rnd = new Random();
private static String randomElement(String[] a){
return a[rnd.nextInt(a.length)];
}
}
總結一下:首先,要當心柵欄柱錯誤。其次,牢記在 switch 語句的每一個 case
中都放置一條 break 語句。第三,要使用常用的慣用法和 API,並且當你在離開老路子的時候,一定要參考相關的文檔。第四,一個 char 不是一個 String,
而是更像一個 int。最後,要提防各種詭異的謎題。

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