项目实践精解:Java核心技术应用开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第5章Java基础语法(二):表达式及流程控制

5.1 运算符

像其他语言一样,Java提供了丰富的运算符。Java的运算符主要包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、条件运算符和其他运算符等。

5.1.1 算术运算符

算术运算符包括+(加)、-(减)、*(乘)、/(除)、%(模)、++(递增)、- -(递减)等。算术运算符的运算数必须是数字类型。算术运算符不能用在布尔类型上,但是可以用在char类型上,因为在Java中,char类型实质上是int类型的一个子集。

1.基本算术运算符

基本算术运算符——加、减、乘、除可以对所有的数字类型数据进行操作。减运算符也用来表示单个操作数的负号。特别要注意的是,对整数进行除法“/”运算时,所有的余数都会被舍去,而对于浮点数除法,则可以保留余数。下面这个例子演示了算术运算符的使用方法。

        /**
        * 演示算术运算符的使用
        */
        package sample;
        public class MathTest {
          public static void main(String args[]) {
            int a = 1 + 2;
            int b = a * 3;
            int c = b / 4;
            int d = c - a;
            int e = -d;
            System.out.println("a = " + a);
            System.out.println("b = " + b);
            System.out.println("c = " + c);
            System.out.println("d = " + d);
            System.out.println("e = " + e);
            double da = 1 + 2;
            double db = da * 3;
            double dc = db / 4;
            double dd = dc - a;
            double de = -dd;
            System.out.println("da = " + da);
            System.out.println("db = " + db);
            System.out.println("dc = " + dc);
            System.out.println("dd = " + dd);
            System.out.println("de = " + de);
          }
        }
        运行这个程序,输出结果如下:
        a = 3
        b = 9
        c = 2
        d = -1
        e = 1
        da = 3.0
        db = 9.0
        dc = 2.25
        dd = -0.75
        de = 0.75

2.模运算符

模运算符“%”可以获取整数除法的余数,它同样适用于浮点类型数据。下面的实例程序说明了模运算符的用法。

        /**模运算符的用法
        */
        package sample;
        public class ModTest {
          public static void main(String args[]) {
            int x = 22;
            double y = 22.34;
            System.out.println("x mod 5 = " + x % 5);
            System.out.println("y mod 5 = " + y % 5);
          }
        }

运行这个程序,输出结果如下:

        x mod 5 = 2
        y mod 5 = 2.34

3.递增和递减运算符

“++”和“--”是Java的递增和递减运算符,下面将对它们进行详细讨论。我们先来看一下递增和递减运算符的含义及功能。递增运算符对其运算数加1,递减运算符对其运算数减1。因此,语句

        x = x + 1;

与下面的语句相同。

        x++;

同样,语句

        x = x -1;

与下面的语句相同。

        x--;

在上面的例子中,递增或递减运算符采用前缀(prefix)或后缀(postfix)格式的运算结果都是相同的。但是,当递增或递减运算符作为一个较大表达式的一部分时,就会有重要区别。如果递增或递减运算符放在其运算数的前面,Java就会在获得该运算数的值之前执行相应的操作,并将其用于表达式的其他部分。如果运算符放在其运算数的后面,Java就会先获得该操作数的值再执行递增或递减运算。例如:

        x = 22 ;
        y =++x;

在这个例子中,y将被赋值为23,因为在将x的值赋给y之前,要先执行递增运算。这样,语句y =++x ;和下面两句是等价的。

        x = x + 1;
        y = x;

但是,当写成如下这样时,

        x = 22;
        y = x++;

在执行递增运算之前,已将x的值赋给了y,因此y的值还是22。当然,在这两个例子中,x都被赋值为23。在本例中,语句y =x++;与下面两个语句等价。

        y = x;
        x = x + 1;

下面的程序说明了递增运算符的使用方法。

        /**
        * 演示递增运算符的使用
        */
        package sample;
        public class IncTest {
          public static void main(String args[]) {
            int a = 1;
            int b = 2;
            int c;
            int d;
            c = ++b;
            d = a++;
            c++;
            System.out.println("a = " + a);
            System.out.println("b = " + b);
            System.out.println("c = " + c);
            System.out.println("d = " + d);
          }
        }

该程序的输出结果如下:

        a = 2
        b = 3
        c = 4
        d = 1

5.1.2 关系运算符

关系运算符包括 >,<,>=,<=,==,!=等。关系运算符(Relational Operator)决定值和值之间的关系,如决定相等、不相等及排列次序等。关系运算符及其含义如表5-1所示。

表5-1 关系运算符及其含义

这些关系运算符产生的结果是布尔类型值。关系运算符常常用在if控制语句和各种循环语句的表达式中。

Java中的任何类型,包括整型、浮点型、字符型及布尔型,都可用“= =”来比较是否相等,用“!=”来比较是否不等。注意:Java比较是否相等的运算符是两个等号,而不是一个(一个等号是赋值运算符)。只有数字类型可以使用关系运算符进行比较。也就是说,只有整数、浮点数和字符运算数可以用来比较哪个大或哪个小。

关系运算符的结果是布尔(boolean)类型值。例如,下面的程序段对变量c的赋值是有效的。

        int a = 2;
        int b = 1;
        boolean c = a < b;

在本例中,a<b(其结果是false)的结果存储在变量c中。

请看以下实例。

        /**
        * Java中关系运算符的使用
        */
        package sample;
        public class RelationOpTest{
          public static void main(String args[]){
          int a=9;
          int b=6;
          int c=6;
          boolean d=a>b;     //true
          boolean e=a<b;     //false
          boolean f=b==c;    //true
          boolean g=b!=c;    //false
          boolean h=b>=c;    //true
          boolean i=b<=c;    //true
          boolean j=a==b;    //false
          System.out.println("d="+d);
          System.out.println("e="+e);
          System.out.println("f="+f);
          System.out.println("g="+g);
          System.out.println("h="+h);
          System.out.println("i="+i);
          System.out.println("j="+j);
          }
        }

程序运行结果如下:

        d=true
        e=false
        f=true
        g=false
        h=true
        i=true
        j=false

5.1.3 逻辑运算符

逻辑运算符包括!,&&,||等。Java提供了逻辑非(!)、逻辑与(&&)和逻辑或(||)三个运算符。逻辑非代表取反,如果当前运算数为真,取反后的值为假;反之,如果当前运算数为假,取反后的值为真。在逻辑与运算中,如果有一个运算数为假,则不管另一个运算数是真还是假,其运算结果为假。同样,在逻辑或运算中,如果有一个运算数为真,则不管另一个运算数是真还是假,其运算结果都为真。因此,在进行逻辑与或逻辑或运算时,有时一个运算数就能决定表达式的值,只有在需要时才对另一个运算数求值。例如,下面的程序语句说明了逻辑运算符的优点,用它可以避免被0除的错误。

        if (denom != 0 && num / denom > 12)

既然用了逻辑与运算符,就不会有当denom为0时产生的运行时异常。

请看以下实例。

        /**
        * Java中关系和逻辑运算符的使用
        */
        package sample;
        import java.util.*;
        public class LogicTest{
          public static void main(String[] args){
            Random rand = new Random();
            int i = rand.nextInt()%100;
            int j = rand.nextInt()%100;
            System.out.println("i = " + i);
            System.out.println("j = " + j);
            System.out.println("i > j is " + (i > j));
            System.out.println("i < j is " + (i < j));
            System.out.println("i >= j is " + (i >= j));
            System.out.println("i <= j is " + (i <= j));
            System.out.println("i == j is " + (i == j));
            System.out.println("i != j is " + (i != j));
            System.out.println("(i < 10) && (j < 10) is "
            + ((i < 10) && (j < 10)) );
            System.out.println("(i < 10) || (j < 10) is "
            + ((i < 10) || (j < 10)) );
          }
        }

程序运行结果如下(因为随机特性,可能会与你的运行结果不一致):

        i = -6
        j = 10
        i > j is false
        i < j is true
        i >= j is false
        i <= j is true
        i == j is false
        i != j is true
        (i < 10) && (j < 10) is false
        (i < 10) || (j < 10) is true

这里的Random类是Java提供的一个工具类,用来产生随机数。

5.1.4 位运算符

位运算符包括>>,<<,>>>,&,|,^,~等。Java定义的位运算符(Bitwise Operator)直接对整数类型的数进行位操作,这些整数类型包括long,int,short,char和byte。表5-2列出了位运算符及其含义。

表5-2 位运算符及其含义

既然位运算符在整数范围内对位进行操作,那么理解这样的操作会对一个值产生什么影响就非常重要。具体地说,我们需要知道Java是如何存储整数值并且如何表示负数的。因此,在继续讨论之前,让我们首先简述一下这些概念。

所有的整数类型都以二进制数字位的变化及其宽度来表示。例如,byte型的值42的二进制代码是00101010,其中每个位置代表2的次方。另外,所有的整数类型(除了char类型之外)都是有符号的整数,这意味着它们既能表示正数,又能表示负数。Java使用补码来表示负数,也就是将与某负数对应的正数的二进制代码取反(即将1变成0,将0变成1),然后对其结果加1。例如,-42就是通过将42的二进制代码的各个位取反,即对00101010取反,得到11010101,然后再加1,得到11010110,即-42。要对一个负数解码,首先对其所有的位取反,然后加1。例如-42,11010110取反后为00101001,即41,然后加1,这样就得到了42。

1.位逻辑运算符

位逻辑运算符有“与”(AND)、“或”(OR)、“异或”(XOR)、“非”(NOT),分别用“&”、“|”、“^”、“~”表示,表5-3为位逻辑运算符的真值表。在继续讨论之前,请记住位运算符应用于每个运算数内的每个单独的位。

表5-3 位逻辑运算符的真值表

(1)按位非(NOT)

按位非也叫做补,一元运算符NOT“~”是对其运算数的每一位取反。例如,数字42,

它的二进制代码为00101010,经过按位非运算变为11010101。

(2)按位与(AND)

按位与运算符为“&”,如果两个运算数都是1,则结果为1。在其他情况下,结果均为0。

例如:

          00101010 42
        & 00001111 15
        --------------
          00001010 10

(3)按位或(OR)

按位或运算符为“|”,如果任何一个运算数为1,则结果为1。例如:

          00101010 42
        | 00001111 15
        --------------
          00101111 47

(4)按位异或(XOR)

按位异或运算符为“^”,只有在两个运算数对应位上的值不同时,其结果是1;否则,结果是0。例如:

              00101010 42
        ^ 00001111 15
        -------------
              00100101 37

下面的例子演示了位逻辑运算符的使用方法。

        /**
        * 演示位逻辑运算符的使用
        */
        package sample;
        public class BitTest {
          public static void main(String args[]) {
            String binary[] = {
            "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111",
            "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"
            };
            int a = 3; // 0 + 2 + 1 或 0011
            int b = 6; // 4 + 2 + 0 或 0110
            int c = a | b;
            int d = a & b;
            int e = a ^ b;
            int f = (~a & b) | (a & ~b);
            int g = ~a & 0x0f;
            System.out.println(" a = " + binary[a]);
            System.out.println(" b = " + binary[b]);
            System.out.println(" a|b = " + binary[c]);
            System.out.println(" a&b = " + binary[d]);
            System.out.println(" a^b = " + binary[e]);
            System.out.println("~a&b|a&~b = " + binary[f]);
            System.out.println(" ~a = " + binary[g]);
          }
        }

在本例中,变量a与b对应位的组合代表了二进制数所有的4种组合模式:0/0,0/1,1/0,和1/1。“|”运算符和“&”运算符分别对变量a与b各个对应位的运算得到了变量c和变量d的值。对变量e和f的赋值说明了“^”运算符的功能。字符串数组binary代表了0~15对应的二进制的值。

在本例中,数组各元素的排列顺序显示了变量对应值的二进制代码。数组之所以这样构造,是因为变量的值n对应的二进制代码可以被正确地存储在数组对应元素binary[n]中。例如变量a的值为3,则它的二进制代码对应地存储在数组元素binary[3]中。

~a的值与数字0x0f(对应的二进制代码为00001111)进行按位与运算的目的是减小~a的值,保证变量g的结果小于16。因此,该程序的运行结果可以用数组binary对应的元素来表示。该程序的输出结果如下:

        a = 0011
        b = 0110
        a|b = 0111
        a&b = 0010
        a^b = 0101
        ~a&b|a&~b = 0101
        ~a = 1100

2.左移运算符

左移运算符“<<”使指定数值的所有位都向左移动指定的位数。它的通用格式如下:

        val << num

这里,num指定值val要移动的位数。也就是说,左移运算符“<<”使指定值的所有位都左移num位。每左移1位,高位都被移出并且丢弃,同时用0填充右边。这意味着当左移的运算数是int类型时,每移动1位,它的第32位就要被移出并且丢弃;当左移的运算数是long类型时,每移动1位,它的第64位就要被移出并且丢弃。

在对byte和short类型的值进行移位运算时,需要特别注意,因为Java在对表达式求值时,将自动把这些类型扩大为int类型,而且表达式的值也是int类型。对byte和short类型的值进行移位运算的结果是int类型,而且如果左移不超过31位,原来对应各位的值也不会丢弃。但是,如果对一个负的byte或者short类型的值进行移位运算,它被扩大为int类型后,符号也被扩展,这样,整数值结果的高位就会被1填充。因此,为了得到正确的结果,就要舍弃所得结果的高位。这样做的最简单办法是将结果转换为byte类型。请看下面的实例。

        /**
        * 演示左移运算符的使用
        */
        package sample;
        public class ByteShiftTest {
          public static void main(String args[]) {
            byte a = 64, b;
            int i;
            i = a << 2;
            b = (byte) (a << 2);
            System.out.println("Original value of a: " + a);
            System.out.println("i and b: " + i + " " + b);
          }
        }

该程序的输出结果如下:

        Original value of a: 64
        i and b: 256 0

因变量a在赋值表达式中被扩大为int类型,64(01000000)被左移两次生成值256(100000000)赋给变量i。然而,a经过左移后赋给变量b,a中唯一的1被移出,低位全部成了0,因此b的值也变成了0。

既然每次左移都可以使原来的操作数翻倍,我们就可以经常使用这个方法来进行快速的乘2运算。但是要谨慎,如果将1移进最高位(第32或64位),那么该值将变为负值。请看下面的实例。

        /**
        * 演示快速乘2方法的使用
        */
        package sample;
        public class MultByTwoTest {
          public static void main(String args[]) {
            int i;
            int num = 0xFFFFFFE;
            for(i=0; i<4; i++) {
            num = num << 1;
            System.out.println(num);
            }
          }
        }

该程序的输出结果如下:

        536870908
        1073741816
        2147483632
        -32

初值左移4位后,变成了-32。正如你所看到的,当1被移进第32位时,数字被解释为负值。

3.右移运算符

右移运算符“>>”使指定数值的所有位都向右移动指定的位数。它的通用格式如下:

        val >> num

这里,num指定值val要移动的位数。也就是说,右移运算符“>>”使指定值的所有位都右移num位。

下面的程序段将值32右移2位,将结果8赋给变量a。

        int a = 32;
        a = a >> 2; // a = 8

当值中的某些位被“移出”时,这些位的值将被丢弃。例如,下面的程序段将35右移2位,它的2个低位被移出且丢弃,也将结果8赋给变量a。

        int a = 35;
        a = a >> 2; // a = 8

用二进制表示该过程可以更清楚地看到程序的运行过程。

        00100011 35
        >> 2
        00001000 8

将值每右移一位,就相当于将该值除以2并且舍弃了余数。我们可以利用这个特点将一个整数进行快速的除2运算,但一定要确保不会将该数原有的任何一位移出。右移时,被移走的最高位(最左边的位)由原来最高位的数字补充。例如,如果要移位的值为负数,每一次右移都在左边补1;如果要移位的值为正数,每一次右移都在左边补0,这叫做符号位扩展(保留符号位),在进行右移操作时用来保持负数的符号。例如,–8 >> 1是–4,用二进制表示如下:

        11111000 –8
        >>1
        11111100 –4

一个需要注意的地方是,由于符号位扩展(保留符号位),在对负数进行右移位操作时,每次都会在高位补1,因此-1右移位的结果总是–1。

4.无符号右移运算符

正如上面所讲到的,每一次右移,右移运算符“>>”总是自动地用它先前最高位的内容补移位后的最高位。这样做保留了原值的符号,但有时这并不是我们想要的结果。例如,如果进行移位操作的运算数不是数字值,就不希望进行符号位扩展(保留符号位)。当处理像素值或图形时,这种情况是相当普遍的。在这种情况下,不管运算数的初值是什么,都希望移位后总是在高位(最左边)补0,这就是人们所说的无符号移动(Unsigned Shift)。这时,就可以使用Java的无符号右移运算符“>>>”,它总是在左边补0。

下面的程序段说明了无符号右移运算符“>>>”的使用方法。在这里,变量a被赋值为-1,用二进制表示就是32位全是1。然后,这个值被无符号右移24位,即不进行符号位扩展,在它的左边总是补0,这样得到的值255被赋给变量a。

        int a = -1;
        a = a >>> 24;

下面用二进制形式进一步说明该操作。

        11111111111111111111111111111111 int类型-1的二进制代码
        >>> 24 无符号右移24位
        00000000000000000000000011111111 int类型255的二进制代码

由于无符号右移运算符“>>>”只是对32位和64位的值有意义,所以它并不像我们所想象的那样有用。在表达式中,过小的值总是被自动扩大为int类型,这意味着符号位扩展和移动总是发生在32位而不是8位或16位数值中。这样,对第8位为1的byte类型的值进行无符号移动是不可能的,因为在实际移位运算时,是对扩大后的32位值进行操作。请看下面的实例。

        /**
        * 演示无符号右移运算符的使用
        */
        package sample;
        public class ByteUShiftTest {
          public static void main(String args[]) {
            char hex[] = {
            '0', '1', '2', '3', '4', '5', '6', '7',
            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
            };
            byte b = (byte) 0xf1;
            byte c = (byte) (b >> 4);
            byte d = (byte) (b >>> 4);
            byte e = (byte) ((b & 0xff) >> 4);
            System.out.println(" b = 0x"+ hex[(b >> 4) & 0x0f] + hex[b & 0x0f]);
            System.out.println(" b >> 4 = 0x"+ hex[(c >> 4) & 0x0f] + hex[c & 0x0f]);
            System.out.println(" b >>> 4 = 0x"+ hex[(d >> 4) & 0x0f] + hex[d & 0x0f]);
            System.out.println("( b & 0xff) >> 4 = 0x"+ hex[(e >> 4) & 0x0f] +
                ex[e & 0x0f]);
          }
        }

此程序的输出结果如下:

        b = 0xf1
        b >> 4 = 0xff
        b >>> 4 = 0xff
        (b & 0xff) >> 4 = 0x0f

该程序的输出结果说明了无符号右移运算符“>>>”在处理byte类型值时,实际上不是对byte类型值进行直接操作,而是将其扩大到int类型后再处理。在本例中,变量b被赋为任意的负byte类型值。对变量b右移4位后转换为byte类型,将得到的值赋给变量c,因为有符号位扩展,所以该值为0xff。对变量b进行无符号右移4位操作后转换为byte类型,将得到的值赋给变量d,你可能期望该值是0x0f,但实际上是0xff,因为在移动之前变量b就被扩展为int类型,已经有符号扩展位。最后一个表达式将变量b的值通过按位与运算变为8位,再右移4位,再将得到的值赋给变量e,这次得到了预期的结果0x0f。由于对变量d(它的值已经是0xff)进行按位与运算后的符号位的状态已经明了,所以注意,对变量d没有进行无符号右移运算。

5.1.5 赋值运算符

赋值运算符包括“=”及其扩展赋值运算符等。

1.赋值运算符及其与算术运算符的结合

除了基本的赋值运算符“=”外,Java还提供特殊的算术赋值运算符,该运算符可用来将算术运算与赋值结合起来。像下列这样的语句在编程中是很常见的。

        a = a +2;

在Java中,可将该语句改写为:

        a += 2;

该语句使用“+=”进行赋值操作。上面两行语句的功能是一样的:使变量a的值增加2。下面是另一个例子。

        a = a % 2;

该语句可简写为:

        a %= 2;

在本例中,“%=”运算的结果是a/2的余数,并把结果重新赋给变量a。

这种简写形式对于Java的二元(即需要两个操作数)运算符都适用,其语句改写方式如下:

        var= var operator expression;

可以被改写为:

        var operator= expression;

这种赋值运算符有两个好处。第一,它们比标准的等式更紧凑;第二,它们有助于提高Java的运行效率。由于这些原因,在Java的专业程序中,会经常看见这些简写的赋值运算符。

2.位运算符赋值

所有的二进制位运算符都有一种将赋值与位运算组合在一起的简写形式。例如,下面两个语句都是将变量a右移4位后赋给a。

        a = a >> 4;
        a >>= 4;

同样,下面两个语句都是将表达式a | b运算后的结果赋给a。

        a = a | b;
        a |= b;

下面的程序定义了几个int类型的变量,然后运用位运算符赋值的简写形式将运算后的结果赋给相应的变量。

        /**
        * 演示位运算符赋值的使用
        */
        package sample;
        public class BitEqualsTest {
          public static void main(String args[]) {
            int a = 1;
            int b = 2;
            int c = 3;
            a |= 4;
            b >>= 1;
            c <<= 1;
            a ^= c;
            System.out.println("a = " + a);
            System.out.println("b = " + b);
            System.out.println("c = " + c);
          }
        }

该程序的输出结果如下:

        a = 3
        b = 1
        c = 6

5.1.6 条件运算符

Java提供一个特殊的三元运算符(Ternary)经常用于取代if-then-else语句。这个运算符就是“?:”,初看起来有些迷惑,但是用“?:”运算符是很方便、高效的。“?:”运算符的通用格式如下:

        expression1 ? expression2 : expression3

其中,expression1是一个布尔表达式。如果expression1为真,那么expression2被求值;否则,expression3被求值。整个“?:”表达式的值就是被求值表达式(expression2或expression3)的值。expression2和expression3是除了void以外的任何类型的表达式,并且它们的类型必须相同。

下面是一个利用“?:”运算符的例子。

        ratio = denom == 0 ? 0 : num / denom;

1.sourceCode目录:包含SuperVCD Store项目的程序代码。2.sampleCode目录:包含各个章节演示例子(Sample)的源代码。3.projectRun(项目的运行说明):在projectRun目录中,保存了两个子目录,一个为SuperVCD,另外一个是SuperVCDJDBC。SuperVCD中保存的是使用文件系统作

下面的程序说明了“?:”运算符的使用方法,该程序可以获得一个变量的绝对值。

        /**
        * 演示三元运算符“?:”的使用
        */
        package sample;
        public class TernaryTest {
          public static void main(String args[]) {
            int i, k;
            i = 10;
            k = i < 0 ? -i : i; // get absolute value of i
            System.out.print("absolute value of "+ i + " is " + k);
            i = -10;
            k = i < 0 ? -i : i; // get absolute value of i
            System.out.print("absolute value of" + i + " is " + k);
          }
        }

该程序的输出结果如下:

        absolute value of 10 is 10
        absolute value of -10 is 10

5.1.7 其他运算符

其他运算符包括分量运算符“· ”、下标运算符“[]”、实例运算符“instanceof”、内存分配运算符“new”、强制类型转换运算符“(类型)”和方法调用运算符“()”等。这里就不再详细讨论了。