Java开发手册
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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。