如同前面我们已经总结过的标示符、关键字、注释一样,运算符和表达式也是Java的基础语言要素和一个Java程序的重要组成部分。
这是因为任何程序通常都会涉及到对数据的运算,因为所谓的编程工作,实际也就是将现实生活中的一系列复杂问题,抽象出来编写成为程序,方便更加容易的处理的过程。所以正如同我们在日常生活中也会涉及到一系列类似加减乘除的运算一样,一个程序也离不开“运算”。
运算符指明对操作数的运算方式,就如同数学中用“+”完成对操作数的加法运算一样,Java也与其类似,不同的是Java中运算的方式要比数学中多。
了解了运算符的基本概念之后,就不得不提及到“表达式”。简单来说,表达式可以认为是数据和运算符的结合。
二者实际可以认为是相辅相成的,因为就如同在数学运算中一样,如果我们对两个数进行加法运算,实际我们的目的是想要得到这两个数相加后的值。
这个时候我们自然就需要一个方式来表达这个值,而这个“表达方式”正是所谓的表达式。
举个简单的例子来说,在数学中一个一元方程式求两个数的和,被描述为:x = 3+5;
在这个一元方程式中,"+"就是执行数学加法运算的运算符,而整个方程“x = 3+5”则是运算的表达式。
这个原理在Java中是一样的,而x是我们定义的一个int型的变量。
废话少说,我们已经了解了Java中提供了很多不同功用的运算符。
那么,接下来就让我们通过这些运算符的功能对其进行分类,从而分别了解它们的功能及使用。
总的来讲,Java中的运算符可以被分为如下几个大类:
除此之外,还有一个最最常用和特别的运算符“=”,这个运算符被称为赋值运算符。我们知道了表达式是数据和运算符的结合,所以,赋值运算符就是将它们结合起来的桥梁。
接着,就分别来看一看这些不同的运算符的功能及使用。
一、算术运算符的使用:(算术运算符实际是最易理解的,其原理都和数学中算数运算是相同的)
//算数运算符的应用
private void arithmeticOperatorDemo(int a,int b){
int result = 0;
//加法运算符:+。
result = a + b;
//减法运算符: -。
result = a - b;
//乘法运算符: *。
result = a * b;
//除法运算符:/。
result = a / b;
//求余运算符:%。
result = a % b;
}
需要注意的是:使用除法运算符对两个整数做除法运算时,运算的结果同样会被提升为整数,也就是说小数点后的数字会被忽略。
例如,我们对两个整数5和2进行除法运算,得到其运算结果。那么:
表达式:int a = 5/2;的运算结果为2。
表达式:double a = 5/2;的运算结果为2.0。
如果想要得到完整的运算结果2.5,那么被用作除法运算的数也必须使用浮点数的形式:double a = 5.0/2.0。
二、自增自减运算符的使用:
首先,顾名思义,自增自减运算,也就是指一个操作数对自身的值进行增加或减少的运算。简单的来说例如我们有一个整形变量“a”,
那么,实际上自增运算:a++的运算效果就等同于a = a + 1;
这样的应用实际上是很简单的,而需要我们注意的是:自增自减运算符有作为“前缀”和“后缀”的两种不同的使用方式,也就是说可以使用“a++”或者“++a”两种方式对一个数进行自增自减运算。那么,这两种方式的区别在于什么?看一段代码:
private static void selfArithmeticOperatorDemo() {
int a = 0;
int b = 0;
int sum_1 = 5 + (a++);
int sum_2 = 5 + (++b);
System.out.println("a="+a+",b="+b);
System.out.println("sum_1="+sum_1+",sum_2="+sum_2);
}
这段代码运行的打印结果为:
a=1,b=1
sum_1=5,sum_2=6
让我们分析一下上面的代码:
首先,我们定义了两个值同样为0的整形变量“a”与“b”;
之后,我们分别对“a”与“b”进行了自增运算,并让其结果与同一个数5进行加法运算。
不同的是,变量“a”我们使用“后缀”自增运算方式,变量“b”我们使用“前缀”的方式。
同时,我们定义了另外两个整型变量sum_1与sum_2分别存放两次运算的结果。
通过程序的输出结果我们发现:
1.“a”与“b”经过自增运算之后,值都由初始值0增长为了1。
2.然而变量a执行自增并与5进行加法运算后得到的结果为5,而变量b执行自增并与5进行加法运算后得到的结果为6.
这也正是自增自减运算符作为“前缀”和“后缀”两种方式使用的不同效果。
我们简单总结来说,自增自减运算符的使用可以分为两种情况:
- 如果我们仅仅是对一个变量自身进行单纯的自增自减运算,使用“前缀”和“后缀”前两种方式达到的效果实际上是相同的。
- 但是如果在将自增自减运算和表达式结合使用的话,二者就有了一定的不同。通常来说,我们会这样归纳其不同之处:当自增自减运算符作为前缀结合表达式使用时,会先执行变量的自增自减运算,再执行表达式;而如果作为后缀,则将先执行表达式,然后再进行自增自减运算。
我们当然可以这样理解“前缀”和“后缀”的不同。但实际上这两种使用方式在内存中的运算过程究竟是怎么样的呢?
随着我们的深入学习,就应当明白:并不是说,自增自减运算符作为前缀,则先执行自增自减运算;而作为后缀,则先执行表达式运算。
例如现有一个值等于1的整形变量num。那么,以表达式int a = num++;和int a = ++num为例,其在内存中的运算过程实际上是:
- 当使用“num++”的方式时:在整个运算过程最初,JVM会将num的初始值“1”取到,然后在内存中开辟一块区域作为“预存区”,并将“1”存储到该区域内。而紧接着,就会对num进行自增运算得到新的值“2”。所以,在这个时候num在内存中的值实际上已经由“1”变成了“2”。当完成这个运算过程后,JVM则会在“预存区”中将事先存放的num初始值“1”取出,参与整个表达式的运算,将“1”赋值给变量a,所以这时得到的“a”的值为1。
- 而当使用“++num”的方式时,JVM则会直接将num的初始值“1”取到,进行自增运算。然后将自增运算后得到的值“2”,参与到整个表达式运算当中,将该值赋值给变量“a”。所以,通过这种方式,变量“a”的值为2。
三、关系运算符的使用:
所谓“关系运算符”,自然是指:用于判断两个变量之间的关系的运算符。而在Java中,所有的关系运算符的比较结果,都返回为一个boolean类型的数据。也就是说比较结果非真即假。同样通过一段简单的代码来看一下Java中各个关系运算符的使用:
private static void relationshipOperatorDemo(int a,int b) {
boolean result = false;
// == ,比较两个变量是否相同,相等返回true,不等返回 false
result = a == b;
// != ,比较两个变量是否不同
result = a != b;
// > ,比较一个变量是否大于另一个变量
result = a > b;
// < ,比较一个变量是否小于另一个变量
result = a < b;
// >=,比较一个变量是否大于或等于另一个变量
result = a >= b;
// <=,比较一个变量是否小于或等于另一个变量
result = a <= b;
}
四、位运算符的使用:
在计算机中,所有的整数都是通过二进制进行保存的,也就是一串由“0”和“1”组成的数字,每一个数字占一个比特位,8个比特位就被称为一个字节。
那么顾名思义,位运算符就是指对一个数进行按比特位的运算。而按位运算最大的好处就在于:
位运算是直接被cpu所支持的,所以其运算速度与效率上就远远高于其它运算方式。
Java中提供了以下4种位运算符:
- 按位与运算符 & :如果对应位的值都为1,则运算结果为1,否则则0.
- 按位或运算符 | :如果对应位的值都为0,则运算结果为0,否则为1.
- 按位异或运算符 ^:如果对应位的值相同 ,则运算结果为0,否则为1.
- 按位取反运算符 ~:将操作数的每一位按位取反,也就是:0变1,1变0.
private static void bitOperatorDemo(){
System.out.println("5与8进行按位与运算的结果是:"+(5&8));
System.out.println("5与8进行按位或运算的结果是:"+(5|8));
System.out.println("5与8进行按位异或运算的结果是:"+(5^8));
System.out.println("5进行按位取反运算的结果是:"+ (~5));
}
这一段测试代码的输出结果为:
5与8进行按位与运算的结果是:0
5与8进行按位或运算的结果是:13
5与8进行按位异或运算的结果是:13
5进行按位取反运算的结果是:-6
为了验证其输出结果,首先我们将两个整型数5和8还原为二进制形式,值分别为:0101和1000。接着,就让我们根据各个位运算符的运算原理进行一次验证:
1.与运算:0101和1000按照与运算的运算原理,得到的运算结果为:0000,也就是十进制当中的0
附:与运算的特性:两个数相与,只要有一个数为0,那么结果就为0。
2.或运算:0101和1000按照或运算的运算原理,得到的运算结果为:1101,转换为十进制的值也就是:13
3.异或运算:0101和1000按照异或运算的运算原理,得到的运算结果为:1101,同样也就是十进制的13.
附:对于异或运算的应用,值得一提的是,异或运算有一个特性:一个数异或运算同一个数两次,得到的结果还是这个数。
就以0101和1000为例,0101异或1000运算一次的结果是1101,1101再与1000进行异或运算,得到的结果为0101.也就是说0101^1000^1000 = 5^8^8 =5。
这实际上也是一种最基础的加密解密的应用方式,例如你的数据为5,与8进行异或后,得到的结果是13,13再与8进行异或运算后,得到的结果为5。
这个过程中,5是原始数据,13是加密后的数据,而8则是密匙。
4.取反运算:这是值得一提的运算方式,0101按位取反得到1010,你可能会想,这不就是十进制当中的10吗?会什么取反运算后变成了-6呢。
这是因为在Java中的,int型的数据在内存中实际长度32位,也就是说5在Java内存中的完整表现形式为:
0000 0000 - 0000 0000 - 0000 0000 - 0000 0101,所以在取反运算后,其值变为了:
1111 1111 - 1111 1111 - 1111 1111 - 1111 1010。而该二进制数正对应于十进制当中的-6.
值得一提的是,在计算机中:
0000 0000 - 0000 0000 - 0000 0000 - 0000 0101这样一个数的绝对值转换为的二进制数,被称为原码。
1111 1111 - 1111 1111 - 1111 1111 - 1111 1010这样一个对原码进行按位取反运算得到的二进制数,则被称为反码。
而对一个数的反码加1,运算后得到的二进制数则被称为补码。
所以,通过位运算符“~”对一个数进行取反运算,实际上正是在获取这个数的反码。
到此,我们观察发现:我们对5进行取反运算后,获取的反码对应的正是十进制当中的-6.
如果我们对-6加1,得到的结果正是:-5.而-5则正是5的负数形式。
这也正是为什么说,在计算机中,一个负数的二进制表现形式是由其补码表示的。
五、移位运算符的使用:
与位运算符类似,移位运算符则是指对一个数的二进制表现形式按指定位数进行移位的运算。
Java中提供了三种移位运算符:
- 左移运算符“<<”:将操作数的比特位向左移动指定位数,移位后右边空缺的位用0填补。
- 右移运算符“>>”:将操作数的比特位向右移动指定位数,移动后用于表示符号的最高位按原有的符号位填补,也就是说如果原本符号位为0,则移动后填补0,为1则补1
- 无符号右移运算符">>>":与">>"的移位运算规则相同,不同之处在于:无论原有最高位为1还是0,填补时都补0(也就是说无论正数还是负数,移位后都将变为正数)
那么,假如我们就以“-6”(1111 1111 - 1111 1111 - 1111 1111 - 1111 1010)为例:
如果对其进行左移3位运算得到的结果为:1111 1111 - 1111 1111 - 1111 1111 - 1101 0000,对应于十进制中的-48.
如果对其进行右移3位运算得到的结果为:1111 1111 - 1111 1111 - 1111 1111 - 1111 1111,对应于十进制中的-1
如果对其进行无符号右移3位运算得到的结果为:0001 1111 - 1111 1111 - 1111 1111 - 1111 1111,对应于十进制中的536870911
然后,我们用一段代码对其加以验证:
private static void bitMoveOperatorDemo(){
System.out.println((-6)<<3);
System.out.println((-6)>>3);
System.out.println((-6)>>>3);
}
运行发现其输出结果正是:
-48
-1
536870911
而提到移位运算,想起曾经看到过类似这样的一道面试题:用最有效率的方法算出2乘以8的结果。
其实解题思路很简单,我们注意到两个关键字:效率和运算。当涉及到操作数的运算,且要求保证最快的效率时,首先应该想到的,就是位运算。
而我们在二进制数中,可以发现这样一个规律:一个数向左移n位,就相当于乘了2的n次方;同理,向右移n位,则相当于除以2的n次方。然后,我们再看这道面试题发现,8正好是2的3次方,也就是说,我们将2进行左移运算3位,则等于乘以2的3次方8.
所以这道题的答案就是:int result = 2<<3;
之所以这样做最有效率,我们已经说过,是因为位运算是直接被cpu支持的,所以不需要做任何额外操作。
六、逻辑运算符的使用:
通俗的说,逻辑运算符用于对多个表达式进行条件判断,与关系运算符相同,其返回结果也为boolean。我们分别来看一下其判断规则:
&:非短路逻辑与运算符,当判断的两个条件都为真时,则判断结果为真;否则只要有一个条件为假,则返回假。
|:非短路逻辑或运算符,用于判断的条件中只要有一个为真,则为真;当判断条件结果全为假,则返回假。
!:对条件判断结果取反,例如:boolean b = !(5>2) 的返回结果为false。
&&:短路与运算符,与&的判断规则大致相同,不同的是:&&只要判断到有一个条件的判断结果为假,则会立即“短路”,即刻返回判断结果为假。而&无论结果,都会一次将所有条件判断完后,才会返回最终的判断结果。
||:短路或运算符,与|的判断规则大致相同,不同的是仍是:||只要判断到有任一一个条件为真,则会立即“短路”,返回判断结果为真。
我们仍然通过一个简单的例子来看一下非短路逻辑运算符合短路运算符之间的区别:
private static void logicOperatorDemo() {
int a = 5;
int b = 5;
if ((++a) > 5 | (++b) > 5) {
System.out.println("非短路逻辑或运算:");
}
System.out.println("a:" + a);
System.out.println("b:" + b);
a = 5;
b = 5;
if ((++a) > 5 || (++b) > 5) {
System.out.println("短路逻辑或运算");
}
System.out.println("a:" + a);
System.out.println("b:" + b);
}
这段程序的输出结果为:
非短路逻辑或运算:
a:6
b:6
短路逻辑或运算
a:6
b:5
这正是体现了非短路逻辑运算符与短路逻辑运算符的区别:
当我们使用非短路逻辑或运算的时候:会依次将判断条件执行完毕后,再返回判断结果,所以(++a) > 5 | (++b) > 5会执行完6>5|6>5,返回结果true。执行后a与b的值都为6。
而当我们使用短路逻辑或运算的时候:只要判断到有任何一个条件结果为true,则会马上返回true,所以(++a) > 5 | (++b) > 5执行到(++a)=6 > 5后,就已经返回了判断结果为true。所以后面的(++b) > 5根本没有执行,自然最后得到的结果就是a=6,而b=5了。
七、三元运算符的使用:
简单的来说,三元运算符就是对类似于下面的一种代码的一种简化书写方式:
private static void demo(int a){
int num = 0;
if(a>0){
num =1;
}else{
num =2;
}
}
通过三元运算符,我们可以将其简化为:
private static void demo(int a) {
int num = a > 0 ? 1 : 2;
}
到这里,对于Java当中各个运算符的使用的总结就告一段落了,更多的使用方法可以自己在实际操作中加以体会、巩固和深入。