下面這個程序看起來是在用一種特殊的方法做一件普通的事。那么,它會打印出什么呢?
public class Greeter{
public static void main(String[] args){
String greeting = "Hello World";
for(int i = 0; i < greeting.length(); i++)
System.out.write(greeting.charAt(i));
}
}
盡管這個程序有點奇怪,但是我們沒有理由懷疑它會產生不正確的行為。它將“Hello World”寫入了System.out,每次寫一個字符。你可能會意識到write方法只會使用其輸入參數(shù)的低位字節(jié)(lower-order byte)。所以當“Hello World”含有任何外來字符的時候,可能會造成一些麻煩,但這里不會:因為“Hello World”完全是由ASCII字符組成的。無論你是每次打印一個字符,還是一次全部打印,結果都應該是一樣的:這個程序應該打印Hello World。然而,如果你運行該程序,就會發(fā)現(xiàn)它不會打印任何東西。那句問候語到哪里去了?難道是程序認為它并不令人愉快?
這里的問題在于System.out是帶有緩沖的。Hello World中的字符被寫入了System.out的緩沖區(qū),但是緩沖區(qū)從來都沒有被刷新(flush)。大多數(shù)的程序員認為,當有輸出產生的時候System.out和System.err會自動地進行刷新,這并不完全正確。這2個流都屬于PrintStream類型,在5.0版[Java-API]中,有關這個類型的文檔敘述道:
一個PrintStream可以被創(chuàng)建為自動刷新的;這意味著當一個字節(jié)數(shù)組(byte array)被寫入,或者某個println方法被調用,或者一個換行字符或字節(jié)(‘\n’)被寫入之后,PrintStream類型的flush方法就會被自動地調用。
System.out和System.err所引用的流確實是PrintStream的能夠自動刷新的變體,但是上面的文檔中并沒有提及write(int)方法。有關write(int)方法的文檔敘述道:將指定的byte寫入流。如果這個byte是一個換行字符,并且流可以自動刷新,那么flush方法將被調用[Java-API]。實際上,write(int)是一個在自動刷新(automatic flushing)功能開啟的情況下不刷新PrintStream的輸出方法(output method)。
令人好奇的是,如果這個程序改用print(char)去替代write(int),它就會刷新System.out并打印出Hello World。這種行為與print(char)的文檔是矛盾的,因為其文檔敘述道[Java-API]:
打印一個字符:這個字符將根據平臺缺省的字符編碼方式被翻譯成為一個或多個字節(jié),并且這些字節(jié)將完全按照write(int)方法的方式被寫出。
類似地,如果程序改用print(String),它也會對流進行刷新,雖然文檔中是禁止這么做的。相應的文檔確實應該被修改為描述該方法的實際行為,而修改方法的行為則會破壞穩(wěn)定性。
修改這個程序最簡單的方法就是在循環(huán)之后加上一個對System.out.flush方法的調用。經過這樣的修改之后,程序就會正常地打印出Hello World。當然,更好的辦法是重寫這個程序,使用我們更熟悉的System.out.println方法在控制臺上產生輸出。
這個謎題的教訓與謎題23一樣:盡可能使用熟悉的慣用法;如果你不得不使用陌生的API,請一定要參考相關的文檔。這里有3條教訓給API的設計者們:請讓你們的方法的行為能夠清晰的反映在方法名上;請清楚而詳細地給出這些行為的文檔;請正確地實現(xiàn)這些行為。
public class Greeter{
public static void main(String[] args){
String greeting = "Hello World";
for(int i = 0; i < greeting.length(); i++)
System.out.write(greeting.charAt(i));
}
}
盡管這個程序有點奇怪,但是我們沒有理由懷疑它會產生不正確的行為。它將“Hello World”寫入了System.out,每次寫一個字符。你可能會意識到write方法只會使用其輸入參數(shù)的低位字節(jié)(lower-order byte)。所以當“Hello World”含有任何外來字符的時候,可能會造成一些麻煩,但這里不會:因為“Hello World”完全是由ASCII字符組成的。無論你是每次打印一個字符,還是一次全部打印,結果都應該是一樣的:這個程序應該打印Hello World。然而,如果你運行該程序,就會發(fā)現(xiàn)它不會打印任何東西。那句問候語到哪里去了?難道是程序認為它并不令人愉快?
這里的問題在于System.out是帶有緩沖的。Hello World中的字符被寫入了System.out的緩沖區(qū),但是緩沖區(qū)從來都沒有被刷新(flush)。大多數(shù)的程序員認為,當有輸出產生的時候System.out和System.err會自動地進行刷新,這并不完全正確。這2個流都屬于PrintStream類型,在5.0版[Java-API]中,有關這個類型的文檔敘述道:
一個PrintStream可以被創(chuàng)建為自動刷新的;這意味著當一個字節(jié)數(shù)組(byte array)被寫入,或者某個println方法被調用,或者一個換行字符或字節(jié)(‘\n’)被寫入之后,PrintStream類型的flush方法就會被自動地調用。
System.out和System.err所引用的流確實是PrintStream的能夠自動刷新的變體,但是上面的文檔中并沒有提及write(int)方法。有關write(int)方法的文檔敘述道:將指定的byte寫入流。如果這個byte是一個換行字符,并且流可以自動刷新,那么flush方法將被調用[Java-API]。實際上,write(int)是一個在自動刷新(automatic flushing)功能開啟的情況下不刷新PrintStream的輸出方法(output method)。
令人好奇的是,如果這個程序改用print(char)去替代write(int),它就會刷新System.out并打印出Hello World。這種行為與print(char)的文檔是矛盾的,因為其文檔敘述道[Java-API]:
打印一個字符:這個字符將根據平臺缺省的字符編碼方式被翻譯成為一個或多個字節(jié),并且這些字節(jié)將完全按照write(int)方法的方式被寫出。
類似地,如果程序改用print(String),它也會對流進行刷新,雖然文檔中是禁止這么做的。相應的文檔確實應該被修改為描述該方法的實際行為,而修改方法的行為則會破壞穩(wěn)定性。
修改這個程序最簡單的方法就是在循環(huán)之后加上一個對System.out.flush方法的調用。經過這樣的修改之后,程序就會正常地打印出Hello World。當然,更好的辦法是重寫這個程序,使用我們更熟悉的System.out.println方法在控制臺上產生輸出。
這個謎題的教訓與謎題23一樣:盡可能使用熟悉的慣用法;如果你不得不使用陌生的API,請一定要參考相關的文檔。這里有3條教訓給API的設計者們:請讓你們的方法的行為能夠清晰的反映在方法名上;請清楚而詳細地給出這些行為的文檔;請正確地實現(xiàn)這些行為。