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的位数)
赋值操作符:
- 除了赋值操作符
=以外,其他的都是复合操作符,也就是赋值操作和运算操作联合在一起形成的 - 除了一元操作符(
--、++、~),凡是运算相关的操作符(不包括关系、逻辑、条件),基本上都能和=联合形成复合操作符 - 复合操作符的优先级是比较低的(和
=的优先级一样)