《java解惑》讀書筆記3——更多字符串之謎

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)];
    }
}
發佈了185 篇原創文章 · 獲贊 63 · 訪問量 146萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章