5. 运算符
5. 运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。Java 语言使用运算符将一个或多个操作数连缀成执行性语句,用以实现特定功能。
算术运算符
算术运算符用在数学表达式中,它们的作用和在数学中的作用一样。
下表列出了所有的算术运算符。假设整数变量 A 的值为 10,变量 B 的值为 20:
符号 | 描述 | 示例 |
---|---|---|
+ | 加法 - 相加运算符两侧的值 | A + B = 30 |
- | 减法 - 左操作数减去右操作数 | A – B = 10 |
* | 乘法 - 相乘操作符两侧的值 | A * B = 200 |
/ | 除法 - 左操作数除以右操作数 | B / A = 2 |
% | 取模 - 左操作数除以右操作数的余数 | B % A = 0 |
++ | 自增 - 操作数的值增加 1 | B++ 或 ++B = 21 |
-- | 自减 - 操作数的值减少 1 | B-- 或 --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)。运算过程如图所示。
//将输出4
System.out.println(~ -5);
//将输出12
System.out.println(5 ^ 9);
程序执行~-5 的结果是 4,执行 5 ^ 9 的结果是 12。
而 5 ^ 9 是 00000101 和 00001001 进行异或运算,运算过程如图所示。
左移运算符是将运算数的二进制码整体左移指定位数,左移后右边空出来的位以 0 填充。
//输出20
System.out.println(5 << 2);
//输出-20
System.out.println(-5 << 2);
上面的 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。
-5 无符号右移 2 位后左边空出 2 位,空出来的 2 位以 0 补充。从图中可以看出,无符号右移运算后的结果总是得到一个正数。图中下面的正数是 1073741822(230-2)
必须指出的是,>>
、>>>
和 <<
3 个移位运算符并不适合所有的数值类型,它们只适合对 byte
、short
、char
、int
和 long
等整数类型进行运算。除此之外,进行移位运算时还要遵循如下规则。
对于低于
int
类型(如byte
、short
和char
)的操作数总是先自动类型转换为int
类型后再移位。对于
int
类型的整数移位 a>>b,当 b>32 时,系统先用 b 对 32 求余(因为int
类型只有 32 位),得到的结果才是真正移位的位数。例如,a>>33 和 a>>1 的结果完全一样,而 a>>32 的结果和 a 相同。对于
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 : 表达式 2
执行原理:
如果布尔表达式结果为 true,则执行表达式 1
如果布尔表达式结果为 false,则执行表达式 2
boolean sex = false;
char c = sex ? '男' : '女';
System.out.println(sex);
// 输出结果为女
运算符的结合性和优先级
运算符有不同的优先级,所谓优先级就是在表达式运算中的运算顺序。
因为 Java 运算符存在这种优先级的关系,有时候能看到这种代码:
int a = 5; int b = 4;
int c = a++ - --b * ++a / b-- >>2 % a--;
// c的值是多少?
这样的代码只能在考试中出现,源代码的可读性比代码运行效率更重要。
- 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成;
- 不要过多地依赖运算符的优先级来控制表达式的执行顺序,这样可读性太差,尽量使用
()
来控制表达式的执行顺序。