你好,欢迎回来,我是BoBo!HAKUNA MATATA!!!

提到运算,你可能会立即想到加、减、乘、除四则运算以及“九九乘法表”。想当年背诵“九九乘法表”,那简直是一场噩梦啊!时隔多年,心中依然隐隐作痛。不过,现在学习使用编程语言来进行运算,要比我们自己用公式和“九九乘法表”高效的多,而且,也容易得多,因为真正的运算过程都交给机器处理,我们只需要提供数据和计算规则就行了。

紧接着上次课程的内容,现在我们学习在 Java 语言中如何进行数据的运算。怀揣着孩子一样的好奇,看看 Java 是怎么把数据一步一步运算出结果的。

以下是这篇文章的主要知识点:

  • 十八般武艺:Java中的运算符
  • 算术运算:先学会舞刀弄枪
  • 赋值运算:给技能加个点
  • 比较运算:是骡子是马,拉出来遛遛
  • 逻辑运算:谁是谁非,总要有个说法
  • 三目运算:选择困难症的终极答案

第一关 十八般武艺:Java中的运算符

拥有程序思维的第一步,就是要学会用计算机、或者说编写程序帮我们处理数据,而不是我们自己动手。Java 语言中有很多进行数据运算的方式,就如前面所说,包括但不限于:算术运算、比较运算、逻辑运算、赋值运算、三目运算等。每一种运算方式,又都包含了很多的运算符,我把这些运算符形象的称为“十八般武艺”,学习 Java 中的运算,就是学习这些运算符的使用,也就是修炼这“十八般武艺”的过程。

运算符,顾名思义,就是对数据(常量和变量)进行运算的符号。我们把数据用运算符连接起来,就构成了可以进行运算的表达式。比如 1 + 23 * 4 等等。看看这行代码:

public class Test{
    public static void main(String[] args) {
        int number = 1 + 2; // 使用加号(+)将常量1和2连接起来,构成了一个加法表达式,并把运算结果赋值给变量number
        System.out.println(number); // 输出number的值
    }
}

上面的式子就是使用运算符加号(+)将常量 12 连接起来,构成了一个加法表达式,并把运算结果赋值给变量 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 + 210 * 205 - 3,它们的运算结果都是整数

浮点型表达式:运算结果为浮点数。比如:3.14 * 20.618 + 0.3823.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'; // 字符型数据进行运算,结果是?

先不着急看结果,你能不能看懂上面的代码?想要弄明白上面的代码,必须解决下面两个问题:

  1. 为什么字符型数据可以进行算术运算?
  2. 字符型数据是如何进行运算的?

上次课我们提到过,字符数据类型(char)代表世界上所有国家和地区的文字、数字、特殊符号等,而这些字符数据,在计算机中是如何存储的呢?其实,计算机中所有数据的存储和运算都是以二进制形式进行的。也就是说,计算机会把所有的数据都转化成数值,然后将该数值以二进制的形式存储在磁盘上,也因此,进行运算的过程,是以二进制的形式进行的。你想知道二进制数据的运算过程?想得美,我是不会教的。回到上面的问题,你需要知道的是:

  1. 每个字符型数据在计算机中都是一个数值,所以它可以进行算术运算。
  2. 既然字符型数据是数值,就把它对应的数值拿过来进行运算好了。

那么问题来了,每个字符对应的数值是多少呢?秀一波操作:

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 类型啊,这是怎么肥四?”

“是这样的,byteshort 这两种类型在运算的时候,会先把它们转成 int,然后再进行运算,运算结果也是 intint 类型就是这么来的。”

“那现在怎么办?”

“需要把运算结果进行数据类型转换。就像这样:

    // 强制类型转换:把 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)不参与算术运算之外,其它的七种类型自动转换的顺序是这样的:

之所以是这样的顺序,是因为它们各自的表示范围不同,从小到大依次是:byteshortcharintlongfloatdouble。小类型与大类型一起运算,除了 byteshortchar 这三种类型或默认提升成 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 算术运算小结

对算术运算符进行小结。

  1. / :除法运算符,得到两个数据相除的

    特点:Java中整数除以整数结果还是整数。

  2. %:取模(取余)运算,得到两个数据相除的余数

    特点:可以用来判断两个数是否能够整除。

  3. 布尔类型(boolean)不参与算术运算。
  4. 加号两边是数值型数据时,进行加法运算

    ‘a’、‘0’等字符型数据参与运算时,用该字符在计算机中所表示的数值进行运算

  5. 加号两边有任意一边是字符串时,进行字符串的拼接。
  6. ++ 和 -- :自增1 / 自减1

    1. 单独使用:

      ​ 放在变量前或后结果一样

    2. 参与运算:

    ​ 在变量前,先自增(自减) ,再进行其它运算

    ​ 在变量后,先以原值进行其它运算,再自增(自减)

  7. 当且仅当大类型数据可以转换为小类型数据(而不会发生精度损失)时,才进行转换。注意强转的固定格式。

第四关 赋值运算:给技能加个点

在学习变量之前,我们可能并不常见“赋值”这种说法,而且,我们也不会把等号(=)说成是赋值符号。没错,这个词应该是计算机的专有词汇。在编程语言里,我们把等号(=)称作“赋值符号”,它的含义是:把右边的数据赋给左边的变量。赋值的过程就像玩游戏时给技能加点一样,原来的技能没有点亮,加点(被赋值)之后就可以使用这个技能了;原来的技能等级太低,也可以通过加点(赋一个更大的值)升级技能。赋值符号不止一种,最常见的就是由基本赋值运算符(=)结合算术运算符,这样就得到一些扩展运算符,比如:

基本赋值运算符的用法,我就不啰嗦了。来看看扩展赋值运算符这几个招式怎么玩。

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。所以,无论谁大谁小,谁胜谁败,比较的结果只有两种情况:truefalse。也就是说,比较/关系运算符的运算结果都是布尔( 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
后:无意义,仅仅为了凑字数
赋值:赋值=


博学谷
27 声望5 粉丝

有趣易学,人人都可以学编程