2.2 操作符

操作符可以对若干个操作数进行特定的运算。根据操作符需要操作数的不同,可以将操作符分为以下3类:

•一元操作符。

•二元操作符。

•三元操作符。

一元操作符只能对一个操作数进行运算。一元操作符可以用两种形式表述:前缀式和后缀式。前缀式是指操作符在前,操作数在后,例如:

     ++a;

++是一元操作符,a为操作数。

后缀式正好相反,操作符在后,操作数在前,例如:

     a++;

二元操作符对两个操作数进行运算。加(+)、减(-)、乘(*)、除(/)、求模(%)以及赋值(=)都是二元操作符。例如:

     a=7+8;

二元操作符“+”对7和8这两个操作数进行运算得到结果15。二元赋值操作符“=”再对15和a这两个操作数进行运算,结果就是将a赋值为15。

Java语言中还有一个特殊的三元操作符“?:”,对3个操作数进行运算。一般表示形式为:

     condition ? result1:result2;

如果第一个操作数condition的值为true,那么取值result1;反之取值result2。例如:

     min=x>y?y:x;

最终,min的值为x与y中较小的值。

注意:三元操作符中的第一个操作数必须为布尔类型的值。

下面按照操作符的功能分别加以分类讲解。

2.2.1 算术操作符

加(+)、减(-)、乘(*)、除(/)、求模(%)是Java编程语言中提供的算术操作符。算术操作符只能对浮点型和整型数据类型的操作数进行运算。算术操作符的功能如表2.5所示。

表2.5 算术操作符

例如:

     int x;      //变量声明
     x=100+50;  //赋值,该语句执行完毕后,x的值为150
     x=x+100;   //赋值,该语句执行完毕后,x的值为250

还可以使用如下语句对变量进行赋值:

     x+=100;

它等价于:

     x=x+100;

每个算术操作符在用于赋值运算时可以有其对应的简捷形式,如表2.6所示。

表2.6 算术操作符用于赋值运算时的简捷形式

在应用程序开发过程中,经常会用到的是让一个变量加1或是减1(例如在循环中)。当然可以使用如下语句:

     i=i+1;

或是:

     i+=1;

但Java语言中提供了一种更加简捷的操作符,称为递增操作符(++)和递减操作符(--)。因此,要让一个变量i加1,可以使用:

     i++;

或是:

     ++i;

同样,让一个变量i减1,可以使用:

     i--;

或是:

     --i;

如前所述,i++是后缀方式,++i是前缀方式。虽然这两种方式最终都会使i的值加1,但还是存在不同之处的。下面的这段代码说明了它们的区别:

     int a=100;
     int b=100;
     int c=++a;  //a先增加1,然后将a的值赋给c
     int d=b++;  //先将b的值赋给d,然后b的值再增加1
     System.out.println("a="+a);
     System.out.println("b="+b);
     System.out.println("c="+c);
     System.out.println("d="+d);

观察这段代码的输出结果,可以发现a、b、c和d的值分别为101、101、101、100。也就是说,a和b的值确实都增加了1。但是c和d的值为何不同呢?

由于前缀方式的自增操作符是“先增加,后使用”,而后缀方式的自增操作符是“先使用,后增加”。这就产生了c和d值不同的结果。同样自减操作符具有类似的性质。

注意:由于自增(自减)操作符的特性,除了在循环中用于循环变量的自增(自减)操作,其他容易产生歧义的代码中尽量不要使用。

2.2.2 关系与条件操作符

关系操作符是二元操作符,用于比较两个操作数的值并确定它们之间的关系,关系操作符的运算结果是一个布尔值。Java编程语言中共有6个关系操作符,如表2.7所示。

表2.7 关系操作符

例如:100==101的值为false;100>=100的值为true。

关系操作符在程序中经常和条件操作符联合使用,用作条件判断以控制程序的执行流程。Java语言中提供了6种条件操作符(见表2.8),条件操作符只能对布尔类型的操作数进行运算,并且运算结果也是布尔类型的值。

表2.8 条件操作符

观察表2.8,可以发现&&和&都需要两个操作数的值均为true时,才取值true。但是这两个操作符还是有区别的,例如:

     (x>y)&&(x>z);

如果x>y的值是false,那么x>z的值将不再计算,(x>y)&&(x>z)直接取值false;而:

     (x>y)&(x>z);

即使x>y的值是false,但x>z的值仍需计算,尽管x>z的值已经不会影响x>y&x>z的结果。这就是为什么称&&为“条件与”的理由:只有在满足第一个操作数的值为true的条件下,才计算第二个操作数的值。类似的区别还存在于“||”和“|”之间。

下面的几个例子说明了条件操作符的应用:

     !(4>3)         //值为false;
     (4>3)^(5>6)   //值为true;
     (3>4)&&(6>5)  //值为false; 6>5的值不需计算
     (4>3)||(5>6)  //值为true; 5>6的值不需计算
     (3>4)&(6>5)   //值为false; 6>5的值仍需计算
     (4>3)|(5>6)   //值为true; 5>6的值仍需计算

注意:在操作数为布尔类型时,操作符&、|和^是作为条件操作符。但是当操作数为数值类型时,操作符&、|和^是作为位操作符,见2.2.3小节。

2.2.3 位操作符

在计算机内部,数据是以二进制编码存储的,Java编程语言允许我们对这些二进制编码进行位运算,位操作符如表2.9所示。

表2.9 位操作符

例如,12的编码是1100,7的编码是0111,那么:

     12&7;  // 结果的二进制编码为0100,对应的值为4
     12|7;  // 结果的二进制编码为1111,对应的值为15
     12^7;  // 结果的二进制编码为1011,对应的值为11

因为:

如果对12进行移位操作:

     12>>2;  //结果的二进制编码为11,对应的值为3
     7<<2;   //结果的二进制编码为11100,对应的值为28

注意:>>和>>>都是右移操作符,但是两者是有区别的。使用>>>时,前面的位填0;而使用>>时,前面填充的是符号位。

2.2.4 其他类型操作符

除了上面介绍的操作符外,Java还提供了如下类型的操作符,如表2.10所示。

表2.10 其他类型的操作符

这里只对这些操作符的功能进行简单介绍,更多内容请参见相关章节。

1. [ ] 操作符

操作符[ ]用于声明、创建数组。还可用于访问数组中的特定元素。例如:

     double [ ]salary=new double[20];

其中第一个[ ]操作符声明salary是一个数组,而第二个[ ]操作符创建一个可以存储20个double类型数据的数组。再如下面这条语句:

     salary[0]=22.2;

上面这条语句中的[ ]操作符用于访问数组中特定位置(这里是第一个元素,Java语言中数组标号是从0开始的,依次递增)的数组元素,这里是将数组salary中的第一个元素赋值为22.2。

2. 操作符

操作符用于访问类的类变量、对象的实例变量或方法。

3. (参数)操作符

操作符(参数)用于声明或是调用一个方法。

4. (数据类型)操作符

操作符(数据类型)称为转型(cast),将一种数据类型转化为另一种数据类型。例如:

     double salary=23.45;
     int intSalary=(int)salary;

这样,通过将double型的salary强制转换成int型的intSalary, intSalary的值是23。

注意:()除了用作操作符的功能外,还可在表达式中用来指示操作数运算的执行顺序。

5. new操作符

new操作符用于创建对象,例如:

     String aString=new String("This is a string");

6. instanceOf操作符

instanceOf操作符的用法是:

     anObject instanceOf aClass

用于判断对象anObject是否为类aClass的一个实例,返回的是布尔类型的值。

2.2.5 数字类型转换

在实际的应用开发过程中,常常会需要在数字类型之间进行转换。一方面,使用算术操作符对数字进行运算时,系统在适当的时候会自动进行数字类型的转换;另一方面,程序开发人员还可以显式地进行数字类型之间的强制类型转换。

1. 自动数字类型转换

下面的代码片段会输出133.34:

     int x=100;
     double y=33.34;
     System.out.println(x+y);

实际上,在运算过程中,x首先被自动转换成double数据类型,然后再进行相加,得到一个double类型的运算结果。因此,上述代码片段中,如果把x+y的值赋给一个整型变量,编译器就会报错。例如:

     int x=100;
     double y=33.34;
     int c=x+y;      //错误,不能将一个double类型的值赋给int类型的变量

使用算术操作符进行运算时,得到的数值类型取决于操作数的类型。在需要时,操作数会自动进行数据类型的转换,如表2.11所示。

表2.11 算术运算返回值类型与操作数类型之间的关系

2. 强制类型转换

虽然系统在需要的时候会自动进行数字类型的转换,但有的时候,我们希望能够主动将一种数据类型转换为另一种数据类型。这时候就可以使用显式的强制类型转换,也称为转型(cast)。例如要知道一个double类型数据的整数部分的值是多少:

     double salary=103.34;
     int intSalary=(int)salary;  //intSalary的值为103

这样的结果是把salary小数部分的值截去,然后把整数部分的值赋给整型变量intSalary。

需要注意的是,在不同数值类型之间转换是有可能丢失信息的。例如将一个long类型的数值强制转化为int类型时,如果该long类型的值在int类型所能表示的范围之外,那么就不能进行正确的转换了。图2.3给出了数字类型之间的合法转换,该图中的实线箭头表示的转换不会丢失信息;虚线箭头表示的转换可能会丢失精度。

图2.3 数字类型之间的合法转换

注意:不仅数字类型之间可以进行类型转换(转型),对象之间也可以进行类型转换(转型)。但布尔类型不能进行任何类型的转换。

2.2.6 操作符优先级

不同的操作符具有不同的运算优先级,如表2.12所示。

表2.12 操作符优先级

注:第一行中的()是指用于方法调用时的操作符,第二行中的()是指用于强制类型转换时的操作符。

同一行上操作符的优先级别相同,但是优先次序从左至右递减(右结合的操作符除外);同一列上操作符的优先级从上至下递减。

注意:并不建议强记表2.12中操作符的优先级。在容易混淆的地方,建议在程序代码中使用圆括号明确指明运算的优先次序。

例如,不建议使用类似下面的语句:

     c=x+++y/100+z;

因为这种语句容易给程序的阅读带来困难,并给日后的维护带来麻烦。

建议使用圆括号明确指出运算的执行次序,如:

     ((x++)+y)/(100+z);

或是:

     (x+(++y))/(100+z);

2.2.7 表达式、语句和块

在前面讲述变量和操作符的过程中,已经涉及表达式、语句和块的概念。这里再进行综合性的阐述。

1. 表达式

表达式是由变量、操作符或是方法调用所组成的一个运算序列,并且返回一个值。表达式用作变量的赋值或是控制程序的执行流程,例如:

     double salary=100;          // ①
     String name="Tom";          // ②
     if(salary>50&&name.equals("Tom")){ // ③
         ...
     }
     else{
         ...
     }

上面的代码片段中,粗体部分(不包括分号)表示的是3个表达式:表达式①和②的作用是给变量赋值,表达式③的作用是控制程序的执行流程。

注意:表达式总是完成一定的运算,然后返回一个值。

例如表达式①先完成赋值运算,然后返回值100。而表达式③先运算salary>50,返回true;然后name.equals("Tom")是实例方法调用,判断name的值是否为Tom,返回值也是true;最后进行关系运算,得到结果true作为表达式③的返回值。

2. 语句

所谓语句,是指程序中的一个完整的执行单元。

表达式后面添加分号(;)可以构成一条语句,例如:

     double salary=100;  //声明语句,而salary=100是表达式
     salary=200;         //赋值语句,而salary=200是表达式
     i--;                //自减语句,而i--是表达式
     i++;                //自增语句,而i++是表达式

方法调用后面添加分号可以构成方法调用语句:

     System.out.println("Hello World");  //方法调用语句

除此以外,还有控制流语句,例如if、for以及switch等,用于控制程序的执行流程,参见2.4节。

3. 块

块由一对花括号之间的零条或多条语句所构成。参见2.1.3小节“变量的作用域”。