11 字符相加
System.out.print("H" + "a");System.out.print('H' + 'a'); //貌似输出
HaHa?
最后输出的是
Ha169。
'H' 和
'a' 都是
char,不是String,+操作符做的是加法操作而不是拼接字符串。编译器提升两个char值到int值,从16位零扩展到32位的int。一个是72另一个是97。从语义上说,char值和字符串的相似是非常迷惑的。java语言仅仅把char看做无符号的16位基本整数。库不这样认为,他里面有许多方法把char参数当作Unicode字符在处理。怎样连接字符?可以使用库方法,如:
StringBuffer sb = new StringBuffer();
sb.append('H');sb.append('a');
System.out.println(sb); //可行但丑陋
有很多方法避免这种繁琐,只要一个操作符是string就能强制+操作符做拼接操作。习惯用法是在前面加一个空字符串“”,如:
System.out.print("" + 'H' + 'a');//虽然有用,但还是有点不雅而且容易导致一点困惑
试试:System.out.println("2 + 2 = " + 2+2); 如果用的是java 5还可以使用printf:System.out.printf("%c%c", 'H', 'a');
总结:小心应对字符串拼接操作符。
“+”只有至少一个是String的时候才做字符串拼接;否则做加法。如果没有String型,有几个选择:加空字符串;用String.valueOf把第一个值显示转换为String;用String buffer;java 5的话用printf 。
12 字符数组
String letters = "ABC";
char[] numbers = { '1', '2', '3' };
System.out.println(letters + " easy as " + numbers); //返回
ABC easy as [C@16f0472
char 虽是基本整数类型,但char值常常表示字符而不是整数,很多库对他特殊对待。比如,把char传给
println 输出的是Unicode 字符而不是数字码。char数组获得同样对待:
char[] 的重载是println输出数组中的所有字符,char[]对
String.valueOf 和
StringBuffer.append 的重载也类似。但是字符拼接操作符不是像这些方法,他是对两边做字符串转换然后再拼接。对于对象引用包括数组,字符串转换是这样定义的:如果应用是null,转换为字符串
"null",否则调用被引用对象的
toString 无参数方法;如果
toString 方法返回的是null,还是用“null”字符串。非null的char数组调用toString做什么操作?数组从
Object 继承来的toString,定义,“返回字符串含有对象实例类名,字符
'@',对象的用无符号十六进制表示的hash码”。
Class.getName 的说明表示对
char[] 该类对象调用该方法返回
"[C"。两个方法修正,拼接之前可显示转换数组为String:
System.out.println(letters + " easy as " + String.valueOf(numbers));
还可以把the
System.out.println 分开成两个来利用
char[] 对println的重载:
System.out.print(letters + " easy as ");System.out.println(numbers);
注意这些修正只是在你正确重载
valueOf 和
println方法才起效。换句话说,他们严重依赖编译时数组引用的类型。 下面例子看起来用来第二个修正方法,但还是输出丑陋字符串。因为他调用了Object对println的重载而不是char[]的重载。
// Broken - invokes the wrong overloading of println!
class Abc {
public static void main(String[] args) {
String letters = "ABC";
Object numbers = new char[] { '1', '2', '3' };
System.out.print(letters + " easy as ");
System.out.println(numbers);
// Invokes println(Object)
}
}
总结:char数组不是字符串。
把char数组转换为字符串,调用 String.valueOf(char[])。一些库方法给char数组提供了像字符串的支持,典型的是给Object提供一个重载再给char[]提供一个重载;后一个才是理想的操作。
13 Interning
final String pig = "length: 10";
final String dog = "length: " + pig.length();
System.out.println("Animals are equal: " + pig == dog);
compile-time constants of type
String are
interned.换句话说,任何两个有相同字符的String类型的常量表达式是同一个对象引用表示的。所以如果用常量表达式初始化的话,pig和dog会指向同一个对象,但dog没用常量表达式。Java语言限制哪些操作可以出现在常量表达式中,方法调用是不允许的。因此,程序应该输出 Animals are equal: false,对吧?事实上不是,运行发现只输出false 。操作符的优先级体现出来,事实上是
System.out.println(("Animals are equal: " + pig) == dog);
有一种方法能避免这种困难:但使用字符拼接操作符的时候,总是给重要操作数加上括号:System.out.println("Animals are equal: " + (pig == dog));
辩证的说,这样还是有问题。Your code should rarely, if ever, depend on the interning of string constants。Interning 只是用来减少虚拟机内存的,不是用来当作程序员工具的。由于字符串intern 失败带来的bug非常难以检测。对比两个对象引用的时候,应该用equals方法而不是==操作符除非你是想比较对象identity而不是值。所以我们的例子应该这样:System.out.println("Animals are equal: " + pig.equals(dog));
14 转义符
下面程序使用
Unicode escapes:用他们的十六进制数字码表示Unicode 字符。
// \u0022 is the Unicode escape for double quote (")
System.out.println("a\u0022.length() + \u0022b".length());
Java provides no special treatment for Unicode escapes within string literals。编译器在把Unicode escapes程序解析为字符串之前,先变为了他们表示的字符。可以使用
escape sequences:即用\"表示双引号 ,例System.out.println("a\".length() + \"b".length());
还有很多escape sequences : single quote (\'), linefeed (\n), tab (\t), and backslash (\\). escape sequences 等程序先解析为符号之后才处理。ASCII是Unicode的子集。ASCII是最小的字符集,只有128个字符,Unicode有 65,000的字符。Unicode escape 能被用来把Unicode 字符插入只能使用ASCII字符的程序中。一个 Unicode escape意味着和他代表的字符完全相同的东西。但程序员用源文件的字符集不能插入一些字符的时候,可以使用 Unicode escape,主要是把非ASCII字符变为标志符,字符串,注释等。
总结:在字符串和字符文字中,用escape sequences不用Unicode escapes 。不要用Unicode escapes 表示ASCII字符。在字符串和字符文字中,用escape sequences;在外面的话直接把ASCII 字符插入源文件。
15
Unicode escapes must be well formed, even if they appear in comments. 下面这个例子编译出错
/**
* Generated by the IBM IDL-to-Java compiler, version 1.0
* from F:\TestRoot\apps\a1\units\include\PolicyHome.idl
* Wednesday, June 17, 1998 6:44:40 o'clock AM GMT+00:00
*/
工具在把Windows 文件名放入注释之前,必须把\去掉。
总之,\u不要出现在有效Unicode escape范围之外,即使注释也不行。特别是自动产生代码的时候。
16
line separator 用来表示分割文本行的字符,每个平台的line separator 不一样。Windows 上,CR character (carriage return) followed by the LF character (linefeed)。UNIX上只有LF字符(经常被较为newline character)。下面把这些字符传给println
// Note: \u000A is Unicode representation of linefeed (LF)
char c = 0x000A;
System.out.println(c);
结果编译错误!仍然是由于注释中Unicode escape,编译器再抛弃注释内容和空格之前就把Unicode escapes 转换为字符。\u000A 代表linefeed character,因此程序最后转换为
// Note:
is Unicode representation of linefeed (LF)
char c = 0x000A;
System.out.println(c);
最简单的修改方法是去掉 Unicode escape ,但更好的方法是用escape sequence 初始化c,而不是用十六进制整数,排除注释的需要
char c = '\n';
System.out.println(c);
这样改后程序还是有问题,有平台依赖。在某些平台,如UNIX,他将输出两行完整的分割符;另外一些平台,如Windows,则不会。虽然肉眼看起来一样,但如果存在一个文件或管道里供其他程序处理话 是很容易出问题的。 如果打算输出两行空白,应该调用println两次。Java 5,你可以使用printf带上格式"%n%n"来代替println,每一个出现的 %n是printf打印出平台相应的行分隔符。
道理:非必需尽量不用Unicode escapes
17
Unicode escapes are essential when you need to insert characters that can't be represented in any other way into your program. Avoid them in all other cases.Unicode escapes 减少程序的清晰性,增加bug的出现。对语言设计者来说,应该使Unicode escapes表示ASCII字符非法。
18 字符集
byte bytes[] = new byte[256];
for(int i = 0; i < 256; i++)
bytes[i] = (byte)i;
String str = new String(bytes);
for(int i = 0, n = str.length(); i < n; i++)
System.out.print((int)str.charAt(i) + " ");
在不同的机器上运行,结果完全不一样。原因是String(byte[])构造器。规范说“用平台默认的字符集给指定的byte数组解码创建一个新字符串。新字符串的长度是字符集的功能,因此可能和byte数组的长度不一样。当给定的字节在默认字符集中无效时,该构造器行为不确定”。什么是字符集?技术上说,他是“the combination of a coded character set and a character-encoding scheme”。换句话说,是一堆字符,表达字符的数字编码,以及一序列字符编码和字节互相转换的方法。转换方案在不同的字符集中差异巨大。一些字符和字节一一对应;大多数不这样。只有默认字符集是 ISO-8859-1(更多被称为Latin-1 )的时候上面的程序才会输出0到255的整数。J2SE运行环境的默认编码是由底层操作系统和区域决定的。在早期的JAVA版本读取系统属性 "file.encoding"来得到JRE默认编码,JAVA 5后以后的版本,可使用 java.nio.charset.Charset.defaultCharset()方法。幸运的是,你不是非得要面对默认字符集的变化多端。char序列和byte序列互相转换的时候,你可以并且大多数时候应当显式的指定字符集。一个以字符集名字和byte数组为参数的String构造器可完成此任务。用下面方法,上面程序就不会受默认字符集的影响了:String str = new String(bytes, "ISO-8859-1"); 该构造器抛出UnsupportedEncodingException,你必须捕获,当更好的方法是声明一个main方法来抛出他否则不能编译通过。事实上,上面的程序不会抛出异常。因为Charset 的规范指明任何JAVA平台的实现必须支持某些字符集,ISO-8859-1是其中之一。
教训:每次从byte序列转换为String,不管显示或隐式都在使用一种字符集。如果想程序不出意外,每次使用时显示指定一种字符集。