就像有知觉的生物一样,程序必须在执行过程中控制它的世界,并做出选择,在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关键词有两方面的用途:
- 指定一个方法返回什么值(假设它没有void返回值)
- 会导致当前的方法退出,并返回那个值
因此改写上面的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:
- 一般的continue会退回最内层循环的开头(顶部),并继续执行。
- 带标签的continue会到达标签的位置,并重新进入紧接在那个标签后面的循环。
- 一般的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;否则,将产生整型输出。