3.1.4 按位逻辑与短路逻辑
前面提到逻辑运算只能操作布尔变量,这其实是不严谨的,因为经过Java编程实践,会发现“&”“|”“^”这3个逻辑运算符竟然可以对数字做运算。譬如下面的代码就直接对数字分别开展了“与”“或”“异或”运算(完整代码见本章源码的src\com\control\logic\ShortCircuit.java):
// 3的二进制为00000011,7的二进制为00000111 int andNumber=3&7; // 对两个数字开展“按位与”运算 System.out.println("andNumber="+andNumber); int orNumber=3|7; // 对两个数字开展“按位或”运算 System.out.println("orNumber="+orNumber); int xorNumber=3^7; // 对两个数字开展“按位异或”运算 System.out.println("xorNumber="+xorNumber);
上述代码也能成功编译运行,运行结果如下:
andNumber=3 orNumber=7 xorNumber=4
究其原因,原来这3个逻辑运算符属于按位逻辑运算符。所谓按位逻辑,指的是先将操作数转换成二进制,再对二进制的操作数逐位开展逻辑运算,最后把每位的逻辑结果重新拼成一个二进制的运算值。例如,数字3的二进制表达为00000011,数字7的二进制表达为00000111,那么对这两个二进制数开展“按位与”运算,得到的二进制结果为00000011,即数字3;对这两个二进制数开展“按位或”运算,得到的二进制结果为00000111,即数字7;对这两个二进制数开展“按位异或”运算,得到的二进制结果为00000100,即数字4。
以上的实验结果说明逻辑与、逻辑或、逻辑异或符号实质上是按位逻辑运算符,之前的布尔变量只是按位逻辑的一种运算类型。既然按位逻辑比较的是左右两边的各个二进制位,就意味着必须先确定左右两边的操作数值,然后才能对两个操作数进行按位运算。这么看来,按位逻辑并非真正意义上的逻辑操作,正常情况的逻辑运算应当具备下列特征:
(1)只判断真和假,不判断0和1,更不是逐位判断。
(2)对于“与”运算,一旦左边的操作数为假,则无论右边的操作数是真还是假,运算结果一定为假。
(3)对于“或”运算,一旦左边的操作数为真,则无论右边的操作数是真还是假,运算结果一定为真。
对比发现,按位逻辑不但不符合上述的第一点特征,也不符合第二点和第三点特征,因为按位逻辑需要左右两边都确定之后,才能开展逻辑运算。显然这样做并不经济,倘若左边的操作数就能确定运算结果,那又何苦画蛇添足进行右边的计算呢?为解决按位逻辑存在的问题,Java引入了新的短路符号来帮忙,包括短路与符号“&&”和短路或符号“||”。短路的意思是:如果左边已经接通,那就不绕道右边了。
话虽如此,但如何证明短路逻辑确实高效呢?下面通过一个例子来分辨按位逻辑和短路逻辑之间的区别,关键在于逻辑符号的右边需要修改变量值,为此引入了自增符号“++”。具体的逻辑运算代码如下,主要是比较逻辑与“&”和短路与“&&”的运算结果:
int i=1, j=1; // 对于按位逻辑运算,需要等待左右两边都计算完毕,才进行按位逻辑判断 boolean result1=3 > 4 & ++i < 5; System.out.println("result1=" + result1 + ", i=" + i); // 对于短路逻辑运算,一旦左边的计算能够确定结果,就立即返回判断结果,不再进行右边的计算 boolean result2=3 > 4 && ++j < 5; System.out.println("result2=" + result2 + ", j=" + j);
运行以上示例代码,得到以下运算日志:
result1=false, i=2 result2=false, j=1
可见,两个逻辑式子的运算结果都为false,不同之处在于:“&”符号同时执行了左右两边的运算,所以i++执行之后i值变为2;而“&&”符号先执行左边的运算,发现3>4为假,说明“与”运算肯定为假,此时整个式子直接返回假,不再执行右边的计算,于是j值仍然为1(j++未执行)。从该实验看到,短路逻辑运算名副其实,其效率高于按位逻辑运算,因而在实际开发过程中更经常使用短路符号“&&”和“||”开展逻辑运算,很少使用单个的“&”和“|”。