按位操作符
按位操作符用来操作基本数据类型中的单个“比特”(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