流程控制對任何一門編程語言都是至關重要的,它提供了控制程序步驟的基本手段。
一、復合語句
? Java語言的復合語句是以整個塊區為單位的語句,又稱塊語句。復合語句由“{”開始,“}”結束。{}
? 對于復合語句,我們只需要知道,復合語句為局部變量創建了一個作用域,該作用域為程序的一部分,在該作用域中某個變量被創建并能夠被使用,如果在某個變量的作用域外使用該變量,則會發生錯誤。并且復合語句中可以嵌套復合語句。
二、條件語句
? 條件語句可根據不同的條件執行不同的語句。包括if條件語句與switch多分支語句。
分類:
if條件語句
? 使用if條件語句,可選擇是否要執行緊跟在條件之后的那個語句。關鍵字if之后是作為條件的“布爾表達式”,如果該表達式返回true,則執行其后的語句;若為false,則不執行if后的語句。可分為簡單的if條件語句、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!");
}