Java编程思想(第四版)阅读笔记(四)—— 控制执行流程

就像有知觉的生物一样,程序必须在执行过程中控制它的世界,并做出选择,在Java中,你要使用执行控制语句来做出选择。

Java使用了C的所有流程控制语句。大多数过程型编程语言都具有某些形式的控制语句。它们通常在各种语言是交迭的。在Java中,涉及的关键字包括if-else、while 、do-while、for、return、break以及选择语句switch。但是,Java并不支持goto语句(该语句引起很多的反对意见,但仍是解决某些特殊问题的最便利的方法)。在Java中,仍然可以进行类似goto那样的跳转,但是与典型的goto,有了很多限制。

1 true & flase

所有条件语句都是利用条件表达式的真或假决定执行路径。

下面是一个条件表达式的例子:a==b

它用“==”来判断a值是否等于b值。该表达式返回true或false。其中所有的关系操作符都可以用来构造条件语句,但是需要注意的是:Java不允许将一个数字作为布尔值使用,虽在这种做法在C和C++里是允许的(在这写语言中,“真”是非零,而“假”是零)。如果想要在布尔测试中使用一个非布尔值,比如在if(c)中,那么首先必须使用一个条件表达式将其转换成布尔值,例如if(a!=0).

2 if-else

if-else语句是控制流程语句的最基本的形式,其中else是可选的。

因此有以下两种形式来使用if:

if (Boolean-expression)
    statement

if (Boolean-expression)
    statement
else
    statement

布尔表达式必须产生一个布尔结果,statement指的是用分号结尾的简单语句,或复合语句——封闭在花括号内的一组简单语句。

下面是if-else的一个例子,它是用来判断猜的数是大于还是等于目标数:

// control/IfElse.java

public class IfElse {
    static int result = 0;
    static void test(int testval,int target) {
        if (testval > target) {
            result = +1;
        } else if (testval < target) {
            result = -1;
        } else {
            result = 0;
        }
    }

    public static void main(String[] args) {
        test(10,5);
        System.out.println(result);
        test(5,10);
        System.out.println(result);
        test(5,5);
        System.out.println(result);
    }
} /* Output:
1
-1
0
*///:~

在test的中间部分,可以看到一个“else if”,这个并非关键字,只是一个else后面紧跟另一个新的if语句。尽管Java与C和C++一样,都是“格式自由”的语言,但是习惯上还是将流程控制语句的主体部分缩进排列,增加可读性。

3 迭代

while、do-while和for用来控制循环,将它们划分为迭代语句(iteration statement)。语句会重复执行,直到起作用的布尔表达式(Booleanexpression)得到“假”结果为止。

while循环格式如下:

while (Booleab-expression)
    statement

在循环刚开始时,就会计算一次布尔表达式的值;而在语句的下一次迭代开始前会再计算一次。

下面是一个简单的例子,可以产生随机数,知道符合特定的条件为止:

package control;

/**
 * @Author: hurst hao
 * @Date: Create in 2018/7/18 7:45
 * @Description: Demonstrates the while loop.
 */
public class WhileTest {

    static boolean condition() {
        boolean result = Math.random() < 0.99;
        System.out.println(result + ", ");
        return result;
    }

    public static void main(String[] args) {
        while (condition()) {
            System.out.println("Inside 'while'");
        }
        System.out.println("Exited 'while'");
    }
} /* (Execute to see output) *///:~

condition()方法用到了Math库里的static(静态)方法random(),这个方法的作用是产生0和1之间的(包括0带,不包括1)之间的一个doubule值。reslut的值通过比较操作符“<”而得到,这个操作符将产生boolean类型的结果。在输出boolean类型的值时,将自动地得到适合的字符true和false。while的条件表达式意思是说:“只要condition()返回true,就重复执行循环体中的语句”。

do-while

do-while的格式如下:

do
    statement
while (Boolean-expression)

while和do-while唯一区别就是do-while中的语句至少会执行一次,即便表达式第一次就被计算为false,而在while循环结构中,如果条件第一次就为false,那么其中的语句就根本不会被执行。在实际应用中,while必do-while更常用一些。

for

for循环可能是最经常使用的迭代形式。

这种在第一次迭代之前要进行初始化。随后它会进行条件测试,而且在每一步迭代结束时,进行某种形式的“步进”,for循环结构如下:

for (initialization;Boolean-expression;step)
    statement

初始化(initialization)表达式,布尔表达式(Boolean-expression),或者步进(step)运算都可以是空。每次迭代前会测试布尔表达式。如获得的结果为false,就会执行for语句后面的代码行,每次循环结束,会执行一次步进:

for循环经常用于执行“计数”任务:

/**
 * @Author: hurst hao
 * @Date: Create in 2018/7/19 6:55
 * @Description: Demonstrates "for" loop by listing all the lowercase ASCII letters
 */
public class ListCharacters {

    public static void main (String[] args) {
        for (char c = 0;c<128;c++) {
            if(Character.isLowerCase(c))
            System.out.println("value:"+(int)c+" character:"+c);
        }
    }
} /* Output:
value:97 character:a
value:98 character:b
value:99 character:c
value:100 character:d
value:101 character:e
value:102 character:f
value:103 character:g
value:104 character:h
value:105 character:i
value:106 character:j
value:107 character:k
value:108 character:l
value:109 character:m
value:110 character:n
value:111 character:o
value:112 character:p
value:113 character:q
value:114 character:r
value:115 character:s
value:116 character:t
value:117 character:u
value:118 character:v
value:119 character:w
value:120 character:x
value:121 character:y
value:122 character:z
*///:~

注意:变量c是在程序用到它的地方被定义的,也就是在for循环的控制表达式里,而不是在main()开始的地方定义的,c的作用域就是for控制的表达式的范围内。这个程序也使用啦java.lang.Character包装器类,这个类不但能把char基本类型的值包装进对象,还提供了一些别的有用的方法,这里用到了static isLowerCase()方法来检查问题中的字符是否为小写字母。

逗号操作符

Java里唯一用到逗号操作符的地方就是for循环的控制表达式。在控制表达式的初始化和步进控制部分,可以使用一系列由逗号分隔的语句;而且那些语句均会独立执行。

通过使用逗号操作符,可以在for语句内定义多个变量,但是它们必须具有相同的类型。

/**
 * @Author: hurst hao
 * @Date: Create in 2018/7/19 7:08
 * @Description: control/CommaOperator.java
 */
public class CommaOperator {
    public static void main(String[] args) {
        for (int i =1,j=i+10;i<5;i++,j=i*2) {
            System.out.println("i = " + i +" j = "+j);
        }
    }
} /* Output
i = 1 j = 11
i = 2 j = 4
i = 3 j = 6
i = 4 j = 8
*///:~

for语句中的int定义涵盖了i和j,在初始化部分实际上可以拥有任意数量的具有相同类型的变量定义。在一个控制表达式中,定义多个变量的这种能力只限于for循环适用,其其他任何选择或迭代语句中都不能使用这种方式。显然,无论是初始化还是步进部分,语句都是顺序执行的。此外,初始化部分可以有任意数量的同一类型的定义。

4 Foreach语法

Java SE5引入了一种新的更加简洁的for语法用于数组和容器,它就是foreach语法,表示不用创建int变量去对由访问项构成的序列进行计数,foreach将自动产生每一项。

例如:假设有一个float数组,要选取该数组中的每一个元素:

package control;

import java.util.Random;

/**
 * @Author: hurst hao
 * @Date: Create in 2018/7/20 7:36
 * @Description:
 */
public class ForEachFloat {

    public static void main(String[] args) {
        Random random = new Random(47);
        float f[] = new float[10];
        for (int i = 0; i < 10; i++) {
            f[i] = random.nextFloat();
        }

        for (float x : f) {
            System.out.println(x);
        }
    }
} /* Outout
0.72711575
0.39982635
0.5309454
0.0534122
0.16020656
0.57799757
0.18847865
0.4170137
0.51660204
0.73734957
*///:~

通过上面的代码可知,这个数组先是由旧式的for循环组装的,因为在组装的时候必须按照索引访问它。而后在下面这行中可以看到foreach语法:

for (float x : f) {

这条语句定义了一个float类型的变量x,从而将每一个f的元素赋值给x。

任何返回一个数组的方法都可以使用foreach,例如,String类有一个方法toCharArray(),它返回一个char数组,因此可以很容易的像下面这样迭代在字符串里面的所有字符:

package control;

/**
 * @Author: hurst hao
 * @Date: Create in 2018/7/20 7:47
 * @Description:
 */
public class ForEachString {

    public static void main(String[] args) {
        for (char c : "An African Swallow".toCharArray()) {
            System.out.print(c + " ");
        }
    } /* Output:
    A n   A f r i c a n   S w a l l o w 
    *///~
}

同样的,foreach还可以用于任何Iterable对象。

许多for语句都会在一个整型值序列中步进,就像下面这样:

for (int i = 0;i < 100; i++)

对于这些语句,foreach语法将不起作用,除非先创建一个int数组。

5 return

在Java中有多个关键词表示无条件分支,它们只是表示这个分支无需任何测试即可发生。这些关键词有:return、break、continue和一种与其他语言中的goto类似的跳转到标号语句的方式。

return关键词有两方面的用途:

  1. 指定一个方法返回什么值(假设它没有void返回值)
  2. 会导致当前的方法退出,并返回那个值

因此改写上面的test()方法,使其利用这些特点:

public class IfElse2 {
    static int test(int testval,int target) {
        if ( testval > target ) {
            return +1;
        }else if (testval < target) {
            return -1;
        }else {
            return 0; //Match
        }
    }
} /* Output:
1
-1
0
*///:~

不必加上else,因为方法在执行了return后不再继续执行。

如果在返回void的方法中没有return语句,那么在该方法的结尾处会有一个隐式的return,因此在方法中并非总是必须要有一个return语句。但是,如果一个方法声明它将返回void之外的其他东西,那么必须确保每一条代码路径都将返回一个值。

6. break和continue

在任何迭代语句的主体部分,都可用break和continue控制循环流程。其中,break用于强行退出循环,不执行循环中剩余的语句,而continue则停止执行当前的迭代,然后退回循环起始处,开始下一个迭代。下面的代码展示了break和continue在for和while循环中的例子:

package control;

/**
 * @Author: hurst hao
 * @Date: Create in 2018/7/20 22:25
 * @Description:
 */
public class BreakAndContinue {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            if (i == 74) {
                break; // Out of for loop
            }
            if (i % 9 != 0) {
                continue; // Next iteration
            }
            System.out.print(i + " ");
        }
        System.out.println();
        // Using foreach
        int[] x = new int[100];
        for (int i = 0; i < 100; i++) {
            x[i] = i;
        }
        for (int i : x) {
            if (i == 74) {
                break; // Out of for loop
            }
            if (i % 9 != 0) {
                continue; // Next iteration
            }
            System.out.print(i + " ");
        }
        System.out.println();
        int i = 0;
        // An "infinite loop"
        while (true) {
            i++;
            int j = i * 27;
            if (j == 1269) {
                break; // Out of loop
            }
            if (i%10 != 0) {
                continue; // Top of loop
            }
            System.out.print(i + " ");
        }
    }
} /* Output
0 9 18 27 36 45 54 63 72 
0 9 18 27 36 45 54 63 72 
10 20 30 40 
*///: ~

在第一个for循环中,i的值永远不会到达100,因为一旦达到74,break语句就会中断循环。通常。只有在不知道中断条件何时满足时,才需要这样使用break。只要i不能被9整除,continue语句就会使执行过程返回到循环的最开头(这会使i递增)。如果能够整除,则直接显示出来。

第二种for循环展示了foreach的用法,它产生了和第一个for循环相同的效果。

最后,是一个“无穷while循环”的情况。然而,循环内部有一个break语句,可中止循环。除此之外,continue语句执行序列移回到循环的开头,而没有去完成continue语句之后的所有内容。

无穷循环的第二种形式是:

for(;;)

编辑器将while(true)和for(;;)看作同一回事。

7. 臭名昭著的goto

在很早就有goto关键词了。goto起源于汇编语言的程序控制:“若条件A成立,则跳到这里,否则跳到那里”。如果阅读由编译器最终生成的汇编代码,会发现程序控制里包含了许多跳转。(Java编译器生成自己的“汇编代码”,但是这个代码是运行在Java虚拟机上的,而不是直接运行在CPU硬件上)。

goto语句是在源码级上的跳转,这使其招致不好的声誉。若一个程序总是从一个地方跳到另一个地方。还有什么办法能识别程序的控制流程呢?自从Edsger Dijkstra发表的论文《Goto considered harmful》(Goto有害),开始痛斥goto的不是,甚至建议将它从关键词集合中扫地出门。

对于这个问题,中庸之道是最好的解决方法。真正的问题并在于使用goto,而在于goto的滥用,而且少数情况下,goto还是组织控制流程的最佳手段。

尽管goto仍是Java中的一个保留字,但在语言中并未使用它;Java没有goto。然而,Java也能完成一些类似于跳转的操作,这与break和continue这两个关键词有关。它们其实不是一个跳转,而是中断迭代语句的一种方法。之所以将它们纳入goto问题中一起讨论,是由于它们使用了相同的机制:标签。

标签是后面跟有冒号的标识符,就像下面这样:

label1;

在Java中,标签起作用的唯一的地方刚好是在迭代语句之前。“刚好之前”的意思说明:在标签和迭代之间置入任何语句都不好。而在迭代之前设置标签的唯一理由是:希望在其中嵌套另一个迭代或者一个开关。这是由于break和continue关键词通常只中断当前循环,但若随同标签一起使用,它们就会中断循环,直到标签所在的地方:

label1:
outer-iteration {
    inner-iteration {
        //...
        break; // (1)
        //...
        continue: // (2)
        //...
        continue label1; // (3)
        //...
        break label1; // (4)
    }
}

在(1),break中断内部迭代,回到外部迭代。在(2)中,continue使执行点移回内部迭代的起始处。在(3)中,continue label1同时中断内部迭代以及外部迭代,直接转到label1处;随后,它实际上是继续迭代过程,但却从外部迭代开始。在(4)中,break label1也会中断所有迭代,并不会回到label1处,但不重新进入迭代。也就是说。它实际是完全中止了两个迭代。。

下面是标签用于for循环的例子:

package control;

/**
 * @Author: hurst hao
 * @Date: Create in 2018/7/22 19:17
 * @Description: For loops with "labeled break" and "labeled continue."
 */
public class LabeledFor {

    public static void main(String[] args) {
        int i = 0;
        outer: // Can't have statements here
        for (; true; ) { // infinite loop
            inner: // Can't have statement here
            for (; i < 10; i++) {
                System.out.println("i = " + i);
                if (i == 2) {
                    System.out.println("continue");
                    continue;
                }
                if (i == 3) {
                    System.out.println("break");
                    i++; // Otherwise i never
                    // gets incremented.
                    break;
                }
                if (i == 7) {
                    System.out.println("continue outer");
                    i++;  // Otherwise i never
                    // gets incremented
                    continue outer;
                }
                if (i == 8) {
                    System.out.println("break outer");
                    break outer;
                }
                for (int k = 0; k < 5; k++) {
                    if (k == 3) {
                        System.out.println("continue inner");
                        continue inner;
                    }
                }
            }
        }
        // Can't break of continue to labels here
    }
} /* Output
i = 0
continue inner
i = 1
continue inner
i = 2
continue
i = 3
break
i = 4
continue inner
i = 5
continue inner
i = 6
continue inner
i = 7
continue outer
i = 8
break outer
*///"~

注意,break会中断for循环,并且在抵达for循环的末尾之前,递增表达式不会执行。由于break跳过了递增表达式,所以在i==3的情况下直接对i执行递增运算。在i==7的情况下,continue outer语句会跳到循环顶部,而且也会跳到递增,所以这里也对i直接递增。

如果没有break outer语句,就没有办法从内部循环里跳出外部循环。这是由于break本身只能中断最内层的循环(continue同样如此)。

当然,如果想在中断循环的同时退出,简单地用一个return即可。

下面的例子是带标签的break以及continue语句在while循环中的用法:

package control;

/**
 * @Author: hurst hao
 * @Date: Create in 2018/7/22 20:31
 * @Description:
 */
public class LabeledWhile {

    public static void main(String[] args) {
        int i = 0;
        outer:
        while (true) {
            System.out.println("Outer while loop");
            while(true) {
                i++;
                System.out.println("i = " + i);
                if ( i == 1) {
                    System.out.println("continue");
                    continue;
                }
                if (i == 3) {
                    System.out.println("continue outer");
                    continue outer;
                }
                if (i == 5) {
                    System.out.println("break");
                    break ;
                }
                if (i == 7) {
                    System.out.println("break outer");
                    break outer;
                }
            }
        }
    }  
} /* Output:
Outer while loop
i = 1
continue
i = 2
i = 3
continue outer
Outer while loop
i = 4
i = 5
break
Outer while loop
i = 6
i = 7
break outer
*/// :~

同样的规则也适用于while:

  1. 一般的continue会退回最内层循环的开头(顶部),并继续执行。
  2. 带标签的continue会到达标签的位置,并重新进入紧接在那个标签后面的循环。
  3. 一般的break会中断并跳出标签所指的循环。

要记住的重点是:在Java里需要使用标签的的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中break或continue.

在Dijkstra的《Goto有害》的论文中,它最反对的就是标签,而非goto。一个程序里随着标签的增多,产生的错误也会越来越多,并且标签和goto使得程序难以分析。但是,Java的标签不会造成这种问题,因为它们的应用场合已经受到了限制,没有特别的方式用于改变程序的控制。由此引出了——通过限制语句的能力,使语言特性更加有用。

switch

switch有时也被认为是一种选择语句。根据整数表达式的值,switch语句可以从一系列代码中选出一段去执行,它的格式如下:

switch (integral-selector) {
    case integral-value1 : statement; break;
    case integral-value2 : statement; break;
    case integral-value3 : statement; break;
    case integral-value4 : statement; break;
    case integral-value5 : statement; break;
    // ...
    default: statement; 
}

其中,integral-selector(整数选择因子)是一个能够产生整数值的表达式,switch能将这个表达式的结果与每个intergral-value(整数值)相比较。若发现相符的,就执行对应的语句(单一语句或多条语句,其中并不需要括号)。若没有发现相符的,就只想default(默认)语句。

在上面的定义中,注意到每个case后面都有一个break结尾,这样可使执行流程跳转至switch主体的末尾。这是构建switch语句的一种传统方式,但是break是可选的。若省略break,会继续执行后面的case语句,直到遇到一个break为止。尽管通常不想出现这种情况,于是需要善加利用这种情况。在最后的default语句没有break,因为执行流程已经到了break的跳转目的地。当然,如果考虑到编程风格方面的原因,完全可以在default语句的末尾放置一个break,即使它没有任何实际的用处。

switch语句是多路选择(就是从一系列执行路径中挑选一个)的一种干净利落的方法。但它要求使用一个选择因子,并且必须是int和char那样的整数值。例如,假若将一个字符串或者浮点数作为选择因子使用,那么它们在switch语句里是不会工作的。对于非整数类型,则必须使用一系列if语句。但是自从Java SE5添加enum后,减少了这种限制,因为enum可以和switch协调工作。

下面的例子是随机生成字母,并判断它们是元音还是辅音字母:

package control;

import java.util.Random;

/**
 * @Author: hurst hao
 * @Date: Create in 2018/7/22 21:55
 * @Description:Demonstrates the swtich statement
 */
public class VowelsAmdConsonants {

    public static void main(String[] args) {
        Random random = new Random(47);
        for (int i = 0; i < 100; i++) {
            int c = random.nextInt(26) + 'a';
            System.out.print((char) c + " , " + c + " : ");
            switch (c) {
                case 'a' :
                case 'e' :
                case 'i'  :
                case 'o' :
                case 'u'  :
                    System.out.println("vowel");
                    break;
                case 'y'  :
                case 'w' :
                    System.out.println("Sometimes a vowel");
                    break;
                default:
                    System.out.println("consonant");
            }
        }
    }
} /* Output
y , 121 : Sometimes a vowel
n , 110 : consonant
z , 122 : consonant
b , 98 : consonant
r , 114 : consonant
n , 110 : consonant
y , 121 : Sometimes a vowel
g , 103 : consonant
c , 99 : consonant
f , 102 : consonant
o , 111 : vowel
w , 119 : Sometimes a vowel
z , 122 : consonant
n , 110 : consonant
t , 116 : consonant
c , 99 : consonant
q , 113 : consonant
r , 114 : consonant
g , 103 : consonant
s , 115 : consonant
e , 101 : vowel
g , 103 : consonant
z , 122 : consonant
m , 109 : consonant
m , 109 : consonant
j , 106 : consonant
m , 109 : consonant
r , 114 : consonant
o , 111 : vowel
e , 101 : vowel
s , 115 : consonant
u , 117 : vowel
e , 101 : vowel
c , 99 : consonant
u , 117 : vowel
o , 111 : vowel
n , 110 : consonant
e , 101 : vowel
o , 111 : vowel
e , 101 : vowel
d , 100 : consonant
l , 108 : consonant
s , 115 : consonant
m , 109 : consonant
w , 119 : Sometimes a vowel
h , 104 : consonant
l , 108 : consonant
g , 103 : consonant
e , 101 : vowel
a , 97 : vowel
h , 104 : consonant
k , 107 : consonant
c , 99 : consonant
x , 120 : consonant
r , 114 : consonant
e , 101 : vowel
q , 113 : consonant
u , 117 : vowel
c , 99 : consonant
b , 98 : consonant
b , 98 : consonant
k , 107 : consonant
i , 105 : vowel
n , 110 : consonant
a , 97 : vowel
m , 109 : consonant
e , 101 : vowel
s , 115 : consonant
b , 98 : consonant
t , 116 : consonant
w , 119 : Sometimes a vowel
h , 104 : consonant
k , 107 : consonant
j , 106 : consonant
u , 117 : vowel
r , 114 : consonant
u , 117 : vowel
k , 107 : consonant
z , 122 : consonant
p , 112 : consonant
g , 103 : consonant
w , 119 : Sometimes a vowel
s , 115 : consonant
q , 113 : consonant
p , 112 : consonant
z , 122 : consonant
d , 100 : consonant
y , 121 : Sometimes a vowel
c , 99 : consonant
y , 121 : Sometimes a vowel
r , 114 : consonant
f , 102 : consonant
j , 106 : consonant
q , 113 : consonant
a , 97 : vowel
h , 104 : consonant
x , 120 : consonant
x , 120 : consonant
h , 104 : consonant
v , 118 : consonant
...
*///:~

由于Random.nextInt(26)会产生0到26之间的一个值,所以在其上加上一个偏移量‘a’,即可产生小写字母。在case语句里,使用单引号引起的字符也会产生用于比较的整数值。

注意,case语句是能够堆叠在一起,为一段代码形成多重匹配,即只要符合多种条件中的一种,就执行那段特别的代码。此时也应该注意将break语句置于特定case的末尾,否则控制流程会简单地下移,处理后面的case。

在下面的语句中:

int c = randDom.nextInt(26) + 'a';

Random.nextInt()将产生0~25之间的一个随机int值,它被加到‘a’上。这表示“a”将自动被转换为int以执行加法。为了把c当作字符打印,必须将其转型为char;否则,将产生整型输出。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值