我们已经知道如何定义数字,有了数字,就有了数字的运算。在java的世界里,除了我们常用的加减乘除四则运算,还有一些其他的运算,下面先介绍java的运算符。
算术运算符:
+ 加法运算符。
- 减法运算符。
* 乘法运算符。
/ 除法运算符。
% 取模运算符。也叫求余运算符,即求取余数,比如9%2,用9除以2得到的余数即为运算后的值。
~ 取反运算。这个运算比较少见,我们等会举例说明。
! 取非运算。这个运算符只能对布尔型的值进行,由于布尔型的值只有两个,所以取非以后值会变成另一个,true会变成false,false会变成true。
- 负号运算,这个其实还是减号,但是当它单独使用时,就是求了一个数的相反数。比如int x = -9; x = -x;此时x就变成9了
++ 自增运算。运行完后原值加1。自增运算符可以在变量前也可以在变量后,比如int b = 9; b++;++b;但是这两种用法区别非常大,一会我们用代码来说明。
-- 自减运算。运算完后值减1。用法同自增运算。
算术运算符中,+ - * / %都是两个数进行的,我们称之为二元运算符, ~ ! - ++ --都是一个数进行的,我们称之为一元运算符,下面我们看看这些运算的例子。
public class MySecondJavaClass{
public static void main(String[] args){
int a = 9;
int b = 2;
boolean flag = false;
int c;
//加法运算
c = a + b;
System.out.println("a+b="+c);
//减法运算
c = a - b;
System.out.println("a-b="+c);
//乘法运算
c = a * b;
System.out.println("a*b="+c);
//除法运算
c = a / b;
System.out.println("a/b="+c);
//取模运算
c = a % b;
System.out.println("a%b="+c);
//对a取反
c = ~a;
System.out.println("a取反="+c);
//对flag取非
flag = !flag;
System.out.println("flag取非="+flag);
//对a取负
c = -a;
System.out.println("a取负="+c);
//后自增
c = a++;
System.out.println("a后自增="+c+" a后自增后值="+a);
//前自增
c = ++a;
System.out.println("a前自增="+c+" a前自增后值="+a);
//后自减
c = b--;
System.out.println("b后自减="+c+" b后自减后值="+b);
//前自减
c = --b;
System.out.println("b前自减="+c+" b前自减后值="+b);
}
}
首先请注意,这是第二个java类了,不再是MyFirstJavaClass了,是MySecondJavaClass了,所以如果你在编译代码的时候报错,注意一下你是不是新建了一个类(其实按照之前已经写过的代码,这应该是第N个了,但是由于本人太懒,一直在原有的代码上改来改去...)
现在我们声明了两个int类型的变量a和b,值分别是9和2,另外声明了一个变量c,用来接收各种运算结果的值,以及一个布尔型的变量flag,专门用来看取非的效果。
我们来看运行结果,加减乘除和取模、取非、取负基本不用多说,只是要注意,我们知道9除以2结果是4.5,但是由于这是整数计算,所以其结果应该是4,余数为1(即为取模的结果,9%2=1),如果我们需要得到4.5这个结果怎么办?这个我们在后面介绍。
接下来我们看三个运算:取反、后自增和前自增(自减的原理和自增一样)。
先看取反,我们声明了a的值为9,取反以后的结果是-10,这是个什么逻辑呢?首先我们知道,计算机是二进制的,所以计算机保存数据实际上也是按照二进制保存的,那么计算机是怎么保存一个数呢?计算机是以补码的形式保存数字的。在计算机中,正数的补码和反码是其本身,负数的补码是符号位不变,其余各位求反,末位加1;负数的反码是符号位不变,其余各位求反,末位不加1。
比如我们的数字9,它的二进制形式是1001,加上符号位,就是01001,那么它的原码、反码、补码均为01001
那么数字-9,它的二进制形式也是1001,加上符号位,就是11001,补码是符号位不变,各位求反,即10110,末位加1就是10111;反码是符号位不变,各位求反,即10110.
了解了计算机的对数字的储存,那么再来看取反运算,我们知道9的补码是01001了,那么取反就是对各位取相反数,0变1,1变0,01001取反就是10110,这是取反的结果。
10110这个结果也是个补码,由于符号位是1,我们知道它是一个负数了,那负数的补码求原码,我们先要末位减1,然后再对非符号位的各位求反。10110减1就是10101,再保持符号位不变,对后四位0101求反,得到1010,这个值转换成十进制就是10,再加上符号位是1,我们最后得到结果-10
取反运算比较麻烦,而且在我们日常的开发中基本用不到,但是对取反运算的了解,可以让我们对计算机的了解更加深入。另外,计算机为什么要使用补码的方式来保存数字,有兴趣的可以去网上搜一下,我们继续讨论java的算术运算符。
看完了取反,我们再看自增运算。取反运算基本很少用到,但是自增运算却是使用非常频繁的,我们在代码里面看到,我们做了两次操作,c = a++; c = ++a;第一次操作后,输出的c的值是9,a的值是10,;第二次操作后,输出的c和a都是11,为什么第一次操作c的值和a的值不一样,第二次操作c的值就和a的值一样了呢?
这是因为自增运算符的位置不一样造成,a++使用在赋值表达式中,会先进行赋值运算,再进行自增运算。 ++a则先进行自增运算,再进行赋值运算。
c = a++;相当于c = a; a ++;首先,a的值还是9,c = a 就是把9赋给了c,然后a进行自增,a的值变成了10,所以输出结果是 c = 9 a = 10。
c = ++a;相当于 a++;c = a; 首先a此时值为10,先进行自增运算,值变成11,然后把a的值也赋给c,c的值也是11.
a++和++a单独作为语句时,基本等效于a = a + 1;
请注意,c = a++;和a = c++;这两个表达式的区别是面试题或者笔试题中经常会出现的,大家可以去网上搜面试题大全,绝对会有这样的题目出现。
自减运算的逻辑和自增完全一样,我们不做过多说明了。
说到面试题,与运算相关的还有一个比较常见的题:已经知道有int变量a和b,不使用第三个变量,如何交换a和b的值?比如我们上面的例子中a = 9,b = 2,不再声明第三个变量c的话,怎么让a = 2,b = 9?
再来问一个冷门的问题,char类型的变量能不能做加减乘除?字符串类型的变量能不能做加减乘除?这些问题虽然奇怪,但是还真的有公司面试会问这些问题,大家可以自己写代码验证一下。
位运算符
& 与运算。与运算会把参与运算的两边的值进行二进制的逐位比较,只有两位都为1时,结果才为1。比如9和2,二进制分别是1001和0010,那么1001 & 0010 的结果是0,因为他们没有哪一位是相同的,所以二进制结果是0000,十进制结果就是0.那么9&10呢,就是1001 & 1010,二进制结果是1000,就是十进制的8。
| 或运算。或运算也会把参与运算的两边的值进行二进制逐位比较,只要有一边为1,结果就为1,如果两边都是0,结果就为0. 还是9和2,二进制 1001 | 0010 的结果是1011,十进制结果是11. 9 | 10就是 1001 | 1010,结果是1011,十进制还是11.
~ 非运算。也就是取反运算,对二进制的值进行逐位取反,1变成0,0变成1.
^ 异或运算。两边的值进行二进制逐位比较,两边的值不一样就为1,两边的值相同就为0.还是以9和2举例,9^2,就是1001^0010,结果是1011,也就是11.
位运算的特点是速度快,效率高。
public class MySecondJavaClass{
public static void main(String[] args){
int a = 9;
int b = 2;
System.out.println(a&b);
System.out.println(a|b);
System.out.println(a^b);
}
}
运行结果和我们上面计算的是一致的。
在上面我们提到a和b不通过第三个变量交换彼此的值,其实通过加法和减法就可以很简单实现
public class MySecondJavaClass{
public static void main(String[] args){
int a = 9;
int b = 2;
a = a+b;
b = a-b;
a = a-b;
System.out.println("a的值是"+a+" b的值是"+b);
}
}
那么在有了位运算以后,我们发现,通过异或也能很轻松实现交换两个数值
public class MySecondJavaClass{
public static void main(String[] args){
int a = 27;
int b = 123456789;
a = a^b;
b = a^b;
a = a^b;
System.out.println("a的值是"+a+" b的值是"+b);
}
}
从结果可以看到,我们把a和b的值换成了其他数值,通过三次异或操作后,a和b的值发生了交换!
而且我们知道了位运算的速度的很快的,效率远高于通过加法减法交换两个变量的值,而且,我们知道无论是int还是long,都是有取值范围的,如果a+b的值超过了取值上限,在实际使用过程中,就会出现溢出,正数变成负数,但是位运算是不会有超出上限的问题的,因为它没有进位的问题。比如,我们知道int类型的正数上限是2的31次方-1,就是2147483647,那我们如果要把它跟2相互交换,通过加法,会看到什么?
public class MySecondJavaClass{
public static void main(String[] args){
int a = 2147483647;
int b = 2;
a = a+b;
System.out.println(a);
b = a-b;
System.out.println(b);
a = a-b;
System.out.println(a);
System.out.println(b);
}
}
可以看到,中间两个数相加时,它溢出了,向第32位借位了,而第32位是符号位,所以就变成负数了。我们再看异或过程
public class MySecondJavaClass{
public static void main(String[] args){
int a = 2147483647;
int b = 2;
a = a^b;
System.out.println(a);
b = a^b;
System.out.println(b);
a = a^b;
System.out.println(a);
System.out.println(b);
}
}
关于异或,程序领域比较常见的是将异或用于校验数据的准确性,但是一般初级的面试题不会出现这些。对于异或校验有兴趣的朋友可以自行搜索相关资料,关于异或的自反性也可以研究一下,还是很有意思的。
关系运算符
== 是否相等。用于判断左右两边的比较对象是否相等。相等则返回true,不相等则返回false。
!= 是否不相等。用于判断左右两边的比较对象是否不相等。不相等则返回true,相等则返回false。
> 是否大于。用于判断左边的比较对象是否大于右边的。大于则返回true,不大于则返回false。
>= 是否大于或者等于。用于判断左边的比较对象是否大于或者等于右边的。大于或者等于则返回ture,否则返回false
< 是否小于。用于判断左边的比较对象是否小于右边的。小于则返回true,否则返回false。
<= 是否小于或者等于。用于判断左边的比较对象是否小于或者等于右边的。小于或者等于则返回ture,否则返回false
可看到,关系运算符总是返回布尔型的值,需要注意的是,判断是否相等是两个等于符号,而不是一个等于符号。一个等于符号不是关系运算符,而是赋值运算符,比如我们之前已经提到过的int a = 9;是等价于int a;a = 9;它首先声明一个int型的变量a,然后给a赋值为9.
public class MySecondJavaClass{
public static void main(String[] args){
int a = 9;
int b = 3;
System.out.println(a==b);
System.out.println(a!=b);
System.out.println(a>b);
System.out.println(a>=b);
System.out.println(a<b);
System.out.println(a<=b);
}
}
我们再把关系型运算符都用代码过一遍,这里的关系显然很简单 ,我们就不再浪费唇舌。另外提出一个小问题,关系运算符是对两个对象进行比较,那么两个char类型的对象能不能进行比较呢?那String类型的呢?
逻辑运算符
& 与运算。当它左右两边是数值时,它进行的是位运算,当它两边是布尔型时(关系运算符的结果也是布尔型),进行的是逻辑运算,只有左边和右边同时为true,才会返回true,否则返回false。
! 非运算。对单个布尔型的求非。
| 或运算。当它左右两边是数值时,它进行的是位运算,当它两边是布尔型时(关系运算符的结果也是布尔型),进行的是逻辑运算,只要左边或者右边有一个为true,就会返回true,否则返回false。
&& 与运算。对左右两边的布尔型值求与,只有左边和右边同时为true时才返回ture,否则返回false。
|| 或运算。对左右两边的布尔型值求或,只要左边或者右边有一个为true,就返回true,否则返回false。
逻辑运算符的结果返回也是布尔型的值。我们注意到有两个与运算 & 和 &&,两个或运算 | 和 ||,那么他们有什么区别?区别在于 & 比较两边的条件时,会把两边都计算一遍,得出最后的值。比如 (3>4)& (3>2).显然,左边3>4是不成立的,结果是false,右边3 > 2是成立的,结果是true。根据与运算的特点我们知道,只有两边都是ture时,结果才是ture,而现在我们左边已经是false了,那右边的结果无论是ture还是false,最终的结果都是false,所以左边已经决定了整个结果,右边的不需要再计算了。但是使用 & 时,系统还是会把右边的结果再计算一遍,才得到最后的结果false。而 && 则不会进行后面的计算了,如果前面的结果已经决定了最终结果, && 会直接得出结果,不再计算后面的内容。(3>4)&&(3>2)的结果和 (3>4)&(3>2)的结果是一样的,但是运行机制不一样。
同样的,| 和 ||的区别也是一样,如果前面的表达式已经能决定结果,那么 || 就不会做后面的计算了。
&& 和 || 这种如果前面的表达式已经决定了最终结果就不再计算后面表达式的特性,被我们成为短路运算符。现在的面试题中可能不会直接问你& 和 && 的区别,但是可能会问你短路运算符的特点。特点就是他们会短路。
位移运算符
<< 带符号左移。将左边的数向左边位移若干位,位移位数为右边的数值。比如9<<2,表示把9向左边移动2位。我们知道9的二进制是1001,向左边移动两位,即变成了100100,转换成十进制就是36。那-9<<2呢,结果是-36。
>> 带符号右移。将左边的数向右边移动若干位,位移位数为右边的数值。比如9>>2,表示把9向右边移动2位。我们知道9的二进制是1001,向右边移动两位,变成了0010,转换成十进制就是2.-9>>2,的结果是-3,是不是有点奇怪?
>>> 无符号右移动。将左边的数向右边移动若干位,位移数为右边的数值。比如9>>>2,结果和9>>2一样,而-9 >>>2呢,结果是1073741821,变成了正数不说,数值还相当巨大。
public class MySecondJavaClass{
public static void main(String[] args){
System.out.println(-9>>>2);
System.out.println(-9>>2);
System.out.println(-9<<2);
}
}
我们主要看一看负数的位移情况,前面我们已经知道,数值在计算机中是以补码形式保存的,负数的补码是其原码的按位取反后末位加1,我们知道9的原码是1001,由于在java中整数默认是int型,所以这是一个32位的二进制数,第一位是符号位,后面31位是数值,负数的第一位是1,所以-9的原码是
10000000000000000000000000001001,它的补码是
11111111111111111111111111110111,当它向左位移2位时,变成了
11111111111111111111111111011100,此时求其原码对应的值,先将末位减1,11111111111111111111111111011011,再按位求反,结果是
10000000000000000000000000100100,就是负的100100,结果是-36(我去,32位的数求补码真是把人都累死了);
当它向右位移2位时,变成了
111111111111111111111111111111101,将末位减1,
111111111111111111111111111111100,再按位求反,得到
10000000000000000000000000000011,就是负的 11,结果是-3。
当它无符号向右位移2位时,变成了
001111111111111111111111111111101,我们知道此时它变成了一个正数,所以它的原码就是它的补码,我们还知道如果把它加上二进制的11,它就变成了
01000000000000000000000000000000,换算成十进制就是2的30次方,所以它现在的值就是2的30次方减去二进制的11,也就是3,最后结果是1073741821.
由于二进制的特性,所以左位移运算其实是将原始数值乘以2的位移次方,比如左位移1位,就是乘以2的1次方,左位移2位,就是乘以2的2次方,即4倍,我们刚才9<<2的结果是36,刚好是9乘以4.
有些公司面试可能会问相关的问题,比如如何把一个数快速翻倍,答案就是把它左位移1位。因为位移运算其实也是位运算,是效率很高的。当然我们在日后的开发过程中其实很少能用到位移运算,但是是否掌握这些知识,也是我们基本功牢靠的一个表现。如果你看到有面试题中讲到把数翻倍,翻4倍,8倍,16倍这样的2的指数次方倍的问题,注意力,这其实是在考位移运算符。
三元运算符
public class MySecondJavaClass{
public static void main(String[] args){
double pre_salary = 2.0;
double my_salary = 1.2;
String str = (my_salary>pre_salary) ? "吸血鬼!你凭啥那那么多!" : "渣渣!拖后腿的货" ;
System.out.println(str);
}
}
一些编程书上有一些简单的题目,比如有a和b两个值,请打印出他们其中较大的一个。我们用三元运算符可以很轻松的写出来
public class MySecondJavaClass{
public static void main(String[] args){
int a = 1000;
int b = 2365;
System.out.println(a>=b ? a : b);
}
}
赋值运算符
+= 将左边的变量值加上右边的值后再把相加后的值赋给左边的变量,如int a = 9; a += 10;相当于 int a = 9; a=a+9;
-= 将左边的变量值减去右边的值后再把相减后的值赋给左边的变量
*= 将左边的变量值乘以右边的值后再把相乘后的值赋给左边的变量
/= 将左边的变量值除以右边的值后再把相除后的值赋给左边的变量
%= 将左边的变量值对右边的值取模后再把取模后的值赋给左边的变量
&= 将左边的变量值与右边的值相与后再把相与后的值赋给左边的变量
^= 将左边的变量值与右边的值相异或后再把相异或后的值赋给左边的变量
|= 将左边的变量值与右边的值相或后再把相或后的值赋给左边的变量
<<= 将左边的变量值左位移右边的值后再把结果赋给左边的变量
>>= 将左边的变量值右位移右边的值后再把结果赋给左边的变量
除了基本的等号赋值运算符之外,其他的运算符都是类似的,需要注意的是,我们在进行赋值运算时,必须是可以使用该运算的数据对象才能使用这样的赋值运算符,比如:
public class MySecondJavaClass{
public static void main(String[] args){
String s = "Hello";
s += ",world";
System.out.println(s);
}
}
我们已经说了 s += ",world";相当于 s = s + ",world";那么说明两个字符串是可以相加的,从运行结果我们也知道,他们相加的结果是把两个字符串拼接起来了。那字符串可以相减吗?
public class MySecondJavaClass{
public static void main(String[] args){
String s = "Hello";
s = s - ",world";
System.out.println(s);
}
}
结果是什么?
编译错误,两个字符串是无法相减的,同样的,你会发现两个字符串除了相加,无法做其他的任何算术运算。其实这是由于字符串的特殊性造成。由于我们在实际的项目中会大量处理字符串,而字符串最常见的问题就是拼接问题,所以我们把一个字符串加上另外一个字符串时,并不是它们真的能相加,而是Java为了方便我们处理字符串,对字符串的拼接做了特殊处理。
另外,关于+=,还有一个很经典的面试题。short s = 3;s += 4;有什么问题?short s = 3;s = s +4;有什么问题?
我们在上面已经说了,这两个语句是基本等价的,但是写到代码里,你会发现short s = 3;s += 4;没有任何问题,执行结果是7,而short s = 3;s = s + 4;却无法通过编译
public class MySecondJavaClass{
public static void main(String[] args){
short s = 3;
s += 4;
System.out.println(s);
}
}
public class MySecondJavaClass{
public static void main(String[] args){
short s = 3;
s = s + 4;
System.out.println(s);
}
}
我们知道,一般情况下整数默认是int类型的,那么s = s + 4;就存在一个short类型的数和一个int类型的数相加,当一个低级别的数和一个高级别的数相加的时,JAVA会默认把低级别的数“升格“成高级别的数,也就是说此时s+4的结果是一个int类型的数了,而我们的s还是一个short类型的,让它接收一个int类型的,就会存在越界的问题了。解决这个问题的办法是我们再把这个结果转换回来,把s+4 再转换成一个short类型的数,操作方式就是在s + 4前面加上(short)。
public class MySecondJavaClass{
public static void main(String[] args){
short s = 3;
s = (short)(s + 4);
System.out.println(s);
}
}
这个时候系统运行没有问题了,结果也正常了。那为什么使用s+=4 的时候没有问题呢?显然是好心的Java为我们自动做了这个转换了。像 s = (short)(s+4) 的操作,把一个int型的数转换成一个short型的数的操作,叫做强制类型转换。
运算符的优先级
2.乘* 除/ 和取模%
3.加+ 和减-
4.左位移<< 右位移>> 和不带符号的右位移>>>
5.小于< 小于等于<= 大于> 大于等于>=
6.是否相等== 和 是否不等 !=
7.与运算 &
8.异或运算^
9.非运算|
10.短路运算符 与运算&&
11.短路运算符 或运算||
12.三元运算符 ?:
13.赋值运算符=,+=等
运算的优先级已经列出,虽然我们可以根据优先级来判断要怎么计算,但是在实际操作中,尽量不要写过于复杂的计算式,如果过程复杂,宁可分成多个步骤进行。如果实在不行,已经明确的逻辑要用括号扩起来,比如,我们知道短路运算符&&前后都是两个关系运算符,那我们写的时候尽量写成(a>b)&&(c==d)这样的,一个是逻辑更清楚,一个是代码更直观。养成良好的代码习惯,是编程最重要的一点,甚至比你的业务熟练度更重要!