02_操作符
操作符
Java 中的操作符有很多,大概可以分成以下几种:
- 关系操作符:>、<、>=、<=、==、!=
- 逻辑操作符:!、&、|、&&、||
- 条件操作符:?:
- 算术操作符:-、++、–、+、-、*、/、%
- 位操作符:~、&、|、^、<<、>>、>>>
- 赋值操作符:=、+=、-=、*=、/=、&=、|=、^=、<<=、>>=、>>>=
一、操作符优先级
当一个表达式存在多个操作符时,操作符的优先级就决定了各部分的计算顺序。
Java 中操作符的优先级共分为 14 级,其中 1 级最高,14 级最低。在表达式中操作符优先级高的优先执行。
下面是各种操作符的优先级和结合性:
优先级 | 操作符 | 结合性 |
---|---|---|
1 | ()、[]、{} | 从左至右 |
2 | !、+、-、~、++、– | 从右至左 |
3 | *、/、% | 从左至右 |
4 | +、- | 从左至右 |
5 | <<、>>、>>> | 从左至右 |
6 | <、<=、>、>=、instanceof | 从左至右 |
7 | ==、!= | 从左至右 |
8 | & | 从左至右 |
9 | ^ | 从左至右 |
10 | | | 从左至右 |
11 | && | 从左至右 |
12 | || | 从左至右 |
13 | ?: | 从右至左 |
14 | =、+=、-=、*=、/=、&=、|=、^=、~=、<<=、>>=、>>>= | 从右至左 |
比如说,下面的表达式:
1 | --a > 0 || ++b <= 1 && c * d > 100 |
然后根据表中的优先级,它的实际计算顺序应该是这样的:
- 先计算a的自减,即
--a
- 再计算
--a > 0
的结果 - 再计算b的自增,即
++b
- 接着计算
++b <= 1
的结果 - 然后计算c、d的乘法,即
c * d
- 再计算
c * d > 100
的结果 - 由于
&&
优先级比||
高,所以再接着是计算++b <= 1 && c * d > 100
的结果 - 最后则是将2的结果和7的结果进行逻辑或(
||
)运算,即计算--a > 0 || ++b <= 1 && c * d > 100
的结果
实际上,不建议直接写这种混合了多种优先级的表达式,因为一旦混合在一起后,就很难识别它们的优先级了。
对于复杂的表达式,一般建议按照以下操作去修改,方便理解:
- 尽量不要在一个表达式中混合使用多种不同优先级的操作符
- 不要写过于复杂的表达式,而是建议把它分成几个简单表达式
- 不要过多地依赖操作符的优先级来控制表达式的执行顺序,尽量使用小括号
()
来控制表达式的执行顺序
就比如上面的表达式,为了方便理解,可以给它加上小括号:
1 | ((--a) > 0) || ((++b) <= 1 && (c * d) > 100) |
虽然小括号不是必须的,但是加上之后,表达式理解起来反而更加容易了,很简单就能识别出各个部分的优先级。
二、关系操作符
关系操作符是用于计算操作数之间的关系,其结果是一个布尔值,即 true
或 false
。
关系操作符包括以下几个:
运算符 | 含义 | 例子 |
---|---|---|
> | 大于 | a > b |
< | 小于 | a < b |
>= | 大于等于 | a >= b |
<= | 小于等于 | a <= b |
== | 等于 | a == b |
!= | 不等于 | a != b |
这个关系运算,和平时数学里的值比较差不多,需要注意的有几个地方:
- 等于操作符(
==
、!=
)可以作用于所有基本数据类型 - 其他的大小比较操作符(
>
、<
、>=
、<=
)能作用于除布尔基本类型外的其他基本数据类型。因为布尔值只有true
和false
这2种,不存在大小关系,没有意义
特别注意,等于操作符(==
、!=
)作用在基本数据类型和对象上时的效果是不一样的:
- 作用在基本数据类型上,比较的就是数据的真实值
- 作用在对象上,实际比较的是对象的引用地址,而不是对象的值
举个栗子来说:
1 | int a = 10; |
d和e都是对象,==
比较的是对象引用地址,而不是对象的值,所以表达式 d == e
的返回值是 false
。因为 d
和 e
是2个对象,虽然这2个对象的数值是相等的,但是它们的引用地址是不同的。
再举个栗子,如果同时对比基本数据类型和包装类型对象的话:
1 | int a = 1000; |
最终c的结果为true
,说明了在对基本数据类型和包装类型进行比较时,包装类型对象会自动拆箱成基本数据类型,然后再比较2个基本数据类型的值。
所以,判断操作数的关系时,需要注意:
- 比较对象的值,一般不用
==
,而是用equals()
方法 - 比较基本数据类型的值,
equals()
方法用不了,直接用==
简单来说,不要用 ==
和 !=
去判断对象就对了。
还有,不要将等于操作符(==
、!=
)作用在浮点数(float
、double
)上,由于计算机内存放的浮点数与实际的实数存在着一定的误差,如果对浮点数进行 ==(相等)或 !=(不相等)的比较,容易产生错误结果。
三、逻辑操作符
逻辑操作符是对布尔类型变量/布尔表达式进行运算,其运算结果也是布尔类型值,即 true
或 false
。
注意,逻辑操作符只能操作布尔值(布尔变量、关系表达式)。
逻辑操作符包括以下几个:
运算符 | 含义 | 例子 |
---|---|---|
! | 逻辑非 | !a |
&& | 逻辑与 | a && b |
|| | 逻辑或 | a || b |
& | 与 | a & b |
| | 或 | a | b |
本来 &
和 |
不算在逻辑操作符内的,不过它也能用来进行逻辑判断,所以姑且放在这里。
逻辑运算符的作用是,把各部分的运算关系表达式连接起来,组成一个复杂的逻辑表达式。
比如说:
1 | boolean b = a > 0 && b < 0 || !(c < 0); |
通过逻辑操作符将 a > 0
、b < 0
、c < 0
这几个简单连接成了一个复杂的逻辑表达式。
逻辑操作符 &&
、||
具有“短路”现象,即一旦能够明确整个表达式的值,就不再计算表达式剩余的部分了。
举个栗子:
1 | boolean a = 1 < 2 && 3 < 4 || 5 > 6; // a = true |
这条表达式,它是这样计算的:
- 首先计算
1 < 2
,结果是true
; - 其次计算
3 < 4
,结果是true
; - 发现后面是逻辑或运算,此时前面的表达式
1 < 2 && 3 < 4
的结果已经是true
了,所以就明确肯定整条表达式的值必然是true
(因为true
逻辑或任何值,结果都是true
),这个时候就不再计算表达式剩余部分5 > 6
的值了,而是直接返回true
给 a。
表达式计算只计算到 1 < 2 && 3 < 4
就返回了,后面的 5 > 6
无需计算。这种行为就是“短路”现象,通过提前返回结果,减少表达式的判断,优化性能。
不过,&
和 |
就没有“短路”操作了,它会执行完整个表达式,所以一般情况下,不会在逻辑表达式中用 &
和 |
,因为它们没有短路优化,但是结果却与 &&
和 ||
是一样的。
一般建议,不要用 &
和 |
来做逻辑判断,这2个操作符主要还是用在位操作上。
四、条件操作符
条件操作符一个三元运算符,表示 if-then-else
行为:
运算符 | 含义 | 例子 |
---|---|---|
?: | if-then-else | a > 0 ? b : c |
举个例子:
1 | int a = 10; // a = 10 |
条件操作符就是一个判断语句,还是比较好理解的:
- 首先判断
a > 0
; - 若
a > 0
为true
,则执行?
后的语句,即a + 1
; - 若
a > 0
为false
,则执行:
后的语句,即a - 1
。
条件操作符和平时写的条件判断 if-else
差不多,相当于它的简写形式。
五、算术操作符
Java 中的算术操作符主要用来对数值类型数据进行算术运算。
按照算术运算中涉及的操作数量,可以分为一元运算符和二元运算符。
5.1 一元运算符
一元运算符包括以下几个:
运算符 | 含义 | 例子 |
---|---|---|
- | 一元减,取反符号 | -a |
+ | 一元加 | +a |
– | 自减一 | a–、–a |
++ | 自增一 | a++、++a |
一元减号 -
用于转变数据的符号,比如正变负,负变正。一元加号 +
只是为了与一元减号相对应,实际作用不大。不过它们有一个特点:
- 一元运算符
-
、+
会先将小整数类型(byte
、char
、short
)提升为int
类型,再执行运算
实际应用效果就类似下面这样子:
1 | int a = 10; // a = 10 |
自增操作符可分为前缀式(--a
)和后缀式(a--
),前缀式是先计算,再返回值;而后缀式则是先返回值,再计算:
1 | int a = 10; // a = 10 |
在表达式中使用前缀式和后缀式时,需要特别注意它们的返回值,要清楚知道表达式的返回值是多少。
不过一般建议使用小括号把它们括起来,这样就无需过多在意前后缀表达式的返回值了。
5.2 二元运算符
二元运算符包括以下几个:
运算符 | 含义 | 例子 |
---|---|---|
+ | 加 | a + b |
- | 减 | a - b |
* | 乘 | a * b |
/ | 除 | a / b |
% | 取余 | a % b |
这个就是平时的基本数学运算,理解起来很简单。
有点不同的就是,Java 中的整数除法会直接去掉小数位来取整,而不是常见的四舍五入。
1 | int a = 10; // a = 10 |
注意整数除法是直接去掉小数位数据,既不是向上/向下取整,也不是四舍五入。
另外,二元运算符也会导致小整数类型(byte
、char
、short
)提升为 int
类型:
1 | short a = 10; |
一般来说,只要是涉及到数学运算的,小整数类型(byte
、char
、short
)都会先提升为 int
类型,再运算。
当然,除了自增(++
)和自减(--
)以外。
六、位操作符
位操作符是直接对整数类型的位进行操作,类型包括 long
,int
,short
,char
和 byte
。
注意,位操作只对整数起作用,浮点数 float
和 double
不能进行位操作(因为它们是按 IEEE754 标准保存的,位运算的意义不是很大)。
按照意义,位操作符可分为2种:
- 按位运算符:对2个操作数对应的位执行布尔代数操作
- 移位运算符:直接对操作数的位移动,并用0/1补充移动后空出来的位
下面分别介绍2种操作符类型。
6.1 按位运算符
按位运算符包括以下几个:
运算符 | 含义 | 例子 |
---|---|---|
& | 按位与(AND) | a & b |
| | 按位或(OR) | a | b |
^ | 按位异或(XOR) | a ^ b |
~ | 按位取反(NOT) | ~a |
按位运算符是直接对位进行操作的,比如:
1 | int a = 10; |
它的一个实际运算过程是这样的,会按照操作数的每一个位进行布尔运算:
1 | 00000000000000000000000000001010 // a = 10 |
注意,布尔类型也可以进行按位运算,但是只能执行 &
、|
、^
这3种操作,而没有按位取反 ~
操作。
至于没有按位取反操作的原因,可能有以下几个:
- 布尔类型只有
true
和false
这2个值,而布尔类型的位数是不确定的,在这种情况下执行按位取反,有可能会出现非布尔类型值- 假设布尔类型是8位,其中
10000000
表示true
,00000001
表示false
,在这种情况下按位取反的话,true
取反后的值并不是false
,而且也不是布尔类型值了 - 除非布尔类型只有1位,它的按位取反才显得正常,因为只有1位的话,就肯定只会有2个值,取反后肯定是相反值,也就不会出现非布尔类型值了
- 假设布尔类型是8位,其中
- 还有可能是为了避免和逻辑非(
!
)混淆,因为布尔值只有2个,所以从理论上来说,按位取反(~
)和逻辑非(!
)的结果是一样的
综上,由于某些原因,布尔类型并没有按位取反(~
)的操作。
6.2 移位运算符
移位运算符包括以下几个:
运算符 | 含义 | 例子 |
---|---|---|
<< | 左移(高位移除、低位补0) | a << 1 |
>> | 有符号右移(高位补符号位、低位移除) | a >> 2 |
>>> | 无符号右移(高位补0、低位移除) | a >>> 3 |
有符号右移和无符号右移,区别就在于高位的补充是什么值。举2个例子:
1 | # b = a >> 2 |
1 | # b = a >>> 2 |
有符号右移(>>
),高位会补充符号位(负数补 1,其他补 0),无符号右移(>>>
)高位统一补 0。
另外,移位操作还有几个特点:
- 小整数类型
byte
、char
、short
执行移位操作前,会先提升为int
类型,再进行移位 int
移位只取操作数的低 5 位数值(2 的 5 次方是 32,也就是int
的位数)long
移位只取操作数的低 6 位数值(2 的 6 次方是 64,也就是long
的位数)
也就是说,小整数类型进行移位操作会出现这种情况:
1 | short a = 10; |
因为移位操作会把 short
提升为 int
,所以最后的运算结果需要强制类型转换。
至于移位操作数的值超出 int
或 long
的位数时,就需要特殊处理:
1 | int a = 10; // a = 1010 |
这种情况,55 明显超出了 int
的 32 位,所以它实际上移位的数值是 55(110111) 的低 5 位,也就是 23(10111),也就是 a << 55
等价于 a << 23
。
同样地,如果移位操作数是负数,也是取低5位(long
取低6位):
1 | int a = 10; // a = 1010 |
55 的低 5 位是 9(1001
),所以 a << -55
等价于 a << 9
。
截取低位来运算,是为了避免移位超出类型的位数,徒做无用功。
七、赋值操作符
赋值操作符就比较简单了,就是给变量赋值。
赋值操作符包括以下几个:
运算符 | 含义 | 例子 |
---|---|---|
= | 直接赋值 | a = 1 |
+= | 先加,再赋值 | a += 2 |
-= | 先减,再赋值 | a -= 3 |
*= | 先乘,再赋值 | a *= 3 |
/= | 先除,再赋值 | a /= 3 |
&= | 先按位与,再赋值 | a &= 3 |
|= | 先按位或,再赋值 | a |= 3 |
^= | 先按位异或,再赋值 | a ^= 3 |
<<= | 先左移位,再赋值 | a <<= 3 |
>>= | 先有符号右移位,再赋值 | a >>= 3 |
>>>= | 先无符号右移位,再赋值 | a >>>= 3 |
除了赋值操作符 =
以外,其他的都是复合操作符,也就是赋值操作和运算操作联合在一起形成的。
除了一元操作符(--
、++
、~
),凡是运算相关的操作符(不包括关系、逻辑、条件),基本上都能和 =
联合形成复合操作符。
不过这种复合操作符的优先级是比较低的(和 =
的优先级一样),举个例子来说:
1 | a *= 1 + 2; |
正常来说,乘法 *
优先级肯定比加法 +
高的,但实际上的它却等价于:
1 | a = a * (1 + 2); |
所以说,复合操作符的优先级比较低,和直接赋值 =
优先级一样。
总结
操作符种类:
- 关系操作符:>、<、>=、<=、==、!=
- 逻辑操作符:!、&、|、&&、||
- 条件操作符:?:
- 算术操作符:-、++、–、+、-、*、/、%
- 位操作符:~、&、|、^、<<、>>、>>>
- 赋值操作符:=、+=、-=、*=、/=、&=、|=、^=、<<=、>>=、>>>=
操作符优先级:
- 操作符的优先级共分为 14 级,其中 1 级最高,14 级最低
- 尽量不要在一个表达式中混合使用多种不同优先级的操作符
- 不要写过于复杂的表达式,而是建议把它分成几个简单表达式
- 不要过多地依赖操作符的优先级来控制表达式的执行顺序,尽量使用小括号
()
来控制表达式的执行顺序
关系操作符:
- 等于操作符(
==
、!=
)可以作用于所有基本数据类型 - 其他的大小比较操作符(
>
、<
、>=
、<=
)能作用于除布尔基本类型外的其他基本数据类型。因为布尔值只有true
和false
这2种,不存在大小关系,没有意义 - 作用在基本数据类型上,比较的就是数据的真实值
- 作用在对象上,实际比较的是对象的引用地址,而不是对象的值
- 比较对象的值,一般不用
==
,而是用equals()
方法 - 比较基本数据类型的值,
equals()
方法用不了,直接用==
逻辑操作符:
- 逻辑操作符
&&
、||
具有“短路”现象,即一旦能够明确整个表达式的值,就不再计算表达式剩余的部分了 &
和|
就没有“短路”操作了,它会执行完整个表达式- 一般建议,不要用
&
和|
来做逻辑判断,这2个操作符主要还是用在位操作上
条件操作符:
- 条件操作符和平时写的条件判断
if-else
差不多,相当于它的简写形式
算术操作符:
- 按照算术运算中涉及的操作数量,可以分为一元运算符和二元运算符
- 自增操作符可分为前缀式(
--a
)和后缀式(a--
),前缀式是先计算,再返回值;而后缀式则是先返回值,再计算 - 注意整数除法是直接去掉小数位数据,既不是向上/向下取整,也不是四舍五入
- 一般来说,凡是涉及到数学运算的,小整数类型(
byte
、char
、short
)都会先提升为int
类型,再运算
位操作符:
- 位操作符是直接对整数类型的位进行操作,类型包括
long
,int
,short
,char
和byte
- 位操作只对整数起作用,浮点数
float
和double
不能进行位操作(因为它们是按 IEEE754 标准保存的,位运算的意义不是很大) - 按照意义,位操作符可分为2种:
- 按位运算符:对2个操作数对应的位执行布尔代数操作
- 移位运算符:直接对操作数的位移动,并用0/1补充移动后空出来的位
- 布尔类型也可以进行按位运算,但是只能执行
&
、|
、^
这3种操作,而没有按位取反~
操作 - 小整数类型
byte
、char
、short
执行移位操作前,会先提升为int
类型,再进行移位 int
移位只取操作数的低 5 位数值(2 的 5 次方是 32,也就是int
的位数)long
移位只取操作数的低 6 位数值(2 的 6 次方是 64,也就是long
的位数)
赋值操作符:
- 除了赋值操作符
=
以外,其他的都是复合操作符,也就是赋值操作和运算操作联合在一起形成的 - 除了一元操作符(
--
、++
、~
),凡是运算相关的操作符(不包括关系、逻辑、条件),基本上都能和=
联合形成复合操作符 - 复合操作符的优先级是比较低的(和
=
的优先级一样)