[JavaSE] 03 - 程序控制结构与方法

3.1 - 顺序结构与语句块

3.1.1 - 顺序结构

顺序结构(Sequential Structure)是程序最基本的执行方式:按照代码从上到下的顺序依次执行语句。

int a = 10;
int b = 20;
int sum = a + b;
System.out.println(sum); // 30

3.1.2 - 语句和语句块

在 Java 中:

  • 语句(Statement):执行某种操作的基本代码单位,例如变量声明、赋值、方法调用等。
  • 语句块(Block):使用 {} 包裹的一组语句,可以在语法上作为一个整体,并形成局部作用域。
int a = 10;
 
{
    // int a = 20; 编译错误,与 C 语言不同,Java 不允许在重叠作用域内重名
    int b = 20;
    System.out.println(a + b); // 30
}
 
// System.out.println(b); // 编译错误


3.2 - 分支结构

分支结构用于根据条件决定程序的执行路径,Java 主要提供 if 和 switch 两种分支结构。

3.2.1 - if 条件语句

唯一与 C 不同的是,Java 的条件表达式必须是 boolean 类型。

int score = 85;
 
if (score >= 90) {
    System.out.println("Good");
} else if (score >= 60) {
    System.out.println("OK");
} else {
    System.out.println("Not ok");
}

3.2.2 - switch 分支语句

一、传统 switch 语句

String command = "start";
 
switch (command) {
    case "start":
        System.out.println("Starting...");
        break;
    case "stop":
        System.out.println("Stopping...");
        break;
    default:
        System.out.println("Unknown command");
}

二、增强 switch 语句

从 Java 14 起引入了箭头形式的 switch,这是推荐的写法:

  • 使用 -> 代替 :;

  • 不需要写 break;

  • 可以使用逗号合并多个匹配值。
int day = 6;
 
switch (day) {
    case 1, 2, 3, 4, 5 -> System.out.println("工作日");
    case 6, 7 -> System.out.println("周末");
    default -> System.out.println("非法日期");
}

三、switch 表达式

Java 的 switch 不仅可以作为语句,还可以作为表达式,直接返回一个结果:

int score = 85;
 
String result = switch (score / 10) {
    case 10, 9 -> "优秀";
    case 8 -> {
        System.out.println("成绩良好");
        yield "良好";
    }
    case 7, 6 -> "及格";
    default -> "不及格";
};
  • 作为语句(如赋值、方法实参、return 值)使用时结尾需要加分号;
  • 单表达式分支用箭头写法直接给出值,无需 yield;
  • 如果某个分支需要执行多条语句,可以使用 {},并通过 yield 返回结果:
  • 表达式形式要求覆盖所有可能取值(穷尽性),否则编译错误。


3.3 - 循环结构

3.3.1 - while 和 do while 循环

  • while 循环:先判断条件,再执行循环体,循环体可能一次也不执行;
  • do while 循环:先执行循环体,再判断条件,循环体至少执行一次。
int i = 0;
 
while (i < 5) {
    System.out.println(i);
    i++;
}
 
do {
    System.out.println(i);
    i++;
} while (i < 5);

3.3.2 - for 循环

一、传统 for 循环

Java 与 C 语言在传统 for 循环中的唯一区别,仍然是条件必须为 boolean。

for (初始化表达式; 循环条件; 更新表达式) {
    // 循环体
}
 
// Java 不允许:
for (int i = 5; i; i--) {
    System.out.println(i);
}

二、增强 for 循环

这是 Java 中非常常用的语法,也称为 for-each 循环:

int[] nums = {10, 20, 30, 40};
 
for (int num : nums) {
    System.out.println(num);
}
 
 
// 修改循环变量不会修改原数组中的基本类型元素
int[] nums = {1, 2, 3};
 
for (int num : nums) {
    num *= 2;
}
 
System.out.println(nums[0]); // 1
 
// 想要修改数组,应当使用普通的 for 循环
for (int i = 0; i < nums.length; i++) {
    nums[i] *= 2;
}

for-each 循环

  • 主要用于遍历数组,和实现了 Iterable 接口的对象(大多数集合);
  • 不能直接获取当前元素的下标;
  • 修改循环变量不会修改原数组中的基本类型元素。


3.4 - 流程控制语句

3.4.1 - break 语句

break 用于立即终止当前最内层的循环或 switch 语句

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break;
    }
 
    System.out.println(i); // 0 1 2 3 4
}

3.4.2 - continue 语句

continue 用于跳过当前循环迭代的剩余部分,进入下一轮循环

for (int i = 0; i < 5; i++) {
    if (i == 2) {
        continue;
    }
 
    System.out.println(i); // 0 1 3 4
}

3.4.3 - 标签

Java 允许为循环添加标签(Label),从而控制多层嵌套循环:

  • 普通 break 只能退出最内层循环,而带标签的 break 可以直接退出指定的外层结构;
  • 带标签的 continue 可以直接进入指定外层循环的下一轮迭代。

标签禁止嵌套重名,即一个带标签的语句,不能出现在另一个具有相同名称的标签语句的“直接包含语句”之内。

outer:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (i == 1 && j == 1) {
            break outer;  // 直接终止整个外层循环(outer 标记的循环)
        }
 
        System.out.println(i + ", " + j);
    }
}
 
outer:  // 因为两个 outer 是平级的,所以不属于嵌套重名
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 1) {
            continue outer;  // 直接跳过当前外层循环剩余部分,进入下一轮 i 的迭代
        }
 
        System.out.println(i + ", " + j);
    }
}

3.4.4 - return 语句

return 用于结束当前方法的执行,并且可以向调用方返回一个值

public static void printNum(int num) {
    if (num < 0) {
        return;
    }
    System.out.println(num);
}


3.5 - 方法的定义和调用 ★

在 Java 中,方法(Method)是一段具有特定功能、可以被重复调用的代码块。它与 C 语言中的函数非常相似,主要作用是:

  • 代码复用:将重复使用的代码封装起来。

  • 模块化:将复杂程序拆分成多个独立功能。

  • 提高可维护性:修改某个功能时,只需要修改对应的方法。

与 C 的不同之处是,Java 中的方法必须定义在类中,不能像 C 语言一样定义独立于类之外的普通函数。

3.5.1 - 方法的定义

Java 的方法在类中定义,不需要像 C 一样写函数原型声明,也可以在类中不论位置先后互相调用。

[修饰符] 返回值类型 方法名(参数列表) {
    // 方法体
    return 返回值;
}
 
// 例如:
public static int max(int a, int b) {
    return a > b ? a : b;
}

修饰符(第 6 章详细介绍)

  • public:属于访问修饰符,表示该方法可以被其他类访问。

  • static:表示静态方法,可以通过类名直接调用,不需要创建对象。

  • Java 支持声明有返回值和无返回值(void)的方法;
  • void 方法也可以使用不带返回值的 return; 提前结束执行;
  • 非 void 方法要求所有能够正常执行结束的路径都返回符合声明类型的值。
  • 方法可以接收零个或多个参数;
  • 声明一个方法时,参数列表中声明的参数叫形参(Formal Parameters),调用时传入的叫实参(Arguments);
  • Java 不支持给参数设定默认值、

3.5.2 - 方法的调用

一、静态方法的调用

对于 static 方法:

  • 在不同的类中,使用 类名.方法名(实参列表) 的语法调用;
  • 如果是在同一个类中的静态方法之间调用,可以直接使用方法名;
// 在 Calculator 类中
public class Calculator {
    public static int add(int a, int b) {
        return a + b;
    }
}
 
// 在其他的类中
public class Main {
    public static void main(String[] args) {
        int result = Calculator.add(10, 20);  // 类名.方法名(实参列表);
        System.out.println(result);
    }
}

二、实例方法的调用

Java 还存在非 static 方法,称为实例方法(Instance Method)

实例方法在调用时,需要先创建对象(第 5 章介绍):

public class Calculator {  // 一个无 static 修饰的方法(即实例方法)
    public int add(int a, int b) {
        return a + b;
    }
}
 
 
// 调用时
Calculator calc = new Calculator();
int result = calc.add(10, 20);  // 调用方法:对象引用.方法名(实参列表);

3.5.3 - 方法的参数传递

Java 中的方法参数传递只有值传递(Pass by Value),即调用方法时,传递给形参的是实参的值的副本

一、基本数据类型的参数传递

基本数据类型的参数传递,行为与 C 语言中的普通值传递一致,对形参的修改并不影响原变量:

public class Main {
    public static void change(int x) {  // 方法接收到的只是一个复制值
        x = 100;
    }
 
    public static void main(String[] args) {
        int a = 10;
        change(a);
        System.out.println(a);  // 10,修改形参,不会影响调用者的原变量
    }
}

二、引用数据类型的参数传递

引用类型(如数组、对象)也是值传递,只不过复制的是引用的值

这使得形参与实参保存的引用指向同一个对象:

  • 修改引用指向的对象内容,可能影响调用者。

  • 修改形参本身保存的引用,不会改变调用者变量保存的引用(与指针的不同之处)。

public static void change(int[] arr) {
    arr[0] = 100;
    arr = new int[]{6767, 1212, 6969}; // 只会让形参 arr 指向新数组,不改变 nums 的引用
}
 
public static void main(String[] args) {
    int[] nums = {10, 20, 30};
 
    change(nums);
 
    System.out.println(nums[0]); // 100
}

3.5.4 - 方法重载

方法重载(Overloading)指的是:在同一个类中,允许定义多个名称相同、但参数列表不同的方法。

调用时,编译器会根据实参的个数、类型、顺序选择合适的方法:

  • f(int, double) 与 f(double, int) 构成重载;
  • int f(int) 与 double f(int) 不构成重载,因为其参数列表一致,只是返回值类型不同;
  • f(int a) 与 f(int b) 不构成重载,因为其只有参数名不同。
public class Calculator {
 
    public static int add(int a, int b) {
        return a + b;
    }
 
    public static double add(double a, double b) {
        return a + b;
    }
 
    public static int add(int a, int b, int c) {
        return a + b + c;
    }
 
    // public static double add(int a, int b, int c) { ... }  // 编译错误
    // public static int add(int K, int F, int C) { ... }  // 编译错误
}
 
// 调用时
Calculator.add(1, 2);        // int 版本
Calculator.add(1.5, 2.5);    // double 版本
Calculator.add(1, 2, 3);     // 三参数版本

3.5.5 - 可变参数

Java 允许方法接收数量不固定的参数,即可变参数(Varargs, /ˈvɑːrˌræɡz/)。

可变参数在方法中,本质就是一个数组,可用下标获取某个元素、可用 .length 获取元素个数。

声明可变参数的方法时:

  1. 一个方法最多只能有一个可变参数。

  2. 可变参数必须放在参数列表的最后。

public static void print(String title, int... nums) {  // 可变参数必须在最后
    // ...
}
public static int sum(int... nums) {  
    int result = 0;
 
    for (int num : nums) {  // 在方法体里,可变参数本质上就是一个数组
        result += num;
    }
 
    return result;
}
 
 
// 调用
sum();            // 0
sum(1, 2);        // 3
sum(1, 2, 3, 4);  // 10
 
int[] arr = {1, 2, 3};
sum(arr);   // 可以直接传入一个数组


3.5 - 递归方法

递归(Recursion)是指一个方法在执行过程中,直接或间接地调用自身。和 C 一样,Java 也支持方法的递归调用。

// 辗转相除法求最大公约数
public static int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

在 Java 中,如果递归调用太深(通常是因为终止条件出错),使得调用栈持续增长,会触发 StackOverflowError 错误,该错误属于 Error。

  • 栈空间越大,通常能够支持越深的递归;

  • 每次递归调用占用的栈空间并不固定;

  • 不同 JVM、不同方法、不同运行环境下,最大递归深度可能不同,不过 Java 提供了 JVM 参数 -Xss 设置线程栈大小,例如 java -Xss2m Main 可将线程大小设置为 2MiB。

Throwable
├── Error
│   └── VirtualMachineError
│       └── StackOverflowError
└── Exception