3.1 算术运算符
与大多数编程语言一样,在Java中使用算术运算符“+”、“-”、“*”、“/”表示加、减、乘、除运算。另外,求余运算使用运算符“%”表示。本节将对上述运算符逐一进行详细介绍。
3.1.1 加法运算符
在Java中,虽然剔除了C++中自定义运算符重载的功能,但却将几处非常易用的运算符进行了重载,“+”运算符就是其中之一。在Java中,将“+”符号重载了以下三种功能。
· 加法运算。
· 数值正号。
· 字符串连接符。
提示:运算符重载是指同一个运算符在不同的情况下执行不同的操作,主要是为了提供算法方便。
1.加法运算
加法运算,就是将两个操作数进行求和操作,其只能对数值型数据进行,例如:
int i=3+2; //将3与2相加,并将结果值赋给变量i
但需要注意的是,当对两个数值型数据进行运算时,其运算的结果至少是int型。也就是说,如果参与运算的两个数级别比int低或是int型,则结果为int型;如果有一个的级别比int高,则运算结果的类型与类型级别高的数相同。
另外要注意,实际上类型的提升是在运算前完成的,也就是说如果int型和double型运算,先将int型提升为double型,然后进行运算,其结果自然是double型的了。其他情况依此类推。
下面用一个例子来说明这个问题,请读者考察如下代码。
1 //代码实现 2 public class Sample3_1 3 { 4 public static void main(String args[]) 5 { 6 byte m=2; 7 byte n=3; 8 byte i=m+n; //将m与n进行加操作,然后赋值给i 9 System.out.println(i); //打印i 10 } 11 }
上面的代码表面上看没有什么问题,两个byte类型数(m与n)相加的结果赋给一个byte型变量(i),但是编译后将报告如图3-1所示的错误。
图3-1 Sample3_1的编译运行结果
原因如下所示:
· 参与运算的两个数级别比int低,首先都提升为int型,再进行运算,结果为int型。
· 将int型结果赋值给byte型变量,不满足自动转换的规则。
因此,将代码Sample3_1中的第8行代码改为:
byte i=(byte)(m+n);
然后编译运行,其结果如图3-2所示。
图3-2 Sample3_1改后的编译运行结果
注意:下面章节将要讲到的所有算术运算符,包括“−”、“*”、“/”、“%”,均遵循上述运算前类型自动提升的规则,后面不再赘述。
2.数值正号
在一个数值前加上符号“+”。此时,操作符“+”便起到数值正号的功能,例如:
1 int m=+2; 2 int n=+m;
提示:因为数值前如果不加数值符号,那么系统会自动默认其为正值,所以,“+”作为正号的功能没什么作用。建议读者开发程序时,尽量不要用此功能;否则可能会降低代码的可读性。
3.字符串连接符
“+”号也被用于把两个字符串连接到一起,例如:
String a = "欢迎来到"+"Java世界"; //将字符串连接起来,并赋给变量a
如果将数字与字符串组合连接到一起时,“+”会将数值作为字符串处理,执行字符串连接功能,例如:
String a="三是"+3;//字符串a的值将为“三是3”
这在表面上看很简单,但有时也很容易犯错误,请读者看如下的代码。
1 //代码实现 2 public class Sample3_2 3 { 4 public static void main(String args[]) 5 { 6 String a="三加七为:"; 7 String b="是结果。"; 8 int m=3; 9 int n=7; 10 System.out.println(a+m+n); //将字符串与数值连接并打印 11 System.out.println(m+n+b); //将字符串与数值连接并打印 12 } 13 }
编译运行如上代码,读者可能认为会打印“三加七为:10”与“10是结果”,但结果并不与想象中的一致,如图3-3所示。
图3-3 Sample3_2的编译运行结果
原因如下所示:
· “a+m+n”进行运算时,因为运算从左至右进行,首先执行“a+m”,而“a”为字符串,故进行字符串连接,结果为字符串“三加七为:3”。接着,将该结果与“n”进行运算,又是字符串连接,最后结果为“三加七为:37”。
· “m+n+b”进行运算时,首先计算“m+n”,为数值相加,然后将该结果与“b”进行字符串连接,故最后结果为“10是结果”。
如果希望得到预期的结果,可以将代码Sample3_2的第10行改为:
System.out.println(a+(m+n));
编译运行,将得到如图3-4所示的结果。
图3-4 Sample3_2改后的编译运行结果
注意:该问题在实际开发中很容易被忽略,请读者注意,否则会引发不必要的错误。另外,读者可能会在很多Java程序中看到类似于""+i(其中i为数值型数据)这样的代码,其作用是将数值型转换为字符串。
3.1.2 减法运算符
同“+”运算符一样,“−”运算符也被重载了,其重载了以下功能:
· 减法运算。
· 取负运算。
1.减法运算
减法运算功能,就是将两个操作数相减。与加法运算一样,该运算也只能对数值型数据进行。运算时同样要遵守自动类型提升规则,例如:
1 byte b1=12; 2 byte b2=11; 3 int i=b1-b2; //如果i为byte类型则编译报错,因为运算结果为int型
2.取负运算
取负运算,就是取一个数值的负数。要注意的是,此运算也遵循自动类型提升规则,例如:
1 byte a= -2; 2 byte b= -a;
· 第1行代码将−2赋给a,其中有隐含的强制转换,是正确的。
· 第2行代码对a变量进行取负运算,类型自动提升为int型,故编译报错,可以改为“byte b=(byte)(-a);”,则编译通过。
整数的正数和负数的个数是不同的,因此取负运算有时不能取得预期的结果,请读者看如下代码。
1 //代码实现 2 public class Sample3_3 3 { 4 public static void main(String args[]) 5 { 6 int a=-2147483648; //此整数为负的2的31次方,是绝对值最大的int型负整数 7 int b=-a; 8 System.out.println(b); //打印b 9 } 10 }
编译运行如上代码,其结果如图3-5所示。
图3-5 Sample3_3的编译运行结果
从图3-5中可以看出,打印的结果是−2147 483648,这是因为int的取值范围是−2147 483648~2147 483647(−231~231−1),其不能正确表示2147 483648,产生了溢出。
注意:其他整型数字一样会出现类似情况,读者可以自行操作验证。另外,在开发中也要小心这个问题,否则可能带来完全错误的计算结果。
3.1.3 乘法运算符
使用“*”运算符将进行乘法运算,但需要注意的是此运算同样遵循类型自动提升的规则,例如:
1 int a=3*5; //结果将得到15,int型 2 double b=3*5.0; //结果将得到15.0,double型
3.1.4 除法运算符
使用“/”运算符将进行除法运算,此运算也遵循类型自动提升规则。Java中的“/” 运算符也被重载了,其重载了以下功能。
· 整除运算。
· 浮点除运算。
1.整除运算
若参与运算的都为整型,则进行整除,规则如下。
· 两个整数进行除法运算,结果将得到一个整数。若结果有小数部分,则结果将截断小数部分只取整数部分。例如:
1 int a=10/4; //结果将为2 2 int b=5/7; //结果为0
提示:如果整除中被除数小于除数,结果将永远为零。所以,开发时要特别注意,以避免不必要的错误。例如:计算扇形面积公式为(x/360)*πR2,因为x的取值范围为0~360的开区间,若x为整数,则结果永远是零,此时只有用浮点数计算方可取得正确的结果。
· 整数除以零时,将会报运行时错误,例如:
int a=10/0; //编译通过,运行时报错
2.浮点除运算
若参与运算的有一个为浮点型,则进行浮点除,规则如下。
· 两个浮点数,或一个浮点数与一个整数进行除法运算,结果将得到一个浮点数。也就是说,即使结果是一个整数,其也被存储为浮点型值,例如:
1 double a=10.0/2.0; //结果将为5.0 2 double b=10.0/5; //结果为2.0
· 正浮点数(非零浮点数)除以零,将得到结果Infinity(正无穷大),例如:
double a=10.0/0 //结果将为Infinity
· 负浮点数(非零浮点数)除以零,将得到结果−Infinity(负无穷大),例如:
double a=-10.0/0 //结果将为-Infinity
· 浮点数零(0.0)除以零,将得到结果NaN,例如:
double a=0.0/0 //结果将为NaN
注意:常量Double.POSITIVE_INFINITY、Double.NEGATIVE_INFINITY及Double.NaN分别表示上述三个特殊的值。若是float型运算,则用常量Float.POSITIVE_INFINITY、Float.NEGATIVE_INFINITY及Float.NaN来表示这三种情况。
3.1.5 取余运算符
使用“%”运算符将进行求余运算(取余运算),实质就是将左边的操作数除以右边的操作数,余数便是得到的结果,此运算也遵循类型自动提升的规则。在Java中,求余运算按如下步骤实现:
(1)求出参与运算两数的绝对值。
(2)用左边操作数的绝对值减去右边操作数的绝对值,得到差。
(3)如果差比右边操作数的绝对值大,就用差减去右边操作数的绝对值,得到新的差。
(4)重复步骤(2),直至差比右边的操作数小,将差作为结果返回。
(5)结果的符号,由左边的操作数决定。
其中,有两个特殊的情况:
· 整数进行求余运算。如果右边的操作数为零,则报运行时错误。
· 浮点数进行求余运算。如果右边的操作数为零,则得到结果NaN。
下面用一个例子来说明这个问题,请读者考察如下代码。
1 //代码实现 2 public class Sample3_4 3 { 4 public static void main(String args[]) 5 { 6 System.out.println(15%-4); 7 System.out.println(-15%4); 8 System.out.println(6.8%6.3); 9 System.out.println(15.0%0); 10 System.out.println(15%0); 11 } 12 }
编译运行如上代码,结果如图3-6所示。
图3-6 Sample3_4的编译运行结果
从图3-6中可以看出:
· “15%−4”的结果为“3”,结果的符号与右边操作数无关。
· “−15%4”的结果为“−3”,结果的符号由左边操作数决定。
· “6.8%6.3”的结果为“0.5”,浮点数一样可以求余数。
· “15.0%0”的结果为“NaN”,表示不知道结果是什么。
· “15%0”运行报错,与整除除以0是一样的。
3.1.6 自增、自减运算
上面介绍了算术运算符的功能及其操作,下面将介绍使用方便的自增、自减运算符。其功能是将被操作数的值递增1或者递减1,如下所示。
· “++”:递增运算符,例如:
1 int a=15; 2 a++; //相当于a=a+1,a的值将变为16
· “--”:递减运算符,例如:
1 int a=15; 2 a--; //相当于a=a-1,a的值将变为14
注意:因为上述运算符改变了变量的值,其结果要有存放的位置,所以操作数不能是字面常量。例如,“15++;”是非法语句。
自增、自减运算符并不进行类型提升,也就是说操作前是什么类型,操作后还是什么类型,例如:
1 byte b=12; 2 byte c=b++;
说明:因为不自动进行类型提升,所以上面的代码是正确的。
递增和递减运算除了上述将符号放在操作数后边的“后缀”形式外,还有将符号放在操作数前面的“前缀”形式。上述两种形式都是对操作数自增1或者自减1,但放在表达式中,上述两种形式就有不同的寓意了:
· 前缀方式是先执行自增或自减运算,再运行表达式。
· 后缀方式是先运行表达式,再执行自增或自减运算。
下面用一个例子来说明,请读者考察如下代码。
1 //代码实现 2 public class Sample3_5 3 { 4 public static void main(String args[]) 5 { 6 int a=5; 7 int b=5; 8 int m=3*a++; 9 int n=3*++b; 10 System.out.println("m="+m+", a="+a); //打印m的值以及a的值 11 System.out.println("n="+n+", b="+b); //打印n的值以及b的值 12 } 13 }
编译运行如上代码,其结果如图3-7所示。
图3-7 Sample3_5的编译运行结果
提示:笔者建议不要在表达式内使用自增或者自减运算符,否则会大大降低代码的可读性,甚至会产生令人困惑的bug。