下面的程序是对两个十六进制(hex)字面常量进行相加,然后打印出十六进制的结果。这个程序会打印出什么呢?


public class JoyOfHex{
	public static void main(String[] args){
		System.out.println( Long.toHexString(0x100000000L + 0xcafebabe));
	}
}



看起来很明显,该程序应该打印出1cafebabe。毕竟,这确实就是十六进制数字

10000000016 与cafebabe16 的和。该程序使用的是long 型运算,它可以支持16 位十六进制数,因此运算溢出是不可能的。


然而,如果你运行该程序,你就会发现它打印出来的是cafebabe,并没有任何前导的1。这个输出表示的是正确结果的低32 位,但是不知何故,第33 位丢失了。看起来程序好像执行的是int 型运算而不是long 型运算,或者是忘了加第一个操作数。这里到底发生了什么呢?


十进制字面常量具有一个很好的属性,即所有的十进制字面常量都是正的,而十六进制或是八进制字面常量并不具备这个属性。要想书写一个负的十进制常量,可以使用一元取反操作符(-)连接一个十进制字面常量。以这种方式,你可以用十进制来书写任何int 或long 型的数值,不管它是正的还是负的,并且负的十进制常数可以很明确地用一个减号符号来标识。但是十六进制和八进制字面常量并不是这么回事,它们可以具有正的以及负的数值。如果十六进制和八进制字面常量的最高位被置位了,那么它们就是负数。在这个程序中,数字0xcafebabe是一个int 常量,它的最高位被置位了,所以它是一个负数。它等于十进制数值-889275714。



该程序执行的这个加法是一种“混合类型的计算(mixed-type computation): 左操作数是long 类型的,而右操作数是int 类型的。为了执行该计算,Java 将 int 类型的数值用拓宽原始类型转换提升为一个long 类型,然后对两个long 类


型数值相加。因为int 是一个有符号的整数类型,所以这个转换执行的是符合扩

展:它将负的int 类型的数值提升为一个在数值上相等的long 类型数值。



这个加法的右操作数0xcafebabe 被提升为了long 类型的数值0xffffffffcafebabeL。这个数值之后被加到了左操作数0x100000000L 上。当作为int 类型来被审视时,经过符号扩展之后的右操作数的高32 位是-1,而左操作数的高32 位是1,将这两个数值相加就得到了0,这也就解释了为什么在程序输出中前导1 丢失了。下面所示是用手写的加法实现。(在加法上面的数字是进位。)


1111111
0xffffffffcafebabeL
+ 0x0000000100000000L
---------------------
0x00000000cafebabeL



订正该程序非常简单,只需用一个long 十六进制字面常量来表示右操作数即可。这就可以避免了具有破坏力的符号扩展,并且程序也就可以打印出我们所期望的结果1cafebabe:


public class JoyOfHex{
	public static void main(String[] args){
		System.out.println(
		Long.toHexString(0x100000000L + 0xcafebabeL));
	}
}



这个谜题给我们的教训是:混合类型的计算可能会产生混淆,尤其是十六进制和

八进制字面常量无需显式的减号符号就可以表示负的数值。为了避免这种窘境, 通常最好是避免混合类型的计算。对于语言的设计者们来说,应该考虑支持无符 号的整数类型,从而根除符号扩展的可能性。可能会有这样的争辩:负的十六进 制和八进制字面常量应该被禁用,但是这可能会挫伤程序员,他们经常使用十六 进制字面常量来表示那些符号没有任何重要含义的