你好,欢迎回来,我是BoBo!HAKUNA MATATA!!!
提到运算,你可能会立即想到加、减、乘、除四则运算以及“九九乘法表”。想当年背诵“九九乘法表”,那简直是一场噩梦啊!时隔多年,心中依然隐隐作痛。不过,现在学习使用编程语言来进行运算,要比我们自己用公式和“九九乘法表”高效的多,而且,也容易得多,因为真正的运算过程都交给机器处理,我们只需要提供数据和计算规则就行了。
紧接着上次课程的内容,现在我们学习在 Java 语言中如何进行数据的运算。怀揣着孩子一样的好奇,看看 Java 是怎么把数据一步一步运算出结果的。
以下是这篇文章的主要知识点:
- 十八般武艺:Java中的运算符
- 算术运算:先学会舞刀弄枪
- 赋值运算:给技能加个点
- 比较运算:是骡子是马,拉出来遛遛
- 逻辑运算:谁是谁非,总要有个说法
- 三目运算:选择困难症的终极答案
第一关 十八般武艺:Java中的运算符
拥有程序思维的第一步,就是要学会用计算机、或者说编写程序帮我们处理数据,而不是我们自己动手。Java 语言中有很多进行数据运算的方式,就如前面所说,包括但不限于:算术运算、比较运算、逻辑运算、赋值运算、三目运算等。每一种运算方式,又都包含了很多的运算符,我把这些运算符形象的称为“十八般武艺”,学习 Java 中的运算,就是学习这些运算符的使用,也就是修炼这“十八般武艺”的过程。
运算符,顾名思义,就是对数据(常量和变量)进行运算的符号。我们把数据用运算符连接起来,就构成了可以进行运算的表达式。比如 1 + 2
、3 * 4
等等。看看这行代码:
public class Test{
public static void main(String[] args) {
int number = 1 + 2; // 使用加号(+)将常量1和2连接起来,构成了一个加法表达式,并把运算结果赋值给变量number
System.out.println(number); // 输出number的值
}
}
上面的式子就是使用运算符加号(+)将常量 1
和 2
连接起来,构成了一个加法表达式,并把运算结果赋值给变量 number
,不出意外的话,打印结果应该是:
3
事实上,参与运算的数据可能会有很多,也有可能是变量、常量等混合在一起进行运算,比如(接上面代码):
public class Test{
public static void main(String[] args) {
int number = 1 + 2; // 使用加号(+)将常量1和2连接起来,构成了一个加法表达式,并把运算结果赋值给变量number
System.out.println(number); // 输出number的值
int count = number + 10; // 变量和常量同时参与运算
System.out.println(count); // 输出计算结果
}
}
打印结果:
13
除此之外,运算的方式也有很多,加法、减法、取余(取模)、比较运算等等,但它们都有一个共同的特点:每个表达式都会有一个运算结果。我们根据表达式运算结果的数据类型,将表达式的类型进行归纳分类,比如:
整型表达式:运算结果为整数。比如:
1 + 2
、10 * 20
、5 - 3
,它们的运算结果都是整数浮点型表达式:运算结果为浮点数。比如:
3.14 * 2
、0.618 + 0.382
、3.0 / 1
,它们的运算结果都是浮点数布尔型表达式:运算结果为布尔类型的值。比如:
2 > 1
、(20-10) < 15
,它们的运算结果都是布尔型:要么true、要么false。
练好了运算符、表达式的基本功,现在,我们可以开始学习真正的武艺了。
第二关 算术运算(上):学会舞刀弄枪
先来几个简单的招式,好好复习我们小学时期的算术运算。Java 中的算术运算符【大概、也许】有七种:
前面四个运算符还算常见:+
、 -
、 *
、 /
,虽然乘号(*)和除号(/)跟我们以前见到的长得不一样,但并不难理解。百分号(%)在这里是“取余”、“取模”的意思,也就是说,使用百分号(%)可以得到数字 7 除以 3 之后的余数:1。而 ++
和 --
就比较陌生了,它们分别代表数据 “自增1” 和 “自减1”,这种运算是我们以前没见过的,接下来,我手把手教你每个招式——运算符的用法。
2.1 加、减、乘、除
先学会舞刀弄枪——四则运算的用法,上代码:
public class Test{
public static void main(String[] args) {
int num1 = 3;
int num2 = 4;
int num3 = 5;
int num4 = 10;
// 1.加法运算
int add = num1 + num2;
// 2.减法运算
int subtract = num2 - num1;
// 3.乘法运算
int multiply = num2 * num3;
// 4.除法运算
int divide = num4 / num3;
// 分别输出运算结果
System.out.println(add); // 输出加法计算结果
System.out.println(subtract); // 输出减法计算结果
System.out.println(multiply); // 输出乘法计算结果
System.out.println(divide); // 输出除法计算结果
}
}
输出结果:
7
1
20
2
运算结果完全没有新意。如果你把上面的数据类型换成其它类型的整数,结果也不会有什么意外——如果不小心翻车,请点击传送门。但是如果换成浮点数,可能会遇到惊喜——Java 中的浮点数运算不够精确,快去试一试,看你能不能找到彩蛋!
除法运算有个细节要注意:如果相除的两个数进行运算,除不尽怎么办?猜想一下,下面这个行代码会得到什么结果:
System.out.println(7 / 3); // 即 7 / 3,结果是什么,2.333...还是2,还是1
看结果:
public class Test{
public static void main(String[] args) {
System.out.println(7 / 3);
}
}
结果居然是2!为什么会这样?
切记一点:除法运算符( / ),得到两个数据相除的商,在 Java 语言中,整数除以整数结果还是整数,如果除不尽,会舍弃余数。也就是说,7 / 3 的商是2,余数为1,因为参与运算的被除数、除数都是整数(int
类型),所以计算结果还是整数,舍弃了余数部分,结果是2。
是不是有一种恍然大悟的感觉。这是 Java 中的运算与我们以前的认知第一个不一样的地方。
考一考:算术运算符
下面代码的运算结果是什么?
public class Test{
public static void main(String[] args) {
int int1 = 10;
long lon1 = 20;
System.out.println(lon1 + int1 * (int1 + lon1) / lon1);
}
}
A. 30
B. 45
C. 35
D. 300
答案:C
解析:依然遵循这样的规则——“先乘除后加减,有括号先算括号里面的”,所以答案是35
2.2 取模、自增(++)和自减(--)
再教你三个进阶招式(%、 ++、 --):
public class Test{
public static void main(String[] args) {
int num1 = 3;
int num2 = 4;
int num3 = 5;
int num4 = 10;
int remainder = num3 % num1; // 取模/取余运算,5对3取模,结果是?
System.out.println(remainder); // 输出取模运算结果
num2++; // num2自增1
num4--; // num4自减1
System.out.println(num2); // 输出自增之后的运算结果
System.out.println(num4); // 输出自减之后的运算结果
}
}
输出结果:
2
5
9
百分号(%)是取模运算,也叫取余运算,是除法运算的一种扩展,只不过除法运算得到的结果是商,而取模运算得到的结果是余数。如果两个数进行取模运算,结果是0,意味着什么?没错,这就是整除的效果,所以,取模运算(%)可以用来判断两个数是否能够整除,也就是说,被除数是除数的倍数。
加加(++)和减减(--)运算是让变量进行自增或自减。这里要注意,不能将这两个运算符直接使用到常量上,比如下面的代码是错误的:
1++; // 不允许常量自增或自减
思考一下,为什么?那是因为常量的概念,规定了它不能够被修改,所以,如果你想要获得2,那么直接使用字面值常量2就行了,完全不需要使用另一个常量进行运算。还有个细节,上面的代码,也可以把 ++
和 --
放到变量的前面,这样的运算结果是一样的(放在变量前、后的代码不能同时存在,否则数据会进行两次运算哦):
来,我们试试把++和--写到前面
++num2; // num2自增1
--num4; // num4自减1
public class Test{
public static void main(String[] args) {
int num1 = 3;
int num2 = 4;
int num3 = 5;
int num4 = 10;
int remainder = num3 % num1; // 取模/取余运算,5对3取模,结果是?
System.out.println(remainder); // 输出取模运算结果
// num2++; // num2自增1
// num4--; // num4自减1
++num2; // num2自增1
--num4; // num4自减1
System.out.println(num2); // 输出自增之后的运算结果
System.out.println(num4); // 输出自减之后的运算结果
}
}
输出结果没有变化:
5
9
当然,加加(++)和减减(--)也可以像别的运算符那样,把结果赋值给一个新的变量,就像这样:
public class Test{
public static void main(String[] args) {
int num1 = 3;
int num2 = 4;
int num3 = 5;
int num4 = 10;
int num5 = num2++; // 猜想:num2自增1,然后赋值给新变量num5
int num6 = num4--; // 猜想:num4自减1,然后赋值给新变量num6
System.out.println(num5); // 输出自增之后的运算结果
System.out.println(num6); // 输出自减之后的运算结果
}
}
输出结果:
4
10
咦,怎么还是原来的值?难道是没有发生运算?
看来我们的猜想不正确,原来
int num5 = num2++; // 结论:num2先赋值给新变量num5,然后才进行自增运算
int num6 = num4--; // 结论:num4先赋值给新变量num6,然后才进行自减运算
我把加加(++)和减减(--)放到变量前面试试:
public class Test{
public static void main(String[] args) {
int num1 = 3;
int num2 = 4;
int num3 = 5;
int num4 = 10;
// int num5 = num2++; // 结论:num2先赋值给新变量num5,然后才进行自增运算
// int num6 = num4--; // 结论:num4先赋值给新变量num6,然后才进行自减运算
int num5 = ++num2; // 猜想:num2自增1,然后赋值给新变量num5
int num6 = --num4; // 猜想:num4自减1,然后赋值给新变量num6
System.out.println(num5); // 输出自增之后的运算结果
System.out.println(num6); // 输出自减之后的运算结果
}
}
输出结果:
5
9
终于变成正确答案了,吓得我长舒了一口气。。。
int num5 = ++num2; // 结论:num2自增1,然后赋值给新变量num5
int num6 = --num4; // 结论:num4自减1,然后赋值给新变量num6
但是,这是为什么呢?加加(++)和减减(--)放到变量前和放到变量后为什么结果不一样,到底数据有没有进行运算呢?剖析一下这两招:
- 单独使用:放在变量前或后结果一样
- 参与其它运算:
在变量前,先自增(自减) ,再进行其它运算
在变量后,先以原值进行其它运算,再自增(自减)
所以,第一次把 ++
和 --
放在变量后,是把变量原来的值赋值给新变量,而自增(或自减)的值被抛弃了,因此打印的是原来的值;第二次把 ++
和 --
放在变量前,是把运算之后的值赋值给新变量,因此打印的是计算之后的数据,这就是加加(++)和减减(--)这两个运算符的底层原理。 ++
和 --
是一种特殊的运算,这是再一次不同于我们以往认知的一个地方。
来,放在一起比较一下:
public class Test{
public static void main(String[] args) {
int num1 = 3;
int num2 = 4;
int num3 = 5;
int num4 = 10;
int remainder = num3 % num1; // 取模/取余运算,5对3取模,结果是?
System.out.println(remainder); // 输出取模运算结果
// num2++; // num2自增1
// num4--; // num4自减1
++num2; // num2自增1
--num4; // num4自减1
System.out.println(num2); // 输出自增之后的运算结果
System.out.println(num4); // 输出自减之后的运算结果
// int num5 = num2++; // num2赋值给新变量num5,然后自增1
// int num6 = num4--; // num4赋值给新变量num6,然后自减1
int num5 = ++num2; // num2自增1,然后赋值给新变量num5
int num6 = --num4; // num4自减1,然后赋值给新变量num6
System.out.println(num5); // 输出自增之后的运算结果
System.out.println(num6); // 输出自减之后的运算结果
}
}
考一考:取模运算
如何获取一个三位数的十位?比如:324。
答案:
public class Test{
public static void main(String[] args) {
int number = 324;
int noGeWei = number / 10; // 除以10,商32(Java中整数除以整数还是整数,舍弃了余数)
int shiWei = noGeWei % 10; // 再对10取余,商3,余数为2,即十位
System.out.println("324的十位数是:" + shiWei);
}
}
考一考:自增(++)、自减(--)运算
下面代码的运算结果是什么?
public class Test{
public static void main(String[] args) {
int a = 5;
a++;
int b = ++a;
System.out.println("a: " + a);
System.out.println("b: " + b);
}
}
A. 5,5
B. 5,6
C. 6,6
D. 6,7
E. 7,6
F. 7,7
答案:F
解析:变量 a
自增了两次,所以是7
变量 b
是在变量 a
第二次自增之后被赋值的,因为加加(++)在变量前(第四行代码),所以 b
也是7
第三关 算术运算(下):学会舞文弄墨
3.1 字符型(char)的算术运算
基础操作太简单,敢不敢玩点高级的?
来一波神奇操作:
int ch = 'a' + '0'; // 字符型数据进行运算,结果是?
先不着急看结果,你能不能看懂上面的代码?想要弄明白上面的代码,必须解决下面两个问题:
- 为什么字符型数据可以进行算术运算?
- 字符型数据是如何进行运算的?
上次课我们提到过,字符数据类型(char)代表世界上所有国家和地区的文字、数字、特殊符号等,而这些字符数据,在计算机中是如何存储的呢?其实,计算机中所有数据的存储和运算都是以二进制形式进行的。也就是说,计算机会把所有的数据都转化成数值,然后将该数值以二进制的形式存储在磁盘上,也因此,进行运算的过程,是以二进制的形式进行的。你想知道二进制数据的运算过程?想得美,我是不会教的。回到上面的问题,你需要知道的是:
- 每个字符型数据在计算机中都是一个数值,所以它可以进行算术运算。
- 既然字符型数据是数值,就把它对应的数值拿过来进行运算好了。
那么问题来了,每个字符对应的数值是多少呢?秀一波操作:
public class Test{
public static void main(String[] args) {
System.out.println('a' + 0); // 任意数值 + 0 = 数值本身
System.out.println('0' * 1); // 任意数值 * 1 = 数值本身
}
}
算术运算符的加法和乘法,都有特殊技巧:任意数值 + 0 = 数值本身;任意数值 * 1 = 数值本身。所以,上面的输出结果是:
97
48
也就是说,字符型字面值常量 'a'
和 '0'
在计算机中存储的数值分别是97和48。所以,这两个字符相加的结果是:
145
这就是字符型(char)的算术运算,归根结底,就是把 char
类型的字符转成了 int
型的数值进行运算的。
如果有个表,能随时查阅常见的字符和对应的数值就好了!
美国信息交换标准码对照表(ASCII码对照表,读音“阿斯key”),里面记录了常见字符和对应数值的对照关系,拿走不谢:
太多记不住?没关系,常用的字符 'a' - 'z'
、'A' - 'Z'
、'0' - '9'
对应的数值都是连续的,只记住它们的开始和结尾字符对应的数值就行了:
怎么样老铁,这服务还算到位不?
到底为止,Java 语言的八种基本数据类型,只剩下布尔(boolean)类型没有涉及。你以为要讲 boolean
类型了?偏不!因为:
Java 语言中,布尔类型(boolean)不参与算术运算。
咳咳。
3.2 数据类型转换
欢迎来到传送门。我是机器人砰砰,有何指示?
“代码翻车了,都是你教的,你自己看:
”
“不就是个编译错误嘛,不怕不怕哈!
我们来分析一下错误信息:
Incompatible types. // 不兼容的类型
Required: byte // 需要 byte 类型
Found: int // 却找到 int 类型
看起来好像是类型不匹配的问题:程序需要一个 byte
类型,却得到了一个 int
类型。”
“可我的代码里根本没有用 int
类型啊,这是怎么肥四?”
“是这样的,byte
、short
这两种类型在运算的时候,会先把它们转成 int
,然后再进行运算,运算结果也是 int
,int
类型就是这么来的。”
“那现在怎么办?”
“需要把运算结果进行数据类型转换。就像这样:
// 强制类型转换:把 b2 - b1 的结果进行转换,从 int 类型转成 byte 类型
byte b3 = (byte)(b2 - b1);
”
这就是强制类型转换操作。把 b2 - b1 的结果进行转换,从 int
类型转成 byte
类型。由于 int
类型的范围大于 byte
类型,所以叫“强制类型转换”,也叫“显式类型转换”;反过来,如果是从 byte
类型转成 int
类型,叫“自动类型转换”,也叫“隐式类型转换”,因为这个过程是默认发生的。比如下面的代码,就是隐式类型转换:
byte x = 3;
int y = x; // 将 byte 类型的变量 x 赋值给 int 类型的变量 y,隐式类型转换
隐式转换是自动发生的,不需要我们进行任何操作。Java 语言中的八种基本数据类型,除了布尔类型(boolean)不参与算术运算之外,其它的七种类型自动转换的顺序是这样的:
之所以是这样的顺序,是因为它们各自的表示范围不同,从小到大依次是:byte
、short
、char
、int
、long
、float
、double
。小类型与大类型一起运算,除了 byte
、short
、char
这三种类型或默认提升成 int
类型进行运算之外,其它情况都会自动将小类型提升成大类型,然后再进行运算,运算结果也是大类型。看看这行代码:
public class Test{
public static void main(String[] args) {
System.out.println(7 / 3.0); // 除法运算,被除数是整数,除数是浮点数,结果是几?什么数据类型?
}
}
输出:
2.3333333333333335
结果是一个无限循环小数,这个结果很有意思:第一:结果是浮点数;第二,结果不是2,跟前面遇到的情况不同;第三,结果最后一位如果是四舍五入的值,应该是3而不是5。解决这三个问题:第一,数字7默认是 int
类型,3.0 是 double
类型,根据上面的自动类型转换图示,运算过程会将 int
类型的7转成 double
类型,所以结果也是 double
类型;第二,既然运算结果是 double
类型,那么除不尽的部分,也就是小数点后面的值,不会被舍弃,所以结果不是2;第三,在 Java 语言中,浮点数参与运算,很有可能会造成精度的损失,这是 Java 语言浮点数运算的一个缺陷,所以小数点最后的数值是不可预知的。
除此之外,强制类型转换也并非是万能的,甚至,有的时候可能会发生意想不到的结果。比如这段代码:
public class Test{
public static void main(String[] args) {
int first = 131;
int second = 1;
byte bys = (byte)(first - second); // 将两个 int 类型的值进行运算,然后将结果强转为小类型 byte
System.out.println(bys); // 输出结果
}
}
输出:
-126
怎么会这样?难道不应该是130吗,居然还是个负数?
那是因为,byte
类型只能表示 -128 到 127
之间的数据,超出这个范围的数据,只能继续从 -128算起,所以,将 int
类型的130转成 byte
类型的数据运算过程是这样的:
127 + 1 = -128 // 130 = 127 + 3
-128 + 1 = -127
-127 + 1 = -126 // 最终结果是 -126。
最终结果是 -126。
多种不同的数据类型的好处在于,可以根据内存和计算量的大小进行选择,缺点也很明显,不同类型的数据进行运算时,有可能发生严重的兼容性问题。Java 是一种强类型的语言,这意味着两点:一,每一种数据都必须有非常清晰的类型;二、不同类型的数据进行运算,必须保证彼此的兼容性。不同类型的数据之间可能会进行运算,而这些数据取值范围不同,存储方式不同,直接进行运算可能会造成数据损失,所以当且仅当大类型数据可以转换为小类型数据(而不会发生精度损失)时,才进行转换。关于数据类型转换,你需要知道的:
强制类型转换操作是要把运算“结果”转成小类型,而不是只转换变量,这是操作中容易出现的错误,为此,进行强转时一定要注意格式:
小类型 变量名 = (小类型)大类型数据;
参考完整代码:
public class Test{
public static void main(String[] args) {
byte b1 = 3;
byte b2 = 4;
// byte b3 = b2 - b1; // 报错
// 强制类型转换:把 b2 - b1 的结果进行转换,从 int 类型转成 byte 类型
byte b3 = (byte)(b2 - b1);
System.out.println(b3); // 输出结果
/* 隐式转换 */
byte x = 3;
int y = x; // 将 byte 类型的变量 x 赋值给 int 类型的变量 y,隐式类型转换
/* 多数据类型参与运算,结果是几?什么数据类型? */
System.out.println(7 / 3.0); // 除法运算,被除数是整数,除数是浮点数,结果是 2.3333333333333335
/* 数据太大,小类型接收不了 */
int first = 131;
int second = 1;
byte bys = (byte)(first - second); // 将两个 int 类型的值进行运算,然后将结果强转为小类型 byte
System.out.println(bys); // 输出结果:-126
}
}
考一考:强制类型转换
下面的代码运算结果是什么?
public class Test{
public static void main(String[] args) {
short s1 = 20;
short s2 = 30;
short s3 = (short)s2 - s1;
System.out.println(s3);
}
}
A. 20
B. 30
C. 10
D. 编译错误
答案:D
解析:强转操作仅仅对变量 s2
有效,所以 (short)s2 - s1
的结果还是int
类型,直接赋值给short
类型的 s3
,编译报错
3.3 加号(+)的骚操作
加号(+)除了作为算术运算符之外,还有一个骚气蓬勃的技能,在开发中使用频率非常高,那就是作为字符串连接符来使用:
public class Test{
public static void main(String[] args) {
int age = 10;
String desc = "我的年龄是";
System.out.println(desc + age); // 直接使用加号,将字符串和数值连接起来
}
}
输出结果:
我的年龄是10
这里的加号起到字符串拼接的作用,所以,它现在是字符串连接符。除了用在两个变量中间,常量也是通吃:
public class Test{
public static void main(String[] args) {
System.out.println("我是一个" + "小小的石头。"); // 直接使用加号,将两个字符串连接起来
}
}
输出结果:
我是一个小小的石头。
可问题是,为什么前面的算术运算可以得到数值的结果,到这里却是字符串了呢?
这就是加号(+)的骚操作了。编译器会自动识别加号(+)两边的数据类型,如果有任意一个数据是字符串类型,那么此时加号(+)就是字符串连接符,否则就是算术运算符,这就是根源。
考一考:加号(+)的特殊用法
下面代码的执行结果是什么?
public class Test{
public static void main(String[] args) {
int a = 3;
int b = 5;
char c = '1';
String s = "A";
System.out.println(a - b + c + s + c + b);
}
}
答案:47A15
解析:
第一个操作: a - b
,算术运算,结果是 -2;
第二个操作:-2 + c
,算术运算,获取字符 '1' 的ASCII码值49,结果是47;
第三个操作:47 + s
,字符串拼接,所以是 "47A";
第四个操作:"47A" + c
,字符串拼接,"47A1";
第五个操作:"47A1" + b
,字符串拼接,最终结果是 47A15
3.4 算术运算小结
对算术运算符进行小结。
/ :除法运算符,得到两个数据相除的商。
特点:Java中整数除以整数结果还是整数。
%:取模(取余)运算,得到两个数据相除的余数。
特点:可以用来判断两个数是否能够整除。
- 布尔类型(boolean)不参与算术运算。
加号两边是数值型数据时,进行加法运算
‘a’、‘0’等字符型数据参与运算时,用该字符在计算机中所表示的数值进行运算
- 加号两边有任意一边是字符串时,进行字符串的拼接。
++ 和 -- :自增1 / 自减1
单独使用:
放在变量前或后结果一样
- 参与运算:
在变量前,先自增(自减) ,再进行其它运算
在变量后,先以原值进行其它运算,再自增(自减)
- 当且仅当大类型数据可以转换为小类型数据(而不会发生精度损失)时,才进行转换。注意强转的固定格式。
第四关 赋值运算:给技能加个点
在学习变量之前,我们可能并不常见“赋值”这种说法,而且,我们也不会把等号(=)说成是赋值符号。没错,这个词应该是计算机的专有词汇。在编程语言里,我们把等号(=)称作“赋值符号”,它的含义是:把右边的数据赋给左边的变量。赋值的过程就像玩游戏时给技能加点一样,原来的技能没有点亮,加点(被赋值)之后就可以使用这个技能了;原来的技能等级太低,也可以通过加点(赋一个更大的值)升级技能。赋值符号不止一种,最常见的就是由基本赋值运算符(=)结合算术运算符,这样就得到一些扩展运算符,比如:
基本赋值运算符的用法,我就不啰嗦了。来看看扩展赋值运算符这几个招式怎么玩。
public class Test{
public static void main(String[] args) {
int num = 10;
num += 10; // 使用 += 赋值运算符
System.out.println(num); // 输出运算后的结果
}
}
输出:
20
很明显,通过加等赋值运算符(+=)的运算,变量 num
的值变成了20,这是怎么回事呢?
其实很简单,扩展赋值运算符相当于算术运算符与赋值运算符的合体,所以这些运算符既有算术运算符的功能,又有赋值运算符的功能,也就是说,下面的两行代码的效果的一样的:
// 这两行代码的效果一样
num += 10;
num = num + 10;
说白了,扩展赋值运算符无非就是一种简写形式。
难道扩展赋值运算符的好处仅仅如此吗?当然不是。再看一段代码:
public class Test{
public static void main(String[] args) {
short s = 1;
// s = s + 1; // 报错
s += 1; // 相当于 s = (short)(s + 1);
System.out.println("s:" + s); // s:2
}
}
通过前面的学习,我们知道,short
类型的变量 s
如果直接加上常量1,会变成 int
类型,所以像 s = s + 1;
这样的代码肯定会报错,但是 s += 1;
却没有任何问题,这说明了加等赋值运算符(+=)是已经进行了强制类型转换的动作的,这就是扩展赋值运算符的好处:省略了强制类型转换的操作。
当然,其它的扩展赋值运算符的特点是一样的,这里就不赘述了。
赋值运算符很好理解,你以后勤加练习就可以了!
第三关 比较运算:是骡子是马,拉出来遛遛
小的时候,常听父母说:你是哥哥/姐姐,比弟弟妹妹们大,要让着点弟弟妹妹。小时候比年龄、比个头,然后比学习成绩;长大了比工资,比男/女朋友,再然后比谁的房子大,谁的车子好;人到中年就开始比孩子,老了之后比儿子、孙子。唉,人这一生,比赢了就得意,比输了就失意。比较无处不在,真是让人又恨又爱。
比较运算符又叫关系运算符,顾名思义,就是用来比较两个值之间的大小关系的。比如下面这段代码:
public class Test{
public static void main(String[] args) {
System.out.println(5 > 3); // 5 和 3比较,5 大于 3成立吗
}
}
输出结果:
true
很明显,5大于3是成立的,所以结果是true。反之,如果不成立,结果就是false。所以,无论谁大谁小,谁胜谁败,比较的结果只有两种情况:true
和 false
。也就是说,比较/关系运算符的运算结果都是布尔( boolean )类型,要么是true,要么是false。常见的关系运算符:
在 Java 中比较两个数据的大小关系,使用比较运算符(或者说关系运算符),这里要注意,比较两个数据是否相等,用的是双等号(==),因为在 Java 中,一个等号(=)的意思是“赋值”;而“不等于”符号是一个感叹号加一个等号(!=),一定要注意它们的写法。来看一段代码:
public class Test{
public static void main(String[] args) {
// 定义三个变量
int a = 10;
int b = 20;
int c = 10;
// ==: 等于
System.out.println(a == b); // false
System.out.println(a == c); // true
System.out.println("-----------------------------");
// !=: 不等于
System.out.println(a != b); // true
System.out.println(a != c); // false
System.out.println("-----------------------------");
// >: 大于
System.out.println(a > b); // false
System.out.println(a > c); // false
System.out.println("-----------------------------");
// >=: 大于等于
System.out.println(a >= b); // false
System.out.println(a >= c); // true
System.out.println("-----------------------------");
}
}
比较运算符和赋值运算一样好理解。唯一值得注意的点,就是双等号(==)和单等号(=)的区别:
public class Test{
public static void main(String[] args) {
// 定义三个变量
int a = 10;
int b = 20;
int c = 10;
//注意: ==是判断是否相等, =是赋值的意思
System.out.println(a == b); //判断变量a和变量b的值是否相等, false
System.out.println(a = b); //将变量b的值赋值给变量a, 然后打印结果, 20
}
}
双等号是比较相等,单等号是赋值的意思,切记。
第四关 逻辑运算:谁是谁非,总要有个说法
一提起“逻辑运算”,我们往往想到的是复杂的运算过程,其实不然,现在介绍的逻辑运算非常简单:用于判断“并且”、“或者”、“除非”等逻辑关系。对比前面介绍的比较运算符,逻辑运算和比较运算好像都需要判断,所以它们的运算结果类型是一致的:boolean
类型,逻辑运算相对于比较运算更复杂的一点是,逻辑运算比较多个结果之间的逻辑,而比较运算一般只需要比较两个数据之间的一个结果。有点绕,举个栗子:
public class Test{
public static void main(String[] args) {
int a = 10, b = 20, c = 30; // 声明三个变量
System.out.println(a > b); // 比较运算,比较两个数据的大小,只有一个结果
System.out.println(a > b && b > c); // 逻辑运算,有两个比较结果参与逻辑与(&&)运算
}
}
输出:
false
false
两次输出都是false。也就是说,比较运算和逻辑运算的结果,都是 boolean
类型,只不过逻辑运算符两端一般连接值为布尔类型的关系表达式,而比较运算两端连接的是数据。常见的逻辑运算符:
上面的代码使用了逻辑运算符:&&(逻辑与,并且)。它的含义是,当两边的表达式结果同时为 true
时,逻辑运算的结果才是 true
,反过来说,只要任意一边的结果为 false
,那么整个逻辑运算的结果就是 false
。相应的,逻辑或(||)的含义是:只要有任意一边是 true
,逻辑运算的结果就是 true
。逻辑非(!)表示否定,它的含义是:如果原来的结果为 true
,加上 !
结果就是 false
,反之,如果原来的结果为 false
,加上 !
则为 true
。
韦小宝喜欢娶媳妇儿,他的要求有三个:第一,是女性;第二,要好看;第三,要身材好。来一段代码:
public class Test{
public static void main(String[] args) {
/*
案例: 韦小宝找媳妇儿
要求有三个:
第一,是女性;
第二,要容貌好看;
第三,要身材好。
*/
int sex = -1; // 性别。-1-女,1-男
int rongMao = 1; // 容貌。0-不好看,>0 好看
int shenCai = 0; // 身材。0-身材不好,1-身材好
// 刚开始眼光比较高, 要求既要长得好看,还要身材好
System.out.println(sex == -1); // 性别是女性
System.out.println(rongMao > 0 && shenCai == 1); // 容貌好看,身材好
System.out.println("---------------------");
// 韦小宝发现那样的媳妇儿不好找, 于是降低了择偶标准, 只要长得好看, 或者身材好就行
System.out.println(sex == -1); // 性别是女性
System.out.println(rongMao > 0 || shenCai == 1);
System.out.println("---------------------");
// 韦小宝发现那样的媳妇儿不好找, 于是降低了择偶标准, 只要不是男的就行
System.out.println(sex != 1); // 性别不是男
System.out.println("---------------------");
}
}
输出结果:
true
false
---------------------
true
true
---------------------
true
---------------------
找个媳妇儿不容易,且行且珍惜。
这里再提一下逻辑非(!)的使用。它表示否定,如果原来的值是 false
,加上 !
就变成了 true
,也就是说:双重否定表示肯定。当代码里有多个逻辑非(!)同时出现时,可以直接把偶数个逻辑非(!)直接去掉,因为结果不会改变:
public class Test{
public static void main(String[] args) {
System.out.println(!!!!!!true); // 6个逻辑非,结果还是 true
}
}
输出:
true
逻辑与(&&)和逻辑或(||)分别有一个双胞胎兄弟,它们也是逻辑与和逻辑或,但是它们看起来瘦骨嶙峋,好像是营养不良:
逻辑与:&
逻辑或:|
没错,我们通常称这些逻辑运算符为:双与(&&)和单与(&);双或(||)和单或(|)。虽然这俩双胞胎兄弟看起来瘦瘦的,但是它们的功能并没有缩水。它们(单与、或)仍然具备和兄长们一样的逻辑处理功能,但是区别在于,双与(&&)和双或(||)都具备“短路”的效果,而瘦兄弟们没有:
public class Test{
public static void main(String[] args) {
int a = 3, b = 5, c = 4;
int d = 0;
System.out.println(a > b && (d = b) > c); // 使用双与进行逻辑运算,第一个条件不成立,第二个条件是否执行
System.out.println(d); // d 的值是几,0还是5?
}
}
使用双与进行逻辑运算,第一个条件 a > b
是不成立的,那么第二个条件是否执行?变量 d
是否被成功赋值成 b
的值?,看运算结果,知道给变量 d
赋值的动作 (d = b)
并没有成功,也就意味着,当双与(&&)前面的条件不成立(结果是 false
),后面的代码就不再执行了,这就是所谓“短路”的效果。换成单与(&)呢:
public class Test{
public static void main(String[] args) {
int a = 3, b = 5, c = 4;
int d = 0;
System.out.println(a > b & (d = b) > c); // 使用单与进行逻辑运算,第一个条件不成立,第二个条件是否执行
System.out.println(d); // d 的值是几,0还是5?
}
}
输出结果:
false
5
很明显,单与(&)没有“短路”的效果。
双或(||)执行逻辑类似:前面的条件结果为 true
,那么就意味着整个逻辑表达式一定为 true
,所有后面的代码就不执行了;反之,前面的条件为 false
,后面仍然进行判断,而单或(|)没有这样的效果。先来看双或(||)的效果:
public class Test{
public static void main(String[] args) {
int a = 3, b = 5, c = 4;
int d = 0;
System.out.println(b > a || (d = b) > c); // 使用双或进行逻辑运算,第一个条件成立,第二个条件是否执行
System.out.println(d); // d 的值是几,0还是5?
}
}
输出:
true
0
换成单或(|):
public class Test{
public static void main(String[] args) {
int a = 3, b = 5, c = 4;
int d = 0;
System.out.println(b > a | (d = b) > c); // 使用双或进行逻辑运算,第一个条件成立,第二个条件是否执行
System.out.println(d); // d 的值是几,0还是5?
}
}
输出:
true
5
总的来说:双与(&&)和单与(&)的逻辑判断能力是一样的,只不过双与(&&)有短路的效果,而单与(&)没有;双或(||)与单或(|)的道理相同。
考一考:逻辑运算符
下面代码的输出结果是什么?
public class Test{
public static void main(String[] args) {
int x, y = 0, z = 1;
boolean r1 = (x = 0) > y & (z = y) == 1;
boolean r2 = y < z || (x = 2) > (z = z +1);
System.out.println(r1 && r2);
System.out.println(x);
System.out.println(y);
System.out.println(z);
}
}
A. true; x=0; y=0; z=0
B. true; x=2; y=0; z=1
C. false; x=2; y=0; z=1
D. false; x=0; y=0; z=2
答案:C
解析:
r1的结果:
(x = 0)
是赋值动作,此时 x = y = 0
,所以 (x = 0) > y
的结果是false
;
(z = y)
也是赋值动作,这是 z = y = 0
,所以 (z = y) == 1
的结果也是false
;
逻辑单与(&),由于两边都会执行,所以最后的结果是:x = y = z = 0
;
r1 是 false & false
,结果是 false
;
r2的结果:
y < z
的结果是 false
;
(x = 2)
和 (z = z + 1)
都是赋值动作,所以这时,x = 2, z = 1
, 双或(||)右边的表达式结果为 true
;
r2 是:false || true
,结果是true
最后的输出:r1 && r2
相当于 false && true
,结果是false
; x = 2, y = 0, z = 1
第五关 三目运算:选择困难症的终极答案
当你面临两难选择时,该怎么办?现在教你终极答案:三目运算。
三目运算,又叫三元运算,即由三部分组成,格式如下:
关系表达式 ? 结果1 : 结果2
三目运算符由问号(?)和冒号(:)两个特殊符号隔开了三部分:
第一部分:关系表达式,代表了你现在遇到的问题
第二部分:结果1,第一个可能的结果,代表了你面临的第一个选择
第三部分:结果2,第二个可能的结果,代表了你面临的第二个选择
世事变化无常,与其面临选择时慌不择路,倒不如将选择权交给前面的条件,条件满足,则选择第一个结果,不满足则选择第二个结果,这就是以不变应万变。所以,三目运算符的执行流程是这样的:
如果关系表达式结果为true,运算后的结果是结果1
如果关系表达式结果为false,运算后的结果是结果2
举个栗子,求两个整数的较大值:
public class Test{
public static void main(String[] args) {
// 定义两个整数变量
int a = 100;
int b = 20;
int max = (a >= b) ? a : b ; // 通过三元运算符计算他们的较大值
System.out.println("max: " + max); // 将结果打印到控制台上
}
}
上面第三行代码的含义是:当 a >= b
这个条件成立时,较大值为 a
,否则为 b
。很明显,最后的输出结果是变量 a
的值:
100
如果有三个数据呢?很简单,三目运算符是可以嵌套的:
public class Test{
public static void main(String[] args) {
// 定义两个整数变量
int a = 100;
int b = 20;
int c = 50;
int max = (a >= b) ? (a > c ? a : c) : (b > c ? b : c) ; // 通过三元运算符计算他们的最大值
System.out.println("max: " + max); // 将最大结果打印到控制台上
}
}
最大值仍然是100:
100
虽然三目运算符可以嵌套,但是代码的可读性明显变差了,所以,开发中是不建议这样做的。你可以把它们拆开,一步一步求取最大值。
考一考:三目运算符
下面代码的运算结果是什么?
public class Test{
public static void main(String[] args) {
int a = 3, b = 5;
int c;
int result = (c = b) > a ? c - a : c + a;
System.out.println(result);
}
}
A. 2
B. 5
C. 8
D. 编译错误
答案:A
解析:
(c = b)
是赋值操作,相当于 b > a
结果是 true
;这时 a = 3, b = 5, c = 5
;
由于三目运算符的问号(?)左边结果为 true
,所以结果是 c - a
,也就是 2。而 c + a
没有执行。
总结
现在,对赋值运算符、比较运算符、逻辑运算符、三目运算符做个总结:
赋值运算符:
1.常见的赋值运算符:=、+=、*=、-=、/=、%=
2.扩展赋值运算符的好处:自动强转
3.注意:
= 表示赋值操作,不是相等
== 用来表示相等
比较/关系运算符:
1.关系运算符是用来描述两个变量的大小是否为相等关系的
2.常见的关系运算符:>、<、==、!=、>=、<=
3.注意:关系运算符 == 和赋值运算符 = 的区别
逻辑运算符:
1.用于判断“并且”、“或者”、“除非”等逻辑关系的运算符
2.逻辑运算符两端连接关系表达式,或逻辑表达式
3.逻辑运算符的运算结果为布尔值:true或false
4.双与和双或都有短路效果,单与和单或没有
5.偶数个逻辑非 ! 结果不变
三目/三元运算符:
1.格式: 关系表达式 ? 结果1 : 结果2;
2.执行流程:
关系表达式结果为true,三元运算符结果为“结果1”;
关系表达式结果为false,三元运算符结果为“结果2”;
这么多运算符,到底它们的优先级顺序是怎样的呢?
一句顺口溜请好好练练~~~
单目乘除为关系,逻辑三目后赋值。
单目:单目运算符+ –(负数) ++ -- 等
乘除:算数单目运算符* / % + -
为:位移单目运算符<< >> (暂时没有讲到)
关系:关系单目运算符> < >= <= == !=
逻辑:逻辑单目运算符&& || & | ^
三目:三目单目运算符A > B ? X : Y
后:无意义,仅仅为了凑字数
赋值:赋值=
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。