
3.2 运算符与表达式
Java语言提供了丰富的运算符和表达式,这为编程带来了方便和灵活,主要包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、条件运算符和其他运算符等。
运算符可按其操作数个数的多少分为3类:单目运算符(一个操作数)、双目运算符(两个操作数)和三目运算符(3个操作数)。
由这些运算符和操作数按一定的语法形式连接起来的式子称为表达式。一个常量或一个变量名字是最简单的表达式,其值即该常量或变量的值。表达式的值还可以用作其他运算的操作数,嵌套在一起形成更复杂的表达式。
3.2.1 算术运算符及其表达式
算术运算符包括+(加)、-(减)、*(乘)、/(除)、%(模)、++(递增)、--(递减)等。算术运算符的运算数必须是数字类型。算术运算符不能用在布尔类型上,但是可以用在char类型上,因为在Java语言中,char类型实质上是int类型的一个子集。
常见的算术运算符有双目算术运算符以及自增和自减运算符,表3-8为双目算术运算符。
表3-8 双目算术运算符

基本算术运算符加、减、乘、除可以对所有的数字类型数据进行操作。加、减运算符也用作表示单个操作数的正、负号。特别要注意的是,对整数进行除法(/)运算时,所有的余数都要被舍去,而对于浮点数除法则可以保留余数。
【例3-7】 算术运算符的使用。

程序运行结果:

模运算符%可以获取整数除法的余数,它同样适用于浮点类型数据(这与C/C++不同,C/C++语言要求%两侧均为整型数据)。
【例3-8】 模运算符的用法。

程序运行结果:

说明:Java对加运算进行了扩展,能够完成字符串的连接,例如"Java"+"Applet"结果为字符串"Java Applet"。
3.2.2 自增和自减运算符
++和--是Java的自增和自减运算符。
作用:自增运算符对其运算数加1,自减运算符对其运算数减1。
两种运算类型说明如下。
①前置运算:++i,--i。
表示先使变量的值增1或减1,再使用该变量。
②后置运算:i++,i--。
表示先使用该变量参加运算,再将该变量的值增1或减1。
自增和自减运算符见表3-9。
表3-9 自增和自减运算符

注意:自增和自减运算符中的4个符号同级,且高于双目算术运算符。自增和自减运算符只作用于变量,而不能作用于常量或表达式上。
下面将对它们进行详细讨论。先来看递增和递减运算符的操作。语句
x++;
与下面语句相同:

同样,语句

与下面语句相同:

在上面例子中,自增或自减运算符采用前缀(prefix)或后缀(postfix)格式都是相同的。但是,当自增或自减运算符作为一个较大表达式的一部分时,就会有重要区别。如果自增或自减运算符放在其运算数前面,Java就会在获得该运算数的值之前执行相应的操作,并将其用于表达式的其他部分。如果运算符放在其运算数后面,Java就会先获得该操作数的值再执行递增或递减运算。例如:

在上面例子中,y将被赋值为11,因为在将x的值赋给y以前,要先执行递增运算。这样,语句“y=++x;”和下面两句是等价的:

但是,当写成如下这样时:

在执行递增运算以前,先将x的值赋给了y,因此y的值还是10。当然,在这两个例子中,x都被赋值为11。在本例中,语句“y=x++;”与下面两个语句等价:

【例3-9】 自增运算符的使用。

程序运行结果:

说明:单独的自增和自减运算,前置和后置等价。例如,“a++;”和“++a;”等价,都相当于“a=a+1;”。
相关知识 自增运算符(++)和自减运算符(--)只能用于变量,不能用于常量或表达式,例如5++或(a+b)++都是不合法的。它们的结合方向是“自右至左”。它们常用于后面章节的循环语句中,使循环变量自动增加1;也用于指针变量,使指针指向下一个地址。
3.2.3 关系运算符及其表达式
所谓“关系运算”(relational operator)实际上就是“比较运算”。将两个值进行比较,判断其比较的结果是否符合给定的条件。关系运算符包括>、<、>=、<=、==、!=等。关系运算符决定值和值之间的关系。例如,决定相等、不相等及排列次序等。关系运算符如表3-10所示。
表3-10 关系运算符

这些关系运算符产生的结果是布尔类型值。关系运算符常常用在if控制语句和各种循环语句的表达式中。
Java中的任何类型,包括整型、浮点型、字符型及布尔型,都可用==来比较是否相等,用!=来比较是否不等。
注意:Java比较是否相等的运算符是用两个等号,而不是一个符号(注意,一个等号是赋值运算符)。只有数字类型可以使用排序运算符进行比较。也就是说,只有整数、浮点数和字符运算数可以用来比较哪个大或哪个小。等于运算符是==,即为代数式中的两个等号。通常容易在使用等于运算符时写成一个等号,使程序出现意想不到的错误。
使用关系运算符构成的关系表达式的值是逻辑值。要么为“真”,要么为“假”。
例如,下面的程序段对变量c的赋值是有效的。

在本例中,a<b(其结果是false)的结果存储在变量c中。
【例3-10】 关系运算符的计算。

程序运行结果:

3.2.4 逻辑运算符
逻辑运算符用来进行逻辑运算,逻辑运算也称为布尔运算。用逻辑运算符连接操作数组成的表达式称为逻辑表达式。逻辑表达式的值或称逻辑运算的结果也只有真和假两个值。当逻辑运算的结果为真时,用1作为表达式的值;当逻辑运算的结果为假时,用0作为表达式的值。当判断一个逻辑表达式的结果时,则是根据逻辑表达式的值为非0时表示真,为0时表示假。逻辑运算符如表3-11所示。
表3-11 逻辑运算符

从表3-11可以看出,逻辑运算符包括!、&&、||。Java提供了逻辑非(!)、逻辑与(&&)和逻辑或(||)3个运算符。
逻辑非代表取反,如果当前运算数为真,取反后的值为假;反之,如果当前运算数为假,取反后的值为真。
在逻辑或运算中,如果第一个运算数为真,则不管第二个运算数是真还是假,其运算结果都为真。
同样,在逻辑与运算中,如果第一个运算数为假,则不管第二个运算数是真还是假,其运算结果都为假。
因此,如果采用||和&&形式,那么一个运算数就能决定表达式的值,只有在需要时才对第二个运算数求值。当右边的运算数取决于左边的运算数是真或者假时,这点是很有用的。例如,下面的程序语句说明了逻辑运算符的优点,用它可以防止被0除的错误。

既然用了逻辑与运算符,就不会有当x为0时产生的运行时异常。
【例3-11】 Java逻辑运算符的使用。

程序运行结果:

注意:除了逻辑非外,逻辑运算符的优先级低于关系运算符。逻辑非这个符号比较特殊,它的优先级高于算术运算符。逻辑运算符的优先级为!→&&→||。
3.2.5 位运算符
位运算符包括>>、<<、>>>、&、|、^、~等。Java定义的位运算符(bitwise operator)直接对整数类型的位进行操作,这些整数类型包括long、int、short、char和byte。表3-12列出了位运算符及其含义。
表3-12 位运算符及其含义

既然位运算符在整数范围内对位操作,那么理解这样的操作会对一个值产生什么影响就很重要。具体地说,需要知道Java是如何存储整数值并且如何表示负数的。因此,在继续讨论之前,首先简述这些概念。
所有的整数类型都以二进制数字位的变化及其宽度来表示。例如,byte型值42的二进制代码是00101010。另外,所有的整数类型(除了char类型之外)都是有符号的整数,这意味着它们既能表示正数,又能表示负数。Java使用2的补码这种编码方式表示负数,也就是通过将与其对应的正数的二进制代码取反(即将1变成0,将0变成1),然后对其结果加1。例如,-42就是通过将42的二进制代码的各个位取反,即对00101010取反得到11010101,然后再加1,得到11010110,即-42。要对一个负数解码,首先对其所有的位取反,然后加1。例如-42,11010110取反后为00101001,即41,然后加1,这样就得到了42。
位逻辑运算符有与(AND)、或(OR)、异或(XOR)、非(NOT),分别用&、|、^、~表示,表3-13显示了每个位逻辑运算的结果。在继续讨论之前,请记住位运算符应用于每个运算数内的每个单独的位。
表3-13 位逻辑运算的结果

1. 按位非
按位非(NOT)也称为补,一元运算符非~是对其运算数的每一位取反。例如,数字42,它的二进制代码为00101010,经过按位非运算成为11010101。
2. 按位与
按位与(AND)运算符为&,如果两个运算数都是1,则结果为1。在其他情况下,结果均为0。例如:

3. 按位或
按位或(OR)运算符为|,如果任何一个运算数为1,则结果为1。例如:

4. 按位异或
按位异或(XOR)运算符为^,只有在两个比较的位不同时,其结果是1;否则,结果是0。例如:

5. 左移
左移运算符为<<,将一个数的各二进制位全部左移若干位,每左移1位,高阶位都被移出并且丢弃,同时右端补0。在不溢出的情况下,每左移1位,相当于乘2。
下面的程序段将值16左移2位,并将结果64赋给变量b。

6. 右移
右移运算符为>>,将一个数的各二进制位全部右移若干位。每右移1位,低阶位都被移出并且丢弃,同时前补符号值(正数补0,负数补1)。每右移1位,相当于除以2。
下面的程序段将值32右移2位,将结果8赋给变量a。

注意:当值中的某些位被“移出”时,这些位的值将被丢弃。例如,下面的程序段将35右移2位,它的2个低位被移出丢弃,也将结果8赋给变量a。

用二进制表示该过程可以更清楚地看到程序的运行过程。

将值每右移1位,就相当于将该值除以2并且舍弃余数。可以利用这个特点将一个整数进行快速的除2运算,但一定要确保不会将该数原有的任何一位移出。右移时,被移走的最高位(最左边的位)由原来最高位的数字补充。例如,如果要移走的值为负数,每一次右移都在左边补1;如果要移走的值为正数,每一次右移都在左边补0,这称为符号位扩展(保留符号位),在进行右移操作时用来保持负数的符号。例如,-8>>1是-4,用二进制表示如下:

需要注意的是,由于符号位扩展(保留符号位)每次都会在高位补1,因此-1右移的结果总是-1。
7. 无符号右移运算符
将一个数的各二进制位全部右移(>>>)若干位。每右移1位,低阶位都被移出并丢弃,同时前面空出的位补0。每右移1位,相当于除以2。
3.2.6 赋值运算符及其表达式
1. 赋值运算
赋值运算符用来构成赋值表达式给变量进行赋值操作。赋值运算符用赋值符号即等号“=”表示,它的作用就是将一个数据赋给一个变量。
由赋值运算符以及相应操作数组成的表达式称为赋值表达式。其一般形式如下:
变量名=表达式
例如:

赋值运算符及其描述如表3-14所示。
表3-14 赋值运算符及其描述

2. 复合赋值运算
复合赋值运算符由一个双目运算符和一个赋值运算符构成。复合赋值运算符及其描述如表3-15所示。
表3-15 复合赋值运算符及其描述

注意:符号⇔表示“相当于”。自反赋值运算符中的5个符号同级,但低于双目算术运算符。
这种赋值运算符有两个好处:一是比标准的等式紧凑;二是有助于提高Java的运行效率。因此,在Java的专业程序中,经常会看见这些简写的赋值运算符。
【例3-12】 复合的赋值运算符的应用。
已知a=12,n=5,求下列表达式的值:

上述(1)~(6)表达式的计算结果分别为24、10、60、0、0、0。
表达式(3)和(4)由于加法的优先级高于自反乘和自反除的赋值运算,所以先运算加法。而表达式(6)中的运算符级别相同,计算时按照从右向左的顺序进行。由于该表达式比较复杂,因此可以分解为3个表达式进行计算,分别为:a=a*a;a=a-a;a=a+a;,所以,所得结果为0。
例如,赋值表达式a+=a-=a*a最终a的值为-264。
具体的求解步骤如下:
①先进行a-=a*a的运算,相当于a=a-a*a=12-144=-132,此时a的值由12变成-132。
②再进行a+=-132的运算,相当于a=a+(-132)=-132-132=-264。
3.2.7 条件运算符和条件表达式
条件运算符为“?:”,是Java提供一个特别的三目运算符,即它有3个参与运算的操作数。由条件运算符组成条件表达式的一般形式为:
表达式1?表达式2:表达式3
其中,表达式1是一个布尔表达式。
条件运算符的求值规则为:条件表达式的运算是先计算表达式1(通常为关系或逻辑表达式)的值,如果表达式1的值为非0,则整个条件表达式取表达式2的值,否则取表达式3的值。表达式2和表达式3是除void以外的任何类型的表达式,并且它们的类型必须相同。
条件表达式通常用于赋值语句之中,例如:

就可以用表达式“max=(a>b)?a:b;”替换,二者的运行结果完全一致。
条件运算符的优先级:条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值运算符。因此,“max=(a>b)?a:b”可以去掉括号而写为“max=a>b?a:b”。例如:

其执行结果:max=20。
注意:条件运算符的结合性:自右至左。
例如:“a>b?a:c>d?c:d”应理解为“a>b?a:(c>d?c:d)”,这也就是条件表达式嵌套的情形,即其中的表达式3又是一个条件表达式。
3.2.8 表达式中运算符的优先顺序
1. 运算符的优先级
在Java语言中,要想正确使用一种运算符,必须清楚这种运算符的优先级。当一个表达式中出现不同类型的运算符时,首先按照它们的优先级顺序进行运算,即先运算优先级高的运算符,再运算优先级低的运算符。当两类运算符的优先级相同时,则要根据运算符的结合性确定运算顺序。当多个运算符同时存在时,需要知道它们之间的优先顺序。运算符的优先级和结合性如表3-16所示。
表3-16 运算符的优先级和结合性

2. 使用括号改变运算的优先级
括号提高了括在其中的运算符的优先级,这常常能帮助我们获得需要的结果。例如,考虑下列表达式:

该表达式首先把3加到变量b,得到一个中间结果;然后将变量a右移。该表达式可用添加括号的办法重写如下:

如果想先将a右移b位,得到一个中间结果;然后对该中间结果加3,就需要对表达式加如下的括号:

括号除了改变一个运算的正常优先级外,有时也被用来帮助澄清表达式的含义。对于阅读程序代码的人来说,理解一个复杂的表达式是困难的。对复杂表达式增加括号能帮助防止理解表达式混乱。下面哪一个表达式更容易阅读呢?显然是第二个。

另外,括号不会降低程序的运行速度。因此,添加括号可以减少含糊不清的地方,不会对程序产生消极影响。