一:方法的控制流

控制流

什么是控制流?

控制流是按照一定的顺序排列程序元素来决定程序执行的顺序,换一种话说,控制流说一种机制,它决定了程序下一步应该怎么执行,比如说洗衣程序:浸泡 -> 洗涤 -> 漂洗 -> 脱水;再比如说一个人从早晨起来到晚上睡觉这一天会经历工作,吃饭,运动等事情,将这些事情安排成一定的顺序执行,这些都是控制流。

Java方法调用栈和栈帧

什么是方法栈?

我们首先要明确什么是栈(Stack)这种数据结构?

栈是一种 LIFO(Last In First Out) 的数据结构,拥有后进先出的特性;向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈按照后进先出的原则存储数据,后进入的数据被压入栈顶,最先进入的元素在栈底,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。

那什么是方法栈(Call Stack)呢?

我们要知道程序的本质就是方法的不断调用,即控制流。在调用任何子程序(方法)时,主程序(方法)都必须暂存子程序运行完毕后应该返回到的地址。因此,如果被调用的子程序还要调用其他的子程序,其自身的返回值就必须存入到某个数据结构上,这个数据结构就是方法调用栈。

我们举个例子:

5、Java的控制流 - 图1

我们在方法 C 的执行语句中打一个断点,调成 debug 模式运行。

5、Java的控制流 - 图2

我们看到调试器显示了如上图所示的调用关系,由底部向上看,方法依次成调用关系。这就是方法调用栈。

C 方法结束以后,C 方法的栈帧会从方法调用栈中弹出,然后到 B 方法,执行完 B 方法后,B 方法的栈帧会从方法栈中弹出。接着就是 A 方法,最后是 main 方法。当 main 方法返回后,该线程的方法栈已经为空,说明程序执行结束了。

上面提到的栈帧是什么东西呢?

栈帧(Stack Frame):是编译器用来实现函数调用的一种数据结构,它是方法调用栈的一个子集。

  • 栈帧是数用于支持 JVM 进行方法调用和方法执行的数据结构
  • 栈帧随着方法调用而创建,随着方法结束而销毁
  • 栈帧里面存储了方法的局部变量,操作数栈,动态连接,方法返回地址等信息

拿上面的图示进行说明:

5、Java的控制流 - 图3

方法栈上存储的这些小块信息就是栈帧,栈帧内有很多信息包括局部变量表,操作数栈等等,这些内容就不在基础部分进行展开了。

二:while 与 do-while 循环

while 循环

语法:

  1. while(返回 boolean 的语句){
  2. ...
  3. }

while 语句非常简单,在这里就不过多赘述了。

do while 循环

语法:

  1. do {
  2. ...
  3. }while(返回 boolean 的语句)

do while 语句和 while 语句有什么不同呢?while 语句会首先判断真假,再去执行代码块里的内容;do while 则是无论真假,都会先执行一次代码块中的内容,见程序示例:

while 语句

  1. public class Main {
  2. public static void main(String[] args) {
  3. int i = 0;
  4. while (i < 0) {
  5. System.out.println("hello");
  6. }
  7. }
  8. }

该程序什么都不会输出。

do while 语句

  1. public class Main {
  2. public static void main(String[] args) {
  3. int i = 0;
  4. do {
  5. System.out.println("hello");
  6. } while (i < 0);
  7. }
  8. }

该程序输出的结果为:

  1. 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 循环中,会仅执行一次 初始化表达式

然后,执行顺序如下图所示:

5、Java的控制流 - 图4

所以,本题的结果为:ABDCBDCB

foreach 循环

foreach 语法:

for(E element : Iterable<E>){
  ...
}

foreach 的集合要求是可遍历的(Iterable),也就是说,任何实现了 Iterable 接口的对象都可以使用 foreach 遍历

JDK1.5 增加了 foreach 循环,在遍历数组,集合的时候 foreach 有着不错的性能。

foreachfor 语句的简化,但是 foreach 并不能替代 for 循环。可以这么说,任何 foreach都 能改写为 for 循环,但是反之则行不通。

foreach 不是 Java 中的关键字,foreach 的循环对象一般是一个集合,例如:ListVector,数组等等。

程序示例:使用 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

breakcontinue 是可以改变循环流程的语句

  • break ;立即结束包裹当前 break 的第一层循环
  • continue;跳过包裹当前 continue 的第一层循环中的其余语句,继续下一次循环
  • break label

breakcontinue 语句比较简单,这里就不再进行赘述了。

五: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 枚举
    • StringJDK7 +
  • swtich 的作用域
    建议每一个 case 后面都加上花括号,避免作用域冲突
  • switch 的穿透
    • switch 语句具有穿透特性,解决方法是:添加 break 语句阻止穿透
    • swtich 穿透特性有利有弊