流程控制对任何一门编程语言都是至关重要的,它提供了控制程序步骤的基本手段。
一、复合语句
? Java语言的复合语句是以整个块区为单位的语句,又称块语句。复合语句由“{”开始,“}”结束。{}
? 对于复合语句,我们只需要知道,复合语句为局部变量创建了一个作用域,该作用域为程序的一部分,在该作用域中某个变量被创建并能够被使用,如果在某个变量的作用域外使用该变量,则会发生错误。并且复合语句中可以嵌套复合语句。
二、条件语句
? 条件语句可根据不同的条件执行不同的语句。包括if条件语句与switch多分支语句。
分类:
if条件语句
? 使用if条件语句,可选择是否要执行紧跟在条件之后的那个语句。关键字if之后是作为条件的“布尔表达式”,如果该表达式返回true,则执行其后的语句;若为false,则不执行if后的语句??煞治虻サ膇f条件语句、if···else语句和if···else if多分支语句。
int a = 100;
if(a == 100) {
System.out.println(a);
}
? 如上方代码,{}之间为复合语句,if为条件语句,翻译过来就是如果a等于100,则输出a的值,否则不执行。
? 如果if后只有一条语句,比如上述代码只有一条输出,可以不加{},但为了代码的可读性,以及防止代码过多出现不必要的错误,建议所有的if、else后都加上相应的{}。
if..else语句
? if..else语句是条件语句中最常用的一种形式,它会针对某种条件有选择的作出处理。通常表现为“如果满足某种条件,就进行某种处理,否则就进行另一种处理”。
? if后的()内的表达式必须是boolean型的。如果为true,则执行if后的复合语句;如果为false,则执行else后的复合语句。如
public class Getifelse {
public static void main(String[] args) {
int math = 80; // 声明,数学成绩为80(及格)
int english = 50; // 声明,英语成绩为50(不及格)
if(math >= 60) { // if判断语句判断math是否大于等于60
System.out.println("math has passed");
} else { // if条件不成立
System.out.println("math has not passed");
}
if(english >= 60) { // if判断语句判断english是否大于等于60
System.out.println("english has passed");
} else { // if条件不成立
System.out.println("english has not passed");
}
}
}
if..else if多分支语句
? if..else if多分支语句用于针对某一事件的多种情况进行处理。通常表现为“如果满足某种条件”,就进行某种处理,否则,如果满足另一种条件,则进行另一种处理。如:
public class GetTerm {
public static void main(String[] args) {
int x = 40;
if(x > 60) {
System.out.println("x的值大于60");
} else if (x > 30) {
System.out.println("x的值大于30但小于60");
} else if (x > 0) {
System.out.println("x的值大于0但小于30");
} else {
System.out.println("x的值小于等于0");
}
}
}
? 在本例中,由于x为40,条件x>60为false,程序向下执行判断;条件x>30为真,所以执行条件x>30后的程序块中的语句。输出结果如下:
x的值大于30但小于60
? 所以,if语句只执行条件为真的语句,其它语句都不会执行。
switch多分支语句
? switch语句是一种比较简单明了的多选一的选择,在Java语言中,可以用switch语句将动作组织起来进行多选一。
一般格式:
switch (表达式)
{
case 常量标号1:语句序列1;
break;
case 常量标号2:语句序列2;
break;
…
case 常量标号n:语句序列n;
break;
default: 语句S;
}
其中:
- 表达式:可以控制程序的执行过程,表达式的结果必须是整数、字符或枚举量值。
- case后面的常量标号,其类型应与表达式的数据类型相同。表示根据表达式计算的结果,可能在case的标号中找到,标号不允许重复,具有唯一性,所以,只能选中一个case标号。尽管标号的顺序可以任意的,但从可读性角度而言,标号应按顺序排列。
- 语句序列是switch语句的执行部分。针对不同的case标号,语句序列的执行内容是不同的,每个语句序列允许有一条语句或多条语句组成,但是case中的多条语句不需要按照复合语句的方式处理(用{}将语句括起来),若某一语句序列i为空,则对应的break语句可以省略(去掉)。
- break是中断跳转语句,表示在完成相应的case标号规定的操作之后,不继续执行switch语句的剩余部分而直接跳出switch语句之外,继而执行switch结构后面的第一条语句,如果不在switch结构的case中使用break语句。程序就会接着执行下面的语句。
- default用于处理所有switch结构的非法操作。当表达式的值与任何一个case都不匹配时,则执行default语句。
- 简单说一下switch语句是怎么执行的:首先switch语句先计算表达式的值,如果表达式的值与case后的常量值相同,则执行该case后的若干个语句,直到遇到break语句为止。如果没有break,则继续执行下一case中的若干语句,直到遇到break为止。若没有一个常量的值与表达式的值相同,则执行default后面的语句。default语句可选,如果不存在default语句,而且switch语句中的表达式的值与任何case的常量值都不相同,则switch不做任何处理。并且,同一个switch语句,case的常量值必须互不相同。
备注:
- java 1.6(包括)以前,只是支持等价成int 基本类型的数据:byte ,short,char,int(其他的都不可以)。
- 1.7加入的新特性可以支持String类型的数据。
- long是不可以的。。就算是通过强制的转化也必须是转成int。
- 使用Java 12,switch不仅可以作为语句也可以作为表达式。
三、循环语句
? 循环语句就是在满足一定条件的情况下反复执行某一个操作。包括while循环语句、do..while循环语句和for循环语句。
while循环语句
? while循环语句的循环方式为利用一个条件来控制是否要继续反复执行这个语句。
假设现在有1~10十个数字,我们要将它们相加求和,在学习while之前可能会直接用+运算符从1加到10,也就是1+2+3+4+5+6+7+8+9+10,但如果现在需要从1加到1万呢?10万?所以,我们要引入while循环来进行循环相加,如下:
public class GetSum {
public static void main(String[] args) {
int x = 1; // 定义初值
int sum = 0; // 定义求和变量,用于存储相加后的结果
while(x <= 10) {
sum += x; // 循环相加,也即 sum = sum + x;
x++;
}
System.out.println(sum);
}
}
? 这就是一个从1加到10的代码,首先定义一个初值x为1,然后定义一个存储相加结果的变量sum为0,循环条件为x<=10,也就是每次判断x<=10是否成立,成立则继续循环。循环内第一句“sum +=x;”其实就是“sum = sum +x;”的另一种写法,是在sum的基础上加x,并赋给sum,那么此时sum的值为0+1=1了,然后x++,x自增1为2,判断x<=10,则继续循环,sum的值变为1+2=3,然后x++变为3,如此循环下去,直到x为11时退出循环,此时sum的值就是1+2+3+4+5+6+7+8+9+10最后的结果55了。
在while循环语句中,如果while语句后直接加分号,如while(a == 5);代表当前while为空语句,进入无线循环。
do..while循环语句
? do..while循环语句与while循环语句的区别是,while循环语句先判断条件是否成立再执行循环体,而do..while循环语句则先执行一次循环后,再判断条件是否成立。也即do..while至少执行一次。语法格式如下:
do
{
执行语句
} while (条件表达式);
? 下面对while循环语句与do..while循环语句进行一个对比:
public class Cycle {
public static void main(String[] args) {
int a = 10;
int b = 10;
// while循环语句
while(a == 8) {
System.out.println("a == " + a);
a--;
}
// do···while循环语句
do {
System.out.println("b == " + b);
b--;
} while(b == 8);
}
}
运行结果为:b==10
这里,a、b都为10,先看while循环语句,进入while下语句块的条件是a == 8,很明显不成立,所以不执行,结果中没有关于a的结果,然后再看do..while循环语句,先执行一次do后的语句块,输出“b == 10”,然后判断while条件b == 8不成立,循环结束,所以结果只有一个do..while语句中执行的第一步b == 10。
for循环语句
for循环语句是Java程序设计中最有用的循环语句之一。一个for循环可以用来重复执行某条语句,知道某个条件得到满足。语法格式如下:
for(表达式1; 表达式2; 表达式3)
{
语句序列
}
? 其中,表达式1为初始化表达式,负责完成变量的初始化;表达式2为循环条件表达式,指定循环条件;表达式3为循环后操作表达式,负责修整变量,改变循环条件。三个表达式间用分号隔开
例:用for循环语句求100以内所有偶数的和。
public class Circulate {
public static void main(String[] args) {
int sum = 0;
for(int i=2; i<=100; i+=2) {
sum += i;
}
System.out.println(sum);
}
}
? for循环内,首先定义一个变量并赋初值,表示循环中i从2开始进行,然后条件为i<=100,即i<=100时进行循环并执行语句块中的语句,第三个表达式“i+=2”表示每次循环执行i=i+1,即没循环一次,i的值为在原来的基础上加2后的值。然后循环求sum的值,即2+4+6+8+···+100,当i=102时退出循环,执行输出语句,输出结果为2550。
增强型for循环
? 说到for循环语句就不得提到foreach语句了,它是Java5后新增的for语句的特殊简化版本,并不能完全替代for语句,但所有foreach语句都可以改写为for语句。foreach语句在遍历数组等时为程序员提供了很大的方便。语法格式如下:
for(元素变量x : 遍历对象obj) {
引用了x的Java语句;
}另:增强型for循环语法:
for(ElementType element:arrayName){
}
注意事项:
public static void testFor() {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
for(Object obj : list){
System.out.println(obj);
list.remove(obj);
}
}
执行上面的方法会报错.
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.test.controller.TestMain.testFor(TestMain.java:108)
at com.test.controller.TestMain.main(TestMain.java:23)
原因分析:
? 迭代器内部的每次遍历都会记录List内部的modcount当做预期值,然后在每次循环中用预期值与List的成员变量modCount作比较,但是普通list.remove调用的是List的remove,这时modcount++,但是iterator内记录的预期值=并没有变化,所以会报错。
==总结:==
- ==使用增强型for循环时,不支持遍历时删除元素==
- ==使用增强型for循环时,对遍历的集合需要做null判断,不然可能引发空指针异常。==
四、跳转语句
1、break
break :跳出当前循环;但是如果是嵌套循环,则只能跳出当前的这一层循环,只有逐层break才能跳出所有循环;
for (int i = 0; i < 10; i++) {
if (i == 6) {
break;
// 在执行i==6时强制终止循环,i==6不会被执行
}
System.out.println(i);
}
//输出结果为0 1 2 3 4 5 ;6以后的都不会输出
2、continue
continue:终止当前循环,但是不跳出循环(在循环中continue后面的语句是不会执行了),继续往下根据循环条件执行循环。
for (int i = 0; i < 10; i++) {
if (i == 6) {
continue;
// i==6不会被执行,而是被中断了
}
System.out.println(i);
}
//输出结果为0 1 2 3 4 5 7 8 9;只有6没有输出
3、return
- return 从当前的方法中退出,返回到该调用的方法的语句处,继续执行。
- return 返回一个值给调用该方法的语句,返回值的数据类型必须与方法的声明中的返回值的类型一致。
- return后面也可以不带参数,不带参数就是返回空,其实主要目的就是用于想中断函数执行,返回调用函数处。
特别注意:返回值为void的方法,从某个判断中跳出,必须用return;
五、流程控制面试题示例
问题
Q1.描述if-then和if-then-else语句。什么类型的表达式可以用作条件?
两个语句都告诉我们的程序只有在特定条件的计算结果为true时才执行它们内部的代码。但是,如果 if子句的计算结果为false,则if-then-else语句会提供辅助执行路径:
if (age >= 21) {
// ...
} else {
// ...
}
与其他编程语言不同,Java仅支持布尔表达式作为条件。如果我们尝试使用不同类型的表达式,我们将得到编译错误。
Q2.描述switch语句。switch子句中可以使用哪些对象类型?
Switch允许根据变量值选择多个执行路径。
每个路径都标有case或default,switch语句计算匹配的每个case表达式,并执行匹配标签后面的所有语句,直到找到break语句。如果找不到匹配项,则会执行默认块:
switch (yearsOfJavaExperience) {
case 0:
System.out.println("Student");
break;
case 1:
System.out.println("Junior");
break;
case 2:
System.out.println("Middle");
break;
default:
System.out.println("Senior");
}
我们可以使用byte,short,char,int,它们的包装版本:枚举和String作为Switch值。
Q3.当我们忘记在Switch的case子句中放入break语句时会发生什么?
该Switch语句将一直向下执行。这意味着它将继续执行所有case标签,直到找到break语句,即使这些标签与表达式的值不匹配。
这是一个证明这一点的例子:
int operation = 2;
int number = 10;
switch (operation) {
case 1:
number = number + 10;
break;
case 2:
number = number - 4;
case 3:
number = number / 3;
case 4:
number = number * 10;
break;
}
运行代码后,数字保存值20而不是6,这在我们想要将同一操作与多个案例相关联的情况下非常有用。
Q4.什么时候最好使用if-then-else和Switch语句?
一个Switch测试多种单值的单个变量或当几个值将执行相同的代码时,声明更适合:
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
case 2:
days = 28;
break;
default:
days = 30;
}
一个IF-THEN-ELSE语句是最好的时候,我们需要检查值或多个条件的范围:
if (aPassword == null || aPassword.isEmpty()) {
// empty password
} else if (aPassword.length() < 8 || aPassword.equals("12345678")) {
// weak password
} else {
// good password
}
Q5.Java支持哪些类型的循环?
Java提供了三种不同类型的循环:for,while和do-while。
for循环用于提供了一种方法来迭代一个范围的值。当我们事先知道任务将重复多少次时,它是最有用的:
for (int i = 0; i < 10; i++) {
// ...
}
而while循环在特定条件为真时可以执行的语句块:
while (iterator.hasNext()) {
// ...
}
DO-While同时是一个的变化而声明,其中所述的布尔表达式是在循环的底部。这可以保证代码至少执行一次:
do {
// ...
} while (choice != -1);
Q6.什么是增强的for循环?
for语句的另一种语法旨在迭代集合,数组,枚举或实现Iterable接口的任何对象的所有元素:
for (String aString : arrayOfStrings) {
// ...
}
Q7.你怎么能从循环中退出?
使用break语句,我们可以立即终止循环的执行:
for (int i = 0; ; i++) {
if (i > 10) {
break;
}
}
Q8.未标记和已标记的中断语句之间有什么区别?
未标记的break语句终止最内层的switch,for,while或do-while语句,而带标签的break则结束外部语句的执行。
让我们创建一个示例来演示:
int[][] table = { { 1, 2, 3 }, { 25, 37, 49 }, { 55, 68, 93 } };
boolean found = false;
int loopCycles = 0;
outer: for (int[] rows : table) {
for (int row : rows) {
loopCycles++;
if (row == 37) {
found = true;
break outer;
}
}
}
当找到数字37时,标记的break语句终止最外面的for循环,并且不再执行循环。因此,loopCycles以值5结束。
然而,未标记的break仅结束最里面的语句,控制流返回到最外对于该继续循环到下一行中的表的变量,使得loopCycles为8的值结束。
Q9.未标记和标记的continue语句之间有什么区别?
未标记的continue语句跳转到最里面for,while或do-while循环的当前迭代的末尾,而标记的continue跳转到标记有给定标签的外循环。
这是一个演示这个的例子:
int[][] table = { { 1, 15, 3 }, { 25, 15, 49 }, { 15, 68, 93 } };
int loopCycles = 0;
outer: for (int[] rows : table) {
for (int row : rows) {
loopCycles++;
if (row == 15) {
continue outer;
}
}
}
推理与前一个问题相同。标记的continue语句终止最外面的for循环。
因此,loopCycles结束保持值5,而未标记的版本仅终止最内层语句,使loopCycles以值9结束。
Q10.描述try-catch-finally构造中的执行流程。
结论:****try->catch->finally按顺序执行,不管是否有异常,不管try中有什么操作,就算是return,也得往后稍稍,最后这个方法一定是要执行finally。
如果try中抛出异常,而异常是留给上层方法处理,那么在抛出后,仍然运行finally,然后再回溯到上层。
自然,如果try中有return——也算是回溯了,返回值会存在栈中等待,等finally运行之后再回溯。
而如果finally中有return,那么直接从finally中结束方法。
如果在方法中直接结束程序,即调用System.exit()方法,那么就直接结束了,此时finally是不执行的。
情形分析:
情况1: try{} catch(){}finally{} return;
显然程序按顺序执行。
情况2: try{ return; }catch(){} finally{} return;
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,最后执行try中return;
finally块之后的语句return,因为程序在try中已经return所以不再执行。
情况3: try{ } catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,
有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,
最后执行catch块中return. finally之后也就是4处的代码不再执行。
无异常:执行完try再finally再return.
情况4: try{ return; }catch(){} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况5: try{} catch(){return;}finally{return;}
程序执行catch块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况6: try{ return;}catch(){return;} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
则再执行finally块,因为finally块中有return所以提前退出。
无异常:则再执行finally块,因为finally块中有return所以提前退出。
最终结论:任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。
Q11.在哪些情况下,finally块可能无法执行?
当执行try或catch块时JVM终止时,例如,通过调用System.exit(),或者当执行线程被中断或终止时,则不执行finally块。
Q12.执行以下代码的结果是什么?
public static int assignment() {
int number = 1;
try {
number = 3;
if (true) {
throw new Exception("Test Exception");
}
number = 2;
} catch (Exception ex) {
return number;
} finally {
number = 4;
}
return number;
}
System.out.println(assignment());
代码输出数字3.即使始终执行finally块,只有在try块退出后才会发生这种情况。
在该示例中,return语句在try-catch块结束之前执行。因此,分配给数在finally块是没有影响,因为变量已经返回到的调用代码testAssignment方法。
Q13.在哪些情况下,即使不能抛出异常,也可以使用try-finally块?
当我们想要确保我们不会通过遇到break,continue或return语句而意外地绕过清理代码中使用的资源时,此块很有用:
HeavyProcess heavyProcess = new HeavyProcess();
try {
// ...
return heavyProcess.heavyTask();
} finally {
heavyProcess.doCleanUp();
}
此外,我们可能会面临无法在本地处理抛出异常的情况,或者我们希望当前方法在允许我们释放资源的同时仍然抛出异常:
public void doDangerousTask(Task task) throws ComplicatedException {
try {
// ...
task.gatherResources();
if (task.isComplicated()) {
throw new ComplicatedException("Too difficult");
}
// ...
} finally {
task.freeResources();
}
}
Q14.try-with-resources如何运作?
在try-with-resources语句声明,并在执行前初始化一个或多个资源的try块,并在发言结束时自动关闭它们无论块是否正常或突然完成。任何实现AutoCloseable或Closeable接口的对象都可以用作资源:
try (StringWriter writer = new StringWriter()) {
writer.write("Hello world!");
}