前言
运算符用于执行程序代码运算,会针对一个以上操作数项目来进行运算。JAVA中常见的运算符有很多种,大致分为以下几种,常见的几种运算符如下图:
算术运算符 | 加、减、乘、除、求余。例++、--、%、/、 |
赋值运算符 | 为变量或常量起到赋值作用的。例如=、+=、*= |
关系运算符 | 判断数据大小的,结果为一个布尔值。例如>、>=、!=、== |
逻辑运算符 | 进行逻辑运算,运算的成员为布尔值,结果也为布尔值。例如&&、||、!。 |
条件运算符 | 也称三目运算符,表达式为(a<b)?a:b |
位运算符 | 对二进制进行操作的,例如&、|、^、~、 |
一、算术运算符
1.1、常见的算术运算符
+ | 加法运算;例如x+y |
- | 减法运算;例如x-y |
* | 乘法运算;例如x*y |
/ | 除法运算;例如x/y 10/3=3; 10/2.0=5.0 |
% | 取模运算(求余运算);例如x%y 10%3=1 |
++ | 自增运算;例如x++,++x |
-- | 自减运算;例如x--,--x |
1.2、算术运算中的类型转换
我在我的上一篇博客里面详细讲解了关于JAVA基本数据类型的类型转换了的,可以参考链接:仔细看一下即可。不过在这里大致说一下其中几个重要的点。
- 运算时,运算结果会向数据类型大的转换;
- 在运算时,byte、short、char类型先自动转换为int类型
@Test
public void a() {
int a=3;
double b=4;
System.out.println(a+b);//输出7.0
float c=3.2f;
/*c=c+3.14; 编译错误,运算之后变为double类型*/
byte a1=3;
byte b1=4;
/*byte c1=a+b;
* 编译错误,此处由于byte类型参与运算时,先直接转换为int类型,
* 所以最后的结果也是int类型,但是得出的结果不能叫做int类型的直接量,所以编译错误
* */
int d1=a1+b1;
}
1.3、关于i++与++i的区别
- i++是++在后,所以先用了再加
- ++i是++在前,所以先加了再用
注意点:a+++b与a+ ++b的区别 a+++b的含义是(a++)+b;而a+ ++b的含义为a+(++b)
@Test
public void b() {
int a=1;
int b=1;
System.out.println(a++);//输出1
System.out.println(++b);//输出2
int a1=10;
int b1=10;
int sum=a1+++b1;
System.out.println(a1);//输出11
System.out.println(b1);//输出10
System.out.println(sum);//输出20
int a2=10;
int b2=10;
int sum1=a2+ ++b2;
System.out.println(a2);//输出10
System.out.println(b2);//输出11
System.out.println(sum1);//输出21
}
二、赋值运算符
2.1、常见的赋值运算符
运算符 | 含义 | 举例 |
= | 等于 | c=a+b就是将a+b的值赋给c |
+= | 加等于 | a+=1等价于a=a+1 |
-= | 减等于 | a-=1等价于a=a-1 |
*= | 乘等于 | a*=b等价于a=a*b |
/= | 除等于 | a/=b等价于a=a/b |
%= | 求余等于 | a%=b等价于a=a%b |
2.2、关于a+=1与a=a+1的区别
虽然在上面说的a+=1是等价于a=a+1,但是他们两还是有区别的。
@Test
public void c() {
byte a=1;
/*a=a+1;
* 这里会报编译错误,由于a+1出现类型转换,变为int类型,所以再赋值给byte类型所以编译错误
* */
a+=1;
/*这里是不会出现编译错误的,这就是它的特殊之处
* */
System.out.println(a);//输出2
byte b=127;
b+=1;
System.out.println(b);
//输出-128,所以其实就是b=(byte)(b+1);b+1为128,超出byte类型的范围,数据溢出为-128
}
原因:+=运算符,是java语言规定的一元运算符(这里我将它归为赋值运算符有些不妥),Java有自动转换机制,java编译器会对其进行特殊处理,默认的向右转换类型,不需要人工转换。
2.3、各种赋值运算符的例子
@Test
public void d() {
int a=10;
double b=10;
System.out.println(a+=b);
//输出20 a=(int)(a+b)=(int)(10+10)=20
System.out.println(b+=a);
//输出30.0 b=a+b;double类型大,所以不需要自动转换 b=20+10=30.0
int a1=10;
int b1=10;
System.out.println(a1*=b1);//输出100
System.out.println(a1/=b1);//输出10
System.out.println(a1%=b1);//输出0
System.out.println(a1-=b1);//-10
}
三、关系运算符
3.1 常见关系运算符
关系运算符有时候也称为比较运算符,运算的结果一定为布尔值
符号 | 含义 | 举例 |
> | 判断是否大于 | boolean a=3>2; a=true |
< | 判断是否小于 | boolean a=3<2; a=false |
>= | 判断是否大于等于 | boolean a=3<=3; a=true |
<= | 判断是否小于等于 | boolean a=3>=4; a=fasle |
== | 判断是否等于 | boolean a=3==4; a=false |
!= | 判断是否不等于 | boolean a=3!=4; a=true |
注意点:
- < 、>、>=、<=只能比较数值类型,所以不能比较布尔类型和大多数应用类型,但是有些基本数据类型包装类型可以。
- ==、!= 既能比较基本数据类型,也能比较引用数据类型。但是在比较引用数据类型的时候比较的就是地址了。
@Test
public void e() {
int a1=10;
int b1=10;
boolean a2=false;
boolean b2=false;
Integer a3=10;
Integer b3=10;
A a4=new A();
A b4=new A();
System.out.println(a1>b1);
/*System.out.println(a2>b2); 非数值类型不能比较大小*/
System.out.println(a3>b3);
/*System.out.println(a4>b4);不能比较大小*/
System.out.println(a1==b1);
System.out.println(a2==b2);//true
System.out.println(a3==b3);
//相等,由于基本数据类型包装类Integer的自动装箱的特性,直接从静态数组中取的,所以不是new的
System.out.println(a4==b4);
//false,由于是new出来的不等
}
3.2、==与equals的区别
首先我们要知道,equals方法是Object类中的一个方法,而Object类是所有类的父类。首先我们先看一下Object类中equals方法的源代码:
public boolean equals(Object obj) {
return (this == obj);
}
可以看出Object类中该方法的逻辑就是==,所以Object类中给出的equals方法就是判断是否==;
那我们再看一下String类的equals方法,如下图:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
可以看出,String类中的equals不再是简简单单的直接判断==;它的equals方法是先判断它们是否==,如果不等再判断两个引用是否为同类或父子类关系,是的话再判断是否值一样,一样则返回true,否则返回fasle。所以使用equals方法时要看该引用对应的类的equals方法的实现逻辑,一般情况下是String的话就是先比较地址,地址相等就为true,不为再比较内容,内容一样也是为true。
所以==与equals的区别可以总结为以下几点:
- ==是既能比较基本数据类型,比较基本数据类型的时候比较的是值是否相等。又能比较引用数据类型,比较引用数据类型比较的是引用对应的地址是否相等。
- 而equals是一个方法,所以只能用来比较引用数据类型,要根据类中该方法实现的逻辑是什么来看比较的是什么。一般情况下就是String类型和自定义的类。对应String类型是判断内容是否一致。
@Test
public void f() {
String a=new String("abc");//new出来的对象放在了堆中
String b=new String("abc");//不管内容中有无,都是直接new出一个新的对象
String c="abc";//放在常量池中
String d="abc";//现在常量池中找师傅有abc,有的话就将d指向该对象
System.out.println(a.equals(b));//true 只判断内容是否相等
System.out.println(a==b);//false 判断地址是的相等
System.out.println(a.equals(c));//true
System.out.println(a==c);//false 地址不同
System.out.println(a==d);//true 地址一样,由于都指向一个常量池中的同一个对象
}
四、逻辑运算符
4.1、常用逻辑运算符
布尔类型。
运算符 | 名称 | 举例 |
&& | 与 | a&&b,a和b都为true,结果才为true,否则为false |
|| | 或 | a||b,a和b都为false,结果才为false,否则为true |
! | 非 | !a,a为false,则结果为true。即结果相反 |
变量a | 变量b | a&&b | a||b | !a |
true | true | true | true | false |
true | false | false | true | false |
false | false | false | false | true |
false | true | false | true | true |
4.2、关于逻辑运算符中"短路"的问题
- 对于&&而言,当第一个操作数为false时,将不会判断第二个操作数。
- 对于 || 而言,当第一个操作数为true是,将不会判断第二个操作数。
@Test
public void g() {
int a=10;
int b=10;
boolean c=a>10 && b++>10;
System.out.println(c);
System.out.println(b);//输出10,由于a>10已经为False之后,后面的b++也就不会执行
boolean d=a<11 || b++>10;
System.out.println(d);
System.out.println(b);//也是输出10,前面的a<11已经为true,所以后面的b++也不执行
}
五、条件运算符
即为三目运算符,形式为:布尔表达式 ? 表达式1 :表达式2
运算过程:如果布尔表达式的值为 true ,则返回 表达式1 的值,否则返回 表达式2 的值
用处:和if else的分支结构作用有点类似。但是写法比 if else简洁。
@Test
public void h() {
int[] a= {-1,2,0};
for(int i=0;i<a.length;i++) {
a[i]=a[i]>=0?a[i]:0;//将数组中小于0的数字变为0
}
for(int b:a) {
System.out.println(b);//利用增强for循环输出数组中的值
}
}
六、位运算符
6.1、常用位运算符
符号 | 含义 |
& | 按位与 |
| | 按位或 |
~ | 非(取反运算符) |
^ | 异或 |
<< | 左移运算符 |
>> | 右移运算符 |
>>> | 无符号右移运算符 |
6.2、按位与 & 详解
且按位与 '&' 不像逻辑与 '&&'一样有短路效应。
- 操作布尔类型的值时,与逻辑与 '&&' 类似,也是见false则false,就是没有短路效应。
- 操作整数基本数据类型是,即将整数化为二进制,然后相同位上计算,相同位都是1则结果为1,否则为0
例如:3&4
3的二进制为 0000 0000 0000 0011
4的二进制为 0000 0000 0000 0100
则结果为 0000 0000 0000 0000 对应的二级制为0,所以3&4=0;
(1)按位与的常见作用:清0,取一个数中的指定位数(例如取int类型值的低八位)
例如int类型变量a对应的二进制为 0010 0001 0000 1010 ,现在求它的低八位,也就是1010,就可以使用a&15
15对应的二进制为 0000 0000 0000 1111 所以 ,a&15的结果就为 0000 0000 0000 1010
(2)判断一个数的奇偶性:n&1 == 1?”奇数”:”偶数”
(3)面试题:优化n%8
解答:n&7
原因:对8取模(求余)运算的定义就是整除8后的余数,而对于8来说,这个余数恰好就是这个数的二进制表示的低3位; 因此对8取模等价于获取数据的低3位,这由恰好等价于 &7 的结果。
6.3、按位或 | 详解
且按位或 '|' 不像逻辑或 ' || '一样有短路效应。
- 操作布尔类型的值时,与逻辑或 ' || ' 类似,也是见true则true,就是没有短路效应。
- 操作整数基本数据类型是,即将整数化为二进制,然后相同位上计算,只要有一个1就为1,否则结果为0
例如 3 | 4
3的二进制为 0000 0000 0000 0011
4的二进制为 0000 0000 0000 0100
则结果为 0000 0000 0000 0111 对应的二级制为7,所以3&4=7;
按位或的作用:将一个数的某些位置变为1
6.4、按位非 ~详解
一元运算符,所以只能对一个操作数进行操作。按位非就是将各位数字取反,0变为1,1变为0;
例如 4的二进制为 0000 0000 0000 0100
所以~4的二进制为 1111 1111 1111 1011 转换为是十进制为-5
一个数的相反数=该数取反+1 例如4的相反数-4=~4+1=-5+1=-4
6.5、异或 ^ 详解
将数转换为二进制之后运算,然后不相同的结果就为1; 0^0=0 1^0=1 0^1=1 1^1=0
例子:4的二进制为 0000 0000 0000 0100
5的二进制为 0000 0000 0000 0110
4^5的二级制为 0000 0000 0000 0010 转换为十进制为2
异或^的作用实例:不适用临时变量交换两个数
我们经常写变量交换时,是以如下,用了一个临时变量来交换的;
@Test
public void a() {
int a=3;
int b=4;
int c;//定义的临时变量
c=a;
a=b;
b=c;
System.out.println(a);//输出4
System.out.println(b);//输出3
}
但是利用异或运算符不用使用临时变量
@Test
public void b() {
int a=3;
int b=4;
a=a^b;
b=b^a;//b=b^(a^b) ->b=a
a=a^b;//a=(a^b)^(b^a) ->a=b
System.out.println(a);//输出4
System.out.println(b);//输出3
}
6.6、左移、右移、无符号右移动
- 左移运算符 << :符号位不变,低位补0;
- 右移运算符 >> :是低位溢出,符号位不变,并用符号位补溢出高位
- 无符号右移运算符>>>:低位溢出,高位补0,注意,无符号右移(>>>)中的符号位(最高位)也跟着变,无符号的意思是将符号位当作数字位看待。
@Test
public void c() {
int a=4;
System.out.println(a<<2);
/* 0000 0000 0000 0100 a的二进制
* 000000 0000 0001 0000 低位补两个0
* 0000 0000 0001 0000 高位溢出,所以a<<2=8
* */
System.out.println(a>>2);
/* 0000 0000 0000 0100
* 0000 0000 0000 000100 符号位补高位
* 0000 0000 0000 0001 低位溢出 所以 a>>2=1
* */
int b=-1;
System.out.println(b>>>1);
/* 1111 1111 1111 1111
* 0111 1111 1111 11111 右移一位,高位补0,
* 0111 1111 1111 1111 低位溢出 结果为 2147483647
* */
}
七、各种运算符的优先级
下图为java中各种运算符的优先级,可以记一下,但有时候不太清楚的时候就直接用括号()吧!
运算符 | 结合性 |
[ ] . ( ) (方法调用) | 从左向右 |
! ~ ++ -- +(一元运算) -(一元运算) | 从右向左 |
* / % | 从左向右 |
+ - | 从左向右 |
<< >> >>> | 从左向右 |
< <= > >= instanceof | 从左向右 |
== != | 从左向右 |
& | 从左向右 |
^ | 从左向右 |
| | 从左向右 |
&& | 从左向右 |
|| | 从左向右 |
?: | 从右向左 |
= | 从右向左 |