一:方法的控制流
控制流
什么是控制流?
控制流是按照一定的顺序排列程序元素来决定程序执行的顺序,换一种话说,控制流说一种机制,它决定了程序下一步应该怎么执行,比如说洗衣程序:浸泡 -> 洗涤 -> 漂洗 -> 脱水;再比如说一个人从早晨起来到晚上睡觉这一天会经历工作,吃饭,运动等事情,将这些事情安排成一定的顺序执行,这些都是控制流。
Java方法调用栈和栈帧
什么是方法栈?
我们首先要明确什么是栈(Stack)这种数据结构?
栈是一种 LIFO(Last In First Out) 的数据结构,拥有后进先出的特性;向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈按照后进先出的原则存储数据,后进入的数据被压入栈顶,最先进入的元素在栈底,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
那什么是方法栈(Call Stack)呢?
我们要知道程序的本质就是方法的不断调用,即控制流。在调用任何子程序(方法)时,主程序(方法)都必须暂存子程序运行完毕后应该返回到的地址。因此,如果被调用的子程序还要调用其他的子程序,其自身的返回值就必须存入到某个数据结构上,这个数据结构就是方法调用栈。
我们举个例子:
我们在方法 C 的执行语句中打一个断点,调成 debug 模式运行。
我们看到调试器显示了如上图所示的调用关系,由底部向上看,方法依次成调用关系。这就是方法调用栈。
当 C 方法结束以后,C 方法的栈帧会从方法调用栈中弹出,然后到 B 方法,执行完 B 方法后,B 方法的栈帧会从方法栈中弹出。接着就是 A 方法,最后是 main 方法。当 main 方法返回后,该线程的方法栈已经为空,说明程序执行结束了。
上面提到的栈帧是什么东西呢?
栈帧(Stack Frame):是编译器用来实现函数调用的一种数据结构,它是方法调用栈的一个子集。
- 栈帧是数用于支持 JVM 进行方法调用和方法执行的数据结构
- 栈帧随着方法调用而创建,随着方法结束而销毁
- 栈帧里面存储了方法的局部变量,操作数栈,动态连接,方法返回地址等信息
拿上面的图示进行说明:
方法栈上存储的这些小块信息就是栈帧,栈帧内有很多信息包括局部变量表,操作数栈等等,这些内容就不在基础部分进行展开了。
二:while 与 do-while 循环
while 循环
语法:
while(返回 boolean 的语句){
...
}
while 语句非常简单,在这里就不过多赘述了。
do while 循环
语法:
do {
...
}while(返回 boolean 的语句)
do while 语句和 while 语句有什么不同呢?while 语句会首先判断真假,再去执行代码块里的内容;do while 则是无论真假,都会先执行一次代码块中的内容,见程序示例:
while 语句
public class Main {
public static void main(String[] args) {
int i = 0;
while (i < 0) {
System.out.println("hello");
}
}
}
该程序什么都不会输出。
do while 语句
public class Main {
public static void main(String[] args) {
int i = 0;
do {
System.out.println("hello");
} while (i < 0);
}
}
该程序输出的结果为:
hello
三:for 与 foreach 循环
for 循环
for 循环语法:
for(初始化表达式;循环变量判定表达式;循环变量修正表达式) {
执行代码...
}
初始化表达式与循环变量修正表达式可以不写,当忽略了这两者时,for 循环就变成了一个 while 循环
如示例程序:
public class Main {
public static void main(String[] args) {
int i = 0;
for(;i < 3;) {
System.out.println(i);
i++;
}
}
}
关于 for 循环的一道笔试题
该程序输出的结果为?
public class Test {
static boolean foo(char c) {
System.out.print(c);
return true;
}
public static void main(String[] args) {
int i = 0;
for(foo('A');foo('B') && (i < 2);foo('C')) {
i++;
foo('D');
}
}
}
答案为:ABDCBDCB
为什么会出现这样的结果呢?
这道题考查的就是 for 循环中代码块的执行顺序问题
for(初始化表达式;循环变量判定表达式;循环变量修正表达式) {
执行代码...
}
首先,在 for 循环中,会仅执行一次 初始化表达式
然后,执行顺序如下图所示:
所以,本题的结果为:ABDCBDCB
foreach 循环
foreach 语法:
for(E element : Iterable<E>){
...
}
foreach 的集合要求是可遍历的(Iterable),也就是说,任何实现了 Iterable 接口的对象都可以使用 foreach 遍历
JDK1.5 增加了 foreach 循环,在遍历数组,集合的时候 foreach 有着不错的性能。
foreach 是 for 语句的简化,但是 foreach 并不能替代 for 循环。可以这么说,任何 foreach都 能改写为 for 循环,但是反之则行不通。
foreach 不是 Java 中的关键字,foreach 的循环对象一般是一个集合,例如:List,Vector,数组等等。
程序示例:使用 foreach 遍历 List
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
for (String s : list) {
System.out.println("s = " + s);
}
}
}
四:break 与 continue
break 与 continue 是可以改变循环流程的语句
- break ;立即结束包裹当前 break 的第一层循环
- continue;跳过包裹当前 continue 的第一层循环中的其余语句,继续下一次循环
- break label
break 与 continue 语句比较简单,这里就不再进行赘述了。
五:switch case语句
switch case 语句语法格式如下:
switch(expression) {
case value1:
...;
break;
case value2:
...;
break;
case value3:
...;
break;
default:
...;
}
程序示例:
import java.util.Random;
public class Main {
public static void main(String[] args) {
int i = new Random().nextInt(5);
switch (i) {
case 0:{
System.out.println(0);
break;
}
case 1:{
System.out.println(1);
break;
}
case 2:{
System.out.println(2);
break;
}
case 3:{
System.out.println(3);
break;
}
default:{
System.out.println("大于 3");
}
}
}
}
关于 switch
- 可以 switch 哪些东西?
- int/byte/shortlong/char
- enum 枚举
- String (JDK7 +)
- swtich 的作用域
建议每一个 case 后面都加上花括号,避免作用域冲突 - switch 的穿透
- switch 语句具有穿透特性,解决方法是:添加 break 语句阻止穿透
- swtich 穿透特性有利有弊