行分隔符(line separator)是為用來(lái)分隔文本行的字符或字符組合而起的名字,并且它在不同的平臺(tái)上是存在差異的。在Windows平臺(tái)上,它是CR字符(回車(chē))和緊隨其后的LF字符(換行)組成的,而在UNIX平臺(tái)上,通常單獨(dú)的LF字符被當(dāng)作換行字符來(lái)引用。下面的程序?qū)⑦@個(gè)字符傳遞給了println方法,那么,它將打印出什么呢?它的行為是否是依賴(lài)于平臺(tái)的呢?
public class LinePrinter{
public static void main(String[] args){
// Note: \u000A is Unicode representation of linefeed (LF)
char c = 0x000A;
System.out.println(c);
}
}
這個(gè)程序的行為是平臺(tái)無(wú)關(guān)的:它在任何平臺(tái)上都不能通過(guò)編譯。如果你嘗試著去編譯它,就會(huì)得到類(lèi)似下面的出錯(cuò)信息:
LinePrinter.java:3: ’;’ expected
// Note: \u000A is Unicode representation of linefeed (LF)
^
1 error
如果你和大多數(shù)人一樣,那么這條信息對(duì)界定問(wèn)題是毫無(wú)用處的。
這個(gè)謎題的關(guān)鍵就是程序第三行的注釋。與的注釋一樣,這條注釋也是一種準(zhǔn)確的表達(dá),遺憾的是,它有一點(diǎn)準(zhǔn)確得過(guò)頭了。編譯器不僅會(huì)在將程序解析成為符號(hào)之前把Unicode轉(zhuǎn)義字符轉(zhuǎn)換成它們所表示的字符(謎題14),而且它是在丟棄注釋和空格之前做這些事的[JLS 3.2]。
這個(gè)程序包含了一個(gè)Unicode轉(zhuǎn)移字符(\u000A),它位于程序的注釋行中。就像注釋所陳述的,這個(gè)轉(zhuǎn)義字符表示換行符,編譯器將在丟棄注釋之前適時(shí)地轉(zhuǎn)換它。遺憾的是,這個(gè)換行符是表示注釋開(kāi)始的兩個(gè)斜杠符之后的第一個(gè)行終結(jié)符(line terminator),因此它將終結(jié)該注釋[JLS 3.4]。所以,該轉(zhuǎn)義字符之后的字(is Unicode representation of linefeed (LF))就不是注釋的一部分了,而它們?cè)谡Z(yǔ)法上也不是有效的。
訂正該程序的最簡(jiǎn)單的方式就是在注釋中移除Unicode轉(zhuǎn)義字符,但是更好的方式是用一個(gè)轉(zhuǎn)義字符序列而不是一個(gè)十六進(jìn)制整型字面常量來(lái)初始化c,從而消除使用注釋的必要:
public class LinePrinter{
public static void main(String[] args){
char c = ’\n’;
System.out.println(c);
}
}
只要這么做了,程序就可以編譯并運(yùn)行,但是這仍然是一個(gè)有問(wèn)題的程序:它是平臺(tái)相關(guān)的,這正是本謎題所要表達(dá)的真正意圖。在某些平臺(tái)上,例如UNIX,它將打印出兩個(gè)完整的行分隔符;但是在其它一些平臺(tái)上,例如Windows,它就不會(huì)產(chǎn)生這樣的行為。盡管這些輸出用肉眼看起來(lái)是一樣的,但是如果它們要被存儲(chǔ)到文件中,或是輸出到后續(xù)的其它處理程序中,那就很容易引發(fā)問(wèn)題。
如果你想打印兩行空行,你應(yīng)該調(diào)用println兩次。如果使用的是JDK 5.0,那么你可以用帶有格式化字符串"%n%n"的printf來(lái)代替println。%n的每一次出現(xiàn)都將導(dǎo)致printf打印一個(gè)恰當(dāng)?shù)?、與平臺(tái)相關(guān)的行分隔符。
我們希望,上面三個(gè)謎題已經(jīng)使你信服:Unicode轉(zhuǎn)義字符絕對(duì)會(huì)產(chǎn)生混亂。教訓(xùn)很簡(jiǎn)單:除非確實(shí)是必需的,否則就不要使用Unicode轉(zhuǎn)義字符。它們很少是必需的。
public class LinePrinter{
public static void main(String[] args){
// Note: \u000A is Unicode representation of linefeed (LF)
char c = 0x000A;
System.out.println(c);
}
}
這個(gè)程序的行為是平臺(tái)無(wú)關(guān)的:它在任何平臺(tái)上都不能通過(guò)編譯。如果你嘗試著去編譯它,就會(huì)得到類(lèi)似下面的出錯(cuò)信息:
LinePrinter.java:3: ’;’ expected
// Note: \u000A is Unicode representation of linefeed (LF)
^
1 error
如果你和大多數(shù)人一樣,那么這條信息對(duì)界定問(wèn)題是毫無(wú)用處的。
這個(gè)謎題的關(guān)鍵就是程序第三行的注釋。與的注釋一樣,這條注釋也是一種準(zhǔn)確的表達(dá),遺憾的是,它有一點(diǎn)準(zhǔn)確得過(guò)頭了。編譯器不僅會(huì)在將程序解析成為符號(hào)之前把Unicode轉(zhuǎn)義字符轉(zhuǎn)換成它們所表示的字符(謎題14),而且它是在丟棄注釋和空格之前做這些事的[JLS 3.2]。
這個(gè)程序包含了一個(gè)Unicode轉(zhuǎn)移字符(\u000A),它位于程序的注釋行中。就像注釋所陳述的,這個(gè)轉(zhuǎn)義字符表示換行符,編譯器將在丟棄注釋之前適時(shí)地轉(zhuǎn)換它。遺憾的是,這個(gè)換行符是表示注釋開(kāi)始的兩個(gè)斜杠符之后的第一個(gè)行終結(jié)符(line terminator),因此它將終結(jié)該注釋[JLS 3.4]。所以,該轉(zhuǎn)義字符之后的字(is Unicode representation of linefeed (LF))就不是注釋的一部分了,而它們?cè)谡Z(yǔ)法上也不是有效的。
訂正該程序的最簡(jiǎn)單的方式就是在注釋中移除Unicode轉(zhuǎn)義字符,但是更好的方式是用一個(gè)轉(zhuǎn)義字符序列而不是一個(gè)十六進(jìn)制整型字面常量來(lái)初始化c,從而消除使用注釋的必要:
public class LinePrinter{
public static void main(String[] args){
char c = ’\n’;
System.out.println(c);
}
}
只要這么做了,程序就可以編譯并運(yùn)行,但是這仍然是一個(gè)有問(wèn)題的程序:它是平臺(tái)相關(guān)的,這正是本謎題所要表達(dá)的真正意圖。在某些平臺(tái)上,例如UNIX,它將打印出兩個(gè)完整的行分隔符;但是在其它一些平臺(tái)上,例如Windows,它就不會(huì)產(chǎn)生這樣的行為。盡管這些輸出用肉眼看起來(lái)是一樣的,但是如果它們要被存儲(chǔ)到文件中,或是輸出到后續(xù)的其它處理程序中,那就很容易引發(fā)問(wèn)題。
如果你想打印兩行空行,你應(yīng)該調(diào)用println兩次。如果使用的是JDK 5.0,那么你可以用帶有格式化字符串"%n%n"的printf來(lái)代替println。%n的每一次出現(xiàn)都將導(dǎo)致printf打印一個(gè)恰當(dāng)?shù)?、與平臺(tái)相關(guān)的行分隔符。
我們希望,上面三個(gè)謎題已經(jīng)使你信服:Unicode轉(zhuǎn)義字符絕對(duì)會(huì)產(chǎn)生混亂。教訓(xùn)很簡(jiǎn)單:除非確實(shí)是必需的,否則就不要使用Unicode轉(zhuǎn)義字符。它們很少是必需的。