按位操作符
按位操作符用来操作基本数据类型中的单个“比特”(bit),即二进制位。按位操作符会对两个参数中对应的位执行布尔代数运算,并最终生成一个结果。
我们常用的按位操作符有以下几种:
- &:与,如果参加运算的两个输入位都是1,则结果为1,否则生成一个输出位0
- |:或,只要有一个输入位为1,则结果为1。换言之,只有两个输入位为0,结果才为0
- ~:非,非运算符为一元运算符,只对一个操作符操作,也叫取反运算符
- ^:异或,只有参加运算的两个输入数相反时,才会输出1
我们做以下实验
public class Main { public static void main(String[] args) { int a = 16; int b = 15; String binaryStringA = Integer.toBinaryString(a); String binaryStringB = Integer.toBinaryString(b); // 填充到32位 String a32 = StringUtils.leftPad(binaryStringA, 32,"0"); String b32 = StringUtils.leftPad(binaryStringB, 32,"0"); System.out.println(a+"的二进制为:"+a32); System.out.println(b+"的二进制为:"+b32); System.out.println("a&b="+(a&b)); } }
输出如下:
16的二进制为:00000000000000000000000000010000
15的二进制为:00000000000000000000000000001111
a&b=0
**解释**:这里因为操作数都是int,所以我将其都填充到32,这样更直观,我们可以看到,转换成二进制后,16,15每个bit上的值都是不一样的,所以最终的运算结果为0 上面我们测试了两个int类型的与操作,不知道大家有没有疑惑,如果参加运算的两个输入数类型不一致会怎么样呢?比如一个为int,另外一个为long呢? 我们来测试下: ```java
结果如下:
我们可以得出结论:
当数据类型不一致时,会自动将低级的数据类型往高级转,在我们上面的例子中,很明显,a被转成了long类型。
我们也可以对编译生成的class文件进行反编译,可以看到如下的代码:
public class Main { public Main() { } public static void main(String[] args) { int a = 16; long b = 9223372036854775807L; String binaryStringA = Integer.toBinaryString(a); String binaryStringB = Long.toBinaryString(b); String a32 = StringUtils.leftPad(binaryStringA, 32, "0"); String b32 = StringUtils.leftPad(binaryStringB, 64, "0"); System.out.println(a + "的二进制为:" + a32); System.out.println(b + "的二进制为:" + b32); System.out.println("a&b=" + ((long)a & b)); } }
可以看到(long)a & b,在这里编译器自动进行了转换。
或跟异或在这里就不多赘述了,跟与操作差不多。
需要注意的是,非运算符,这是一个一元运算符,也就是说参与运算的只能有一个输入数,所以对于与,或,非,我们可以与赋值运算符“=”一起使用,但是非不行。比如我们可以写成a&=b或者a|=b,但是非是不行的。另外对于布尔类型,不能执行非操作
移位操作符
移位操作符操作的运算对象也是二进制的bit位。移位运算符只能用来处理整数类型(基本类型的一种)。左移运算符(<<)能按照操作符右侧指定的位数将操作符左边的数字向左移动(低位自动补0),对于右移这一操作,分为无符号右移(>>>)跟有符号右移(>>),所谓有符号右移是指,在进行运算时,如果原操作符的符号为正,则在高位补0,若为负,则在高位补1。而无符号右移,代表了不论正负,都会在高位补0。
如果对char,byte,short类型的数值进行移位处理,那么在进行操作之前他们会被转成int类型,并且得到的结果也是一个int类型的值。到这里不知道大家会不会有一个疑惑,如果会被转成int类型的话,我们知道int类型的长度为32位(其中一位为符号位),那么如果移位的位数超过32怎么办呢?我们以a>>>n这个表达式为例,n代表移位的位数,实际上更准确的讲,移位的位数=n%32,即n除以32取余。
测试代码如下:
public class Main { public static void main(String[] args) { // 32%32=0,所以结果还是8 System.out.println("8>>32=" + (8 >> 32)); // 33%32=1,所以相当于右移一位,其实右移一位,就是除以2,左移一位就是乘以2 // 以此类推,右移n位,相当于除以2的n次方 // 左移n位,相当于乘以2的n次方(在数字没有溢出的前提下结论才成立) // 这个结论大家可以用画图的方式自己推导下 System.out.println("8>>33=" + (8 >> 33)); } }
移位操作符与“=”赋值运算符配合使用
在这里我们讨论下<<=,>>=,>>>=这几个运算符。
主要讨论下面这种情况:我们知道对char,byte,short类型的数据进行移位运算时,会将其转换为int类型,那么在这种情况下,对一个byte类型的数据使用>>=,或者>>>=,会怎么样呢?主要就是讨论,如果运算后的结果超过了byte类型的上限怎么办呢?(这里只是以byte为例,对于其他两种类型也是一样的)
测试代码:
public class Main { public static void main(String[] args) { byte b = -1; System.out.println(Integer.toBinaryString(b)); b >>>= 10; System.out.println(Integer.toBinaryString(b)); System.out.println(Integer.toBinaryString(b>>>10)); } }
结果如下:
11111111111111111111111111111111 11111111111111111111111111111111 (0000000000)1111111111111111111111
这是因为,在运算过程中,byte类型会先转成int类型,但是当被赋值到一个byte上时,会被截断。在这种情况下就出现了这种诡异的情况(括号中的0是我手动补的)
补充知识:
在计算机中,负数以原码的补码形式表达。 什么叫补码呢?这得从原码,反码说起。 原码:一个正数,按照绝对值大小转换成的二进制数;一个负数按照绝对值大小转换成的二进制数,然后最高位补1,称为原码。 比如 00000000 00000000 00000000 00000101 是 5的 原码;10000000 00000000 00000000 00000101 是 -5的 原码。 反码:正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。 取反操作指:原为1,得0;原为0,得1。(1变0; 0变1) 比如:正数00000000 00000000 00000000 00000101 的反码还是 00000000 00000000 00000000 00000101 ; 负数10000000 00000000 00000000 00000101每一位取反(除符号位),得11111111 11111111 11111111 11111010。 称:10000000 00000000 00000000 00000101 和 11111111 11111111 11111111 11111010互为反码。 补码:正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1. 比如:10000000 00000000 00000000 00000101 的反码是:11111111 11111111 11111111 11111010。 那么,补码为: 11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011 所以,-5 在计算机中表达为:11111111 11111111 11111111 11111011