
1. 定义
运算符是用来指明对于操作数(运算符左右两侧的数或者变量)的运算方式。
2. 分类方式
i. 按照操作数数目分类。
ii. 按照功能分类。
按照运算符操作数的数目来进行分类可以分为:单目、双目、三目运算符。如:
[1] a++,++是单目运算符,a是操作数。
[2] a+b,+是双目运算符,a和b是操作数。
[3] (a>b) ? a : b,? : 是三目运算符,a和b是操作数。
按照运算符的功能来进行分类可以分为:赋值、算术、关系、逻辑、条件、位运算符。
根据操作数来分类不便于记忆,因此我们一般按照运算符的功能来进行分类。
3. 赋值运算符
=、+=(递增)、-=(递减)、*=(倍增)、/=、%=
赋值运算符将=号右边的内值存入到=号左边的变量空间中去。除了赋值符号=外的都是复合型赋值符号。如:
[1] var = value; 意思就是把value的值赋值给var变量。
[2] x+=10; 相当于 x = x + 10;
以下代码会输出什么?
public static void main(String[] args) {
byte x = 1;
x = x + 2;
System.out.println(x);
}
编译不通过!这里首先在变量空间x中取出其内容,然后从常量区复制一份2,执行加法运算,最后将结果重新存储到变量空间x内。注意,这里的变量空间x是一个byte类型的数据1,即只有8个bit位,而常量区中的整型常量默认是int类型,这里是int类型的常量2,即有32bit位,然后通过算术运算符+时会自动将8bit位的数据1提升为32bit位的数据1,然后再进行+运算,最后再将32bit位的结果,即int类型的数据3赋值给变量空间x,因为变量空间x存储的内容是byte类型,所以会报损失的错误。因此只需将最后的运算结果通过强制类型转换即可解决这个问题。
public static void main(String[] args) {
byte x = 1;
x = (byte) (x + 2);
System.out.println(x);
}
那么为什么byte x = 1;能自动将int类型的1转化为byte类型的1,而x = x + 2;不能将int类型的3转化为byte类型的3呢?
因为byte x = 1;的1是一个常量值,JVM虚拟机编译的时候认识这是一个int类型的1,所以能进行自动类型转换,而x = x + 2;,JVM虚拟机编译的时候不认识x + 2是什么,只知道是一个表达式,所以没有进行自动类型转换,即表达式x + 2的值是多少就直接赋值到=号的左边,所以会报损失错误。剩下的赋值运算符类似。
而+=是一个整体,所以可以进行自动类型转换。
public static void main(String[] args) {
byte x = 1;
x += 2;
System.out.println(x);
}
总结:在进行运算符操作时,自动类型转化的前提是一个已经确定数据类型的变量,才能进行自动类型转换。
4. 算术运算符
+、-、*、/、%(取余)、++(自增)、--(自减)
注意事项:
[1] / 在java中表示整除,整数和整数运算得到的一定是整数;只要有小数参与整除/得到的一定是小数。
[2] / 中被除数不能为0。
[3] % 取余(求模),模谁就不会超过谁! e.g : 5 % 2 结果是1
[4] ++/--在前,先+1/-1,后运算。
[5] ++/--在后,先运算,后+1/-1。
--需求:给变量x加1有多少种方式?
[1] X = x + 1;
[2] x++;
[3] ++x;
这三种方式在内存中的执行过程是一样,而++表现形式更加的简洁
--需求:把12345s 转化成_时_分_秒
public static void main(String[] args){
int val = 12345;
int h = val / 3600;
int m = val % 3600 / 60;
int s = val % 60;
System.out.println(h + ":" + m + ":" + s);
}
i++/i--
i先参与运算,运算完成i自加/自减1。
public static void main(String[] args) {
// [1]
int a = 10;
int b = a++;
System.out.println("a = " + a); // 11
System.out.println("b = " + b); // 10
// [2]
int c = 10;
c++;
System.out.println("c = " + c); // 11
// [3]
int d = 10;
int e = d++ + d++;
System.out.println("d = " + d); // 12
System.out.println("e = " + e); // 21
}
++i/--i
i在参与运算前就自加/自减1。
public static void main(String[] args) {
// [1]
int a = 10;
int b = ++a;
System.out.println("a = " + a); // 11
System.out.println("b = " + b); // 11
// [2]
int c = 10;
++c;
System.out.println("c = " + c); // 11
// [3]
int d = 10;
int e = ++d + ++d;
System.out.println("d = " + d); // 12
System.out.println("e = " + e); // 23
}
如果是按照++在操作数前,先执行++,++在操作数后,后执行++,这种思维那么下面的代码会输出什么?
public static void main(String[] args) {
int a = 1;
a = a++;
System.out.println("a = " + a);
}
会输出1,在计算机底层中,算术运算的优先级是高于赋值运算的,先计算,后赋值。所以,变量想要做值交换(值计算)的时候,会产生一个临时的副本空间(备份),如果++在变量的前面,先自增后备份,如果++在变量的后面,先备份后自增,最后才会将副本空间中内的值赋值另一个变量。通过这种理解,可以很好的对上面的代码进行一个理解。(任何赋值情况都会开辟一个临时的副本空间,如果++在前那么就先自增在开辟,如果++在后那么先开辟再自增,最后再将开辟的副本空间中的值赋值赋值运算符左侧的变量空间)。
内存图:

如果理解算术和赋值运算的关系,那么下面的笔试题就非常好理解了。
public static void main(String[] args) {
int a = 1;
for (int i = 1; i <= 100; i++) {
a = a++;
}
System.out.println("a = " + a);
}
注意,在java中不允许定义相同的变量空间,因为变量空间是存储在栈内存中的,而且有且只有一份,因此不能定义数据类型和变量空间名相同的变量。

5. 关系运算符
>、>=、<、<=、== (相等号)、!= (!非的意思,不等号)、instanceof
其中instanceof 用于判断一个对象对应的类是否能顺着继承链往上找到这个类,返回值为boolean,即true/false,用法:对象 instanceof 类。
public static void main(String[] args) {
Person person = new Person();
Student student = new Student();
System.out.println(person instanceof Person);
System.out.println(person instanceof Student);
}
这里Student继承Person类。
小数不能用于关系运算符的比较,因为小数的精确程度不准确,如0.1不等于1.0 / 10;
public static void main(String[] args) {
System.out.println(0.1 == (1 / 10));
}
=和==的区别?
=赋值符号,将=右侧的结果(原始值/引用值)存入=左侧的变量空间内。
==比较符号,比较==左侧和==右侧结果(原始值/引用值)是否一致。
比较运算符的最终结果是什么?
因为比较只有两种情况,即true或者false,所以使用布尔型boolean来表示。因为布尔类型不能和其他基本数据类型进行转换,因此只有true和false而没有0和1。
6. 逻辑运算符
& 逻辑与、| 逻辑或、^ 逻辑异或、! 逻辑非、&& 短路与、|| 短路或
逻辑运算符用于罗列一堆条件的满足情况,是满足所有还是满足其中一个还是一个都不满足等等之类的问题,逻辑运算符前后连接的是两个boolean值。其中只有逻辑非是一个单目运算符,逻辑非只能在后面接一个判定表达式。
关系和逻辑运算符的区别?
关系运算符只能用于一个条件的判定。
而逻辑运算符既能用于一个条件也能用于多个条件的判定。
逻辑与&,可以理解为中文的和或者并且的意思,只有逻辑与&前后两个条件必须同时满足,即boolean值都是true,最终才为true。理解为:老师需要张三和李四来一趟。
public static void main(String[] args) {
boolean b1 = (3 > 2) & (3 > 4);
System.out.println(b1);
boolean b2 = (3 > 2) & (3 > 1);
System.out.println(b2);
}
逻辑或|,可以理解为中文或者的意思,逻辑或|前后的两个条件只要有一个满足,最终就为true。理解为:老师让张三或者李四来一趟。
public static void main(String[] args) {
boolean b1 = (3 > 2) | (3 > 4);
System.out.println(b1);
boolean b2 = (3 > 5) | (3 > 4);
System.out.println(b2);
}
逻辑异或^,异就是不同的意思,所以如果前后两个表达式的结果不一致,就为true,即一侧为true,一侧为false时逻辑异或^的结果才为true,否则就为false,即两侧都是true或者两侧都是false时逻辑异或^的结果为false。
public static void main(String[] args) {
boolean b1 = (3 > 2) ^ (3 > 4);
System.out.println(b1);
boolean b2 = (3 > 5) ^ (3 > 4);
System.out.println(b2);
boolean b3 = (3 > 1) ^ (3 > 2);
System.out.println(b3);
}
逻辑非!,是一个单目运算符,非!是取反的意思,可以理解为中文的不,会将后面的表达式的boolean的值取反,即true取反为false,false取反为true。
public static void main(String[] args) {
boolean b1 = !(3 > 2);
System.out.println(b1);
boolean b2 = !(3 > 4);
System.out.println(b2);
}
短路与&&,与是两个条件同时满足,如果当第一个条件已经为false,最终肯定是false。
i. 什么情况下发挥发生短路?当前面的boolean值结果为false的时候会发生短路。
ii. 到底短路的是什么?短路的是短路与&&之后所有计算的过程。
iii. 如果发生了短路情况,性能会比&稍微好一点,因为少判断一些表达式的boolean值。
iv. 逻辑与&和短路与&&从执行的最终结果来看没有任何区别。
v. 短路与&&不一定提高了性能,只有当第一个表示的boolean值为false的时候才会发生短路,即才会提高性能。
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = 30;
boolean r2 = (a > b) && (++c > b);
System.out.println("r2 = " + r2);
System.out.println("c = " + c);
}
短路或||,当第一个表达式的boolean值为true时,就终止判断了,直接返回true,当第一个表达式的boolean值为false时,就直接返回第二个表达式的boolean值。短路或相对逻辑或在在效率上会有一点优势,但不是绝对的,前提条件是第一个表达式的boolean值为true。
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = 30;
boolean r2 = (a < b) || (++c > b);
System.out.println("r2 = " + r2);
System.out.println("c = " + c);
}
7. 条件运算符
语法:(条件表达式) ? (表达式1) : (表达式2)
如果条件表达式的值为true,整个表达式的结果取表达式1的结果,否则取表达式2的结果。
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = (a > b) ? a : b;
System.out.println("c = " + c);
}
8. 位运算符
按位与&、按位或|、按位异或^、按位取反~、按位左位移<<、按位右位移>>、无符号按位右位移>>>
位运算符主要对二进制位进行运算。
按位与(&)
public static void main(String[] args) {
System.out.println(1 & 2);
}
运算过程:首先将1和2转换为二进制表示,随后,比较相同位置上的bit,如果同1才1,否则为0,最后将得到的二进制再转换为十进制输出。
0001
& 0010
-----------------
0000 => 0
按位或(|)
public static void main(String[] args) {
System.out.println(1 | 2);
}
运算过程:首先将1和2转换为二进制表示,随后,比较相同位置上的bit,如果有1就为1,全0才为0,最后将得到的二进制再转换为十进制输出。
0001
| 0010
-----------
0011 => 3
按位异或(^)
public static void main(String[] args) {
System.out.println(1 ^ 2);
}
运算过程:首先将1和2转换为二进制表示,随后,比较相同位置上的bit,如果相同就为0,不同才为1,最后将得到的二进制再转换为十进制输出。
0001
^ 0010
----------------
0011 => 3
按位左位移<<
<< n 表示二进制数左移n位,空位补0,保留符号位。
1 << 1 ====> 2
0001 << 1 ====> 0010
按位右位移>>
>>n 表示二进制数右移n位,空位补0,保留符号位。
2 >> 1 ====> 1
0010 >> 1 ====> 0001
无符号按位右位移>>
>>n 表示二进制数右移n位,空位补0,不保留保留符号位。
--需求:计算2*8的结果
计算机中的乘法运算和我们数学中的乘法运算是类似的,不同的是将2和8换算成二进制再进行数学乘法运算。

最后再换成成十进制数输入,即16。这种乘法算法效率是很复杂,即效率比较低。
其实乘法可以使用左位移,因为左位移就相当于乘以2的位移次幂。所以这里的2*8可以使用2<<3来计算,而且位移运算没有进行任何算术运算,只是将二进制串整体进行位置挪动,效率是比计算机乘法运算要高的。除法(右位移)类似。
public static void main(String[] args) {
System.out.println(2 << 3);
}
要想使用位移运算来替代原始乘法运算,乘数必须是2的次幂,即1、2、4、8、16....2^n。
如2 * 5。
public static void main(String[] args) {
System.out.println(5 << 1);
}
只要有一个乘数是2的次幂即可,如果两个乘数都不是2的次幂,那么只能使用底层乘法运算进行计算。
因此,在进行乘除法运算的时候,优先考虑使用位移运算。
9.运算符的优先级
实际开发过程中,表达式有可能是多种运算符混合运算,此时要搞清楚运算符的优先级。

总结:=优先级最低,()优先级最高想让谁先计算就加()。
10. 字符串连接符
+ 两边的表达式如果都是数值型(类似数值型),表示加法操作;如果+两边的表达式有一个为字符串,+就会把另外一个操作数转为字符串后连接第一个操作数。
11. 总结
java运算符:
[1] 按照操作数的数目分类:单目、双目、三目运算。
[2] 按照运算符的功能分类:赋值、算术、关系、逻辑、条件、位运算。
[3] 按照功能分类记忆会比较容易。
赋值运算符:
[1] 赋值运算符有:=、+=(递增)、-=(递减)、*=(倍增)、/=、%=。
[2] 赋值运算符将=号右边的内值存入到=号左边的变量空间中去。
算术运算符:
[1] 算术运算符有:+、-、*、/、%、++、--。
[2] / 在java中表示整除,整数和整数运算得到的一定是整数;只要有小数参与整除/得到的一定是小数。
[3] / 中被除数不能为0。
[4] % 取余(求模),模谁就不会超过谁! e.g : 5 % 2 结果是1
[5] ++/--在前,先+1/-1,后运算。
[6] ++/--在后,先运算,后+1/-1。
关系运算符:
[1] 关系运算符有:>、>=、<、<=、== (相等号)、!= (!非的意思,不等号)、instanceof。
[2] 关系运算符用于比较大小关系,以及是否是某个类的子类。
[3] 小数不能用于关系运算符的比较,因为小数的精确程度不准确,如0.1不等于1.0 / 10。
逻辑运算符:
[1] 逻辑运算符有:逻辑与&、逻辑或|、逻辑非!、逻辑异或^、短路与&&、短路或||。
[2] 逻辑与&和短路与&&都需要两侧表达式的boolean值全为true最终结果才为true,不同之处在于判断的范围。
[3] 逻辑与&会判断所有表达式的boolean值,而短路与&&,当第一个表达式的boolean值为false时,就终止判断后续表达式的boolean值,所以短路与&&在效率上会比逻辑与&有一些优势,但不是绝对的,前提条件是第一个表达式的boolean值为false.
[4] 逻辑或|和短路或||都是只需一则表达式的boolean值为true最终结果就为true,不同之处在于判断的范围。
[5] 逻辑或|会判断所有表达式的boolean值,而短路或||,当第一个表达式的boolean值为true时,就终止判断后续表达式的boolean值,所以短路或||在效率上会比逻辑或|有一些优势,但不是绝对的,前提条件时第一个表达式的boolean值为true。
[6] 逻辑异或^,当两侧表达式的boolean值不同时最终结果才为true,若两侧表达式的boolean值都相同最终结果为false。
[7] 逻辑非!,会将表达式的boolean值取反,即由true变为false,由false变为true。
位运算:
[1] 位运算符有:按位与&、按位或|、按位异或^、按位取反~,按位左位移<<、按位右位移>>、无符号按位右位移>>>。
[2] 位运算是对二进制数计算的。
[3] 按位与&,首先将按位与&两侧的十进制数转化为二进制数,接着对应位置进行判断,同为1才为1,否则为0,最后将得到的二进制串再转化为十进制数输出。
[4] 按位或|,首先将按位或|两侧的十进制数转化为二进制数,接着对应位置进行判断,只要有1即为1,全为0才为0,最后将得到的二进制串转化为十进制数输出。
[5] 按位异或^,首先将按位异或^两侧的十进制数转化为二进制数,接着对应位置进行判断,只有当一侧为0,另一侧为1时才为1,否则为0,最后将得到的二进制串转化为十进制数输出。
[6] 按位取反~,按位取反~需要用到原码、反码和补码的知识。
[7] 按位左位移<<,首先将十进制数转化为二进制数,然后左移位,如按位左位移2位,那么就左移位两次,并且直接抛弃高位,低位用0填充,相当于在原十进制数的基础上乘以2的位移位置次幂,移动的位数超过了最大位数时会对其取模再移位。
[8] 按位右位移>>,首先将十进制数转化为二进制数,然后右位移,如按位右位移2位,那么就右位移两次,并且高位右0填充,直接抛弃低位,相当于在原十进制数的基础上除以2的位移位置次幂,注意符号位是保留的,即最左的bit位是不变的,并且移动的位数超过了最大位数时会对其取模再移位。
[9] 无符号按位右位移>>>,首先将十进制数转化为二进制数,然后右位移,如无符号按位右位移2位,那么就整体右位移两位,并且直接用0填充高位,直接抛弃低位,注意符号位是不保留的,即最左的bit位直接由0填充,并且移动的位数超过了最大位数时会对其取模再移位。
[10] 在进行乘除法运算时,优先考虑位运算。
[11] &和&&的区别
&可以视为逻辑运算,也可以视为位运算,而&&只能视为逻辑运算。如果两个符号都视为逻辑运算符来使用的时候会有如下区别:1、逻辑与&只有前后两个条件都是true时,最终结果才为true。2、短路与&&正常情况下和逻辑与&的执行结果是一致的,即前面的条件为true。只有当短路与&&前面的条件为false的时候,才会发生短路,即最终结果为false。
字符串连接符+:
[1] 如果+两边的表达式都是数值型(类数值型),表示加法运算。
[2] 如果+两边的表达式有一个为字符串,那么+就会把另外一个操作书转换为字符串后连接改字符串。
运算符的优先级问题:
[1] 赋值运算符的优先级最低,小括号()的优先级最高。
[2] 想让谁先运算就给谁加小括号()。