第3章 流程控制
在大量代码实践的基础上,可以发现所有的程序都可以抽象为三类程序结构:顺序结构、分支结构、循环结构。
顺序结构是指,通常情况下,我们编写的代码都是自上而下,依次执行,不会出现跳行执行、逆向执行等情况。
其中分支结构用于实现根据条件来选择性执行某一段代码,因此分支结构也被叫做选择结构。
循环结构则是用于实现根据循环条件重复执行某段代码。
Java提供了if和switch两种分支语句,还有while,do while和for三种循环语句。除此以外,JDK5还提供了增强for循环,foreach循环。以更简洁的书写方式遍历集合、数组。
3.1 顺序结构
任何编程语言中最常见的程序结构就是顺序结构。顺序结构就是程序从上而下逐行执行,中间没有任何的判断和跳转。
如果main方法的多行代码之间没有任何流程控制,则程序总是从上而下依次执行,排在前面的代码先执行,排在后面的代码后执行。
3.2 分支结构
Java提供了两种常见的分支结构:if语句和switch语句,其中if语句使用布尔表达式或者布尔值作为分支结构条件来进行分支控制。
3.2.1 if条件语句
if 语句使用布尔表达式或者布尔值作为分支条件来进行分支控制,if语句有以下三种形式。
- 单分支结构
if(logic expression){
statement
}
单分支情况一般用于判断是否满足某个条件,如果括号内表达式返回值为true,则执行后面大括号中的语句。
示例:
package cn.bytecollege;
/**
* 本例将演示单分支结构
* @author MR.W
*
*/
public class SingleIfDemo {
public static void main(String[] args) {
int age = 18;
if(age>17) {
System.out.println("您已年满18岁");
}
}
}
在上例代码中,我们定义了整型变量age,并赋值为18。在if分支中我们判断age是否大于18,如果大于18则执行大括号中的内容。根据代码可以看出age是大于17的,因此会执行大括号中的语句。
- 双分支结构
单分支具有一定的局限性,因为单分支结构只能判断一种情况,如果两个条件互斥,则不能很简洁的进行判断。因此有了双分支结构,我们经常会遇到这种情况:我们在注册账号时,需要填写性别,我们知道表示性别的两种情况往往是互斥的,即非此即彼的关系。不是男性,便是女性。当我们用1和2代替性别时,我们可以做以下判断,首先我们使用单分支进行判断。
首先,我们先了解一下双分支结构的语法:
if(logic expression){
statement
}else{
statement
}
示例:
package cn.bytecollege;
/**
* 本例将演示使用单分支判断两种情况
*/
import java.util.Scanner;
public class SingleIfDemo2 {
public static void main(String[] args) {
System.out.println("---请选择性别---");
System.out.println("1.男性");
System.out.println("2.女性");
Scanner scanner = new Scanner(System.in);
int gender = scanner.nextInt();
if(gender == 1) {
System.out.println("您选择了男性");
}
if(gender==2) {
System.out.println("您选择了女性");
}
}
}
在上例中,我们使用了Scanner类的对象,该类是用于等待键盘输入,此处不做过多介绍。
接下来,我们使用双分支来重构上述代码。
package cn.bytecollege;
import java.util.Scanner;
public class DoubleIfDemo {
public static void main(String[] args) {
System.out.println("---请选择性别---");
System.out.println("1.男性");
System.out.println("2.女性");
Scanner scanner = new Scanner(System.in);
int gender = scanner.nextInt();
if(gender == 1) {
System.out.println("您选择了男性");
}else {
System.out.println("您选择了女性");
}
}
}
从示例中我们可以看到,使用双分支结构代码更简洁,阅读方便,书写简便。如果使用单分支结构去判断,那么每种情况都需要判断,这无疑增加了代码的运行时间,降低了效率。但是当使用双分支结构时,因为两个条件互斥,所以只要一个条件不满足,那么一定是另外一种情况。这样提升了代码运行效率。
- 多分支结构,多分支结构一般用于判断多种情况,条件间也是互斥的,例如,评定学生成绩等级,90分以上评定为A,70到90分评定为B,60-70分评定为C,60分以下评定为D,由于判断情况比较多,很明显单分支效率低,双分支只能判断两种情况,都不能满足需要,此时,就需要用到多分支了。首先咱们了解一下多分支结构的语法
if(logic expression){
statement
}else if(logic expression){
statement
}
//else if 可以有多个
//else 是指上述情况都不满足时,即进入else,else可以省略
else{
}
接下来,我们通过示例来完成上述案例。
package cn.bytecollege;
import java.util.Scanner;
/**
* 本例将演示多分支结构
* @author MR.W
*
*/
public class MutiIfDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double score = scanner.nextDouble();
if(score>=90) {
System.out.println("A");
}else if(score>=70&&score<90) {
System.out.println("B");
}else if(score>=60&&score<70) {
System.out.println("C");
}else if(score>=0&&score<60) {
System.out.println("D");
}
}
}
上例中,通过else if判断了多种情况,但是上述代码还是有一些缺陷,要知道用户并不会完全按照开发者的意愿来使用软件,假如用户此时输入了200,那么程序就会没有任何响应便结束了,这样的用户体验是非常糟糕的,那么如何才能尽可能的让这个程序健壮一些呢,我们只需要在最后一个else if后添加else即可,也就是说添加else以后,当else之前所有的判断条件都不满足的时候,就会进入else中去。那么我们现在重构上述代码。
package cn.bytecollege;
import java.util.Scanner;
/**
* 本例将演示多分支结构
* @author MR.W
*
*/
public class MutiIfDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double score = scanner.nextDouble();
if(score>=90) {
System.out.println("A");
}else if(score>=70&&score<90) {
System.out.println("B");
}else if(score>=60&&score<70) {
System.out.println("C");
}else if(score>=0&&score<60) {
System.out.println("D");
}else {
System.out.println("您的输入有误,请重试!");
}
}
}
3.2.2 switch条件语句
switch 语句有一个控制表达式和多个case标签组成,和if语句不同的是,switch语句后面的控制表达式的数据类型只能是byte、short、char、int四种类型,从Java 7以后添加了枚举类型和String类型。
switch语句通常需要在case标签后紧跟一个代码块,case标签作为这个代码块的标识,switch语句的语法格式如下:
switch (expression){ case condition1: { statement(s) break; } case condition2: { statement(s) break; } case condition2: { statement(s) break; } .... case condition N: { statement(s) break; } default:{ statement (s) } }
switch分支语句的执行是先对expression求值,然后依次匹配condition1、condition2 …condition N等值,遇到匹配的值即执行对应的执行体;如果所有的case标签后的值都不与expression表达式的值相等,则执行default标签后的代码块。
和if语句不同的是,switch语句中各case标签后代码块的开始点和结束点非常清晰,因此完全可以省略case后代码块的花括号。与if语句中的else类似,switch语句中的default看似没有条件,其实是有条件的,条件就是expression表达式的值不能与前面任何一个case标签后的值相等。
package cn.bytecollege;/** * 本例将演示switch语句 * @author MR.W * */public class SwitchDemo { public static void main(String[] args) { //声明变量score,并赋值为'C' char score = 'C'; switch (score) { case 'A': System.out.println('A'); break; case 'B': System.out.println('B'); break; case 'C': System.out.println('C'); break; case 'D': System.out.println('D'); break; default: System.out.println("输入有误,请重试"); } }}
运行上面的程序可以看出输出了“不及格”,而变量score的值对应的就是不及格,结果没有任何问题。需要注意的是在每个case中代码末尾都有break,break在这里指中断的意思,也就是当条件匹配以后,switch语句即可中断,再不进行以后的判断。那么如果省略了break,程序又会发生什么呢?
package cn.bytecollege;/** * 本例将演示switch语句 * @author MR.W * */public class SwitchDemo { public static void main(String[] args) { //声明变量score,并赋值为'C' char score = 'C'; switch (score) { case 'A': System.out.println("优秀"); case 'B': System.out.println("良好"); case 'C': System.out.println("及格"); case 'D': System.out.println("不及格"); default: System.out.println("输入有误,请重试"); } }}
运行以上程序:
这个结果有些怪异,但是这正是有switch语句的运行流程决定的:switch语句会先求出expression表达式的值,然后用这个表达式的值和case标签后的值进行比较,一旦遇到相等的值,程序就执行case标签后的代码,不在判断与后面case、default标签的条件是否匹配,除非遇到break才会结束。
Java 7增强了switch语句的功能,switch后的表达式支持String(字符串)类型的变量或表达式。下面我们通过示例来学习。
package cn.bytecollege;public class SwitchDemo2 { public static void main(String[] args) { String week = "周三"; switch (week) { case "周一": System.out.println("今天是周一"); break; case "周二": System.out.println("今天是周二"); break; case "周三": System.out.println("今天是周三"); break; case "周四": System.out.println("今天是周四"); break; case "周五": System.out.println("今天是周五"); break; case "周六": System.out.println("今天是周六"); break; case "周日": System.out.println("今天是周日"); break; default: break; } }}
有关枚举类型,我们将会在后续的内容中学习,此处不做过多解释。
3.2.3 分支嵌套
如果把一个分支结构放进另一个分支结构中,这种情况叫做分支嵌套,分支嵌套可以是if中嵌套switch,switch中嵌套if,也可以是if互相嵌套或者switch自身嵌套。例如,计算一个年份是否是闰年。
package cn.bytecollege;import java.util.Scanner;/** * 本例将演示输入一个年份 * 判断是否是闰年 * @author MR.W * */public class LeapYear { public static void main(String[] args) { System.out.println("请输入年份:"); Scanner scanner = new Scanner(System.in); int year = scanner.nextInt(); if(year%4==0) { if(year%100==0) { if(year%400==0) { System.out.println(year+"是闰年"); }else { System.out.println(year+"不是闰年"); } }else { System.out.println(year+"是闰年"); } }else { System.out.println(year+"不是闰年"); } }}
3.3 循环结构
循环结构是指在满足某个循环条件的情况下,反复执行同一段代码,直到不满足循环条件为止。被循环执行的代码叫做循环体。当反复执行循环体时,要在适当的时机修改循环条件,从而结束循环,否则循环会一直进行下去,形成死循环。一个恰当的循环结构应该包含以下4个组成部分:
-
初始化语句:一个或多个语句,这些语句用来完成一些初始化工作,在循环开始之前执行
-
循环条件:循环条件是一个布尔表达式,该表达式决定是否执行循环体
-
循环体:这部分是循环的主题,如果循环条件允许,该代码块将被反复执行,如果这个代码块只有一条语句,则代码块的花括号可以省略。
-
迭代语句:这部分在一次循环体执行结束后执行,在循环条件求值前执行,通常用于控制循环条件中的变量,使得循环在合适的时候结束。
3.3.1 for循环
for循环是循环结构中常用的一中,其语法格式如下:
for([init_statment];[test_exression];[iteration_statement]){ statement; }
其中init_statment指初始化条件,test_expression指循环条件,statement是指循环体,iteration_statement是指迭代语句,在statement执行结束以后执行。程序执行for循环时,先执行init_statment,初始化语句只在循环条件开始前执行1次,每次执行循环体之前,先计算
test_exression循环条件的值,如果循环条件返回为true,则执行循环体,循环体执行结束后执行循环迭代语句。因此对于for循环而言,循环条件总比循环体要多执行1次,因为最后1次执行循环条件返回false,将不再执行循环体。
下面,我们通过示例来学习for循环,通过本示例,我们计算1-100的和
package cn.bytecollege;/** * 本示例将演示for循环 * @author MR.W * */public class ForDemo { public static void main(String[] args) { //定义变量保存结果 int sum= 0; for (int i = 1;i<=100;i++) { sum += i; } System.out.println(sum); }}
在上例中int sum = 0
是指定义1个变量,用于保存最后的结果,for循环则是定义了一个循环结构。
在for循环中int i =1
定义了初始化语句,i<=100
则是循环条件,当i的值是101的时候该表达式返回false则终止循环,sum+=i
等价于sun=sum+i
是循环体,用于累加值,i++
则是用于改变循环变量的值,递增循环变量的值。下面我们来分析这段代码的执行过程:
-
当i = 1时,此时先判断循环条件是否成立,i<=100返回值为true。
-
循环条件成立,则进入循环体,此时sum= 0,sum = sum+i运算后sum的值变为1
-
循环体执行结束后,改变循环变量的值i的值为2
-
此时再次判断循环条件,2<=100成立,因此再次进入循环,执行2直至条件不成立为止
注意:除非特殊情况尽量不要在循环体内修改循环变量的值。
此外,需要注意的是for循环圆括号中只有两个分号是必须的,初始化语句、循环条件、迭代语句部分都是可以省略的,如果省略了循环条件,则这个循环条件默认为true,将会产生死循环。例如下面的代码。
package cn.bytecollege;/** * 本例将演示死循环 * @author MR.W * */public class EndlessForLoop { public static void main(String[] args) { for (;;) { System.out.println("-----endless-----"); } }}
运行上例中的代码,会发现程序一直输出"-----endless-----",表明此程序是一个是损坏。
在使用for循环时,初始化条件不需要一定要定义在for循环括号中,也可以定义在循环外。如下例所示:
package cn.bytecollege;/** * 本示例将演示for循环 * @author MR.W * */public class ForDemo { public static void main(String[] args) { //定义变量保存结果 int sum= 0; int i = 1; for (;i<=100;i++) { sum += i; } System.out.println(sum); }}
3.3.3 while循环
while循环的语法格式如下:
[init_statement]while(test_expression){ statement; [iteration_statment]}
while循环每次执行循环体之前,先对test_expression循环条件求值,如果循环条件为true,则执行循环体部分。从上述伪代码中来看,iteration_statment位于循环体的最后,因此只有当循环体成功执行完成是,while循环才会执行iteration_statment语句。
下面,我们通过示例学习while循环:
package cn.bytecollege;/** * 本例将演示while循环 * @author MR.W * */public class WhileDemo { public static void main(String[] args) { int i = 1; int sum = 0; while(i<=100) { sum+=i; i++; } System.out.println(sum); }}
如果while循环的循环体部分和迭代语句合并在一起,且只有一行代码,则可以省略。但是通常不建议省略。
使用while循环是,一定要保证循环条件有变成false的时候,否则这个循环将变成死循环,用于无法结束循环。如下例所示:
package cn.bytecollege;/** * 本例将演示while死循环 * @author MR.W * */public class EndlessWhileLoop { public static void main(String[] args) { int count = 0; while(count < 5) { System.out.println("进入死循环"); count --; } System.out.println("无法跳出循环体"); }}
上面的示例中,count的值越来越小,小于5用于成立,循环条件一直为true,从而导致这个循环永远无法结束。
3.3.4 do while循环
do while循环与while循环的区别在于:while循环是先判断循环条件,如果条件为真则执行循环体;而do while循环则新执行循环体,然后才判断循环条件,如果循环条件为真,则执行下一次循环,否则终止循环。do while循环语法格式如下:
[init_statement]do{ statement; [iteration_statment]}while(test_expression);
注意:do while循环的循环条件必须有一个分号,该分号代表该循环结构结束。
下面我们通过示例示范do while循环的用法
package cn.bytecollege;/** * 本示例将演示dowhile循环用法 * @author MR.W * */public class DoWhileDemo { public static void main(String[] args) { int sum = 0; int i = 1; do { sum += i; i++; }while(i<=100); System.out.println(sum); }}
需要注意的是,即便此时循环条件的开始值为false,dowhile循环体也会执行1次,因此do while的循环体至少执行1次,而while循环的循环条件执行1次。下面我们通过示例来演示:
package cn.bytecollege;/** * 本例将演示while和dowhile的区别 * @author MR.W * */public class WhileAndDoWhile { public static void main(String[] args) { int i = 0; int k = 0; while (++i>1) { System.out.println("循环条件至少执行1次"); } System.out.println(i); do { k++; }while(false); System.out.println(k); }}
从上例中可以看出,++i=1执行后i的值为1,此时该表达式结果为false,不执行循环体,而do while循环中循环条件直接是false,但是此时仍旧执行了循环体,k的值成为了1。
4.控制循环结构
在C语言中提供了goto语句控制程序的跳转,但是Java中goto只作为了保留字,而没有提供该语句,这种做法提高了程序流程控制的可读性,但是降低了程序流程控制的灵活性。为了弥补此处的不足,Java提供了continue和break控制循环结构。
4.1 使用break结束循环
在某些特定的条件下我们需要强制终止循环,而不是等到循环条件为false时才退出循环。此时可以使用break来完成这个功能。break用于完全结束一个循环,跳出循环体,不管是哪种循环,一旦循环体中遇到break,系统将完全结束该循环,开始执行循环结构以后的代码。例如下列示例:
package cn.bytecollege;/** * 本例将演示break * @author MR.W * */public class BreakDemo { public static void main(String[] args) { for (int i = 0; i < 10; i++) { if(i==7) { System.out.println(i); break; } } }}
上例代码将循环打印0-6,当变量i的值变为7的时候,满足if条件判断,此时进入if分支结构,进入以后遇到break,此时跳出循环。
因此,break可以视为中断循环,及时循环还没有结束,也再不循环,直接结束,当然break也可以跳出多层循环,跳出多层循环我们将在下一节进行讲解。
4.2 使用continue跳过此次循环
continue的功能和break有点类似,区别是continue只是跳出此次循环,继续执行剩下的循环,并不是完全终止循环。下面我们通过示例学习:
package cn.bytecollege;/** * 本例将演示continue * @author MR.W * */public class ContinueDemo { public static void main(String[] args) { for (int i = 0; i < 10; i++) { if(i==7) { continue; } System.out.println(i); } }}
我们将break换成continue,运行程序我们发现程序打印了0-6,8-9,这说明程序并没有完全退出,只是i==7时忽略了此次循环,继续执行剩下的循环。
5.循环嵌套
如果把一个循环放进另一个循环体内,那么就可以形成循环嵌套,循环嵌套可以是上述3种循环的任意嵌套,例如:for循环和while循环的相互嵌套,while循环和do while循环的相互嵌套,也可以是while循环和do while循环的相互嵌套。
当程序遇到循环嵌套是,如果外层循环的循环条件允许,则开始执行外层循环的循环体,而内层循环将被外层循环的循环体来执行,只是内层循环需要反复执行自己的循环体而已。当内层循环执行结束,且完成循环的循环体执行结束时,在再次计算外层循环的循环条件,决定是否再次执行外层循环的循环体。下面我们通过示例来学习。
示例:打印如下图形:
**
通过分析我们可以发现我们需要两个循环其中外层控制打印的行数,而内层循环则用来控制打印*的个数。
package cn.bytecollege;/** * 本例将演示打印* * @author MR.W * */public class PrintDemo { public static void main(String[] args) { for (int i = 1; i < 4; i++) { for (int j = 0; j < i; j++) { System.out.print("*"); } System.out.println(); } }}
在上例中,当外层i =1时,满足外层循环的循环条件,因此进入循环体,而内层循环则是外层循环的循环体,此时j = 0,并且满足循环条件,执行循环体,打印1一个*,改变循环变量j的值后j<i返回值为false,此时内层循环结束,并换行,此时改变外层循环的循环变量i,继续执行重复以上步骤。
5.1 循环嵌套中的break
break语句仅结束其所在的循环,例如在双层循环嵌套中,只结束内层循环,而不能结束外层循环。下面我们通过示例来学习。
package cn.bytecollege;/** * 本例将演示break跳出自身所在循环 * @author MR.W * */public class BreakDemo2 { public static void main(String[] args) { for (int i = 0; i < 10; i++) { for (int j = 0; j < 5; j++) { if(j==2) { break; } System.out.println("内存循环:"+j); } System.out.println("外存循环:"+i); } }}
运行上述代码,我们可以发现break只是结束了内层自己所在的循环,外层循环并没有被结束,仍旧在继续循环。
那么, 如何让break结束外层循环呢,此时就要在break后面紧跟一个标签了,这个标签用于表示一个外层循环。
Java中的标签就是一个紧跟着因为冒号的标识符,与其他语言不同的是,Java中的标签只有放在循环语句前才有作用。
package cn.bytecollege;/** * 本例将演示循环嵌套中break结束外层循环 * @author MR.W * */public class BreakDemo3 { public static void main(String[] args) { outer: for (int i = 0; i < 5; i++) { //内层循环 for (int j = 0; j < 3; j++) { if(j==1) { //跳出outer所标识的循环 break outer; } System.out.println("i的值为:"+i+",j的值为:"+j); } } }}
运行上面的代码,看到如下运行结果:
程序从外层循环进入内层循环,当j等于1时,程序遇到一个break outer语言,该语句将会导致结束outer标签指定的循环,不是break所在的循环,而是结束break循环的外层循环。
需要注意的是,break后的标签必须是一个有效的标签,这个标签必须在break语句所在的循环之前定义,或者在所在循环的外层循环之前定义。当然,如果把这个标签放在break语句所在的循环之前定义,也就失去了标签的意义,因为break默认就是结束其所在的循环。
5.2 循环嵌套中使用continue
同样,我们可以将上例中break换成continue,此处continue的作用不变,只是在多层循环中,continue会忽略当前循环,直接到外层循环继续进行循环
package cn.bytecollege;/** * 本例将演示多层循环嵌套中使用Continue * @author MR.W * */public class ContinueDemo2 { public static void main(String[] args) { outer: for (int i = 0; i < 5; i++) { //内层循环 for (int j = 0; j < 3; j++) { if(j==1) { //跳出outer所标识的循环 continue outer; } System.out.println("i的值为:"+i+",j的值为:"+j); } } }}
根据运行结果可以看到内层循环当执行到j==1时,此时继续执行外层循环。需要注意的是,除非特殊情况,一般情况下我们并不使用这种方式来控制循环。