如下代码:
public class Example006 { public static void main(String[] args) { System.out.println("(byte) -1 = " + (byte) -1); // System.out.println("(char) -1 = " + (char) -1); // System.out.println("(char)((byte) -1) = " + (char) ((byte) -1)); System.out.println("(int)((char)((byte) -1)) = " + (int) ((char) ((byte) -1))); } }
输出结果:
(byte) -1 = -1 (int)((char)((byte) -1)) = 65535
原因分析:
该程序的行为紧密依赖于转型的符号扩展行为。Java 使用了基于 2 的补码的二进制运算,因此 int 类型的数值-1 的所有 32 位都是置位的(补码:正数同原码,负数符号位不变,其他位取反后加以,所以-1的所有32位都为1)。从 int 到 byte 的转型是很简单的,它执行了一个窄化原始类型转化 直接将除低 8 位之外的所有位全部砍掉。这样做留下的是一个8位都被置位了的 byte,它仍旧表示-1(8个1)。
从 byte 到 char 的转型稍微麻烦一点,因为 byte 是一个有符号类型,而 char是一个无符号类型。在将一个整数类型转换成另一个宽度更宽的整数类型时, 通常是可以保持其数值的,但是却不可能将一个负的 byte 数值表示成一个 char。因此, 从 byte 到 char 的转换被认为不是一个拓宽原始类型转换,而是一个拓宽并窄化原始类型的转换:byte 被转换成了 int,而这个 int 又被转换成了 char。
有一条很简单的规则能够描述从较窄的整型转换成较宽的整型时的符号扩展行为:如果最初的数值类型是有符号的,那么就执行符号扩展;如果它是 char,那么不管它将要被转换成什么类型,都执行
零扩展。
因为 byte 是一个有符号的类型,所以在将 byte 数值-1 转换成char 时,会发生符号扩展。作为结果的 char 数值的16 个位就都被置位了,因此它等于 2^16-1,即 65535。从 char 到 int 的转型也是一个拓宽原始类型转换,所以这条规则告诉我们,它将执行零扩展而不是符号扩展。作为结果的 int 数值也就成了65535,这正是程序打印出的结果。
如果你在将一个 char 数值 c 转型为一个宽度更宽的类型,并且你不希望有符号扩展,那么为清晰表达意图,可以考虑使用一个位掩码,即使它并不是必需的:
int i = c & 0xffff;
或者, 书写一句注释来描述转换的行为:
int i = c; //不会执行符号扩展
如果你在将一个 char 数值 c 转型为一个宽度更宽的整型,并且你希望有符号扩展,那么就先将 char 转型为一个 short,它与 char 具有同样的宽度,但是它是有符号的。在给出了这种细微的代码之后,你应该也为它书写一句注释:
int i = (short) c; //转型将引起符号扩展
如果你在将一个 byte 数值b 转型为一个 char,并且你不希望有符号扩展,那么你必须使用一个位掩码来限制它。这是一种通用做法,所以不需要任何注释:
char c = (char) (b & 0xff);
更多参考资料:
1、关于Java符号扩展问题的解释:http://bbs.csdn.net/topics/310238373
2、补码至少介绍:http://baike.so.com/doc/4026477.html