[JavaSE] 03 - 程序控制结构与方法
3.1 - 顺序结构与语句块
3.1.1 - 顺序结构
顺序结构(Sequential Structure)是程序最基本的执行方式:按照代码从上到下的顺序依次执行语句。
int a = 10;
int b = 20;
int sum = a + b;
System.out.println(sum); // 303.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 获取元素个数。
声明可变参数的方法时:
-
一个方法最多只能有一个可变参数。
-
可变参数必须放在参数列表的最后。
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