众所周知,每种基本数据类型都有一个固定的位数,比如byte占8位,short占16位,int占32位等。正因如此,当把一个低精度的数据类型转成一个高精度的数据类型时,必然会涉及到如何扩展位数的问题。这里有两种解决方案:
(1)补零扩展:填充一定位数的0。
(2)补符号位扩展:填充一定位数的符号位(非负数填充0,负数填充1)。
对于无符号类型(相当于都是非负数)与有符号类型中的非负数部分,这两种方法没有区别,都是填充0;对于有符号类型中的负数部分,这两种方法就会产生差异了,补零扩展会填充0,而补符号位扩展会填充1。下面将byte类型的-127转为int类型为例,探讨一下这两种方法的区别。
首先必须明确一些知识点:

  • 计算机是用补码来存储数字的;
  • 正数的补码等于原码;
  • 负数的补码等于反码+1;
  • 一个数的补码的补码等于原码。

127原码1111 1111,反码1000 0000,补码1000 0001。计算机存储的是1000 0001,用十六进制表示为0x81。
当使用补零扩展时,结果为:
0000 0000 0000 0000 0000 0000 1000 0001
用十六进制表示为0x81。为了计算十进制值,计算它的补码,结果为:
0000 0000 0000 0000 0000 0000 1000 0001
将这个二进制数转成十进制的结果是129。
当使用补符号位扩展时,结果为:
1111 1111 1111 1111 1111 1111 1000 0001
用十六进制表示为0xFFFFFF81。为了计算十进制值,计算它的补码,结果为:
1000 0000 0000 0000 0000 0000 0111 1111
将这个二进制数转成十进制的结果是-127。
由此可以得出结论:
(1)使用补零扩展能够保证二进制存储的一致性,但不能保证十进制值不变。
(2)使用补符号位扩展能够保证十进制值不变,但不能保证二进制存储的一致性。
下面以Java为例进行验证(Java中没有无符号类型,只有有符号类型):

public static void main(String[] args){
    byte b = -127;
    System.out.println(Integer.toBinaryString((int)b));
    System.out.println(Integer.toBinaryString((int)(b & 0xFF)));
}

输出:
11111111111111111111111110000001
10000001

由此又可以得出结论:
(1)对于有符号类型,Java使用的是补符号位扩展,这样能保证十进制的值不变;
(2)如果想要用补零扩展来确保二进制的内容不变,可以通过& 0xFF(不一定是8位,根据待转换的数据位数而定)实现。本质是将前面补上的1都通过与运算改成了0。
关于b & 0xFF为什么会导致结果改变的问题,个人认为应当是在进行这个运算之前,b已经进行了补符号位扩展,变成了32位(否则 b & 0xFF → 1000 0001 & 1111 1111 == 1000 0001 == b)。