疯狂
讲义
不论哪一种编程语言,都会提供两种基本的流程控制结构:分支结构和循环结构。其中分支结构用
于实现根据条件来选择性地执行某段代码,循环结构则用于根据循环条件重复执行某段代码。Java 同样
提供了这两种流程控制结构的语法,Java 提供了 if 和 switch 两种分支语句,并提供了 while、do while
和 for 三种循环语句,除此之外,JDK1.5 还提供了一种新的循环:foreach 循环,能以更简单的方式来遍
历集合、数组的元素。除此之外,Java 还提供了 break 和 continue 来控制程序的循环结构。
数组也是大部分编程语言都支持的数据结构,Java 也不例外。Java 的数组类型是一种引用类型的
变量,Java 程序通过数组引用变量来操作数组,包括获得数组的长度,访问数组元素的值等。本章将
会详细介绍 Java 数组的相关知识,包括如何定义、初始化数组等基础知识,并会深入介绍数组在内存
中的运行机制。
4.1 顺序结构
任何编程语言中最常见的程序结构就是顺序结构。顺序结构就是程序从上到下一行一行地执行,
中间没有任何判断和跳转。
如果 main 方法多行代码之间没有任何流程控制,则程序总是从上向下依次执行,排在前面的代码
先执行,排在后面的代码后执行。这意味着:如果没有流程控制,Java 方法里的语句是一个顺序执行
流,从上向下依次执行每条语句。
4.2 分支结构
Java 提供了两种常见的分支控制结构:if 语句和 switch 语句,其中 if 语句使用布尔表达式或布尔值
作为分支条件来进行分支控制;而 switch 语句则用于对多个整型值进行匹配,从而实现分支控制。
4.2.1 if 条件语句
if 语句使用布尔表达式或布尔值作为分支条件来进行分支控制,其中 if 语句有如下三种形式:
第一种形式:
if ( logic expression )
{
}
statements...
第二种形式:
if (logic expression)
{
}
statements...
else
{
}
statements...
第三种形式:
if (logic expression)
{
statements...
}
else if(logic expression)
{
statements...
}
72
...//可以有零个或多个 else if 语句
else//最后的 else 语句也可以省略
{
statement..
}
在上面 if 语言的三种形式中,放在 if 之后的括号里的只能是一个逻辑表达式,即这个表达式的返
回值只能是 true 或 false。第二种情形和第三种情形是相通的,如果第三种形式中 else if 块不出现,则
变成了第二种形式。
上面的条件语句中,if(logic expression)、else if(logic expression)以及 else 后花括号括起来多行代码
被称为代码块,一个代码块通常被当成一个整体来执行(除非运行过程中遇到 return、break、continue
等关键字,或者遇到了异常),因此这个代码块也被称为条件执行体。例如如下程序:
程序清单:codes/04/4-2/TestIf.java
public class TestIf
{
public static void main(String[] args)
{
int age = 30;
if (age > 20)
//只有当 age > 20 时,下面花括号括起来的语句块才会执行
//花括号括起来的语句是一个整体,要么一起执行,要么一起不会执行
{
System.out.println("年龄已经大于 20 岁了");
System.out.println("20 岁以上的人应该学会承担责任...");
}
}
}
因此,如果 if(logic expression)、else if(logic expression)和 else 后的语句块只有一行语句时,则可
以省略花括号,因为单行语句本身就是一个整体,无须花括号来把它们定义成一个整体。下面代码完
全可以正常执行(程序清单同上):
//定义变量 a ,并为其赋值
int a = 5;
if (a > 4)
//如果 a>4,执行下面的执行体,只有一行代码作为代码块
System.out.println("a 大于 4");
else
//否则,执行下面的执行体,只有一行代码作为代码块
System.out.println("a 不大于 4");
通常,我们建议不要省略 if、else、else if 后执行块的花括号,即使条件执行体只有一行代码,因
为保留花括号会有更好的可读性,而且保留花括号会减少发生错误的可能,例如如下代码,则不可正
常执行:
//定义变量 b ,并为其赋值
int b = 5;
if (b > 4)
//如果 b>4,执行下面的执行体,只有一行代码作为代码块
System.out.println("b 大于 4");
else
//否则,执行下面的执行体
b--;
//对于下面代码而言,它已经不再是条件执行体的一部分,因此总会执行
System.out.println("b 不大于 4");
上面代码中以粗体字标识的代码行:System.out.println("b 不大于 4");,将总是会执行,因为这行
73
疯狂
代码并不属于 else 后的条件执行体,else 后的条件执行体就是 b--;这行代码。
讲义
if、else、else if 后条件执行体要么是一个花括号扩起来的语句块,则这个语句块整
体作为条件执行体;要么是以分号为结束符的一行语句,甚至可能是一个空语句(空语
句是一个分号)。
如果 if 块后有多条语句作为条件执行体,如果省略了这个条件执行体的花括号,则会引起编译错
误,看下面代码(程序清单同上):
//定义变量 c ,并为其赋值
int c = 5;
if (c > 4)
//如果 c>4,执行下面的执行体,将只有 c--;一行代码为条件执行体
c--;
//下面是一行普通代码,不属于条件执行体
System.out.println("c 大于 4");
//此处的 else 将没有 if 语句,因此编译出错
else
//否则,执行下面的执行体,只有一行代码作为代码块
System.out.println("c 不大于 4");
在上面代码中,因为 if 后的条件执行体省略了花括号,则系统只把 c--;一行代码作为条件执行体,
当 c-;语句结束后,if 语句也就结束了。后面的 System.out.println("c 大于 4");代码已经是一行普通代码
了,不再属于条件执行体,从而导致 else 语句没有 if 语句,从而引起编译错误。
对于 if 语句,还有一个很容易出现的逻辑错误,这个逻辑错误并不属于语法问题,但引起错误的
可能性更大。看下面程序:
程序清单:codes/04/4-2/TestIfError.java
public class TestIfError
{
public static void main(String[] args)
{
int age = 45;
if (age > 20)
{
}
System.out.println("青年人");
else if (age > 40)
{
}
System.out.println("中年人");
else if (age > 60)
System.out.println("老年人");
{
}
}
}
表面上看起来,上面的程序没有任何问题:人的年龄大于 20 岁时是青年人,年龄大于 40 岁是中
年人,年龄大于 60 岁是老年人。但运行上面程序,发现打印结果是:青年人,而实际上我们希望 45
岁应判断为中年人——这显然出现了一个问题。
对于任何的 if else 语句,表面上看起来 else 后没有任何条件,或者 else if 后只有一个条件——
但这不是真相:因为 else 的含义是“否则”——else 本身就是一个条件!这也是笔者把 if、else 后代
码块统称为条件执行体的原因,else 的隐含条件对前面条件取反。因此上面代码实际上可改写为:
74
程序清单:codes/04/4-2/TestIfError2.java
public class TestIfError
{
public static void main(String[] args)
{
int age = 45;
if (age > 20)
{
System.out.println("青年人");
}
//在原本的 if 条件中增加了 else 的隐含条件
else if (age > 40 && !(age > 20))
{
System.out.println("中年人");
}
//在原本的 if 条件中增加了 else 的隐含条件
else if (age > 60 && !(age > 20) && !(age > 40 && !(age > 20)))
{
}
System.out.println("老年人");
}
}
此时就比较容易看出为什么发生上面的错误了,对于 age > 40 && !(age > 20)这个条件,又可改写
成 age > 40 && age <= 20,这样情况永远也不会发生了。对于 age > 60 && !(age > 20) && !(age > 40
&& !(age > 20))这个条件,则更不可能发生了。因此,无论如何,程序永远都不会判断中年人和老年
人的情形。
为了达到正确的目的,我们把程序改写成如下形式:
程序清单:codes/04/4-2/TestIfCorrect.java
public class TestIfCorrect
{
public static void main(String[] args)
{
int age = 45;
if (age > 60)
{
}
{
}
System.out.println("老年人");
else if (age > 40)
System.out.println("中年人");
else if (age > 20)
System.out.println("青年人");
{
}
}
}
运行程序,得到了正确结果。实际上,上面程序等同于下面代码:
public class TestIfCorrect
public static void main(String[] args)
{
int age = 45;
{
75
疯狂
讲义
if (age > 60)
{
System.out.println("老年人");
}
//在原本的 if 条件中增加了 else 的隐含条件
else if (age > 40 && !(age >60))
{
System.out.println("中年人");
}
//在原本的 if 条件中增加了 else 的隐含条件
else if (age > 20 && !(age > 60) && !(age > 40 && !(age >60)))
System.out.println("青年人");
{
}
}
}
上面程序的判断逻辑即转为如下三种情形:
age 大于 60 岁,判断为“老年人”。
age 大于 40 岁,且 age 小于等于 60 岁,判断为“中年人”。
age 大于 20 岁,且 age 小于等于 40 岁,判断为“青年人”。
上面的判断逻辑才是实际希望的判断逻辑。因此,当我们使用 if...else 语句进行流程控制时,一定
不要忽略了 else 所带的隐含条件。
如果每次都去计算 if 条件和 else 条件的交集也是一件非常烦琐的事情,为了避免出现上面的错误,
在使用 if...else 语句有一条基本规则:总是优先把包含范围小的条件放在前面处理。如 age>60 和 age>20
两个条件,明显 age>60 的范围更小,所以应该先处理 age>60 的情况。
使用 if...else 语句时,一定要先处理包括范围更小的情况。
4.2.2 switch 分支语句
switch 语句由一个控制表达式和多个 case 标签组成,和 if 语句不同的是,switch 语句后面的控制
表达式的数据类型只能是整型,不能是 boolean 型。case 标签后紧跟一个代码块,case 标签作为这个
代码块的标识。switch 语句的语法格式如下:
switch (expression)
{
case condition1:
{
}
statement(s)
break;
case condition2:
{
statement(s)
break;
}
...
case conditionN:
{
}
statement(s)
break;
76
default:
{
}
}
statement(s)
这种分支语句的执行是先对 expression 求值,然后依次匹配 condition1,condition2...conditionN 等
值,遇到匹配的值即执行对应的执行体;如果所有 case 标签后的值都不与 expression 表达式的值相等,
则执行 default 标前后的代码块。
和 if 语句不同的是,switch 语句中各 case 标签前后代码块的开始点和结束点非常清晰,因此完全
可以省略 case 后代码块的花括号。与 if 语句中 else 类似,switch 语句中 default 标签看似没有条件,
其实是有条件的:条件就是 expression 表达式的值不能与前面任何一个 case 标签后的值相等。
下面程序示范了 switch 语句的用法:
程序清单:codes/04/4-2/TestSwitch.java
public class TestSwitch
{
public static void main(String[] args)
{
//声明变量 score,并为其赋值为'C'
char score = 'C';
//执行 swicth 分支语句
switch (score)
{
case 'A':
System.out.println("优秀.");
break;
case 'B':
System.out.println("良好.");
break;
case 'C':
System.out.println("中");
break;
case 'D':
System.out.println("及格");
break;
case 'F':
System.out.println("不及格");
break;
default:
System.out.println("成绩输入错误");
}
}
}
运行上面程序,看到输出“中”,这个结果完全正常,字符表达式 score 的值为'C',对应结果为“中”。
值得指出的是,switch 语句中控制表达式的类型只能是 byte、short、char 和 int!不能是字符串,
这与 C#中有所不同。
在 case 标签后的每个代码块后都有一条 break;语句,这个 break;语句有极其重要的意义,Java 的
switch 语句允许省略 case 后代码块的 break;语句,但这种省略可能引入一个陷阱。如果我们把上面程
序中的 break;语句都注释掉,将看到如下运行结果:
中
及格
不及格
77
疯狂
讲义
成绩输入错误
这个运行结果看起来比较奇怪,但这正是由 switch 语句的运行流程决定的:switch 语句会先求出
expression 表达式的值,然后拿这个表达式和 case 标签后的值进行比较,一旦遇到相等的值,程序开始执行
这个 case 标签后代码,不再判断与后面 case、default 标签的条件是否匹配,除非遇到 break;才会结束。
使用 switch 语句时,有两个值得注意的地方:第一个地方是 switch 语句后的
expression 表达式的数据类型叧能是 byte、short、char 和 int 类型;第二个地方是如果省
略了 case 后代码块的 break;时所引入的陷阱。
4.3 循环结构
循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循
环体。当反复执行这段循环体时,需要在合适的时候把循环条件改为假,从而结束循环,否则循环将
一直执行下去,形成死循环。循环语句可能包含如下四个部分:
初始化语句(init_statements):一条或多条语句,这些代码用于完成一些初始化工作,初
始化语句在循环开始之前执行。
循环条件(test_ expression):这是一个 boolean 表达式,这个表达式能决定是否执行循环体。
循环体(body_statements):这个部分是循环的主体,如果循环条件允许,这个代码块将
被重复执行。如果这个代码块只有一行语句,则这个代码块的花括号是可以省略的。
迭代语句(iteration_statements):这个部分在一次循环体执行结束后,对循环条件求值之
前执行,通常用于控制循环条件中的变量,使得循环在合适时候结束。
上面四个部分只是一般分类,并不是每个循环中都非常清晰地分出了上面四个成分。
4.3.1 while 循环语句
while 循环的语法格式如下:
[init_statements]
while(test_ expression)
{
}
statements;
[iteration_statements]
while 循环每次执行循环体之前,先对 test_ expression 循环条件求值,如果循环条件为 true,则运
行循环体部分。从上面语法格式中来看,迭代语句 iteration_statements 总是位于循环体的最后,因此
只有当循环体能成功执行完成时,while 循环才会执行 iteration_statements 迭代语句。
从这个意义上来看,while 循环也可被当成条件语句——如果 test_ expression 条件一开始就为
false,则循环体部分将永远不会获得执行。
下面语句示范了一个简单的 while 循环:
程序清单:codes/04/4-3/TestWhile.java
public class TestWhile
{
public static void main(String[] args)
{
//循环的初始化条件
int count = 0;
//当 count 小于 10 时,执行循环体
78
while (count < 10)
{
System.out.println(count);
//迭代语句
count++;
}
System.out.println("循环结束!");
}
}
如果 while 循环的循环体部分和迭代语句合并在一起,且只有一行代码,则可以省略 while 循环后
的花括号。但这种省略花括号的做法,可能降低程序的可读性。
使用 while 循环时,一定要保证循环条件有变成 false 的时候,否则这个循环将成为一个死循环,
永远无法结束这个循环。例如如下代码(程序清单同上):
int count = 0;
while (count < 10)
{
System.out.println("不停执行的死循环 " + count);
count--;
}
System.out.println("永远无法跳出的循环体");
在上面代码中,count 的值越来越小,这将导致 count 值永远小于 10,count < 10 循环条件一直为
true,从而导致这个循环永远无法结束。
除此之外,对于许多初学者而言,使用 while 循环时还有一个陷阱:while 循环的循环条件后紧跟
一个分号。如果有如下程序片段(程序清单同上):
int count = 0;
//while 后紧跟一个分号,表明循环体是一个分号(空语句)
while (count < 10);
//下面的代码块与 while 循环已经没有任何关系
{
System.out.println("------" + count);
count++;
}
乍一看上,这段代码片段没有任何问题,但仔细看一下这个程序,不难发现 while 循环的循环条
件表达式后紧跟了一个分号。在 Java 程序中,一个单独的分号表示一个空语句,不作任何事情的空语
句,这意味着这个 while 循环的循环体是空语句。空语句作为循环体也不是最大的问题,问题是当 Java
反复执行这个循环体时,循环条件的返回值没有任何改变,这就成了一个死循环。分号后面的代码块
则与 while 循环没有任何关系。
4.3.2 do while 循环语句
do while 循环与 while 循环的区别在于:while 循环是先判断循环条件,如果条件为真才执行循环
体;而 do while 循环则先执行循环体,然后判断循环条件,如果循环条件为真,则执行下一次循环,
否则中止循环。do while 循环的语法格式如下:
[init_statements]
statements;
[iteration_statements]
do
{
}
79