运算符
# 定义
运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。
- 算术运算符
- 赋值运算符
- 比较运算符(关系运算符)
- 逻辑运算符
- 位运算符
- 三元运算符
其中,位运算符,在实际开发中用得很少。但看系统源码的时候,会有位运算符,所以还是需要去了解。
# 算术运算符
# 算术运算符概览
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | b=4;-b | -4 |
+ | 加 | 5+5 | 10 |
- | 减 | 6-4 | 2 |
* | 乘 | 3*4 | 12 |
/ | 除 | 5/5 | 1 |
% | 取模(取余) | 7%5 | 2 |
前缀++ | 自增前:先运算再取值 | a=2;b=++a | a=3;b=3 |
后缀++ | 自增后:先取值后运算 | a=2;b=a++; | a=3;b=2 |
前缀— | 自减前:先运算后取值 | a=2;b=— a; | a=1;b=1 |
后缀— | 自减后:先取值后运算 | a=2;b=a— | a=1;b=2 |
+ | 字符串连接 | "He"+"llo" | "Hello" |
在Java中
# 除号 /
整数12/整数5结果是?
int num1 = 12;
int num2 = 5;
int result1 = num1 / num2; //2
2
3
整形会截尾整数,所以结果是2
接下来在上面案例中,再加上
int result2 = num1 / num2 * num2;
结果是10。
猜猜下面的结果:
double result3 = num1 / num2
结果是2.0。
为什么?
因为先算num1 / num2
,得出的结果是2
,然后把2赋值给result3
那么不改变num1和num2的情况下,如何能达到精确结果?
double result4 = num1 / (num2 + 0.0); // 2.4
double result5 = (double)num1 / num2; // 2.4
- 小结
只要把其中一个转换为double
,就可以求得到精确结果。
# 取模 %
取模其实就是取余数,比如7/5=1余2,所以取出2作为结果。开发中,经常使用%来判断能否被除尽的情况。
比如:取模2,就可以判断奇偶数,10%2=0,偶数,11%2=1,奇数。
在取模运算中,除数叫模数,被除数叫被模数。例如10%2=0,这个公式中,10是被模数,2是模数。跟除数和被除数的概念一样。
来看看下面的案例,每个result的结果分别是多少?
int m1 = 12;
int n1 = 5;
int result1 = m1 % n1;
int m2 = -12;
int n2 = 5;
int result2 = m2 % n2;
int m3 = 12;
int n3 = -5;
int result3 = m3 % n3;
int m4 = -12;
int n4 = -5;
int result1 = m4 % n4;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
12 % 5 = 2
-12 % 5 = -2
12 % -5 = 2
-12 % -5 = -2
2
3
4
- 小结
取模结果的符号与被模数的符号一致。
# 前缀++和后缀++
a1,b1和a2,b2分别是多少?
int a1 = 10;
int b1 = ++a1;
int a2 = 10;
int b2 = a2+=;
2
3
4
5
a1 = 11,b1 = 11;
a2 = 11,b2 = 10;
2
- 小结:
前缀++先自增1,然后再运算。
后缀++先运算,然后再自增1。
a3和b3分别是多少?
int a3 = 10;
a3++;
int b3 = a3;
2
3
a3=11,b3=11
a4和b4分别是多少?
int a4 = 10;
++a4;
int b4 = a4;
2
3
a4=11,b4=11
如果单独一行写自增运算,其实没有区别,也不会影响后面的赋值运算。
- 注意点
short s1 = 10;
s1 = s1 + 1;//编译失败
s1 = (short)(s1 + 1);//正确的
s1++;//编译通过
2
3
4
小结:自增1不会改变本身变量的数据类型。
- 问题
下面能编译通过吗?会转换为int?结果是多少?
byte b1 = 127;
b1++;
2
编译会通过,因为自增1不会改变本身变量的数据类型。
结果是-128,因为已经超出byte的范围了。
用二进制的方式解释:下面是127的二进制图
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|
当自增1后
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|
由于第一位是符号位,所以变成-128。
# 前缀--
和后缀--
前缀--
:先自减1,后运算
后缀--
:先运算,后子减1
跟前缀++和后缀++类似,可以触类旁通。
int a1 = 10;
int b1 = --a1;
2
结果a1=9,b1=9。
int a2 = 10;
int b2 = a2--;
2
结果a2=9,b2=10。
# 练习1
int i1 = 10;
int i2 = 20;
int i = i1++;
System.out.print("i="+i);//?
System.out.print("i1="+i1);//?
i = ++i1;
System.out.print("i="+i);//?
System.out.print("i1="+i1);//?
i = i2--;
System.out.print("i="+i);//?
System.out.print("i2="+i2);//?
i = --i2;
System.out.print("i="+i);//?
System.out.print("i2="+i2);//?
2
3
4
5
6
7
8
9
10
11
12
13
14
i=10;i1=11;
i=12;i1=12;
i=20;i2=19;
i=18;i2=18;
2
3
4
# 练习2
随意给出一个整数,打印显示它的个位数,十位数,百位数的值。
格式如下:
数字XXX的情况如下:
个位数:
十位数:
百位数:
例如:
数字153的情况如下:
个位数:3
十位数:5
百位数:1
class AreExer{
public statis void main(String[] args){
int num = 187;
int bai = num / 100;
int shi = num % 100 / 10; // int shi = num / 10 % 10
int ge = num % 10;
System.out.println("数字"+num+"的情况如下:");
System.out.println("个位数:"+ge);
System.out.println("十位数:"+shi);
System.out.println("百位数:"+bai);
}
}
2
3
4
5
6
7
8
9
10
11
12
# 赋值运算符
- 符号:=
- 当“=”两侧数据类型不一致时,可以使用自动类型转换或使用枪支类型转换原则进行处理。
- 支持持续赋值。
- 拓展赋值运算符:
+=
,-=
,*=
,/=
,%=
# 演示
int i1 = 10;
int j1 = 10;
//连续赋值
int i2,j2;
i2 = j2 = 10;
int i3 = 10, j3 = 10;
int num1 =10;
num += 2;//num1 = num + 2;
System.out.println(num1);
int num2 = 12;
num2 %= 5;//num2 = num2 % 5;
System.out.println(num2);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 案例1
- 拓展赋值运算符不会改变本身的数据类型
short s1 = 10;
//s1 = s1 + 2;//编译失败
s1 += 2;//这种写法不会改变本身的数据类型
System.out.println(s1;)
2
3
4
# 案例2
开发中,如果希望变量实现+2的操作,有几种方法?(前提:int num = 10)
有两种方法:
num = num + 2;
num += 2;//推荐
2
# 案例3
开发中,如果希望变量实现+1的操作,有几种方法?(前提:int num = 10)
有三种方法:
num = num +1;
num += 1;
num++;//推荐
2
3
# 案例4
思考,第一种写法和第二种写法有什么区别?
short s = 2;
s = s + 2;
s += 2;
2
3
第一种编译不通过。
# 案例5
思考,下面案例编译能通过吗?结果是?
int i = 1;
i *= 0.1;
System.out.println(i);
i++;
System.out.println(i);
2
3
4
5
编译能过,拓展赋值运算符不会改变本身的数据类型。
第一次结果是0,第二次结果是1。
# 案例6
思考,下面案例的m,n的结果是?
int m = 2;
int n = 3;
n *= m++;
2
3
n *= m++; => n = n * m++;
m=3,n=6
# 案例7
思考,下面案例n的结果是?
int n = 10;
n += (n++) + (++n);
2
可以转换为
n = n + (n++) + (++n)
=>
10 + 10 + 12 = 32
2
3
这个12是因为
第一个括号内n++所以变成11
第二个括号先自增,所以再加1 变成 12
# 比较运算符
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
== | 相等于 | 4==3 | false |
!= | 不等于 | 4!=3 | true |
< | 小于 | 4<3 | false |
> | 大于 | 4>3 | true |
<= | 小于或等于 | 4<=3 | false |
>= | 大于或等于 | 4>=3 | true |
instanceof | 检查是否是类的对象 | "Hello" instanceof String | true |
- 比较运算符的结果都是
boolean
型,也就是要么是true
,要么是false
。 - 比较运算符
==
不能误写成=
# 案例1
int i = 10;
int j = 20;
System.out.println(i == j); // false
System.out.println(i = j); //20,
2
3
4
先将j赋值给i,然后输出i
# 案例2
boolean b1 = true;
boolean b2= false;
System.out.println(b2 == b1);//fasle
System.out.println(b2 = b1);//true
2
3
4
# 逻辑运算符
逻辑运算符都是操作boolean
类型的变量。
&
:逻辑与|
:逻辑或!
:逻辑非&&
:短路与||
:短路或^
:落于异或
a | b | a&b | a&&b | a|b | a||b | !a | a^b |
---|---|---|---|---|---|---|---|
true | true | true | true | true | true | false | false |
true | false | false | false | true | true | false | true |
false | true | false | false | true | true | true | true |
false | false | false | false | false | false | true | false |
# 区分 &与 &&
&
和&&
的作用:左右两边都为true
时,才返回true
,否则返回false
。
相同点:运算结果相同。当符号左边是true
时,&
和&&
都会执行符号右边的运算。
区别:当符号左边是false
时,&
继续执行符号右边的运算,&&
不再执行符号右边的运算。
boolean b1 = false;
int num1 = 10;
if(b1 & (num1++) > 0){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num1="+num1);
boolean b2 = false;
int num2 = 10;
if(b2 && (num2++) > 0){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num2="+num2);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
输出结果:
我现在在南京
num1=11
我现在在南京
num2=10
2
3
4
# | 与 ||
| 与 ||的作用:只要符号左右两边任意一个为true
,则返回true
,只有两边都不是true
时,才返回false
。
相同点:运算结果相同。当符号左边是false
时,|
和||
都会执行符号右边的运算。
区别:当符号左边是true
时,|
继续执行符号右边的运算,||
不再执行符号右边的运算。
boolean b1 = true;
int num1 = 10;
if(b1 | (num1++) > 0){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num1="+num1);
boolean b2 = true;
int num2 = 10;
if(b2 || (num2++) > 0){
System.out.println("我现在在北京");
}else{
System.out.println("我现在在南京");
}
System.out.println("num2="+num2);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
输出结果:
我现在在北京
num1=11
我现在在北京
num2=10
2
3
4
在开发中,推荐使用|| 和&&,因为当条件达成时,不会继续运算,效率更高。
# 练习1
最终x,y分别是多少?
int x = 1;
int y = 1;
if(x++ == 2 & ++y == 2){
x = 7;
}
2
3
4
5
x=2,y=2
# 练习2
最终x,y分别是多少?
int x =1,y=1;
if(x++ == 2 && ++y == 2){
x = 7;
}
2
3
4
x=2,y=1
# 练习3
最终x,y分别是多少?
int x = 1, y = 1;
if(x++ == 1 | ++y == 1){
x = 7;
}
2
3
4
x=7,y=2
# 练习4
最终x,y分别是多少?
int x = 1, y = 1;
if(x++ == 1 || ++y == 1){
x = 7;
}
2
3
4
x=7,y=1
# 面试题
请问z的最终结果是多少?
class Test{
public statis void main(String[] args){
boolean x = true;
boolean y = false;
short z = 42;
//if(y == true)
if ((z++ == 42) && (y = true)) z++;
if ((x = false) || (++z == 45)) z++;
System.out.println("z=" + z);
}
}
2
3
4
5
6
7
8
9
10
11
最终:z=46
,如果去掉注释是43
。
- 先来看第7行代码:&&只要两边都是true就执行后面的代码
左边括号:z++ == 42,是后缀++,所以先运算再自增,此时z=42,所以返回true,然后自增1,z=43
右边括号:y=true是赋值操作,然后返回true
两边都是true,执行z++,z= 43+1 = 44
- 第8行代码:|| 只要一个是true就执行后面的代码
左边括号:x = false是赋值操作,返回false
右边括号:++z == 45,这是前缀++,所以先自增再运算,此时z=44,自增1,z=45,然后45==45,返回true
右边括号返回true,所以执行z++,此时z=45+1=46。
去掉注释这里就不展开讲了,道理都一样的。
# 位运算符
位运算符是直接对整数的二进制进行的运算。在日常开发中实际上很少使用位运算符,但是看后期看底层源码的时候会出现位运算符,所以还是需要了解一下。
运算符 | 运算 | 范例 | 细节 |
---|---|---|---|
<< | 左移 | 3 << 2 = 12 => 3 * 2 * 2 = 12 | 空位补0,被移除的高位丢弃,空缺位补0. |
>> | 左移 | 3 >> 1 = 1 => 3 / 2 = 1 | 被移位的二进制最高位是0,右移后,空缺位补0;最高位是1,空缺位补1。 |
>>> | 无符号右移 | 3 >>> 1 = 1 => 3 / 2 = 1 | 被移位二进制最高位无论是0或者是1,空缺位都用0补。 |
& | 与运算 | 6 & 3 =2 | 二进制位进行& 运算,只有1&1时结果是1,否则是0。 |
| | 或运算 | 6 | 3 = 7 | 二进制位进行| 运算,只有0|0时结果是0,否则是1。 |
^ | 异或运算 | 6 ^ 3 = 5 | 相同二进制位进行^运算,结果是0:1^1=0,0^0=0 不同二进制位进行^运算结果是1。1^0=1,0^1=1 |
~ | 取反运算 | ~ 6 = -7 | 正数取反,各二进制码按补码各位取反 负数取反,各二进制码按补码各位取反 |
注意:
&
,|
如果左右是boolean
类型,则是逻辑运算符,不是位运算符。
# <<
左移
class BitTest{
public statis void main(String[] args){
int i = 12;
System.out.println("i <<< 2 :" + (i<<2));//84
}
}
2
3
4
5
6
通过计算器(当然也可以自己计算),获取到21的二进制是0001 0101
0 | 0 | 0 | 1 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|
当上面二进制数,向做移动两位后就变成01 0101,后面自动补0,就变成下面这样:
0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
---|---|---|---|---|---|---|---|
//原来的算法
1*2^0 + 1*2^2 + 1*2^4 = 21;
//现在
1*2^2 + 1*2^4 + 1*2^6 = 84 => 21*2^2
2
3
4
5
那试试左移三位:
class BitTest{
public statis void main(String[] args){
int i = 12;
System.out.println("i <<< 2 :" + (i<<2));//84
System.out.println("i <<< 3 :" + (i<<3));//168
}
}
2
3
4
5
6
7
- 小结
对比以前和现在,其实x变量左移2位,其实就是x*2^2,那么x变量左移n位的公式是:
x*2^n
但是这里还是有一个小问题,在java中,整形是32位二进制数,那么21的二进制数0001 0101,前面一共有27个0,那如果左移27位,会发生什么事情?
class BitTest{
public statis void main(String[] args){
int i = 12;
System.out.println("i <<< 2 :" + (i<<2));//84
System.out.println("i <<< 3 :" + (i<<3));//168
System.out.println("i <<< 27 :" + (i<<27));//-1476395008 在二进制中最高位都变成1了。
}
}
2
3
4
5
6
7
8
那么触类旁通
总结:
- 位运算符都是操作整型的数据
<<
左移,在一定范围内,每向左移1位,相当于 * 2。>>
右移,在一定范围内,每向右移1位,相当于 / 2。
面试题:最高效方式的计算2 * 8 ? 2 << 3 或者 8 << 1
# &
与运算
二进制位进行&
运算,只有1&1时结果是1,否则是0。
比如12 & 5
- 12
0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
---|---|---|---|---|---|---|---|
- 5
0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|
小技巧,0当成false,1当成true,每位0&1 推算二进制码
- 结果
0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
---|---|---|---|---|---|---|---|
1*2^2 = 4
所以结果是4
用java计算也是4。
public class BitTest {
public static void main(String[] args) {
int m = 12;
int n = 5;
System.out.println("m & n :" + (m & n));//4
}
}
2
3
4
5
6
7
# |
或运算
二进制位进行|
运算,只有0|0时结果是0,否则是1。
比如12 | 5 = 13
public class BitTest2 {
public static void main(String[] args) {
int m = 12;
int n = 5;
System.out.println("m | n :" + (m | n));//13
}
}
2
3
4
5
6
7
用二进制来推算
那么跟上面与运算一样,0代表false,1代表true,任意一个是1就是1,两个都为0才是0
- 12
0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
---|---|---|---|---|---|---|---|
- 5
0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|
- 结果
0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|
1*2^0 + 1*2^2 + 1*2^3 = 13
# ^
异或运算
在二进制运算中,只要相异,结果是1,相同则是0。
public class BitTest3 {
public static void main(String[] args) {
int m = 12;
int n = 5;
System.out.println("m ^ n :" + (m ^ n));//9
}
}
2
3
4
5
6
7
用二进制来推算:
- 12
0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
---|---|---|---|---|---|---|---|
- 5
0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
---|---|---|---|---|---|---|---|
- 结果
0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
---|---|---|---|---|---|---|---|
1*2^0 + 1*2^3 = 9
# ~
取反运算
正数取反,各二进制码按补码各位取反
负数取反,各二进制码按补码各位取反
包括符号位在内都要取反
public class BitTest4 {
public static void main(String[] args) {
int m = 6;
System.out.println(~m);// -7
}
}
2
3
4
5
6
二进制推算:
0000 0000 0000 0000 0000 0000 0000 0110 | 6 |
---|---|
1111 1111 1111 1111 1111 1111 1111 1001 | ~6 = -7 |
如果将上面例子的m改为-7,得出的结果是6,这说明6和-7互为取反。
# 练习
交换两个变量的值
- 方式一,使用临时变量
/*
交换两个变量的值
*/
public class ChangeTest1 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
int temp = num1;
num1 = num2;
num2 = temp;
System.out.println(num1);
System.out.println(num2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 方式二,
/*
交换两个变量的值
*/
public class ChangeTest2 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
num1 = num1 + num2;
num2 = num1 - num2;
num1 = num1 - num2;
System.out.println(num1);
System.out.println(num2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
虽然方式一简单粗暴,方式二不用临时变量,节省内存空间。但是实际开发中还是选择方式一,方式二有一个弊端,有可能两者相加的时候超出存储范围,而且只用于数值型,要是非数值型不好办了,而方式一适用于任何类型的变量。
- 方式三:使用位运算符。
/*
交换两个变量的值
*/
public class ChangeTest3 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
num1 = num1 ^ num2;
num2 = num1 ^ num2;
num1 = num1 ^ num2;
System.out.println(num1);
System.out.println(num2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
方式三的局限性跟方式二的一样。
# 三元运算符
# 格式
(条件表达式)?表达式1:表达式2;
当结果true,执行表达式1,否则执行表达式2。
- 条件表达还是必须是
boolean
类型 - 表达式1和表达式2必须要求是同一返回类型。
import java.util.Random;
public class IfElseTest {
public static void main(String[] args) {
//获取两个整数的较大值
int m = new Random().nextInt(100);
System.out.println(m);
int n = new Random().nextInt(100);
System.out.println(n);
int max = m > n ? m : n;
System.out.println("max:" + max);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- 输出:
97
77
max:97
2
3
三元运算符是可以嵌套的,比如获取三个数的最大值
import java.util.Random;
public class IfElseTest {
public static void main(String[] args) {
int m = new Random().nextInt(100);
System.out.println(m);
int n = new Random().nextInt(100);
System.out.println(n);
int k = new Random().nextInt(100);
System.out.println(k);
int max = m > n ? (m>k?m:k) : (n>k?n:k);
System.out.println("max:" + max);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
输出:
12
48
84
max:84
2
3
4
凡是可以使用三元运算符的地方都可以改写为
if-else
,反之,不一定成立。如果程序某处既可以使用三元运算符又可以用if-else结构,那么优先选择三元运算符,因为简洁,效率高。
# 运算符的优先级
当我们学完了所有的运算符,知道运算符是有优先级的。
下面是运算符的顺序,从高到低
. () {} ; , | |
---|---|
R => L | ++ -- ~ !(data type) |
L => R | * / % |
L => R | + - |
L => R | << >> >>> |
L => R | < > <= >= instanceof |
L => R | == != |
L => R | & |
L => R | ^ |
L => R | | |
L => R | && |
L => R | || |
R => L | ? : |
R => L | = *= /= %= |
R => L | += -= <<= >>= |
R => L | >>>= &= ^= |= |
只有弹幕运算符、三元运算符、赋值运算符是从右向左运算的。
可以看到,实际开发中,直接使用小括号来控制就行了。