就像有知觉的生物一样,程序必须在执行过程中控制它的世界,并做出选择。在Java中,你要使用执行控制语句来做出选择。
Java 使用了C的所有流程控制语句,所以如果读者以前用过C或C++编程,那么应该非常熟悉了。大多数过程型编程语言都有某些形式的控制语句,它们通常在各种语言间是交迭的。
在Java总,设计的关键字包括:if-else、do-while、for、return、break以及选择语句switch。
然而,Java并不支持goto语句(该语句引起许多反对意见,但它仍是解决某些特殊问题的最便利的方法)。
在Java中,仍然可以进行类似goto那样的跳转,但比起典型的goto,有了很多限制。
4.1 true和false
所有条件语句都利用条件表达式的真或假来决定执行路径。这里有一个条件表达式的例子:
a==b。
注意Java不允许我们将一个数字作为布尔型值使用,虽然这在C和C++是允许的(在这些语言里,“真”是非零,而“假”是零)。
4.2 if-else
if-else语句是控制程序流程最基本的形式。其中的else是可选的,所以可按下述两种形式来使用if:
if(boolean-expression)
statement
或
if(boolean-expression)
statement
else
statement
布尔表达式必须产生必须产生一个布尔结果,statement指用分号结尾的简单语句,或复合语句——封闭在花括号内的一组简单语句。在本书任何地方,只要提及“ 语句 ”这个词,就指的是简单语句或符合语句。
可以看到“ else if”,那并非新的关键字,而仅仅只是一个else后面紧跟另一个新的if语句。
尽管Java与它之间产生的C和C++一样,都是“ 格式自由 ”的语言,但习惯上还是将流程控制语句的主体部分缩进排列,使读者能方便地确定起始与终止。
4.3 迭代
while、do-while和for用来控制循环,有时将它们划分为 迭代语句(iteration statement)。语句会重复执行,知道起控制作用的布尔表达式(booleanexpression)得到“ 假 ”的结果为止。
while循环的格式如下:
while(boolean-expression)
statment
在循环刚开始时,会计算第一次布尔表达式的值,而在语句的下一次迭代开始前会再计算一次。
4.3.1 do-while
do-while的格式如下:
do
statement
while(boolean-expression)
while和do-while唯一的区别就是do-while的语句至少执行一次,即便表达式第一次被计算为false。而在while循环结构中,如果条件第一次就是false,那么其中的语句根本不会执行。在实际应用总,while比do-while更常用一些。
4.3.2 for
for循环可能是最经常使用的迭代形式,这种在第一次迭代之前要进行初始化。随后,它会进行条件测试,而且在每一次迭代结束时,进行某种形式的“ 步进 ”。for循环的格式如下:
for(initialization;boolean-expression;step)
statement
初始化(initialization)表达式、布尔表达式(boolean-expression),或者步进(step)运算,都可以为空。
每次迭代前会测试布尔表达式。若获得的结果是false,就会执行for语句后面的代码行。每次循环结束,会执行一次步进。
注意,变量c是在程序用到它的地方被定义的,也就是在for循环的控制表达式里,而不是在main()开始的地方定义的。c的作用域就是for控制的表达式的范围内。
对于像C语言那样的传统的过程型语言,要求所有变量都在一个块的开头定义,以便编译器在创建这个块的时候,可以为哪些变量分配空间。
而在Java和C++中,则可在整个块的范围内分散变量申明,在真正需要的地方才加以定义。这样便可形成更自然的编程风格,也更易理解。
4.3.3 逗号操作符
本章前面已经提到了逗号操作符(注意不是逗号分隔符,逗号用作分隔符时用来分隔函数的不同参数),Java里唯一用到逗号操作符的地方就是for循环的控制表达式。在控制表达式的初始化和步进控制部分,可以使用一系列由逗号分隔的语句;而且哪些语句均会独立执行。
通过使用逗号操作符,可以在for语句内定义多个变量,但是它们必须具有相同的类型。
可以看到,无论在初始化还是在步进部分,语句都是顺序执行的。此外,初始化部分可以有任意数量的同一类型的定义。
4.4 Foreach语法
Java SE5引入了一种新的更加简洁的for语法用于数组和容器,即Foreach语法,表示不必创建int变量去对由访问构成的序列进行计数,foreach将自动产生每一项。
假如,假设有一个float数组,我们要选取该数组中的每一个元素:
这个数组是用旧式的for循环组装的,因为在组装时必须按索引访问它。
在下面看到foreach语法:
for(float x:f){}
这条语句定义了一个float类型的变量x,继而将每个f的元素赋值给x。
任何返回一个数组的方法都可以使用foreach。
foreach语法不仅在录入代码时可以节省时间,更重要的是,它阅读起来要容易的多,它说明您正在努力做什么,而不是给出你正在如何做的细节(例如正在创建索引,因此可以使用它来选取数组中的每一个元素)。
4.5 return
在Java中有多个关键字表示无条件分支,它们只是表示这个分值无需任何测试即可发生。
这些关键词包括 return、break、continue和一种与其他语言中的goto类似的跳转到标号语句的方式。
return 关键词有两个方面的用途:一方面指定一个方法返归什么值(假设它没有void返回值),另一方面它会导致当前的方法退出,并返回那个值。可据此改写上面的test()方法,使其利用这些特点:
不必加上else,因为方法在执行了return后不再继续执行。
如果在返回void的方法中没有return语句,那么在该方法的结尾处会有一个隐式的return,因此在方法中并非总是必须要有一个return语句。但是,如果一个方法声明它将返回void之外的其他东西,那么必须确保每一条代码路径都将返回一个值。
4.6 break和continue
在任何迭代语句的主体部分,都可用break和continue控制循环的流程。
其中,break用于强行退出,不执行循环中剩余的语句。
而continue则停止执行当前的迭代,然后退出循环起始处,开始下一次迭代。
下面这个程序向大家展示了break和continue在for和while循环中的例子:
无穷循环的第一种形式:while
无穷循环的第二种形式:for(;;)
编译器将while(true)与for(;;)看作是同一回事。所以具体选用哪个取决于自己的编程习惯。
4.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)
}
}
同样的规则亦适用于while:
1)一般的continue会退回最内层循环的开头(顶部),并继续执行。
2)带标签的continue会达标签的位置,并重新进入紧接在那个标签后面的循环。
3)一般的break会中断并跳出当前循环。
4)带标签的break会中断并跳出标签所指的循环。
要记住的重点是:Java里需要使用标签的唯一理由就是因为循环嵌套存在,而且想从多层嵌套中break或continue。
在Dijkstra的《Goto有害》的论文中,他最反对的就是标签,而非goto。他发现在一个程序里随着标签的增多,产生的错误也会越来越多,并且标签和goto使得程序难以分析。但是,Java的标签不会造成这种问题,因为他们的应用场合已经收到了限制,没有特别的方式用于改变程序的控制。由此也引出了一个有趣的现象:通过限制语句的能力,反而能使一项语言特性更加有用。
4.8 switch
switch有时也被归为一种选择语句。根据整数表达式的值,switch语句可以从一系列代码中选出一段去执行。它的格式如下:
其中:Integral-selector(整数选择因子)是一个能够产生数值的表达式,switch能将这个表达式的结果与每个integral-value(整数值)相比较。若发现相符的,就执行对应的语句(单一语句或多条语句,其中并不需要括号)。若没有发现相符的,就执行default(默认)语句。
在上面的定义中,大家会注意到每个case均以一个break结尾,这样可使执行流程跳转至switch主体的末尾。这是构建switch语句的一种传统方式,但break是可选的。若省略break,会继续执行后面的case语句,直到遇到一个break为止。尽管通常不想出现这种情况,但对有经验的程序员来说,也许能够改善加利用这种情况。注意最后的default语句没有break,因为执行流程已经到了break的跳转的目的地。
当然,如果考虑到编程风格方面的原因,完全可以在default语句的末尾放置一个break,尽管它并没有任何实际的用处。
switch语句是实现多路选择(也就是说从一系列执行路径中挑选一个)的一种干净利落的方法。
但它要求使用一个选择因子,并且必须是int或char那样的整数值。
例如:
假若一个字符串或者浮点数作为选择因子使用,那么它们在switch语句中是不会工作的。对于非整数类型,则必须使用一系列if语句。
4.9 总结
本章介绍了大多数编程语言都具有的基本特性:运算、操作符优先级、类型转换以及选择和循环等等。
现在我们已经做好了准备,以使自己更靠近面向对象的程序设计世界。