2.5 运算符
Java的运算符风格和功能与C\C++类似,运算符接1~2个操作数,运算后产生一个新的值。在Java中,运算符有许多,不过常用的是“+”“-”“*”“/”“=”等。
2.5.1 赋值运算符
扫码看视频
赋值操作是由“=”运算符来完成的,意思是等号左边的变量(左值)取右边的值。赋值运算符的右边(右值)可以是任意的常量、变量、能返回值的表达式,但是赋值运算符左边必须是明确的变量。例如:a = 4,即将a变量赋值为4;但是4 = a就是错误的,因为赋值运算符左边是一个字面常量。
2.5.2 自增和自减运算符
扫码看视频
有过C++语言经验的读者或许对C++中的自增和自减运算符记忆犹新,它可以把变量增加1。
自增运算符:
它相当于i = i + 1。
自减运算符:
它相当于i = i - 1。
有些读者会对++和--在变量前后的运算顺序搞不明白,你只需要记住,如果++(或--)运算符在变量前面,就先计算自增(或自减),如果++(或--)运算符在变量后面就先取变量的值再计算自增(或自减)。把下面的代码理解之后,对于自增和自减运算符在变量前后的区别就明白了。
2.5.3 算术运算符
扫码看视频
在Java中,我们接触到的算术运算符有加(+)、减(-)、乘(*)、除(/)、取模(%)。整数相除(/)如果有余数,则舍弃余数,保留整数(即商),不进行四舍五入。整数取模运算(%),即整数相除求余数;浮点数取模运算会返回小数余数。
在Java中,同样借鉴了C++的运算并赋值的简化符号(即复合赋值运算符),如果想把x加4然后把结果赋值给x可以写成“x += 4”。下面的例子演示了算术运算符的使用。
2.5.4 关系运算符
扫码看视频
关系运算符用于在两个操作数之间判断关系,如果关系成立,则返回boolean类型的true值,如果关系不成立,则返回boolean类型的false值。这些关系运算符与C++语言的关系运算符相同:等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=)。
例如:4 == 5的值为false,5 >= 2的值为true。
注意:在进行等于判断时,不要将两个等号写成一个,否则会变成赋值操作,导致不必要的问题出现。
2.5.5 布尔运算符
扫码看视频
Java沿用了C++的习惯,用&&表示逻辑与,|| 表示逻辑或,!表示逻辑非。布尔运算符根据它的操作数的逻辑关系计算结果为true或false。逻辑与(&&)只有在两个操作数都为true时,其结果才为true;逻辑或(||)只要任何一个操作数为true,其结果就是true。逻辑非(!)顾名思义,非真即为假,非假即为真。
例如:true && false的结果为false,true || false的结果为true,!ture的结果为false。
此外,要注意的是,&& 和 || 都是按照“短路”的方式求值的,我们看下面的例子:
程序的计算结果为:
从计算结果可以看出,当逻辑与运算符的第一个表达式为false时,才会计算第二个表达式,因为false和任何操作数进行逻辑与运算其结果都是false,因此没有必要去计算第二个表达式。当逻辑或运算的第一个表达式为true时,不会计算第二个表达式,因为true和任何操作数进行逻辑或运算,其结果都是true,因此没有必要去计算第二个表达式。换句话说,&& 和 || 是按照“短路”的方式求值的。
2.5.6 位运算符
扫码看视频
为了方便对二进制位进行操作,Java提供了4个位运算符:按位与(&)、按位或(|)、按位异或(^)、按位取反(~)。
1.按位与
按位与和逻辑与的计算有些类似,只不过是把true改为了二进制的1,把false改为了二进制的0,不过,逻辑与是针对布尔值进行逻辑判断,而按位与则是对一个数的二进制位进行操作。
按位与其实很好掌握,你只需记住只有1、1为1就行了,其他都是0。
2.按位或
同样,按位或预算与逻辑或运算也是类似的,只不过把true改为二进制的1,把false改为二进制的0即可。按位或是对一个数的二进制位进行操作。
按位或也很好掌握,你只需记住只有0、0为0就可以了,其他都是1。
3.按位异或
按位异或有些不太好理解:两个二进制位的值不同时为1,相同时则为0。我们看表2-4。
表2-4 按位异或操作
从上面的例子可以看出,只要A和B的相同位的值不同时计算,结果就为1,否则为0。
位异或运算在某些场景下是非常有用的,比如我们要交换两个变量的值,通常的做法是定义一个临时变量来辅助完成两个变量值的交换,如代码2.9所示。
在去企业面试的时候可能会让你不使用临时变量完成两个数的交换,为此我们可以修改代码2.9的实现,如代码2.10所示。
这也实现了两个数的交换,不过这种实现方式有些问题,我们知道整数类型都有其表数范围,一旦超过了该范围,数据就会溢出。如果是两个很大的整数,你先进行相加操作,就有可能会造成数据的溢出,从而导致结果不正确,这个时候,使用位异或运算就能完美地解决这个问题,而且执行效率会很高,因为CPU执行位运算的效率是非常高的,这也是为什么很多算法都采用了位运算的原因。
继续修改代码2.10的实现,如代码2.11所示。
读者可以将5和3的二进制位列出来,然后按照位异或运算自己演算一下,就能更好地明白这个交换过程。
4.按位取反
取反就是把0变为1,1变为0,这是非常简单的一个概念。下面是一个按位取反的例子。
在对某个变量进行位运算后再赋值给该变量时,也可以使用复合赋值运算符,例如,把变量a与数值0xFF进行与运算并把结果赋值给a,可以写成:a &= 0xFF。
下面的例子综合了所有的位运算符。
程序的计算结果为:
读者可以使用Windows自带的计算器,查看十六进制值对应的十进制值。代码中Integer类的toHexString方法可以把一个整数转换成十六进制表示的字符串。
2.5.7 移位运算符
扫码看视频
移位运算符也是对二进制位的操作,在Java中,移位运算符包括左移运算符(<<)、带符号右移运算符(>>)和无符号右移运算符(>>>)。
下面我们来看一下移位运算符的计算方式(假定整数17占一个字节),首先是左移运算:
当进行左移运算时,17的二进制值向左移动两位,并在右边补0,而左边多出的两位舍弃,得到结果:01000100,把它转换为十进制数就是68,而17×22=68,也就是说左移运算相当于对源操作数进行乘法运算,乘数是2的左移位数次方。不过这仅限于无符号整数,且没有溢出的情况下(也就是左边抛弃的值均为0时)。-17的位移运算原理相同。
接下来是带符号右移运算:
当进行带符号右移运算时,17的二进制值向右移动两位,并在左边补0,而右边多出的两位舍弃,得到结果:00000100,把它转换为十进制数就是4,而17÷22=4余1。也就是说,带符号右移运算相当于对源操作数进行除法,除数是2的右移位数次方,而余数就是舍弃的值。不过这也仅限于无符号整数。
对于-17来说,带符号右移只是在左边补位时补1,而右边多出的两位舍弃,得到结果:11111011。
最后是无符号右移运算:
对于正数来说,无符号右移与带符号右移计算结果相同,对于负数来说,无符号右移是在左边的空位上补0。
下面用一个例子来熟悉一下移位运算符。
计算结果为:
注意:本例的数值17是作为int类型参与运算的,移位运算符的计算结果也是int类型,所以“-17 >>> 2”的计算结果很大。由于是无符号右移,最左边的位置补0,因此结果变成了正数。
2.5.8 一元和二元运算符
一元和二元运算符是针对操作数的个数来说的,只需要一个操作数的运算符就称为一元运算符,如正号(+)、负号(-)、逻辑非(!)、自增(++)、自减(--)等。需要两个操作数的运算符就称为二元运算符,如算术运算符和关系运算符等。
2.5.9 三元运算符
扫码看视频
熟悉C++的读者肯定会想到“?:”运算符,这个运算符接受三个操作数。它很像if/else语句,下面用一个例子来说明这个三元运算符的运算方式。
“?”前面的表达式要返回一个布尔值,当“a == 3”成立时,则执行冒号(:)左边的表达式,否则执行冒号右边的表达式。有时,这个三元运算符可以完成条件赋值的工作,而且比使用if/else语句要简便一些。
2.5.10 优先级
运算符优先级是规定表达式中出现多个不同运算符时的计算顺序。Java有着一套严格的运算符优先级定义,如表2-5所示。
表2-5 运算符的优先级
运算符的优先级从上往下依次递减,即表格第一行的运算符优先级最高,最后一行的运算符优先级最低,同一行内的运算符具有相同的优先级。
在小学学数学时,老师会告诉我们先乘除、后加减的运算顺序,知道这一点就差不多够用了,没有必要去死记表2-5列出的运算符之间的优先级。在遇到运算符优先顺序不是很明确的情况下,最简单的方法就是使用圆括号来包裹表达式。例如:
如果是想计算b-3与3-c相除的结果再加上a,那么可以使用圆括号来界定表达式的计算顺序,如下所示:
提示:不要痴迷于运算符的优先级,写出“i = ++i+i+++i+++i;”这样的表达式并不能说明你的水平很高,使用圆括号比研究那些晦涩难理解的运算符优先级要简单许多。