Java Puzzlers(二-二 字符)
19 單行注釋
public static void main(String[] args) {
System.out.println(classify('n') + classify('+') + classify('2'));
}
static String classify(char ch) {
if ("0123456789".indexOf(ch) >= 0)
return "NUMERAL ";
if ("abcdefghijklmnopqrstuvwxyz".indexOf(ch) >= 0)
return "LETTER ";
/* (Operators not supported yet)
if ("+-*/&|!=".indexOf(ch) >= 0)
return "OPERATOR ";
*/
return "UNKNOWN ";
}
編譯出錯,塊注釋不能嵌套,在注釋內的文本都不會被特殊對待。
// Code commented out with an if statement - doesn't always work!
if (false) {
/* Add the numbers from 1 to n */
int sum = 0;
for (int i = 1; i <= n; i++)
sum += i;
}
這是語言規范推薦的一種條件編譯的技術,但不是非常適合注釋代碼。除非包含的語句都是有效的表達式,否則這種條件編譯不能用作注釋。最好的注釋代碼方法是用單行注釋。
20 反斜杠
Me.class.getName() 返回的是Me類的完整名,如"com.javapuzzlers.Me"。
System.out.println( Me.class.getName().replaceAll(".", "/") + ".class");
應該得到com/javapuzzlers/Me.class?不對。問題出在String.replaceAll把正則表達式作為第一個參數,而不是字符。正則表達是“.”表示配對任何單獨的字符,所以類名的每一個字符都被斜線替代。為了只匹配句號,必須用反斜線(\)轉義。因為反斜線在字符串中有特殊意義——它是escape sequence的開始——反斜線自身也必須用一個反斜線轉義。
正確:System.out.println( Me.class.getName().replaceAll("\\.", "/") + ".class");
為了解決這類問題,java 5提供了一個新的靜態方法java.util.regex.Pattern.quote。用一個字符串作為參數,增加任何需要的轉義,返回一個和輸入字符串完全匹配的正則表達式字符串:
System.out.println(Me.class.getName().replaceAll(Pattern.quote("."), "/") + ".class");
這個程序的另外一問題就是依賴于平臺。不是所有的文件系統都是用斜線來組織文件。為了在你運行的平臺取得正確的文件名,你必須使用正確的平臺分隔符來替換斜線。
21
System.out.println(MeToo.class.getName().
replaceAll("\\.", File.separator) + ".class");
java.io.File.separator 是一個公共的String 屬性,指定用來包含平臺依賴的文件名分隔符。在UNIX上運行打印com/javapuzzlers/MeToo.class。然而,在Windows上程序拋出異常:
StringIndexOutOfBoundsException: String index out of range: 1
結果是String.replaceAll 的第二個參數不是普通字符串而是一個在java.util.regex 規范中定義的 replacement string,反斜線轉義了后面的字符。當在Windows上運行的時候,替換字符是一個單獨的反斜線,無效。JAVA 5提供了兩個新方法來解決這個問題,一個是java.util.regex.Matcher.quoteReplacement,它替換字符串為相應的替換字符串:
System.out.println(MeToo.class.getName().replaceAll(
"\\.", Matcher.quoteReplacement(File.separator))+".class");
第二個方法提供了更好的解決方法。String.replace(CharSequence, CharSequence)和String.replaceAll做同樣的事情,但他把兩個參數都作為字符串處理:System.out.println(MeToo.class.getName().replace(".", File.separator) + ".class");
如果用的是java早期版本就沒有簡單的方法產生替換字符串。完全不用正則表達式,使用String.replace(char, char)跟容易一些:
System.out.println(MeToo.class.getName().replace('.', File.separatorChar) + ".class");
教訓:當用不熟悉的庫方法的時候,小心點。有懷疑的話,查看Javadoc。當然正則表達式也很棘手:他編譯時可能沒問題運行時卻更容易出錯。
22 statement label
認真寫注釋,及時更新。去掉無用代碼。如果有東西看起來奇怪不真實,很有可能是錯誤的。
23
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?答案它總是在打印ain。一共有三個bug導致這種情況。
一是 Random.nextInt(int) ,看規范可知這里返回的是0到int值之間的前閉后開區間的隨機數。因此程序中永遠不會返回2。這是一個相當常見的問題源,被熟知為“柵欄柱錯誤(fencepost error)”。這個名字來源于對下面這個問題最常見的但卻是錯誤的答案,如果你要建造一個100英尺長的柵欄,其柵欄柱間隔為10英尺,那么你需要多少根柵欄柱呢?11根或9根都是正確答案,這取決于是否要在柵欄的兩端樹立柵欄柱,但是10根卻是錯誤的。要當心柵欄柱錯誤,每當你在處理長度、范圍或模數的時候,都要仔細確定其端點是否應該被包括在內,并且要確保你的代碼的行為要與其相對應。
第二個bug是 case沒有配套的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構造器。
public static void main(String[] args) {
System.out.println(classify('n') + classify('+') + classify('2'));
}
static String classify(char ch) {
if ("0123456789".indexOf(ch) >= 0)
return "NUMERAL ";
if ("abcdefghijklmnopqrstuvwxyz".indexOf(ch) >= 0)
return "LETTER ";
/* (Operators not supported yet)
if ("+-*/&|!=".indexOf(ch) >= 0)
return "OPERATOR ";
*/
return "UNKNOWN ";
}
編譯出錯,塊注釋不能嵌套,在注釋內的文本都不會被特殊對待。
// Code commented out with an if statement - doesn't always work!
if (false) {
/* Add the numbers from 1 to n */
int sum = 0;
for (int i = 1; i <= n; i++)
sum += i;
}
這是語言規范推薦的一種條件編譯的技術,但不是非常適合注釋代碼。除非包含的語句都是有效的表達式,否則這種條件編譯不能用作注釋。最好的注釋代碼方法是用單行注釋。
20 反斜杠
Me.class.getName() 返回的是Me類的完整名,如"com.javapuzzlers.Me"。
System.out.println( Me.class.getName().replaceAll(".", "/") + ".class");
應該得到com/javapuzzlers/Me.class?不對。問題出在String.replaceAll把正則表達式作為第一個參數,而不是字符。正則表達是“.”表示配對任何單獨的字符,所以類名的每一個字符都被斜線替代。為了只匹配句號,必須用反斜線(\)轉義。因為反斜線在字符串中有特殊意義——它是escape sequence的開始——反斜線自身也必須用一個反斜線轉義。
正確:System.out.println( Me.class.getName().replaceAll("\\.", "/") + ".class");
為了解決這類問題,java 5提供了一個新的靜態方法java.util.regex.Pattern.quote。用一個字符串作為參數,增加任何需要的轉義,返回一個和輸入字符串完全匹配的正則表達式字符串:
System.out.println(Me.class.getName().replaceAll(Pattern.quote("."), "/") + ".class");
這個程序的另外一問題就是依賴于平臺。不是所有的文件系統都是用斜線來組織文件。為了在你運行的平臺取得正確的文件名,你必須使用正確的平臺分隔符來替換斜線。
21
System.out.println(MeToo.class.getName().
replaceAll("\\.", File.separator) + ".class");
java.io.File.separator 是一個公共的String 屬性,指定用來包含平臺依賴的文件名分隔符。在UNIX上運行打印com/javapuzzlers/MeToo.class。然而,在Windows上程序拋出異常:
StringIndexOutOfBoundsException: String index out of range: 1
結果是String.replaceAll 的第二個參數不是普通字符串而是一個在java.util.regex 規范中定義的 replacement string,反斜線轉義了后面的字符。當在Windows上運行的時候,替換字符是一個單獨的反斜線,無效。JAVA 5提供了兩個新方法來解決這個問題,一個是java.util.regex.Matcher.quoteReplacement,它替換字符串為相應的替換字符串:
System.out.println(MeToo.class.getName().replaceAll(
"\\.", Matcher.quoteReplacement(File.separator))+".class");
第二個方法提供了更好的解決方法。String.replace(CharSequence, CharSequence)和String.replaceAll做同樣的事情,但他把兩個參數都作為字符串處理:System.out.println(MeToo.class.getName().replace(".", File.separator) + ".class");
如果用的是java早期版本就沒有簡單的方法產生替換字符串。完全不用正則表達式,使用String.replace(char, char)跟容易一些:
System.out.println(MeToo.class.getName().replace('.', File.separatorChar) + ".class");
教訓:當用不熟悉的庫方法的時候,小心點。有懷疑的話,查看Javadoc。當然正則表達式也很棘手:他編譯時可能沒問題運行時卻更容易出錯。
22 statement label
認真寫注釋,及時更新。去掉無用代碼。如果有東西看起來奇怪不真實,很有可能是錯誤的。
23
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?答案它總是在打印ain。一共有三個bug導致這種情況。
一是 Random.nextInt(int) ,看規范可知這里返回的是0到int值之間的前閉后開區間的隨機數。因此程序中永遠不會返回2。這是一個相當常見的問題源,被熟知為“柵欄柱錯誤(fencepost error)”。這個名字來源于對下面這個問題最常見的但卻是錯誤的答案,如果你要建造一個100英尺長的柵欄,其柵欄柱間隔為10英尺,那么你需要多少根柵欄柱呢?11根或9根都是正確答案,這取決于是否要在柵欄的兩端樹立柵欄柱,但是10根卻是錯誤的。要當心柵欄柱錯誤,每當你在處理長度、范圍或模數的時候,都要仔細確定其端點是否應該被包括在內,并且要確保你的代碼的行為要與其相對應。
第二個bug是 case沒有配套的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構造器。
posted on 2010-10-30 13:46 yuxh 閱讀(479) 評論(0) 編輯 收藏 所屬分類: jdk