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

然后根据表中的优先级,它的实际计算顺序应该是这样的:

  1. 先计算a的自减,即 --a
  2. 再计算 --a > 0 的结果
  3. 再计算b的自增,即 ++b
  4. 接着计算 ++b <= 1 的结果
  5. 然后计算c、d的乘法,即 c * d
  6. 再计算 c * d > 100 的结果
  7. 由于 && 优先级比 || 高,所以再接着是计算 ++b <= 1 && c * d > 100 的结果
  8. 最后则是将2的结果和7的结果进行逻辑或(||)运算,即计算 --a > 0 || ++b <= 1 && c * d > 100 的结果

实际上,不建议直接写这种混合了多种优先级的表达式,因为一旦混合在一起后,就很难识别它们的优先级了。

对于复杂的表达式,一般建议按照以下操作去修改,方便理解:

  • 尽量不要在一个表达式中混合使用多种不同优先级的操作符
  • 不要写过于复杂的表达式,而是建议把它分成几个简单表达式
  • 不要过多地依赖操作符的优先级来控制表达式的执行顺序,尽量使用小括号 () 来控制表达式的执行顺序

就比如上面的表达式,为了方便理解,可以给它加上小括号:

1
((--a) > 0) || ((++b) <= 1 && (c * d) > 100)

虽然小括号不是必须的,但是加上之后,表达式理解起来反而更加容易了,很简单就能识别出各个部分的优先级。

二、关系操作符

关系操作符是用于计算操作数之间的关系,其结果是一个布尔值,即 truefalse

关系操作符包括以下几个:

运算符 含义 例子
> 大于 a > b
< 小于 a < b
>= 大于等于 a >= b
<= 小于等于 a <= b
== 等于 a == b
!= 不等于 a != b

这个关系运算,和平时数学里的值比较差不多,需要注意的有几个地方:

  • 等于操作符(==!=)可以作用于所有基本数据类型
  • 其他的大小比较操作符(><>=<=)能作用于除布尔基本类型外的其他基本数据类型。因为布尔值只有 truefalse 这2种,不存在大小关系,没有意义

特别注意,等于操作符(==!=)作用在基本数据类型和对象上时的效果是不一样的:

  • 作用在基本数据类型上,比较的就是数据的真实值
  • 作用在对象上,实际比较的是对象的引用地址,而不是对象的值

举个栗子来说:

1
2
3
4
5
6
7
int a = 10;
int b = 10;
boolean c = a == b; // c = true,因为a和b都是基本数据类型,它们的值是相等的

Integer d = 1000;
Integer e = 1000;
boolean f = d == e; // f = false,因为d和e都是对象,==比较的是对象引用地址,而不是对象的值

d和e都是对象,== 比较的是对象引用地址,而不是对象的值,所以表达式 d == e 的返回值是 false。因为 de 是2个对象,虽然这2个对象的数值是相等的,但是它们的引用地址是不同的。

再举个栗子,如果同时对比基本数据类型和包装类型对象的话:

1
2
3
int a = 1000;
Integer b = 1000;
boolean c = a == b; // c = true

最终c的结果为true,说明了在对基本数据类型和包装类型进行比较时,包装类型对象会自动拆箱成基本数据类型,然后再比较2个基本数据类型的值。

所以,判断操作数的关系时,需要注意:

  • 比较对象的值,一般不用 ==,而是用 equals() 方法
  • 比较基本数据类型的值,equals() 方法用不了,直接用 ==

简单来说,不要用 ==!= 去判断对象就对了。

还有,不要将等于操作符(==!=)作用在浮点数(floatdouble)上,由于计算机内存放的浮点数与实际的实数存在着一定的误差,如果对浮点数进行 ==(相等)或 !=(不相等)的比较,容易产生错误结果。

三、逻辑操作符

逻辑操作符是对布尔类型变量/布尔表达式进行运算,其运算结果也是布尔类型值,即 truefalse

注意,逻辑操作符只能操作布尔值(布尔变量、关系表达式)。

逻辑操作符包括以下几个:

运算符 含义 例子
! 逻辑非 !a
&& 逻辑与 a && b
|| 逻辑或 a || b
& a & b
| a | b

本来 &| 不算在逻辑操作符内的,不过它也能用来进行逻辑判断,所以姑且放在这里。

逻辑运算符的作用是,把各部分的运算关系表达式连接起来,组成一个复杂的逻辑表达式。

比如说:

1
boolean b = a > 0 && b < 0 || !(c < 0);

通过逻辑操作符将 a > 0b < 0c < 0 这几个简单连接成了一个复杂的逻辑表达式。

逻辑操作符 &&|| 具有“短路”现象,即一旦能够明确整个表达式的值,就不再计算表达式剩余的部分了。

举个栗子:

1
boolean a = 1 < 2 && 3 < 4 || 5 > 6; // a = true

这条表达式,它是这样计算的:

  1. 首先计算 1 < 2,结果是 true
  2. 其次计算 3 < 4,结果是 true
  3. 发现后面是逻辑或运算,此时前面的表达式 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
2
int a = 10;                    // a = 10
int b = a > 0 ? a + 1 : a - 1; // b = 11

条件操作符就是一个判断语句,还是比较好理解的:

  1. 首先判断 a > 0
  2. a > 0true,则执行 ? 后的语句,即 a + 1;
  3. a > 0false,则执行 : 后的语句,即 a - 1

条件操作符和平时写的条件判断 if-else 差不多,相当于它的简写形式。

五、算术操作符

Java 中的算术操作符主要用来对数值类型数据进行算术运算。

按照算术运算中涉及的操作数量,可以分为一元运算符和二元运算符。

5.1 一元运算符

一元运算符包括以下几个:

运算符 含义 例子
- 一元减,取反符号 -a
+ 一元加 +a
自减一 a–、–a
++ 自增一 a++、++a

一元减号 - 用于转变数据的符号,比如正变负,负变正。一元加号 + 只是为了与一元减号相对应,实际作用不大。不过它们有一个特点:

  • 一元运算符 -+ 会先将小整数类型(bytecharshort)提升为 int 类型,再执行运算

实际应用效果就类似下面这样子:

1
2
3
4
5
int a = 10;            // a = 10
int b = -a; // b = -10
byte c = 10; // c(byte) = 10
short d = (short) +c; // d(short) = 10,+号会自动提升操作数类型为int,所以这里需要强制转换为short
short e = (short) -c; // e(short) = 10,-号会自动提升操作数类型为int,所以这里需要强制转换为short

自增操作符可分为前缀式(--a)和后缀式(a--),前缀式是先计算,再返回值;而后缀式则是先返回值,再计算:

1
2
3
4
int a = 10;  // a = 10
int b = 20; // b = 20
int c = --a; // c = 9,a = 9,先计算自减--,再返回值
int d = b--; // d = 20,b = 19,先返回值,再计算自减--

在表达式中使用前缀式和后缀式时,需要特别注意它们的返回值,要清楚知道表达式的返回值是多少。

不过一般建议使用小括号把它们括起来,这样就无需过多在意前后缀表达式的返回值了。

5.2 二元运算符

二元运算符包括以下几个:

运算符 含义 例子
+ a + b
- a - b
* a * b
/ a / b
% 取余 a % b

这个就是平时的基本数学运算,理解起来很简单。

有点不同的就是,Java 中的整数除法会直接去掉小数位来取整,而不是常见的四舍五入。

1
2
3
4
5
int a = 10;     // a = 10
int b = a / 3; // b = 3,会直接去掉小数位

int c = -10; // c = -10
int d = c / 3; // d = -3,会直接去掉小数位

注意整数除法是直接去掉小数位数据,既不是向上/向下取整,也不是四舍五入。

另外,二元运算符也会导致小整数类型(bytecharshort)提升为 int 类型:

1
2
3
4
5
short a = 10;
a = (short) (a + 1); // a 会先提升为 int 类型,再执行运算
a = (short) (a * 2); // a 会先提升为 int 类型,再执行运算

short b = --a; // 注意,自增/自减不会提升为 int 类型,或者说底层已经帮我们转好类型了

一般来说,只要是涉及到数学运算的,小整数类型(bytecharshort)都会先提升为 int 类型,再运算。

当然,除了自增(++)和自减(--)以外。

六、位操作符

位操作符是直接对整数类型的位进行操作,类型包括 longintshortcharbyte

注意,位操作只对整数起作用,浮点数 floatdouble 不能进行位操作(因为它们是按 IEEE754 标准保存的,位运算的意义不是很大)。

按照意义,位操作符可分为2种:

  • 按位运算符:对2个操作数对应的位执行布尔代数操作
  • 移位运算符:直接对操作数的位移动,并用0/1补充移动后空出来的位

下面分别介绍2种操作符类型。

6.1 按位运算符

按位运算符包括以下几个:

运算符 含义 例子
& 按位与(AND) a & b
| 按位或(OR) a | b
^ 按位异或(XOR) a ^ b
~ 按位取反(NOT) ~a

按位运算符是直接对位进行操作的,比如:

1
2
3
int a = 10;
int b = -20;
int c = a ^ b;

它的一个实际运算过程是这样的,会按照操作数的每一个位进行布尔运算:

1
2
3
4
   00000000000000000000000000001010        // a = 10
^ 11111111111111111111111111101100 // b = -20
-------------------------------------
11111111111111111111111111100110 // c = -26

注意,布尔类型也可以进行按位运算,但是只能执行 &|^ 这3种操作,而没有按位取反 ~ 操作。

至于没有按位取反操作的原因,可能有以下几个:

  • 布尔类型只有 truefalse 这2个值,而布尔类型的位数是不确定的,在这种情况下执行按位取反,有可能会出现非布尔类型值
    • 假设布尔类型是8位,其中 10000000 表示 true00000001 表示 false,在这种情况下按位取反的话,true 取反后的值并不是 false,而且也不是布尔类型值了
    • 除非布尔类型只有1位,它的按位取反才显得正常,因为只有1位的话,就肯定只会有2个值,取反后肯定是相反值,也就不会出现非布尔类型值了
  • 还有可能是为了避免和逻辑非(!)混淆,因为布尔值只有2个,所以从理论上来说,按位取反(~)和逻辑非(!)的结果是一样的

综上,由于某些原因,布尔类型并没有按位取反(~)的操作。

6.2 移位运算符

移位运算符包括以下几个:

运算符 含义 例子
<< 左移(高位移除、低位补0) a << 1
>> 有符号右移(高位补符号位、低位移除) a >> 2
>>> 无符号右移(高位补0、低位移除) a >>> 3

有符号右移和无符号右移,区别就在于高位的补充是什么值。举2个例子:

1
2
3
4
5
# b = a >> 2

>> 11111111111111111111111111110110 // a = -10
-------------------------------------
11111111111111111111111111111101 // b = a >> 2
1
2
3
4
5
# b = a >>> 2

>>> 11111111111111111111111111110110 // a = -10
-------------------------------------
00111111111111111111111111111101 // b = a >>> 2

有符号右移(>>),高位会补充符号位(负数补 1,其他补 0),无符号右移(>>>)高位统一补 0。

另外,移位操作还有几个特点:

  • 小整数类型 bytecharshort 执行移位操作前,会先提升为 int 类型,再进行移位
  • int 移位只取操作数的低 5 位数值(2 的 5 次方是 32,也就是 int 的位数)
  • long 移位只取操作数的低 6 位数值(2 的 6 次方是 64,也就是 long 的位数)

也就是说,小整数类型进行移位操作会出现这种情况:

1
2
short a = 10;
a = (short) (a >> 1); // 返回值要强制类型转换

因为移位操作会把 short 提升为 int,所以最后的运算结果需要强制类型转换。

至于移位操作数的值超出 intlong 的位数时,就需要特殊处理:

1
2
int a = 10;      // a = 1010
int b = a << 55; // b = 101000000000000000000000000, 55 = 110111

这种情况,55 明显超出了 int 的 32 位,所以它实际上移位的数值是 55(110111) 的低 5 位,也就是 23(10111),也就是 a << 55 等价于 a << 23

同样地,如果移位操作数是负数,也是取低5位(long 取低6位):

1
2
int a = 10;       // a = 1010
int b = a << -55; // b = 1010000000000, -55 = 11111111111111111111111111001001

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 级最低
  • 尽量不要在一个表达式中混合使用多种不同优先级的操作符
  • 不要写过于复杂的表达式,而是建议把它分成几个简单表达式
  • 不要过多地依赖操作符的优先级来控制表达式的执行顺序,尽量使用小括号 () 来控制表达式的执行顺序

关系操作符:

  • 等于操作符(==!=)可以作用于所有基本数据类型
  • 其他的大小比较操作符(><>=<=)能作用于除布尔基本类型外的其他基本数据类型。因为布尔值只有 truefalse 这2种,不存在大小关系,没有意义
  • 作用在基本数据类型上,比较的就是数据的真实值
  • 作用在对象上,实际比较的是对象的引用地址,而不是对象的值
  • 比较对象的值,一般不用 ==,而是用 equals() 方法
  • 比较基本数据类型的值,equals() 方法用不了,直接用 ==

逻辑操作符:

  • 逻辑操作符 &&|| 具有“短路”现象,即一旦能够明确整个表达式的值,就不再计算表达式剩余的部分了
  • &| 就没有“短路”操作了,它会执行完整个表达式
  • 一般建议,不要用 &| 来做逻辑判断,这2个操作符主要还是用在位操作上

条件操作符:

  • 条件操作符和平时写的条件判断 if-else 差不多,相当于它的简写形式

算术操作符:

  • 按照算术运算中涉及的操作数量,可以分为一元运算符和二元运算符
  • 自增操作符可分为前缀式(--a)和后缀式(a--),前缀式是先计算,再返回值;而后缀式则是先返回值,再计算
  • 注意整数除法是直接去掉小数位数据,既不是向上/向下取整,也不是四舍五入
  • 一般来说,凡是涉及到数学运算的,小整数类型(bytecharshort)都会先提升为 int 类型,再运算

位操作符:

  • 位操作符是直接对整数类型的位进行操作,类型包括 longintshortcharbyte
  • 位操作只对整数起作用,浮点数 floatdouble 不能进行位操作(因为它们是按 IEEE754 标准保存的,位运算的意义不是很大)
  • 按照意义,位操作符可分为2种:
    • 按位运算符:对2个操作数对应的位执行布尔代数操作
    • 移位运算符:直接对操作数的位移动,并用0/1补充移动后空出来的位
  • 布尔类型也可以进行按位运算,但是只能执行 &|^ 这3种操作,而没有按位取反 ~ 操作
  • 小整数类型 bytecharshort 执行移位操作前,会先提升为 int 类型,再进行移位
  • int 移位只取操作数的低 5 位数值(2 的 5 次方是 32,也就是 int 的位数)
  • long 移位只取操作数的低 6 位数值(2 的 6 次方是 64,也就是 long 的位数)

赋值操作符:

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

jiaduo

发布于

2021-10-15

更新于

2023-04-03

许可协议