Android系统下Java编程详解
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第4章 运算符、表达式与流程控制

本章介绍了Java运算符的概念和用法,并论述在表达式中各种运算符结合使用的情况,分析了运算符的优先级;介绍了分支语句和循环语句等流程控制语句的用法。

4.1 运算符

Java语言提供了丰富的运算符环境,其中主要包括四大类运算符,即算术运算符、位运算符、关系运算符和逻辑运算符。本章主要介绍Java运算符的概念和各种运算符的用法,并对各种运算符在表达式中的结合性和优先级做了论述;分支语句和循环语句及continue/break的用法。

4.1.1 知识准备:算术运算符

Java常见的算术运算符有5种,如表4-1所示。

表4-1 常见算数运算符

这些算术运算符可以用于Java基本数据类型中的数值型(byte、short、char、int、long、float、double)数据。对于+、-和*运算符,都是很容易理解的,它们分别接受两个操作数,通过运算后返回得到新值。需要注意的是除法运算和取模运算。

1.除法运算

在数学运算中,0作为除数是没有意义的,在Java程序中,对于以0作为除数的情况,根据操作数的数据类型,做了不同的处理:对于整形的运算,它将会出现异常;而对于浮点型数据的运算,它将得到一个无穷大值或者NaN(not a number)。示例如下:

源文件:Division.Java

    public class Division {
    public static void main(String[] args) {
        System.out.println("123.0/0 = " + 123.0 / 0);
        System.out.println("123/0 = " + 123/ 0);
    }
    }

编译运行这个程序,将得到如下的结果:

    123.0/0 = Infinity
    Exception in thread "main" Java.lang.ArithmeticException: / by zero
    at Division.main(Division.Java:5)

关于Exception,是Java的异常处理机制,将在后面的章节详细讲解。

2.取模运算

取模运算即求余运算,对于Java语言来说,其操作数可以是浮点数,计算结果也将是浮点数。

源文件:Mode.Java

    public class Mode {
    public static void main(String args[]) {
        System.out.println("123.5 mod 4 = " + 123.5 % 4);
        System.out.println("123 mod 4 = " + 123 % 4);
    }
    }

编译运行这个程序,将得到如下的输出:

    123.5 mod 4 = 3.5
    123 mod 4 = 3

取模运算可以用于判别奇偶数,一个整数n对2取模,如果余数为0,则表示n为偶数,否则n为奇数。此外,判别素数,求最大公约数的运算中也会用到取模运算。

注意:

取模运算也会执行除法操作,所以,对于整形数据来说,也不能使用0 作为取模运算中的“除数”,否则也会出现和除法运算一样的异常。

3.二元运算符简捷赋值方式

+、-、*、/、%运算如果用在赋值语句中,还可以使用二元运算符的简捷方式来实现,比如:

    a = a+5;

可以使用如下的运算式表示:

    a +=5;

它们在运算结果上是相等的。其他4个运算符也可以像上面这个例子中的运算符一样使用,也就是说,将运算符放在“=”的左边,如a*=5、a/=5等。

4.1.2 知识准备:递增、递减运算符

在编写Java程序时,经常需要对一个变量加一或者减一,这时通常使用递增或递减运算符来完成。其中递增运算符对操作数加1,递减运算符从操作数减1。

递增和递减操作符有两种形式:“前缀版”和“后缀版”。“前递增”表示++运算符位于变量或表达式的前面;而“后递增”表示++运算符位于变量或表达式的后面。类似地,“前递减”意味着--运算符位于变量或表达式的前面;而“后递减”意味着--运算符位于变量或表达式的后面。对于“前递增”和“前递减”(如++A 或--A),会先执行运算,再生成值。而对于“后递增”和“后递减”(如a++或a--),会先生成值,再执行运算,示例如下:

    int counter =20;
    counter++;

此时,counter的值为21。

前缀方式和后缀方式的作用都是对操作数加上或减去 1,区别在于用在表达式中的时候。例如:

    int a = 10;
    int b = 10;
    int m = 2*++a;
    int n = 2*b++;

此时,m的值是22,n的值是20,a和b的值都是11。这是因为,在进行m = 2*++a运算时,程序会先将a加上1,然后再进行乘法运算。而对于n=2*b++的后缀递增运算,则会先取出b的数值进行乘法运算,然后再将b递增1。所以,此时m的值是22(m=2*(10+1)), n的值是20(n=2*10),a和b的值都为11。

注意:

递增/递减操作符只能用于变量而不能用在数字本身,如这样的用法是错误的:10--,5++。

4.1.3 知识准备:关系和布尔运算符

Java中提供了完整的关系运算符,共有6种,用来对两个简单类型操作数进行比较运算,所组成的表达式结果为boolean类型的值true或false,如表4-2所示。

表4-2 Java关系运算符

关系运算符的优先顺序为:

(1)前4种关系运算符的优先级别相同,后两种也相同。前4种高于后两种。

(2)关系运算符的优先级低于算术运算符。

(3)关系运算符的优先级高于赋值运算符。

来看一些简单的例子:

    2>3;       //返回false
    2==3;      //返回false
    2!=3;      //返回true

这里要提醒一些有编程经验的读者需要注意的是,在Java中,“不等于”是用“!=”表示的,而不是一些编程语言的“<>”,而等于使用“==”而非“=”,在Java中,“=”用于赋值操作,而非关系运算符。“==”和“!=”除了用于简单类型的操作数外,还可以用于比较引用类型数据。

注意:

除了“==”和“!=”外,其他的关系运算符都不能用在boolean类型的操作数中。

1.逻辑运算符

逻辑运算符也称为布尔运算符,是指进行逻辑运算的符号。逻辑运算符主要包括:!、&、|、^、&&、||,这些运算符分别实现“非”、“与”、“或”、“异或”、“短路与”、“短路或”等逻辑运算。和关系运算一样,逻辑运算结果也是布尔类型值true或false,参与逻辑运算的数据也必须是boolean类型。关于逻辑运算符的种类和功能说明如表4-3所示。

表4-3 逻辑运算符的种类和功能说明

在布尔运算符中,需要特别说明的是,短路与“&&”和短路或“||”,这两个运算符是按照“短路”的方式进行求值的,也就是说,如果第一个表达式已经可以判断出整个表达式的值时,就不进行后面的运算了。例如,当对表达式 a&&b 进行运算时,如果表达式 a的值为false,将不对b的值进行计算。而当对表达式a||b进行运算时,如果a的值为true,将不对b的值进行计算。

4.1.4 任务一:短路布尔运算

1.任务描述

编写一个程序,包含几个返回值为布尔类型的方法,方法中向控制台输出调用信息。在主函数中通过含“短路与”和“短路或”的逻辑表达式调用这些方法,验证短路布尔运算的特点。随后将短路布尔运算符换乘普通布尔运算符,比较输出结果。

2.技能要点

□ 使用布尔运算符。

□ 比较短路布尔运算符与普通布尔运算符的异同。

3.任务实现过程

(1)编写一个名为LogicalOperators的类,定义3个返回值为boolean类型的方法Msg1、Msg2和Msg3,返回值分别为true、false、false,并在每个方法里都输出一条语句,显示该方法被调用。在Mail()方法中,调用3个方法,将3个方法的返回值进行短路与运算和短路或运算。查看输出结果和方法调用情况。

源文件:LogicalOperators.Java

    public class LogicalOperators {
    public static void main(String[] args) {
        LogicalOperators lg= new LogicalOperators();
        System.out.println("短路或运算");
        System.out.println(lg.Msg1() || lg.Msg2() || lg.Msg3());
        System.out.println("短路与运算");
        System.out.println(lg.Msg1() && lg.Msg2() && lg.Msg3());
    }
    boolean Msg1() {
        System.out.println("显示信息1");
        return 1 < 2;// true
    }
    boolean Msg2() {
        System.out.println("显示信息2");
        return 1 == 2;// false
    }
    boolean Msg3() {
        System.out.println("显示信息3");
        return 1 > 2;// false
    }
    }

(2)编译运行上面的程序,将得到如下的输出:

    短路或运算
    显示信息1
    true
    短路与运算
    显示信息1
    显示信息2
    false

分析:上面的程序中,因为方法Msg1()的值为true,而“或”运算中如果有一个表达式为真(true),则整个表达式均为真(true),因此,无须计算后面方法Msg2()和方法Msg3()两个表达式就可以得到整个表达式的值了。而第二条“短路与”语句,因为在逻辑“与”运算中,只需要一个表达式的值为假(false),则整个表达式的值都为假(false)。Msg1()为真(true),所以将进行第二个表达式的运算,它将调用方法Msg2(),而此时,Msg2()方法的返回值为假(false),所以将不用进行后面的运算了。

(3)将LogicalOperators.Java中的短路布尔运算符修改成普通布尔运算符。即将如下语句修改,程序其他语句不变:

    System.out.println("短路或运算");
    System.out.println(lg.Msg1() | lg.Msg2() | lg.Msg3());
    System.out.println("短路与运算");
    System.out.println(lg.Msg1() & lg.Msg2() & lg.Msg3());

输出结果如下:

    短路或运算
    显示信息1
    显示信息2
    显示信息3
    true
    短路与运算
    显示信息1
    显示信息2
    显示信息3
    false

分析:运算符“&&”和“&”、“||”和“|”所求得的结果是一样的,它们的区别在于,“&”和“|”不会进行“短路”运算,而是会计算运算符两边的各个参数的值。

4.1.5 知识准备:三元运算符

Java还支持三元运算符“?:”(也称为条件运算符),这个运算符的用法如下:

    condition? a:b

其中条件condition是一个布尔表达式, 如果condition为true,则表达式的值为a;否则,表达式的值为b。来看一个简单的例子:

    public class demo {
    public static void main(String[]args){
    int a=10,b=20,y;
    a>b?(y=a):(y=b;)  //1,这样写是错误的
    y=a>b?a:b; //2,这样写是正确的}
    }

可以看到,a>b的时候,执行y=a ,本例中,a<b,所以条件表达式a>b的值为false,执行y=b,此时y的值为20。

4.1.6 知识准备:位运算符

位运算是以二进制位为单位进行的运算,其操作数和运算结果都是整型值。可以使用运算符直接处理组成这些整数的各个二进制位。适用的数据类型有byte、short、char、int、long。

位运算符共有7个,分别是位与(&)、位或(|)、位非(~)、位异或(^)、右移(>>)、左移(<<)、0填充的右移(>>>)。其功能说明如表4-4所示。

表4-4位运算符功能表

其中位运算的位与(&)、位或(|)、位非(~)、位异或(^)与逻辑运算的相应操作的真值表完全相同,其差别只是位运算操作的操作数和运算结果都是二进制整数,而逻辑运算相应操作的操作数和运算结果都是逻辑值。

而>>、<<、>>>也被称为移位运算符,作用是左移运算是将一个二进制位的操作数按指定移动的位数向左移位,移出位被丢弃,右边的空位一律补 0。右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位或者一律补0,或者补符号位。在使用补码作为机器数的机器中,正数的符号位为0,负数的符号位为1。程序示例如下。

(1)有如下程序段:

        int x = 64;      //x等于二进制数的01000000
        int y = 70;      //y等于二进制数的01000110
        int z = x&y      //z等于二进制数的01000000

即运算结果为z等于二进制数01000000。位或、位非、位异或的运算方法类同。

(2)右移是将一个二进制数按指定移动的位数向右移位,移掉的被丢弃,左边移进的部分或者补0(当该数为正时),或者补1(当该数为负时)。这是因为整数在机器内部采用补码表示法,正数的符号位为0,负数的符号位为1。例如,对于如下程序段:

        int x = 70;           //x等于二进制数的01000110
        int y = 2;
        int z = x>>y          //z等于二进制数的00010001

即运算结果为z等于二进制数00010001,即z等于十进制数17。

对于如下程序段:

        int x = -70;          //x等于二进制数的11000110
        int y = 2;
        int z = x>>y          //z等于二进制数的11101110

即运算结果为z等于二进制数11101110,即z等于十进制数-18。要透彻理解右移和左移操作,读者需要掌握整数机器数的补码表示法。

(3)0填充的右移(>>>)是不论被移动数是正数还是负数,左边移进的部分一律补0。

注意:

没有与>>>对应的<<<操作。

4.1.7 知识准备:赋值运算符

在前面的章节中,已经在很多地方都用到了赋值运算符。赋值运算符“=”将“=”右边的值赋给(更准确地说是“复制到”)左边的变量。“=”右边的值可以是任何的变量、常量或者一个可以产生值的表达式,而“=”的左边必须是一个明确的、命名的变量,不可以为常量,如a= 10是合法的,而10 = a 却是不允许的。

对于基本数据类型的赋值,其非常简单,它直接将“=”右边的值复制到左边的变量中;对于引用数据类型的赋值,操作的并非是对象本身,而是它的“对象引用”,它实际上是将“=”右边的引用(而非对象本身)复制到左边的变量中。

1.扩展赋值运算

将赋值运算符和其他的运算符结合起来,就可以作为一种特别的“扩展”赋值运算符。扩展赋值运算符有:+=、-=、*=、/=、%=、&=、|=、^=、>>=、<<=、>>>=等。注意,并非所有的运算符都可以和赋值运算符结合成扩展赋值运算符。

扩展赋值运算符的引入只是为了简化赋值运算的表达形式,将“a=a operator b;”简化为“ a operator=b;”,其作用是相同的。

2.运算中的数据类型转换

在第3章中,已经知道了,数值简单数据类型数据之间是可以相互转换的。那么,在表达式中,它是如何转换的呢?比如,一个表达式中既有float类型的数据,又有double类型的数据,那么,得出来的结果到底是什么数据类型呢?

Java在编译期间就会进行扩展类型检查,并且数据从一种类型转换到另一种类型时有严格的限制。在Java中,存在两种不同类型的类型转换:

隐式转换:在对包含非boolean简单数据类型(primitive type)的表达式求值的时候, Java会进行大量的隐式类型转换。这些转换有很大的限制,但最基本的原则是这种转换必须是提升(widening,或称为扩大)而不是下降(narrowing,或称为缩小)转换。也就是说,隐式转换只能将一种简单数据类型转换到比它范围更大的类型。

强制类型转换:当隐式转换不能被所要求的表达式支持,或者是有特殊的需求,则需要进行强制的类型转换,这时候需要使用类型转换运算符进行强制转换。

对于一元运算符,例如++或--,隐式转换比较简单: by te、short、char类型的数被转换成int的,而其他类型的数据保持不变。

对于二元运算符,情况比较复杂,但是这种转换基本上遵循如下的基本方式:表达式中最长的类型为表达式的类型。下面是具体的运算规则:

(1)如果两个操作数中有一个是double类型的,则另一个也将会转换成double类型,最后的运算结果也是double类型的,也就是说,表达式的类型为double类型。

(2)如果两个操作数中有一个是float类型的,则另一个操作数也会转换成float类型,此时表达式类型是float类型。

(3)如果两个操作数中有一个是long类型的,另一个将会转换成long类型,此时,表达式的类型也为long类型,否则,两个操作数都会转换成int类型。

(4)对于byte/char/short类型的数据,在进行计算时都会转换成int类型来计算,得出的结果也是int类型。

下面来看一个例子:

源文件:TypeConversion.Java

    public class TypeConversion {
    public static void main(String[] args) {
        short s = 11;
        long l = 111;
        int  i = 1;
        byte b1 = 2, b2 = 3;
        char c = 'c';
        System.out.println(l * s);  // 将会得到一个long类型的数值
        int j = b1 + c;              //byte类型+char类型结果为int类型
        // byte f = b+e;             //将会报错,因为计算得出的结果应该是int类型
        int m = 1123456789;
      float     n = m;     //            将会损失精度,得到的结果是1.12345677E9
        System.out.println(j);
        System.out.println(n);
    }
    }

在这个例子中,如果将两个byte类型的数据相加,将结果赋给一个byte类型的变量,编译的时候将会出错,这是因为两个byte类型的值相加返回的是int类型的值。而如果将一个整型的值赋给一个float类型的变量,则会保留正确的数值级数,但是,从例子中的结果可以看出,转换后已经损失了一些精度。

虽然不能将一个会产生int类型结果的表达式的值赋给byte类型变量,但是实际上,可以将整型值直接赋值给byte/short/char等“更窄”类型的变量,而不需要进行强制类型转换,只要不超出其表数范围即可。

例如:

    byte b1 = 33;            //合法
    short s = 456;           //合法
    char ch = 345;           //合法
    byte b2 = 142            //非法,超出byte型数据表数范围

隐式转换不但发生在表达式中,还发生在方法调用的时候。比如,当调用方法时的参数不是方法定义中所规定的参数类型的时候。

在上面已经讲过,Java 可以自动“提升”数据类型。但是,经常需要将数据从较长的类型转换到较短的类型,如将double类型的数据转换成int类型的数据,这时,Java 不会自动完成这个动作(默认情况下只会将int类型的数据转换成double类型的),所以,需要在程序中对其进行强制转换。当然,这种操作可能会引起信息的丢失,所以,应该尽量小心使用。

除了简单数据类型外,类型转换还可以引用于引用类型的数据。任何对象都可以被转换成它的基类或任何它所实现的接口。一个接口也可以被转换成它所扩展的任何其他接口。

4.1.8 任务二:简单数据类型和引用数据类型的赋值操作

1.任务描述

写一段程序,分别给简单类型变量和引用类型变量进行赋值操作,要求输出赋值后变量值,并体现出引用类型赋值后变量指向同一对象。

2.技能要点

□ 简单的赋值操作。

□ 了解引用类型变量赋值和简单类型变量赋值的区别。

3.任务实现过程

(1)编写源程序,定义了一个类“Clock”,它有一个“time”的属性。在类“Assignment”的main()方法中,定义了两个int简单数据类型的变量a、b,并给b赋值100,然后将b的值赋给变量a,此时实际上是将b的值的一个“副本”复制给了a,因此,a和b中任何一方的变化,都不会影响到另一方。

(2)定义了两个Clock引用类型的变量c1、c2,并给c1初始化了一个对象引用,然后,将c1的值赋给c2,此时,这个操作实际上是将c1的对象引用复制给了c2,此时,c1和c2所指向的是同一个对象!因此,无论通过变量c1还是c2去改变对象,改变的都是同一个对象。

源文件:Assignment.Java

    public class Assignment {
    public static void main(String[] args) {
        // 简单数据类型
        int a, b = 100;
        a = b;
        b = 10;
        System.out.println("a = " + a);
        System.out.println("b= " + b);
        Clock c1 = new Clock(10);
        Clock c2;
        c2 = c1;
        c1.setTime(12);
        System.out.println("Clock1的time=" + c1.getTime());
        System.out.println("Clock2的time=" + c2.getTime());
    }
    }
    class Clock {
        private int time;
        // 构造器
        public Clock(int clockTime) {
        time         = clockTime;
        }
        public int getTime() {
        return         time;
        }
        public void setTime(int time) {
        this.time         = time;
        }
    }

(3)编译并运行上面的类“Assignment”,将得到如下的输出:

    a = 100
    b= 10
    Clock1的time=12
    Clock2的time=12

4.1.9 知识准备:运算符的优先顺序

除了上面的这些运算符外,Java还提供其他非常丰富的运算符来进行其他运算。

Java运算符在风格和功能上都与C和C++极为相似。下面按优先顺序列出了各种运算符:

    分隔符:  []   ()   ;   ,
    从右到左结合: ++   -- + - ~ ! (data type)
    从左到右结合:  *  /  %
    从左到右结合:  +   -
    从左到右结合:  <<  >>  >>>
    从左到右结合:  <   >  <=  >= instanceof
    从左到右结合:  ==  !=
    从左到右结合:  &
    从左到右结合:  ^
    从左到右结合:  |
    从左到右结合:  &&
    从左到右结合:  ||
    从右到左结合: ?:
    从右到左结合:  =   *=  /=  %=   +=   -=    <<=  >>=  >>>=   &=   ^=  |=

注意:

instanceof是Java编程语言特有的运算符。

4.1.10 技能拓展任务:字符串连接运算符

1.任务描述

运算符“+”除了用于数值类型的加法运算外,在字符串类型(String)数据中,它还是一个用于连接字符串的特殊的运算符。在表达式中用“+”连接两个操作数,其中有一个操作数是字符串类型(String),Java 自动将另一个操作数也转换成字符串,然后将这两个字符串相连起来生成一个新的字符串。

要求通过实例来验证这一转换过程,编写程序实现输出一月和二月的手机数据流量值。

2.技能要点

□ 使用“+”连接字符串。

3.任务实现过程

(1)写一个类StringConnect.Java,在该类中可以通过将数字和一个空字符串相连的方式,将数字转换成字符串类型。

源文件:StringConnect.Java

    public class StringConnect {
    public static void main(String[] args) {
        double jan = 98.987;
      double     feb = 76;       //                自动将int型的数值1提升到double类型1.0
        double total = jan + feb;
        String flow = "January Dataflow is: " + jan;
    //上面得到一个字符串:"Price is:9.987"
        String sflow = "The Total DataFlow is: " + total;
    //上面得到一个字符串:"Total Price is:10.987"
        System.out.println(flow);
        System.out.println(sflow);
        System.out.println("" + jan + feb);  // 打印出一个字符串:"9.9871.0"
        System.out.println(jan + feb +"");   // 打印出一个字符串:"10.987"
    }
    }

(2)运行程序,输出结果是:

    January Dataflow is: 98.987
    The Total DataFlow is: 174.987
    98.98776.0
    174.987

从上面的例子中可以看到,String和一个数字类型的数据进行“+”运算,将会得到一个新的字符串,这个字符串由旧的字符串和这个数字组成。

再来看这行程序:

    System.out.println("" + jan + feb);

根据运算符从左到右的结合原则,空字符串“""”首先和jan进行运算,得到一个字符串,这个字符串的内容就是“98.987”,然后,这个字符串再和数字y进行运算,此时得到一个由x和y组合成的新的字符串:98.98776.0。比较一下下面这条语句:

      System.out.println(jan + feb +"");

这条语句首先进行数值的相加运算,得到一个新的数值:174.987,然后再和空字符串进行连接运算,此时得到一个新的字符串,内容为“174.987”。