1.字符串替換:
問題:
下面這段程序把類全路經名中的"."替換爲"/",代碼如下:
package com.javapuzzlers;
public class Test {
public static void main(String[] args){
System.out.println(Test.class.getName().replaceAll(".", "/") + ".class");
}
}
原本期望輸出的結果是:com/javapuzzlers/test.class,但是程序運行真正的輸出是://///////////////////.class。
原因:
String.replaceAll方法接受一個正則表達式作爲它的第一個參數,而並非一個字符序列字面常量,正則表達式"."可以匹配任意單個字符,因此類名中的每一個字符都被替換成了斜槓,也就是我們意想不到的結果。
結論:
解決這個問題有兩個方法:
方法一:
在正則表達式中的句點前面添加一個反斜槓"\"進行轉義,由於反斜槓在正則表達式中表示轉義字符的開始,因此反斜槓自身也必須使用另一個反斜槓來轉義,代碼如下:
package com.javapuzzlers;
public class Test {
public static void main(String[] args){
System.out.println(Test.class.getName().replaceAll("\\.", "/") + ".class");
}
}
方法二:
JDK5之後引入了java.util.regex.Pattern.quote方法,用於接受一個字符串作爲參數,並可以添加必需的轉義字符,從而將其返回一個正則表達式字符串,該字符串將精確匹配輸入的字符串,代碼如下:
package com.javapuzzlers;
import java.util.regex.Pattern;
public class Test {
public static void main(String[] args){
System.out.println(Test.class.getName().replaceAll(Pattern.quote("."), "/") + ".class");
}
}
2.跨平臺的字符串替換:
問題:
第一個例子中我們把類名全路經中的句點替換爲Unix/Linux文件路徑斜槓,但是如果在Windows操作系統中,文件的分隔是反斜槓,因此上述程序不具有跨平臺性,在JDK的java.io.File.separator被定義爲一個公有域,用於指定操作系統平臺相關的文件路徑分隔符(Unix/Linux中是斜槓,Windows中是反斜槓),因此我們程序修改一下,使用java.io.File.separator來寫出跨平臺的程序如下:
package com.javapuzzlers;
import java.io.File;
import java.util.regex.Pattern;
public class Test {
public static void main(String[] args){
System.out.println(Test.class.getName().replaceAll(Pattern.quote("."), File.separator) + ".class");
}
}
經測試,改程序在Unix/Linux平臺上運行正常,打印出com/javapuzzlers/test.class,而在Windows平臺上我們原本期望輸出com\javapuzzlers\Test.class,但是報如下運行時異常:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 1
at java.lang.String.charAt(String.java:658)
at java.util.regex.Matcher.appendReplacement(Matcher.java:762)
at java.util.regex.Matcher.replaceAll(Matcher.java:906)
at java.lang.String.replaceAll(String.java:2162)
at com.javapuzzlers.Test.main(Test.java:9)
分析:
在Windows平臺之所以出現運行時異常,是因爲String.replaceAll方法的第二個參數不是一個普通的字符串,而是一個替代字符串,在java.util.regex規範中規定,在替代字符串中出現的反斜槓會把緊隨其後的字符進行轉義,從而導致其被按字面含義處理了。
在JDK5之後,有兩種方法解決該問題:
方法一:
使用java.util.regex.Matcher.quoteReplacement方法將字符串轉換成相應的替代字符串,代碼如下:
package com.javapuzzlers;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Test {
public static void main(String[] args){
System.out.println(Test.class.getName().replaceAll(Pattern.quote("."), Matcher.quoteReplacement(File.separator)) + ".class");
}
}
方法二:
使用String.replace方法替代String.replaceAll方法,這兩個方法功能相同,不同之處在於replace方法接受的兩個參數都當作字面含義字符串處理,而非正則表達式,代碼如下:
package com.javapuzzlers;
import java.io.File;
public class Test {
public static void main(String[] args){
System.out.println(Test.class.getName().replace(".", File.separator) + ".class");
}
}
如果使用的是JDK5之前的老版本JDK,則可以使用String.replace(char, char)方法來解決這一問題,代碼如下:
package com.javapuzzlers;
import java.io.File;
public class Test {
public static void main(String[] args){
System.out.println(Test.class.getName().replace('.', File.separatorChar) + ".class");
}
}
3.另類詭異的標號:
問題:
下面的程序能否通過編譯,輸出結果是什麼:
public class Test {
public static void main(String[] args){
System.out.print("iexplore:");
http://www.google.com
System.out.println(":maximize");
}
}
改程序可以正常通過編譯,輸出結果爲:iexplore::maximize。
原因:
咋一看在程序中添加了一句十分詭異的“http://www.google.com”讓很多人琢磨不定,該URL的前面部分“http:”被當作了java語言中內置的標號(沒有goto語句,用於標識跳轉位置的標號),後面部分被當作了單行註釋。
結論:
很多人可能在java編程中極少碰到標號,但要記得有這樣一個java語法特性,如果想要使的程序看起來更加容易理解,最好把程序格式化,將標號和註釋分開,代碼如下:
public class Test {
public static void main(String[] args) {
System.out.print("iexplore:");
http: // www.google.com
System.out.println(":maximize");
}
}
4.字符串拼接:
問題:
猜猜下面程序的打印輸出結果:
import java.util.Random;
public class Test {
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,也有可能任務switch的case穿透,因此該程序應該總打印輸出Main。
改程序的真實運行結果總是令人奇怪的ain。
原因:
之所以出現這種令人驚異的運行結果是因此改程序總共有3個bug,這3個bug碰巧湊到一塊引發令人驚異的結果。
第一個bug:選取的僞隨機數使的switch語句只能達到其三種情況中的兩種,Random.nextInt(int)的規範描述返回一個僞隨機數均勻分佈在從0(包括)到指定數值(不包括)之間的一個int數值,因此Random.nextInt(2)取值只能爲0和1,不可能爲2,因此switch的2分支永遠不可能執行,若想要達到2,則必須將僞隨機數修改爲Random.nextInt(3)。
第二個bug:switch的case語句沒有break,因此總會穿透順序執行到default語句,即總會執行word = new StringBuffer('M');覆蓋前面的程序賦值。
第三個bug:StringBuffer根本沒有StringBuffer(char)構造函數,StringBuffer只有如下三個構造函數:
(1).默認無參數構造函數:StringBuffer();
(2).指定字符串初始緩衝區內容的構造函數:StringBuffer(String);
(3).指定字符串初始緩衝區初始容量的構造函數:StringBuffer(int);
當使用word = new StringBuffer('M');時,編譯器會將字符M自動類型轉換爲int數值77,從而選擇第三個構造函數,因此改程序總打印輸出ain也就不難理解了。
結論:
有三種辦法修改上述程序:
方法一,修改程序bug:
import java.util.Random;
public class Test {
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);
}
}
方法二,精簡版:
import java.util.Random;
public class Test {
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 Test {
private static Random rnd = new Random();
public static void main(String[] args) {
String[] a = {"Main", "Pain", "Gain"};
System.out.println(randomElement(a));
}
private static String randomElement(String[] a){
return a[rnd.nextInt(a.length)];
}
}