5. 运算符

空~2022年6月6日
  • java
大约 16 分钟

5. 运算符

运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。Java 语言使用运算符将一个或多个操作数连缀成执行性语句,用以实现特定功能。

算术运算符

算术运算符用在数学表达式中,它们的作用和在数学中的作用一样。

下表列出了所有的算术运算符。假设整数变量 A 的值为 10,变量 B 的值为 20:

符号描述示例
+加法 - 相加运算符两侧的值A + B = 30
-减法 - 左操作数减去右操作数A – B = 10
*乘法 - 相乘操作符两侧的值A * B = 200
/除法 - 左操作数除以右操作数B / A = 2
取模 - 左操作数除以右操作数的余数B % A = 0
++自增 - 操作数的值增加 1B++ 或 ++B = 21
--自减 - 操作数的值减少 1B-- 或 --B = 19

加法运算符:

double a = 5.2;
double b = 3.1;
double sum = a + b;
// 8.3
System.out.println(sum);

除此之外,+ 还可以作为字符串的连接运算符。

String a = "5.2";
String b = "3.1";
String sum = a + b;
// 5.23.1
System.out.println(sum);

减法运算符:

double a = 5.2;
double b = 3.1;
double sub = a - b;
// 2.1
System.out.println(sub);

- 作为求负运算符。

//定义double变量x,其值为-5.0
double x = -5.0;
//将x求负,其值变成5.0
× = -x;
System.out.println(x);

乘法运算符:

double a = 5.2;
double b = 3.1;
double mul = a * b;
// 16.12
System.out.println(mul);

除法运算符:

除法运算符有些特殊,如果除法运算符的两个操作数都是整数类型,则计算结果也是整数,就是将自然除法的结果截断取整,例如 19/4 的结果是 4,而不是 5。如果除法运算符的两个运算数都是整数类型,则除数不可以是 0,否则将引发除以零异常。

但如果除法运算符的两个操作数有 1 个是浮点数,或者 2 个都是浮点数,则计算结果也是浮点数,这个结果就是自然除法的结果。而且此时允许除数是 0,或者 0.0,得到结果是正无穷大或负无穷大。

public class DivTest {
    public static void main(String[] args) {
        double a = 5.2;
        double b = 3.1;
        double div = a / b;
        // div的值将是1.6774193548387097
        System.out.println(div);
        // 输出正无穷大:Infinity
        System.out.println("5除以0.0的结果是:" + 5 / 0.0);
        // 输出负无穷大:-Infinity
        System.out.println("-5除以0.0的结果是:" + -5 / 0.0);
        // 下面代码将出现异常
        // java.lang.ArithmeticException: / by zero
        System.out.println("-5除以0的结果是:" + -5 / 0);
    }
}

求余运算符:

求余运算的结果不一定总是整数,它的计算结果是使用第一个操作数除以第二个操作数,得到一个整除的结果后剩下的值就是余数。

由于求余运算也需要进行除法运算,因此如果求余运算的两个操作数都是整数类型,则求余运算的第二个运算数不能是 0,否则将引发除以零异常。

如果求余运算的两个操作数中有 1 个或者 2 个都是浮点数,则允许第二个操作数是 0 或 0.0,只是求余运算的结果是非数:NaN

0 或 0.0 对零以外的任何数求余都将得到 0 或 0.0。

public class ModTest {
    public static void main(String[] args) {
        double a = 5.2;
        double b = 3.1;
        double mod = a % b;
        // mod的值为2.1
        System.out.println(mod);
        // 输出非数︰NaN
        System.out.println("5对0.0求余的结果是:" + 5 % 0.0);
        // 输出非数︰NaN
        System.out.println("-5.0对0求余的结果是:" + -5.0 % 0);
        // 输出0
        System.out.println("O对5.0求余的结果是:" + 0 % 5.0);
        // 输出非数:NaN
        System.out.println("O对0.0求余的结果是:" + 0 % 0.0);
        // 下面代码将出现异常
        // java.lang.ArithmeticException: / by zero
        System.out.println("-5对0求余的结果是:" + -5 % 0);
    }
}

自加:

这是个单目运算符,运算符既可以出现在操作数的左边,也可以出现在操作数的右边。但出现在左边和右边的效果是不一样的。

如果把++放在左边,则先把操作数加 1,然后才把操作数放入表达式中运算;如果把++放在右边,则先把操作数放入表达式中运算,然后才把操作数加 1。

int a = 5;
//让a先执行算术运算,然后自加
int b = a++ + 6;
//输出a的值为6,b的值为11
System.out.println(a + ":" + b);

当++在操作数右边时,先执行 a+6 的运算(此时 a 的值为 5),然后对 a 加 1。

int a = 5;
//让a先自加,然后执行算术运算
int b = ++a + 6;
//输出a的值为6,b的值为12
System.out.println(a + ":" + b);

自减:

也是个单目运算符,用法与++基本相似,只是将操作数的值减 1。

注意

加和自减只能用于操作变量,不能用于操作数值直接量或常量。例如,5++、6--等写法都是错误的。

Java 并没有提供其他更复杂的运算符,如果需要完成乘方、开方等运算,则可借助于java.lang.Math类的工具方法完成复杂的数学运算。

public class MathTest {
    public static void main(String[] args) {
        // 定义变量a为3.2
        double a = 3.2;
        // 求a的5次方,并将计算结果赋给b
        double b = Math.pow(a, 5);
        // 输出b的值
        System.out.println(b);
        // 求a的平方根,并将结果赋给c
        double c = Math.sqrt(a);
        // 输出c的值
        System.out.println(c);
        // 计算随机数,返回一个0~1之间的伪随机数
        double d = Math.random();
        // 输出随机数d的值
        System.out.println(d);
        // 求1.57的sin函数值:1.57被当成弧度数
        double e = Math.sin(1.57);
        // 输出接近1
        System.out.println(e);
    }
}

Math 类下包含了丰富的静态方法,用于完成各种复杂的数学运算。

关系运算符

比较运算符用于判断两个变量或常量的大小,比较运算的结果是一个布尔值(true 或 false)。

Java 支持的比较运算符如下。

假设整数变量 A 的值为 10,变量 B 的值为 20:

==:检查如果两个操作数的值是否相等,如果相等则条件为真。(A == B)为假(非真)

!=:检查如果两个操作数的值是否相等,如果值不相等则条件为真。 (A != B) 为真

>:检查左操作数的值是否大于右操作数的值,如果是那么条件为真。 (A > B)非真

<:检查左操作数的值是否小于右操作数的值,如果是那么条件为真。 (A < B)为真

>=:检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。 (A >= B)为假

<=:检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。 (A <= B)为真

注意

基本类型的变量、值不能和引用类型的变量、值使用 == 进行比较;

boolean 类型的变量、值不能与其他任意类型的变量、值使用==进行比较;

如果两个引用类型之间没有父子继承关系,那么它们的变量也不能使用 == 进行比较。

!=:如果两个操作数都是引用类型,只有当两个引用变量引用的相同类的实例时才可以比较,只要两个引用指向的不是同一个对象就会返回 true

public class ComparableOperatorTest {
    public static void main(String[] args) {
        // 输出true
        System.out.println("5是否大于4.0:" + (5 > 4.0));
        // 输出true
        System.out.println("5和5.0是否相等: " + (5 == 5.0));
        // 输出true
        System.out.println("97和'a'是否相等:" + (97 == 'a'));
        // 输出false
        System.out.println("true和false是否相等:" + (true == false));
        // 创建2个ComparableOperatorTest对象,分别赋给t1和t2两个引用
        ComparableOperatorTest t1 = new ComparableOperatorTest();
        ComparableOperatorTest t2 = new ComparableOperatorTest();
        // t1和t2是同一个类的两个实例的引用,所以可以比较
        // 但t1和t2是引用不同的对象,所以返回false
        System.out.println("t1是否等于t2:" + (t1 == t2));
        // 直接将t1的值赋给t3,即让t3指向t1指向的对象
        ComparableOperatorTest t3 = t1;
        // t1和t3指向同一个对象,所以返回true
        System.out.println("t1是否等于t3: " + (t1 == t3));
    }
}

位运算符

Java 定义了位运算符,应用于整数类型 int、长整型 long、短整型 short、字符型 char 和字节型 byte 等类型。

位运算符作用在所有的位上,并且按位运算。

假设整数变量 A 的值为 60 和变量 B 的值为 13:

:如果相对应位都是 1,则结果为 1,否则为 0(A&B)得到 12,即 0000 1100

|:如果相对应位都是 0,则结果为 0,否则为 1(A | B)得到 61,即 0011 1101

^:如果相对应位值相同,则结果为 0,否则为 1(A ^ B)得到 49,即 0011 0001

:按位取反运算符翻转操作数的每一位,即 0 变成 1,1 变成 0。(〜A)得到-61,即 1100 0011

<<:按位左移运算符。左操作数按位左移右操作数指定的位数。(A << 2)得到 240,即 1111 0000

>>:位右移运算符。左操作数按位右移右操作数指定的位数。(A >> 2)得到 15,即 1111

>>>:按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。(A>>>2)得到 15,即 0000 1111

//将输出1
System.out.println(5 & 9);
//将输出13
System.out.println(5 | 9);

5 的二进制码是 00000101(省略了前面的 24 个 0),而 9 的二进制码是 00001001(省略了前面的 24 个 0)。运算过程如图所示。

img

//将输出4
System.out.println(~ -5);
//将输出12
System.out.println(5 ^ 9);

程序执行~-5 的结果是 4,执行 5 ^ 9 的结果是 12。

img

而 5 ^ 9 是 00000101 和 00001001 进行异或运算,运算过程如图所示。

img

左移运算符是将运算数的二进制码整体左移指定位数,左移后右边空出来的位以 0 填充。

//输出20
System.out.println(5 << 2);
//输出-20
System.out.println(-5 << 2);

img

上面的 32 位数是-5 的补码,左移两位后得到一个二进制补码,这个二进制补码的最高位是 1,表明是一个负数,换算成十进制数就是-20。

Java 的右移运算符有两个:>>>>>,对于 >> 运算符而言,把第一个操作数的二进制码右移指定位数后,左边空出来的位以原来的符号位填充,即如果第一个操作数原来是正数,则左边补 0;如果第一个操作数是负数,则左边补 1。

>>> 是无符号右移运算符,它把第一个操作数的二进制码右移指定位数后,左边空出来的位总是以 0 填充。

//输出-2
System.out.println(-5 >> 2);
//输出1073741822
System.out.println(-5 >>> 2);

-5 右移 2 位后左边空出 2 位,空出来的 2 位以符号位补充。从图中可以看出,右移运算后得到的结果的正负与第一个操作数的正负相同。右移后的结果依然是一个负数,这是一个二进制补码,换算成十进制数就是-2。

img

-5 无符号右移 2 位后左边空出 2 位,空出来的 2 位以 0 补充。从图中可以看出,无符号右移运算后的结果总是得到一个正数。图中下面的正数是 1073741822(230-2)

img

必须指出的是,>>>>><< 3 个移位运算符并不适合所有的数值类型,它们只适合对 byteshortcharintlong 等整数类型进行运算。除此之外,进行移位运算时还要遵循如下规则。

  1. 对于低于 int 类型(如 byteshortchar)的操作数总是先自动类型转换为 int 类型后再移位。

  2. 对于 int 类型的整数移位 a>>b,当 b>32 时,系统先用 b 对 32 求余(因为 int 类型只有 32 位),得到的结果才是真正移位的位数。例如,a>>33 和 a>>1 的结果完全一样,而 a>>32 的结果和 a 相同。

  3. 对于 long 类型的整数移位 a>>b,当 b>64 时,总是先用 b 对 64 求余(因为 long 类型是 64 位),得到的结果才是真正移位的位数。

注意

当进行移位运算时,只要被移位的二进制码没有发生有效位的数字丢失(对于正数而言,通常指被移出的位全部都是 0),不难发现左移 n 位就相当于乘以 2 的 n 次方,右移 n 位则是除以 2 的 n 次方。

这里存在一个问题:左移时左边舍弃的位中数字通常是无效的,但右移时右边舍弃的位常常是有效的,因此左移和右移更容易看出这种运行效果。

不仅如此,进行移位运算不会改变操作数本身,只是得到了一个新的运算结果,而原来的操作数本身是不会改变的。

逻辑运算符

逻辑运算符用于操作两个布尔型的变量或常量。

假设布尔变量 A 为真,变量 B 为假

&&:短路与。当且仅当两个操作数都为真,条件才为真。 (A && B)为假。

||:短路或。如果任何两个操作数任何一个为真,条件为真。 (A || B)为真。

!:非。用来反转操作数的逻辑状态。如果条件为 true,则逻辑非运算符将得到 false!(A && B)为真。

&:逻辑与。作用与 && 相同,但不会短路。

|:逻辑或。作用与 || 相同,但不会短路。

^:异或,当两个操作数不同时才返回 true,如果两个操作数相同则返回 false

短路判断与逻辑判断的区别:

逻辑判断全部走一遍,短路判断有结果就停。

// 直接对false求非运算,将返回true
System.out.println(!false);
// 5>3返回true,'6'转换为整数54,'6'>10返回true,求与后返回true
System.out.println(5 > 3 && '6' > 10);
// 4>=5返回false,'c'>'a'返回true。求或后返回true
System.out.println(4 >= 5 || 'c' > 'a');
// 4>=5返回false,'c'>'a'返回true。两个不同的操作数求异或返回true
System.out.println(4 >= 5 ^ 'C' > 'a');

对于|与||的区别。

// 定义变量a,b,并为两个变量赋值
int a = 5;
int b = 10;
// 对a > 4和b++ >10求或运算
if (a > 4 | b++ > 10) {
    // 输出a的值是5,b的值是11
    System.out.println("a的值是:" + a + "b的值是:" + b);
}

b 的值为 11,这表明 b++> 10 表达式得到了计算,但实际上没有计算的必要,因为 a > 4 已经返回了 true,则整个表达式一定返回 true。

// 定义变量c,d,并为两个变量赋值
int c = 5;
int d = 10;
// c >4 || d++ >10求或运算
if (c > 4 || d++ > 10) {
    // 输出c的值是5,d的值是10
    System.out.println("c的值是:" + c + " d的值是:" + d);
}

对于短路逻辑或||而言,如果第一个操作数返回 true,|| 将不再对第二个操作数求值,直接返回 true。

赋值运算符

赋值运算符用于为变量指定变量值,与 C 类似,Java 也使用 = 作为赋值运算符。通常,使用赋值运算符将一个常量值赋给变量。

= 简单的赋值运算符,将右操作数的值赋给左侧操作数 C = A + B 将把 A + B 得到的值赋给 C

+= 加和赋值操作符,它把左操作数和右操作数相加赋值给左操作数 C += A 等价于 C = C(C 的数据类型) + A

-= 减和赋值操作符,它把左操作数和右操作数相减赋值给左操作数 C -= A 等价于 C = C(C 的数据类型) - A

*= 乘和赋值操作符,它把左操作数和右操作数相乘赋值给左操作数 C *= A 等价于 C = C(C 的数据类型) * A

/= 除和赋值操作符,它把左操作数和右操作数相除赋值给左操作数 C /= A,C 与 A 同类型时等价于 C = C(C 的数据类型) / A

%= 取模和赋值操作符,它把左操作数和右操作数取模后赋值给左操作数 C %= A 等价于 C = C(C 的数据类型) % A

<<= 左移位赋值运算符 C << = 2 等价于 C = C << 2

>>= 右移位赋值运算符 C >> = 2 等价于 C = C >> 2

&= 按位与赋值运算符 C &= 2 等价于 C = C(C 的数据类型)&2

^= 按位异或赋值操作符 C ^= 2 等价于 C = C(C 的数据类型) ^ 2

|= 按位或赋值操作符 C |= 2 等价于 C = C(C 的数据类型) | 2

//为变量str赋值Java
String str = "Java";
//为变量pi赋值3.14
double pi = 3.14;
//为变量visited赋值true
boolean visited = true;

除此之外,也可使用赋值运算符将一个变量的值赋给另一个变量。

//将变量str的值赋给str2
String str2 = str;

赋值运算符支持连续赋值,通过使用多个赋值运算符,可以一次为多个变量赋值。

int a; int b; int c;
//通过为a, b , c赋值,3个变量的值都是7
a = b = c = 7;
//输出3个变量的值
System.out.println(a + ":" + b + ":" + c);

提示

虽然 Java 支持这种一次为多个变量赋值的写法,但这种写法导致程序的可读性降低,因此不推荐这样写。

赋值运算符还可用于将表达式的值赋给变量。

double d1 = 12.34;
//将表达式的值赋给d2
double d2 = d1 + 5;
//输出d2的值
System.out.println(d2);

三目运算符

  1. 语法格式:

    布尔表达式 ? 表达式 1 : 表达式 2

  2. 执行原理:

    如果布尔表达式结果为 true,则执行表达式 1

    如果布尔表达式结果为 false,则执行表达式 2

boolean sex = false;
char c = sex ? '男' : '女';
System.out.println(sex);
// 输出结果为女

运算符的结合性和优先级

运算符有不同的优先级,所谓优先级就是在表达式运算中的运算顺序。

img

因为 Java 运算符存在这种优先级的关系,有时候能看到这种代码:

int a = 5; int b = 4;
int c = a++ - --b * ++a / b-- >>2 % a--;
// c的值是多少?

这样的代码只能在考试中出现,源代码的可读性比代码运行效率更重要。

  1. 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成;
  2. 不要过多地依赖运算符的优先级来控制表达式的执行顺序,这样可读性太差,尽量使用()来控制表达式的执行顺序。