Java更多的库谜题81:烧焦到无法识别
下面这个程序看起来是在用一种特殊的方式做一件普通的事情。那么,它会打印出什么呢?
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方法只使用其输入参数的低位字节。所以当“Hello World”包含任何外来字符时,可能会造成一些麻烦,但在这里不会:因为“Hello World”完全是由ASCII字符组成的。无论是一次打印一个字符还是一次打印所有字符,结果都应该是一样的:这个程序应该打印Hello World。但是,如果你运行程序,你会发现它不会打印任何东西。那句问候呢?是节目觉得不好听吗?
这里的问题是System.out是缓冲的。Hello World中的字符被写入System.out的缓冲区,但缓冲区从不刷新。大多数程序员认为System.out和System.err会在生成输出时自动刷新,这并不完全正确。两个流都属于PrintStream类型。在5.0版[Java-API]中,关于这种类型的文档说明:
可以创建一个PrintStream自动刷新;这意味着当写入字节数组,或者调用println方法,或者写入换行符或字节(' \ n ')时,将自动调用PrintStream类型的flush方法。
引用的流
System.out和System.err确实是PrintStream的变种,可以自动刷新,但是上面的文档中没有提到write(int)方法。关于write(int)方法的文档说明了指定的字节被写入流中。如果这个字节是换行符,并且流可以自动刷新,那么flush方法将被调用[Java-API]。实际上,write(int)是一种在自动刷新功能打开时不刷新PrintStream的输出方法。
奇怪的是,如果这个程序使用print(char)而不是write(int),它会刷新System.out并打印出Hello World。这种行为与print(char)的文档相矛盾,因为它的文档声明[Java-API]:
Print一个字符:这个字符会按照平台默认的字符编码方式被翻译成一个或多个字节,这些字节会以write(int)方法的方式准确地写出。
同样,如果程序使用print(String)代替,它也会刷新流,尽管这在文档中是禁止的。相应的文档确实应该修改来描述方法的实际行为,但是修改方法的行为会破坏稳定性。
修改此程序最简单的方法是在循环后添加对System.out.flush方法的调用。修改后,程序通常会打印出Hello World。当然,更好的方法是重写这个程序,使用更熟悉的System.out.println方法在控制台上生成输出。
这个谜题的教训和谜题23一样:尽可能使用熟悉的成语;如果您必须使用不熟悉的API,请务必参考相关文档。这里给API设计者三个教训:请让你的方法的行为清晰地反映在方法名中;请清楚详细地记录这些行为;请正确实施这些行为。
0条评论