
2.3 运算符
运算符和表达式是Java程序的基本组成要素。把表示各种不同运算的符号称为运算符(operator),参与运算的各种数据称为操作数(operand)。为了完成各种运算,Java提供了多种运算符,不同的运算符用来完成不同的运算。
表达式(expression)是由运算符和操作数按一定语法规则组成的符号序列。以下是合法的表达式:

一个常量或一个变量是最简单的表达式。每个表达式经过运算后都会产生一个确定的值。
2.3.1 算术运算符
算术运算符一般用于对整型数和浮点型数运算。算术运算符有加(+)、减(−)、乘(*)、除(/)和取余数(%)5个二元运算符和正(+)、负(−)、自增(++)、自减(−−)4个一元运算符。
1.二元运算符
二元运算符有加(+)、减(−)、乘(*)、除(/)和取余数(%)。这些运算符都可以应用到整数和浮点数上。
在使用除法运算符(/)时,如果两个操作数都是整数,商为整数。例如,5/2的结果是2而不是2.5,而5.0 / 2的结果是2.5。
“%”运算符用来求两个操作数相除的余数,操作数可以为整数,也可以为浮点数。例如,7 % 4的结果为3,10.5 % 2.5的结果为0.5。当操作数含有负数时,情况有点复杂。这时的规则是余数的符号与被除数相同且余数的绝对值小于除数的绝对值。例如:

在操作数涉及负数求余运算中,可通过下面规则计算:先去掉负号,再计算结果,结果的符号取被除数的符号。如求−10 % −3的结果,去掉负号求10 % 3,结果为1。由于被除数是负值,因此最终结果为−1。
在程序设计中,求余数运算是非常有用的。例如,偶数%2的结果总是0而正奇数%2的结果总是1。所以,可以利用这一特性来判定一个数是偶数还是奇数。如果今天是星期三,7天之后就又是星期三。那么10天之后是星期几呢?使用下面的表达式,就可以知道那天是星期六(余数0是星期日)。

在整数除法及取余运算中,如果除数为0,则抛出ArithmeticException异常。当操作数有一个是浮点数时,如果除数为0,除法运算将返回Infinity或−Infinity,求余运算将返回NaN。有关异常的概念请参阅第12章。
“+”运算符不但用于计算两个数值型数据的和,还可用于字符串对象的连接。例如,下面的语句输出字符串"abcde"。

当“+”运算符的两个操作数一个是字符串而另一个是其他数据类型,系统会自动将另一个操作数转换成字符串,然后再进行连接。例如,下面代码输出“sum = 123”。

但要注意,下面代码输出“sum = 6”。

2.自增(++)和自减(−−)运算符
“++”和“−−”运算符主要用于对变量的操作,分别称为自增和自减运算符,“++”表示加1,“−−”表示减1。它们又都可以使用在变量的前面或后面,如果放在变量前,表示给变量加1后再使用该变量;若放在变量的后面,表示使用完该变量后再加1。例如,假设当前变量x的值为5,执行下面语句后y和x的值如下所示:

自增和自减运算符可用于浮点型变量,如下代码是合法的。

请注意下面程序的输出结果。
程序2.4 IncrementTest.java

程序的输出结果为:

第一次计算s时是3+4+5,最后i的值为6,第二次计算s时是4+5+6,最后i的值也为6。
2.3.2 关系运算符
关系运算符(也称比较运算符)用来比较两个值的大小或是否相等。Java有6种关系运算符,如表2-4所示。
表2-4 关系运算符

关系运算符一般用来构成条件表达式,比较的结果返回true或false。假设定义了下面的变量。

下面的语句的输出都是true。

在Java语言中,任何类型的数据(包括基本类型和引用类型)都可以用“==”和“!=”比较是否相等,但只有基本类型的数据(布尔型数据除外)可以比较哪个大哪个小。比较结果通常作为判断条件,如下所示。

2.3.3 逻辑运算符
逻辑运算符的运算对象只能是布尔型数据,并且运算结果也是布尔型数据。逻辑运算符包括逻辑非(!)、短路与(&&)、短路或(||)、逻辑与(&)、逻辑或(|)、逻辑异或(^)。假设A、B是两个布尔型数据,则逻辑运算的规则如表2-5所示。
从表2-5可以看到,对一个逻辑值A,逻辑非(!)运算是当A为true时,!A的值为false;当A为false时,!A的值为true。
对逻辑“与”(&&或&)和逻辑“或”(||或|)运算都有两个运算符,它们的区别是:“&&”和“||”为短路运算符,而“&”和“|”为非短路运算符。对短路运算符,当使用“&&”进行“与”运算时,若第一个(左面)操作数的值为false时,就可以判断整个表达式的值为false,因此,不再继续求解第二个(右边)表达式的值。同样当使用“||”进行“或”运算时,若第一个(左面)操作数的值为true时,就可以判断整个表达式的值为true,因此,不再继续求解第二个(右边)表达式的值。对非短路运算符(&和|),将对运算符左右的表达式求解,最后计算整个表达式的结果。
表2-5 逻辑运算的运算规则

对“异或”(^)运算,当两个操作数一个是true而另一个是false时,结果就为true,否则结果为false。
程序2.5 LogicalDemo.java

程序输出结果为:

程序在第一次求u时先计算a >= −−b,结果为true,此时不再计算b++ <c−−,因此b的值为1,c的值为3,再计算b ==c结果为false,因此u的值为false。在第二次计算u的值时,a、b、c的值仍然是1、2、3,在计算a >= −−b的结果为true后,仍然要计算b++ <c−−的值,结果b与c的值都为2,因此最后u值为true。
上面的结果说明,在相同的条件下,使用哪种逻辑运算符(短路的还是非短路的),计算的结果可能不同。
2.3.4 赋值运算符
赋值运算符(assignment operator)用来为变量指定新值。赋值运算符主要有两类,一类是使用等号(=)赋值,它把一个表达式的值赋给一个变量或对象;另一类是复合的赋值运算符。下面分别讨论这两类赋值运算符。
1.赋值运算符
赋值运算符“=”的一般格式为:

这里,variableName为变量名,expression为表达式。其功能是将等号右边表达式的值赋给左边的变量。例如:

赋值运算必须是类型兼容的,即左边的变量必须能够接受右边的表达式的值,否则会产生编译错误。例如,下面的语句会产生编译错误。

因为3.14是double型数据,不能赋给整型变量,因为可能丢失精度。编译器的错误提示是Type mismatch:cannot convert double to int(类型不匹配,不能将double型值转为int型值)。
使用等号(=)可以给对象赋值,这称为引用赋值。将右边对象的引用值(地址)赋给左边的变量,这样,两个变量地址相同,即指向同一对象。例如:

此时s1、s2指向同一个对象。对象引用赋值与基本数据类型的复制赋值是不同的。在第4章将详细讨论对象的引用赋值。
2.复合赋值运算符
在赋值运算符(=)前加上其他运算符,即构成复合赋值运算符。它的一般格式为:

这里op为运算符,其含义是将变量variableName的值与expression的值做op运算,结果赋给variableName。例如,下面两行是等价的:

复合赋值运算符有11个,设a = 15, b = 3,表2-6给出了所有的复合的赋值运算符及其使用方法。
在复合赋值运算中,如果等号右侧是一个表达式,表达式将作为一个整体参加运算。例如,下面代码的输出结果为13。

表2-6 扩展的赋值运算符

上面的复合赋值运算等价于下面代码:

2.3.5 位运算符
位运算是在整数的二进制位上进行的运算。在学习位运算符之前,先回顾一下整数是如何用二进制表示的。在Java语言中,整数是用二进制的补码表示的。在补码表示中,最高位为符号位,正数的符号位为0,负数的符号位为1。若一个数为正数,补码与原码相同;若一个数为负数,补码为原码的反码加1。
例如,int型整数+ 42用4个字节32位的二进制补码表示为:

−42的补码为:

位运算有两类:位逻辑运算(bitwise)和移位运算(shift)。位逻辑运算符包括按位取反(~)、按位与(&)、按位或(|)和按位异或(^)4种。移位运算符包括左移(<<)、右移(>>)和无符号右移(>>>)3种。位运算符只能用于整型数据,包括byte、short、int、long和char类型。设a = 10, b = 3,表2-7列出了各种位运算符的功能与示例。
表2-7 位运算符

1.位逻辑运算符
位逻辑运算是对一个整数的二进制位进行运算。设A、B表示操作数中的一位,位逻辑运算的规则如表2-8所示。
表2-8 位逻辑运算的运算规则

~运算符是对操作数的每一位按位取反。例如,~42的结果为−43。因为42的二进制补码为00000000 00000000 00000000 00101010,按位取反后结果为11111111 1111111 11111111 11010101,即为−43。对任意一个整型数i,都有等式成立:

再看以下按位与运算。

上面代码的输出结果为:

按位与运算的过程如下:

如果两个操作数宽度(位数)不同,在进行按位运算时要进行扩展。例如,一个int型数据与一个long型数据按位运算,先将int型数据扩展到64位,若为正,高位用0扩展;若为负,高位用1扩展,然后再进行位运算。
2.移位运算符
Java语言提供了3个移位运算符:左移运算符(<<)、右移运算符(>>)和无符号右移运算符(>>>)。
(1)左移运算符(<<)用来将一个整数的二进制位序列左移若干位。移出的高位丢弃,右边添0。例如,整数7的二进制序列为?

若执行7 << 2,结果为

7左移2位结果是28,相当于7乘4。
(2)右移运算符(>>)用来将一个整数的二进制位序列右移若干位。移出的低位丢弃。若为正数,移入的高位添0;若为负数,移入的高位添1。
(3)无符号右移运算符(>>>)也是将一个整数的二进制位序列右移若干位。它与右移运算符的区别是,不论是正数还是负数左边一律移入0。例如,−192的二进制序列为

若执行−192 >> 3,结果为−24。

若执行−192 >>> 3,结果为536870888。

注意:位运算符和移位运算符都只能用于整型数或字符型数据,不能用于浮点型数据。
2.3.6 运算符的优先级和结合性
运算优先级是指在一个表达式中出现多个运算符又没有用括号分隔时,先运算哪个后运算哪个。常说的“先算乘除后算加减”指的就是运算符优先级问题。不同的运算符有不同的运算优先级。
假设有下面一个表达式:

这个表达式的结果是多少呢?这涉及运算符的优先级问题。程序首先计算括号中的表达式(如果有嵌套括号,先计算里层括号中的表达式)。当计算没有括号的表达式时,会按照运算符的优先级和结合性进行运算。
结合性是指对某个运算符构成的表达式,计算时如果先取运算符左边的操作数,后取运算符,则该运算符是左结合的,若先取运算符右侧的操作数,后取运算符,则是右结合的。所有的二元运算符(如+、<<等)都是左结合的,而赋值运算符(=、+=等)就是右结合的。表2-9按优先级的顺序列出了各种运算符和结合性。
表2-9 按优先级从高到低的运算符

不必死记硬背运算符的优先级。必要时可以在表达式中使用括号,括号的优先级最高。括号还可以使表达式显得更加清晰。例如,考虑以下代码:

因为“*”和“+”的优先级比“==”高,比较运算之后,z的值是true。但是,这个表达式的可读性较差。使用括号把最后一行修改如下:

最后结果相同。该表达式要比不使用括号的表达式清晰得多。