Java运算符
Java提供了丰富的运算符环境。Java有4大类运算符:算术运算、位运算、关系运算和逻辑运算。Java还定义了一些附加的运算符用于处理特殊情况。本章将描述Java所有的运算符,而比较运算符instanceof将在第12章讨论。
注意:如果你对C/C++熟悉,你将会高兴,因为Java的绝大多数运算符和C/C++中的用法一样。但有一些微妙的差别
算术运算符
算术运算符用在数学表达式中,其用法和功能与代数学(或其他计算机语言)中一样,Java定义了下列算术运算符:
+ 加法
– 减法(一元减号)
* 乘法
/ 除法
% 模运算
++ 递增运算
+= 加法赋值
–= 减法赋值
*= 乘法赋值
/= 除法赋值
%= 模运算赋值
-- 递减运算
算术运算符的运算数必须是数字类型。算术运算符不能用在布尔类型上,但是可以用在char类型上,因为实质上在Java中,char类型是int类型的一个子集。
基本算术运算符
基本算术运算符——加、减、乘、除可以对所有的数字类型操作。减运算也用作表示单个操作数的负号。记住对整数进行“/”除法运算时,所有的余数都要被舍去。下面这个简单例子示范了算术运算符,也说明了浮点型除法和整型除法之间的差别。
// Demonstrate the basic arithmetic operators.
class BasicMath {
public static void main(String args[]) {
// arithmetic using integers
System.out.println("Integer Arithmetic");
int a = 1 + 1;
int b = a * 3;
int c = b / 4;
int d = c - a;
int e = -d;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
System.out.println("d = " + d);
System.out.println("e = " + e);
// arithmetic using doubles
System.out.println("\nFloating Point Arithmetic");
double da = 1 + 1;
double db = da * 3;
double dc = db / 4;
double dd = dc - a;
double de = -dd;
System.out.println("da = " + da);
System.out.println("db = " + db);
System.out.println("dc = " + dc);
System.out.println("dd = " + dd);
System.out.println("de = " + de);
}
}
当你运行这个程序,你会看到输出如下:
Integer Arithmetic
a = 2
b = 6
c = 1
d = -1
e = 1
Floating Point Arithmetic
da = 2.0
db = 6.0
dc = 1.5
dd = -0.5
de = 0.5
模运算符
模运算符%,其运算结果是整数除法的余数。它能像整数类型一样被用于浮点类型(这不同于C/C++,在C/C++中模运算符%仅仅能用于整数类型)。下面的示例程序说明了模运算符%的用法:
// Demonstrate the % operator.
class Modulus {
public static void main(String args[]) {
int x = 42;
double y = 42.25;
System.out.println("x mod 10 = " + x % 10);
System.out.println("y mod 10 = " + y % 10);
}
}
当你运行这个程序,你会看到输出如下:
x mod 10 = 2
y mod 10 = 2.25
算术赋值运算符
Java提供特殊的算术赋值运算符,该运算符可用来将算术运算符与赋值结合起来。你可能知道,像下列这样的语句在编程中是很常见的:
a = a +4;
在Java中,你可将该语句重写如下:
a += 4;
该语句使用“+=”进行赋值操作。上面两行语句完成的功能是一样的:使变量a的值增加4 。下面是另一个例子:
a = a % 2;
该语句可简写为:
a %= 2;
在本例中,%=算术运算符的结果是a/2的余数,并把结果重新赋给变量a。这种简写形式对于Java的二元(即需要两个操作数的)运算符都适用。其语句格式为:
var= var op expression;
可以被重写为:
var op= expression;
这种赋值运算符有两个好处。第一,它们比标准的等式要紧凑。第二,它们有助于提高Java的运行效率。由于这些原因,在Java的专业程序中,你经常会看见这些简写的赋值运算符。
下面的例子显示了几个赋值运算符的作用:
// Demonstrate several assignment operators.
class OpEquals {
public static void main(String args[]) {
int a = 1;
int b = 2;
int c = 3;
第1 部分 Java 语言
a += 5;
b *= 4;
c += a * b;
c %= 6;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
}
该程序的输出如下:
a = 6
b = 8
c = 3
递增和递减运算
在第2章中曾经介绍过,“++”和“--”是Java的递增和递减运算符。下面将对它们做详细讨论。它们具有一些特殊的性能,这使它们变得非常有趣。我们先来复习一下递增和递减运算符的操作。
递增运算符对其运算数加1,递减运算符对其运算数减1。因此:
x = x + 1;
运用递增运算符可以重写为:
x++;
同样,语句:
x = x - 1;
与下面一句相同:
x--;
在前面的例子中,递增或递减运算符采用前缀(prefix)或后缀(postfix)格式都是相同的。但是,当递增或递减运算符作为一个较大的表达式的一部分,就会有重要的不同。如果递增或递减运算符放在其运算数前面,Java就会在获得该运算数的值之前执行相应的操作,并将其用于表达式的其他部分。如果运算符放在其运算数后面,Java就会先获得该操作数的值再执行递增或递减运算。例如:
x = 42 ;
y =++x ;
在这个例子中,y将被赋值为43,因为在将x的值赋给y以前,要先执行递增运算。这样,语句行y =++x ;和下面两句是等价的:
x = x + 1;
y = x;
但是,当写成这样时:
x = 42;
y = x++;
在执行递增运算以前,已将x的值赋给了y,因此y的值还是42 。当然,在这两个例子中,x都被赋值为43。在本例中,程序行y =x++;与下面两个语句等价:
y = x;
x = x + 1;
下面的程序说明了递增运算符的使用:
// Demonstrate ++.
class IncDec {
public static void main(String args[]) {
int a = 1;
int b = 2;
int c;
int d;
c = ++b;
d = a++;
c++;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
System.out.println("d = " + d);
}
}
该程序的输出如下:
a = 2
b = 3
c = 4
d = 1
4.2 位 运 算 符
Java定义的位运算(bitwise operators)直接对整数类型的位进行操作,这些整数类型包括long,int,short,char,and byte。表4-2列出了位运算:
位运算符及其结果
运算符 结果
~ 按位非(NOT)(一元运算)
& 按位与(AND)
| 按位或(OR)
^ 按位异或(XOR)
>> 右移
>>> 右移,左边空出的位以0填充
续表
运算符 结果
<< 左移
&= 按位与赋值
|= 按位或赋值
^= 按位异或赋值
>>= 右移赋值
>>>= 右移赋值,左边空出的位以0填充
<<= 左移赋值
既然位运算符在整数范围内对位操作,因此理解这样的操作会对一个值产生什么效果是重要的。具体地说,知道Java是如何存储整数值并且如何表示负数的是有用的。因此,在继续讨论之前,让我们简短概述一下这两个话题。
所有的整数类型以二进制数字位的变化及其宽度来表示。例如,byte型值42的二进制代码是00101010,其中每个位置在此代表2的次方,在最右边的位以20开始。向左下一个位置将是21,或2,依次向左是22,或4,然后是8,16,32等等,依此类推。因此42在其位置1,3,5的值为1(从右边以0开始数);这样42是21+23+25的和,也即是2+8+32 。
所有的整数类型(除了char类型之外)都是有符号的整数。这意味着他们既能表示正数,又能表示负数。Java使用大家知道的2的补码(two's complement)这种编码来表示负数,也就是通过将与其对应的正数的二进制代码取反(即将1变成0,将0变成1),然后对其结果加1。例如,-42就是通过将42的二进制代码的各个位取反,即对00101010取反得到11010101,然后再加1,得到11010110,即-42 。要对一个负数解码,首先对其所有的位取反,然后加1。例如-42,或11010110取反后为00101001,或41,然后加1,这样就得到了42。
如果考虑到零的交叉(zero crossing)问题,你就容易理解Java(以及其他绝大多数语言)这样用2的补码的原因。假定byte类型的值零用00000000代表。它的补码是仅仅将它的每一位取反,即生成11111111,它代表负零。但问题是负零在整数数学中是无效的。为了解决负零的问题,在使用2的补码代表负数的值时,对其值加1。即负零11111111加1后为100000000。但这样使1位太靠左而不适合返回到byte类型的值,因此人们规定,-0和0的表示方法一样,-1的解码为11111111。尽管我们在这个例子使用了byte类型的值,但同样的基本的原则也适用于所有Java 的整数类型。
因为Java使用2的补码来存储负数,并且因为Java中的所有整数都是有符号的,这样应用位运算符可以容易地达到意想不到的结果。例如,不管你如何打算,Java用高位来代表负数。为避免这个讨厌的意外,请记住不管高位的顺序如何,它决定一个整数的符号。
位逻辑运算符
位逻辑运算符有“与”(AND)、“或”(OR)、“异或(XOR)”、“非(NOT)”,分别用“&”、“|”、“^”、“~”表示,4-3表显示了每个位逻辑运算的结果。在继续讨论之前,请记住位运算符应用于每个运算数内的每个单独的位。
表4-3 位逻辑运算符的结果
A B A | B A & B A ^ B ~A
0 0 0 0 0 1
1 0 1 0 1 0
0 1 1 0 1 1
1 1 1 1 0 0
按位非(NOT)
按位非也叫做补,一元运算符NOT“~”是对其运算数的每一位取反。例如,数字42,它的二进制代码为:00101010
经过按位非运算成为
11010101
按位与(AND)
按位与运算符“&”,如果两个运算数都是1,则结果为1。其他情况下,结果均为零。
看下面的例子:
00101010 42
&00001111 15
--------------
00001010 10
按位或(OR)
按位或运算符“|”,任何一个运算数为1,则结果为1。如下面的例子所示:
00101010 42
| 00001111 15
--------------
00101111 47
按位异或(XOR)
按位异或运算符“^”,只有在两个比较的位不同时其结果是 1。否则,结果是零。下面的例子显示了“^”运算符的效果。这个例子也表明了XOR运算符的一个有用的属性。注意第二个运算数有数字1的位,42对应二进制代码的对应位是如何被转换的。第二个运算数有数字0的位,第一个运算数对应位的数字不变。当对某些类型进行位运算时,你将会看到这个属性的用处。
00101010 42
^ 00001111 15
-------------
00100101 37
位逻辑运算符的应用
下面的例子说明了位逻辑运算符:
// Demonstrate the bitwise logical operators.
class BitLogic {
public static void main(String args[]) {
String binary[] = {
"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"
};
int a = 3; // 0 + 2 + 1 or 0011 in binary
int b = 6; // 4 + 2 + 0 or 0110 in binary
int c = a | b;
int d = a & b;
int e = a ^ b;
int f = (~a & b) | (a & ~b);
int g = ~a & 0x0f;
System.out.println(" a = " + binary[a]);
System.out.println(" b = " + binary[b]);
System.out.println(" a|b = " + binary[c]);
System.out.println(" a&b = " + binary[d]);
System.out.println(" a^b = " + binary[e]);
System.out.println("~a&b|a&~b = " + binary[f]);
System.out.println(" ~a = " + binary[g]);
}
}
在本例中,变量a与b对应位的组合代表了二进制数所有的 4 种组合模式:0-0,0-1,1-0,和1-1。“|”运算符和“&”运算符分别对变量a与b各个对应位的运算得到了变量c和变量d的值。对变量e和f的赋值说明了“^”运算符的功能。字符串数组binary代表了0到15对应的二进制的值。在本例中,数组各元素的排列顺序显示了变量对应值的二进制代码。数组之所以这样构造是因为变量的值n对应的二进制代码可以被正确的存储在数组对应元素binary[n]中。例如变量a的值为3,则它的二进制代码对应地存储在数组元素binary[3]中。~a的值与数字0x0f (对应二进制为0000 1111)进行按位与运算的目的是减小~a的值,保证变量g的结果小于16。因此该程序的运行结果可以用数组binary对应的元素来表示。该程序的输出如下:
a = 0011
b = 0110
a|b = 0111
a&b = 0010
a^b = 0101
~a&b|a&~b = 0101
~a = 1100
左移运算符
左移运算符<<使指定值的所有位都左移规定的次数。它的通用格式如下所示:
value << num
这里,num指定要移位值value移动的位数。也就是,左移运算符<<使指定值的所有位都左移num位。每左移一个位,高阶位都被移出(并且丢弃),并用0填充右边。这意味着当左移的运算数是int类型时,每移动1位它的第31位就要被移出并且丢弃;当左移的运算数是long类型时,每移动1位它的第63位就要被移出并且丢弃。
在对byte和short类型的值进行移位运算时,你必须小心。因为你知道Java在对表达式求值时,将自动把这些类型扩大为 int型,而且,表达式的值也是int型 。对byte和short类型的值进行移位运算的结果是int型,而且如果左移不超过31位,原来对应各位的值也不会丢弃。但是,如果你对一个负的byte或者short类型的值进行移位运算,它被扩大为int型后,它的符号也被扩展。这样,整数值结果的高位就会被1填充。因此,为了得到正确的结果,你就要舍弃得到结果的高位。这样做的最简单办法是将结果转换为byte型。下面的程序说明了这一点:
// Left shifting a byte value.
class ByteShift {
public static void main(String args[]) {
byte a = 64, b;
int i;
i = a << 2;
b = (byte) (a << 2);
System.out.println("Original value of a: " + a);
System.out.println("i and b: " + i + " " + b);
}
}
该程序产生的输出下所示:
Original value of a: 64
i and b: 256 0
因变量a在赋值表达式中,故被扩大为int型,64(0100 0000)被左移两次生成值256(10000 0000)被赋给变量i。然而,经过左移后,变量b中惟一的1被移出,低位全部成了0,因此b的值也变成了0。
既然每次左移都可以使原来的操作数翻倍,程序员们经常使用这个办法来进行快速的2的乘法。但是你要小心,如果你将1移进高阶位(31或63位),那么该值将变为负值。下面的程序说明了这一点:
// Left shifting as a quick way to multiply by 2.
class MultByTwo {
public static void main(String args[]) {
int i;
int num = 0xFFFFFFE;
for(i=0; i<4; i++) {
num = num << 1;
System.out.println(num);
}
}
}
该程序的输出如下所示:
536870908
1073741816
2147483632
-32
初值经过仔细选择,以便在左移 4 位后,它会产生-32。正如你看到的,当1被移进31位时,数字被解释为负值。
右移运算符
右移运算符>>使指定值的所有位都右移规定的次数。它的通用格式如下所示:
value >> num
这里,num指定要移位值value移动的位数。也就是,右移运算符>>使指定值的所有位都右移num位。
下面的程序片段将值32右移2次,将结果8赋给变量a:
int a = 32;
a = a >> 2; // a now contains 8
当值中的某些位被“移出”时,这些位的值将丢弃。例如,下面的程序片段将35右移2次,它的2个低位被移出丢弃,也将结果8赋给变量a:
int a = 35;
a = a >> 2; // a still contains 8
用二进制表示该过程可以更清楚地看到程序的运行过程:
00100011 35
>> 2
00001000 8
将值每右移一次,就相当于将该值除以2并且舍弃了余数。你可以利用这个特点将一个整数进行快速的2的除法。当然,你一定要确保你不会将该数原有的任何一位移出。
右移时,被移走的最高位(最左边的位)由原来最高位的数字补充。例如,如果要移走的值为负数,每一次右移都在左边补1,如果要移走的值为正数,每一次右移都在左边补0,这叫做符号位扩展(保留符号位)(sign extension),在进行右移操作时用来保持负数的符号。例如,–8 >> 1 是–4,用二进制表示如下:
11111000 –8
>>1
11111100 –4
一个要注意的有趣问题是,由于符号位扩展(保留符号位)每次都会在高位补1,因此-1右移的结果总是–1。有时你不希望在右移时保留符号。例如,下面的例子将一个byte型的值转换为用十六进制表示。注意右移后的值与0x0f进行按位与运算,这样可以舍弃任何的符号位扩展,以便得到的值可以作为定义数组的下标,从而得到对应数组元素代表的十六进制字符。
// Masking sign extension.
class HexByte {
static public void main(String args[]) {
char hex[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f''
};
byte b = (byte) 0xf1;
System.out.println("b = 0x" + hex[(b >> 4) & 0x0f] + hex[b & 0x0f]);
}
}
该程序的输出如下:
b = 0xf1
无符号右移
正如上面刚刚看到的,每一次右移,>>运算符总是自动地用它的先前最高位的内容补它的最高位。这样做保留了原值的符号。但有时这并不是我们想要的。例如,如果你进行移位操作的运算数不是数字值,你就不希望进行符号位扩展(保留符号位)。当你处理像素值或图形时,这种情况是相当普遍的。在这种情况下,不管运算数的初值是什么,你希望移位后总是在高位(最左边)补0。这就是人们所说的无符号移动(unsigned shift)。这时你可以使用Java的无符号右移运算符>>>,它总是在左边补0。下面的程序段说明了无符号右移运算符>>>。在本例中,变量a被赋值为-1,用二进制表示就是32位全是1。这个值然后被无符号右移24位,当然它忽略了符号位扩展,在它的左边总是补0。这样得到的值255被赋给变量a。
int a = -1;
a = a >>> 24;
下面用二进制形式进一步说明该操作:
11111111 11111111 11111111 11111111 int型- 1的二进制代码
>>> 24 无符号右移24位
00000000 00000000 00000000 11111111 int型255的二进制代码由于无符号右移运算符>>>只是对32位和64位的值有意义,所以它并不像你想象的那样有用。因为你要记住,在表达式中过小的值总是被自动扩大为int型。这意味着符号位扩展和移动总是发生在32位而不是8位或16位。这样,对第7位以0开始的byte型的值进行无符号移动是不可能的,因为在实际移动运算时,是对扩大后的32位值进行操作。下面的例子说明了这一点:
// Unsigned shifting a byte value.
class ByteUShift {
static public void main(String args[]) {
char hex[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
byte b = (byte) 0xf1;
byte c = (byte) (b >> 4);
byte d = (byte) (b >>> 4);
byte e = (byte) ((b & 0xff) >> 4);
System.out.println(" b = 0x"
+ hex[(b >> 4) & 0x0f] + hex[b & 0x0f]);
System.out.println(" b >> 4 = 0x"
+ hex[(c >> 4) & 0x0f] + hex[c & 0x0f]);
System.out.println(" b >>> 4 = 0x"
+ hex[(d >> 4) & 0x0f] + hex[d & 0x0f]);
System.out.println("( b & 0xff) >> 4 = 0x"
+ hex[(e >> 4) & 0x0f] + hex[e & 0x0f]);
}
}
该程序的输出显示了无符号右移运算符>>>对byte型值处理时,实际上不是对byte型值直接操作,而是将其扩大到int型后再处理。在本例中变量b被赋为任意的负byte型值。对变量b右移4位后转换为byte型,将得到的值赋给变量c,因为有符号位扩展,所以该值为0xff。对变量b进行无符号右移4位操作后转换为byte型,将得到的值赋给变量d,你可能期望该值是0x0f,但实际上它是0xff,因为在移动之前变量b就被扩展为int型,已经有符号扩展位。最后一个表达式将变量b的值通过按位与运算将其变为8位,然后右移4位,然后将得到的值赋给变量e,这次得到了预想的结果0x0f。由于对变量d(它的值已经是0xff)进行按位与运算后的符号位的状态已经明了,所以注意,对变量d再没有进行无符号右移运算。
B = 0xf1
b >> 4 = 0xff
b >>> 4 = 0xff
(b & 0xff) >> 4 = 0x0f
位运算符赋值
所有的二进制位运算符都有一种将赋值与位运算组合在一起的简写形式。例如,下面两个语句都是将变量a右移4位后赋给a:
a = a >> 4;
a >>= 4;
同样,下面两个语句都是将表达式a OR b运算后的结果赋给a:
a = a | b;
a |= b;
下面的程序定义了几个int型变量,然后运用位赋值简写的形式将运算后的值赋给相应的变量:
class OpBitEquals {
public static void main(String args[]) {
int a = 1;
int b = 2;
int c = 3;
a |= 4;
b >>= 1;
c <<= 1;
a ^= c;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
}
该程序的输出如下所示:
a = 3
b = 1
c = 6
关系运算符
关系运算符(relational operators)决定值和值之间的关系。例如决定相等不相等以及排列次序。关系运算符如表4-4所示:
表4-4 关系运算符及其意义
运算符 意义
== 等于
!= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于
这些关系运算符产生的结果是布尔值。关系运算符常常用在if控制语句和各种循环语句的表达式中。
Java中的任何类型,包括整数,浮点数,字符,以及布尔型都可用“==”来比较是否相等,用“!=”来测试是否不等。注意Java(就像C和C++一样)比较是否相等的运算符是2个等号,而不是一个(注意:单等号是赋值运算符)。只有数字类型可以使用排序运算符进行比较。也就是,只有整数、浮点数和字符运算数可以用来比较哪个大哪个小。前面已经说过,关系运算符的结果是布尔(boolean)类型。例如,下面的程序段对变量c的赋值是有效的:
int a = 4;
int b = 1;
boolean c = a < b;
在本例中,a如果你有C/C++语言知识的背景,请注意下面的几条语句。在C/C++中,这些类型的语
句是很常见的:
int done;
// ...
if(!done) ... // Valid in C/C++
if(done) ... // but not in Java.
在Java中,这些语句必须写成下面这样:
if(done == 0)) ... // This is Java-style.
if(done != 0) ...
这样做的原因是Java定义真和假的方法和C/C++中的不一样。在C/C++中,真是任何非0的值而假是值0。在Java中,真值假值是非数字的,它和0或非0联系不到一起。因此,为了测试0值或非0值,你必须明确地用一个或多个关系运算符。
布尔逻辑运算符
布尔逻辑运算符的运算数只能是布尔型。而且逻辑运算的结果也是布尔类型(见表4-5)。
表4-5 布尔逻辑运算符及其意义
运算符 含义
& 逻辑与
| 逻辑或
^ 异或
|| 短路或
&& 短路与
! 逻辑反
&= 逻辑与赋值(赋值的简写形式)
|= 逻辑或赋值(赋值的简写形式)
^= 异或赋值(赋值的简写形式)
== 相等
!= 不相等
?: 三元运算符(IF-THEN-ELSE)
布尔逻辑运算符“&”、“ |”、“ ^”,对布尔值的运算和它们对整数位的运算一样。
逻辑运算符“!”的结果表示布尔值的相反状态:!true == false 和 !false == true。各个逻辑运算符的运算结果如表4-6所示:
表4-6 布尔逻辑运算符的运算
A B A|B A&B A^B !A
False False False False False True
True False True False True False
False True True False True True
True True True True False False
下面的例子和前面讲的位逻辑运算的几乎一样,只不过本例中的运算数是布尔逻辑值而不是二进制的位值:
// Demonstrate the boolean logical operators.
class BoolLogic {
public static void main(String args[]) {
boolean a = true;
boolean b = false;
boolean c = a | b;
boolean d = a & b;
boolean e = a ^ b;
boolean f = (!a & b) | (a & !b);
boolean g = !a;
System.out.println(" a = " + a);
System.out.println(" b = " + b);
System.out.println(" a|b = " + c);
System.out.println(" a&b = " + d);
System.out.println(" a^b = " + e);
System.out.println("!a&b|a&!b = " + f);
System.out.println(" !a = " + g);
}
}
在你运行该程序后,你会发现逻辑运算符对布尔值和位运算值的逻辑规则一样。而且
从下面的输出你也可以看出Java中的布尔值是字符串常量“true”或“false”:
a = true
b = false
a|b = true
a&b = false
a^b = true
a&b|a&!b = true
!a = false
短路(short-circuit)逻辑运算符
Java 提供了两个在大多数其他计算机语言中没有的有趣的布尔运算符。这就是逻辑AND和逻辑OR的特殊的短路版本。从上表可以看出,在逻辑OR的运算中,如果第一个运算数A为真,则不管第二个运算数B是真是假,其运算结果为真。同样,在逻辑AND的运算中,如果第一个运算数A为假,则不管第二个运算数是真是假,其运算结果为假。如果运用||和&&形式,而不是|和&,那么一个运算数就能决定表达式的值,Java的短路版本就不会对第二个运算数求值,只有在需要时才对第二个运算数求值。为完成正确的功能,当右边的运算数取决于左边的运算数是真或是假时,短路版本是很有用的。例如,下面的程序语句说明了短路逻辑运算符的优点,用它来防止被0除的错误:
if (denom != 0 && num / denom > 10)
既然用了短路AND运算符,就不会有当denom为0时产生的意外运行时错误。如果该行代码使用标准AND运算符(&),它将对两个运算数都求值,当出现被0除的情况时,就会产生运行时错误。既然短路运算符在布尔逻辑运算中有效,那么就在布尔逻辑运算中全用它,而标准的AND和OR运算符(只有一个字符)仅在位运算中使用。然而,这条规则也有例外。例如,考虑下面的语句:
if(c==1 & e++ < 100) d = 100;
这里,使用标准AND运算符(单个的&)来保证不论c是否等于1,e都被自增量。
赋值运算符
在第2章中你已经使用过赋值运算符。下面我们正式讨论它。赋值运算符是一个等号“=”。它在Java中的运算与在其他计算机语言中的运算一样,其通用格式为:
var = expression;
这里,变量var的类型必须与表达式expression的类型一致。
赋值运算符有一个有趣的属性,你或许并不熟悉:它允许你对一连串变量赋值。例如,请看下面的例子:
int x,y,z;
x = y = z = 100; // set x,y,and z to 100
该例子使用一个赋值语句对变量 x、y、z 都赋值为100。这是因为“=”运算符产生右边表达式的值,因此 z = 100 的值是 100,然后该值被赋给 y ,并依次被赋给 x 。使用“串赋值”是给一组变量赋同一个值的简单办法。
?运算符
Java提供一个特别的三元运算符(ternary)经常用于取代某个类型的if-then-else 语句。这个运算符就是?,并且它在Java中的用法和在C/C++中的几乎一样。该符号初看起来有些迷惑,但是一旦掌握了它,用?运算符是很方便高效的。?运算符的通用格式如下:
expression1 ? expression2 : expression3
其中,expression1是一个布尔表达式。如果expression1为真,那么expression2被求值;否则,expression3被求值。整个?表达式的值就是被求值表达式(expression2或expression3)的值。expression2和expression3是除了void以外的任何类型的表达式,且它们的类型必须相同。
下面是一个利用?运算符的例子:
ratio = denom == 0 ? 0 : num / denom;
当Java计算这个表达式时,它首先看问号左边的表达式。如果 denom 等于0,那么在问号和冒号之间的表达式被求值,并且该值被作为整个?表达式的值。如果 denom 不等于零,那么在冒号之后的表达式被求值,并且该值被作为整个?表达式的值。然后将整个?表达式的值赋给变量ratio。下面的程序说明了?运算符,该程序得到一个变量的绝对值。
// Demonstrate ?.
class Ternary {
public static void main(String args[]) {
int i, k;
i = 10;
k = i < 0 ? -i : i; // get absolute value of i
System.out.print("Absolute value of ");
System.out.println(i + " is " + k);
i = -10;
k = i < 0 ? -i : i; // get absolute value of i
System.out.print("Absolute value of ");
System.out.println(i + " is " + k);
}
}
该程序的输出如下所示:
Absolute value of 10 is 10
Absolute value of -10 is 10
运算符优先级
表4-7显示了Java 运算符从最高到最低的优先级。注意第一行显示的项你通常不能把它们作为运算符:圆括号,方括号,点运算符。圆括号被用来改变运算的优先级。从前面章节我们知道,方括号用来表示数组的下标。点运算符用来将对象名和成员名连接起来,这将在本书的后面讨论。
表4-7 Java 运算符优先级
最高
( ) [ ] .
++ – – ~ !
* / %
+ –
>> >>> <<
> >= < <=
续表
== !=
&
^
|
&&
||
?:
= op=
最低
使用圆括号
圆括号(Parentheses)提高了括在其中的运算的优先级。这常常对于获得你需要的结果是必要的。例如,考虑下列表达式:
a >> b + 3
该表达式首先把 3 加到变量 b,得到一个中间结果,然后将变量a右移该中间结果位。该表达式可用添加圆括号的办法重写如下:
a >> (b + 3)
然而,如果你想先将a右移b位,得到一个中间结果,然后对该中间结果加3,你需要对表达式加如下的圆括号:
(a >> b) + 3
除了改变一个运算的正常优先级外,括号有时被用来帮助澄清表达式的含义。对于阅读你程序代码的人来说,理解一个复杂的表达式是困难的。对复杂表达式增加圆括号能帮助防止以后的混乱。例如,下面哪一个表达式更容易读呢?
a | 4 + c >> b & 7
(a | (((4 + c) >> b) & 7))
另外一点:圆括号(不管是不是多余的)不会降低你程序的运行速度。因此,添加圆括号可以减少含糊不清,不会对你的程序产生消极影响。
用途
用途之一:计算
既然是运算符,首要功能当然是运算了。使用位运算符进行运算,会在很多时候给我们带来不可思议的便利。
如:左移一位相当于除以2;右移一位相当于除以2。
用途之二:flag
由于位运算符都是对二进制数进行运算,所以我们也可以利用这一点来做为区分各种不同情况的flag。
下列代码就是一个简单的示例应用。对于一个java文件来说,它有以下几种modifier:[public, package, protected, private, static, abstract, final]等等。这些modifier中,有些是可以同时存在的(如:public和static),有些则是互斥的,也就是说只能出现一种(如:public和private)。
那么我们就可以对这些modifier进行分类,分类的方法就是让它们每一个都占据一个二进制位。下列代码中,public占据了低位第一个二进制位,而private则占据了低位第4个二进制位。使用这种分类方法,我们就可以很轻松的判断是否包含某一个modifier,也可以判断是否包含一系列modifier。如下列代码中的isPublic(int)和hasModifier(int)。
这种用法还可以用在权限管理系统,因为权限系统也会有这种类似的部分可以同时具有,部分必须是互斥的需求。
当然,除了以上两种应用外,位运算符应该还有很多的应用方法。