函数方法
# 06.函数方法
# 目录介绍
# 6.1 概述和定义
# 6.1.1 方法概念
方法(Method)是 Java 中一段可重用的代码块,用于执行特定的任务。Java 中的方法必须定义在类中,不能像 C++ 那样定义独立的函数。
Java 中"函数"叫做"方法",因为所有代码都必须在类中。
# 6.1.2 方法声明和定义
// 方法定义的语法
访问修饰符 返回类型 方法名(参数列表) {
方法体;
return 返回值;
}
2
3
4
5
示例:
public class Calculator {
// 有参有返回值
public int add(int a, int b) {
return a + b;
}
// 无参无返回值
public void sayHello() {
System.out.println("Hello!");
}
// 有参无返回值
public void printSum(int a, int b) {
System.out.println("sum = " + (a + b));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
对比 C++:C++ 的函数可以定义在类外面,Java 的方法必须在类里面。C++ 需要在头文件中声明、在 cpp 文件中实现,Java 声明和实现在一起。
# 6.1.3 方法分类
public class Account {
private double balance;
// 实例方法:需要通过对象调用
public double getBalance() {
return balance;
}
// 静态方法:通过类名调用
public static double calculateInterest(double amount, double rate) {
return amount * rate;
}
}
// 调用
Account acc = new Account();
acc.getBalance(); // 实例方法
Account.calculateInterest(1000, 0.05); // 静态方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 6.1.4 静态方法和实例方法
| 对比项 | 静态方法 | 实例方法 |
|---|---|---|
| 关键字 | static 修饰 | 无 static |
| 调用方式 | 类名.方法名() | 对象.方法名() |
| 能否访问实例变量 | 不能 | 能 |
| 能否使用 this | 不能 | 能 |
| 典型用途 | 工具方法、工厂方法 | 操作对象状态 |
public class MathUtils {
// 静态方法:不依赖对象状态
public static int max(int a, int b) {
return a > b ? a : b;
}
}
// 直接用类名调用
int result = MathUtils.max(10, 20);
2
3
4
5
6
7
8
9
# 6.1.5 综合案例:字符串工具类
编写一个工具类,综合运用静态方法和实例方法的定义,展示方法声明、访问修饰符和方法分类的使用。
public class StringHelper {
// 静态方法:不依赖对象状态
public static boolean isEmpty(String s) {
return s == null || s.isEmpty();
}
public static String reverse(String s) {
if (isEmpty(s)) return s;
return new StringBuilder(s).reverse().toString();
}
public static String capitalize(String s) {
if (isEmpty(s)) return s;
return Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
public static int countOccurrences(String text, String target) {
if (isEmpty(text) || isEmpty(target)) return 0;
int count = 0, index = 0;
while ((index = text.indexOf(target, index)) != -1) {
count++;
index += target.length();
}
return count;
}
// 静态方法:重复字符串
public static String repeat(String s, int times) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < times; i++) sb.append(s);
return sb.toString();
}
public static void main(String[] args) {
// 直接通过类名调用静态方法
System.out.println("===== 静态方法调用 =====");
System.out.println("isEmpty(null): " + StringHelper.isEmpty(null));
System.out.println("isEmpty(\"\"): " + StringHelper.isEmpty(""));
System.out.println("isEmpty(\"hi\"): " + StringHelper.isEmpty("hi"));
System.out.println("reverse(\"Hello\"): " + StringHelper.reverse("Hello"));
System.out.println("capitalize(\"java\"): " + StringHelper.capitalize("java"));
System.out.println("repeat(\"*\", 10): " + StringHelper.repeat("*", 10));
String text = "Java is great. Java is popular. Learn Java!";
System.out.println("\"Java\"出现次数: " + StringHelper.countOccurrences(text, "Java"));
// 对比:实例方法需要创建对象
System.out.println("\n===== 实例方法 vs 静态方法 =====");
System.out.println("Math.max(10,20) = " + Math.max(10, 20) + " (静态)");
String str = "Hello";
System.out.println("str.length() = " + str.length() + " (实例)");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 6.1.6 方法定义训练题
训练1:创建一个 StringUtils 工具类,包含以下静态方法:isEmpty(String s) 判断字符串是否为空或 null、reverse(String s) 反转字符串、capitalize(String s) 将首字母大写。
训练2:创建一个 Circle 类,包含实例方法 getArea() 和 getPerimeter(),以及静态方法 create(double radius) 作为工厂方法创建 Circle 对象。
思考:为什么 Java 的 main 方法必须是 public static void main(String[] args) 这个固定签名?如果去掉 static 会怎样?
# 6.2 方法参数
# 6.2.1 值传递
Java 只有值传递,没有 C++ 那样的引用传递。
值传递的底层原理:在 JVM 层面,每次方法调用会在栈帧(Stack Frame)中创建一个新的局部变量表(Local Variable Table)。方法参数被复制到新栈帧的局部变量表中。对于基本类型,复制的是值本身(如 int a = 10,复制的是 10);对于引用类型,复制的是引用值(即对象在堆上的地址),所以通过这个引用副本可以修改堆上的对象内容,但无法让原引用指向新对象。这就是"Java 只有值传递"的本质。
疑惑:既然引用类型传递的是地址副本,那和 C++ 的指针传递有什么区别?
看起来 Java 的引用传递和 C++ 的指针传递效果一样——都可以修改对象内容,但不能让原变量指向新对象。那 Java 到底是"值传递"还是"指针传递"?
答疑:本质区别在于语义。C++ 的指针传递中,你拿到的是一个指针变量,可以对它做指针运算(p++、*(p+1) 等),也可以传 NULL。Java 的引用传递中,你拿到的是一个引用的副本,不能做任何"指针运算",也没有 -> 操作符。
论证:
Java 值传递(引用类型)的内存模型:
main() 栈帧:
┌───────────┐
│ a = 0x100 │──────┐ 堆内存:
└───────────┘ │ ┌────────────────┐
└─────→│ Account("张三") │
changeName() 栈帧: ┌────→│ │
┌───────────┐ │ └────────────────┘
│ acc=0x100 │──────┘
└───────────┘
// acc 是 a 的副本(都指向 0x100)
// 通过 acc 修改对象 → a 也能看到修改
// acc = new Account() → 只改了副本,a 不受影响
2
3
4
5
6
7
8
9
10
11
12
13
14
结果展示:所以准确地说,Java 的引用类型传参是"传引用的值"(pass reference by value),而不是 C++ 的"传引用"(pass by reference)。理解这个概念,就能避免"为什么我在方法里 new 了一个新对象但外面没变"这类常见困惑。
对于基本类型,传递的是值的副本:
public static void change(int num) {
num = 100; // 只修改了副本
}
public static void main(String[] args) {
int a = 10;
change(a);
System.out.println(a); // 10(没有改变)
}
2
3
4
5
6
7
8
9
# 6.2.2 引用类型传参
对于引用类型,传递的是引用的副本(地址的副本),所以可以通过引用修改对象的内容:
public static void changeName(Account acc) {
acc.setName("李四"); // 通过引用修改对象内容,有效
}
public static void reassign(Account acc) {
acc = new Account("王五"); // 只改变了副本的指向,无效
}
public static void main(String[] args) {
Account a = new Account("张三");
changeName(a);
System.out.println(a.getName()); // 李四(对象被修改了)
reassign(a);
System.out.println(a.getName()); // 李四(没有变成王五)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
对比 C++:C++ 有值传递、引用传递(&)、指针传递(*)三种方式。Java 只有值传递,但引用类型的值传递效果类似于 C++ 的指针传递。
# 6.2.4 综合案例:值传递实验室
编写一个程序,通过多个实验系统性演示 Java 值传递的行为,加深对基本类型、引用类型和可变参数的理解。
import java.util.Arrays;
public class PassByValueLab {
// 实验1:基本类型 - 无法修改
static void trySwap(int a, int b) {
int temp = a; a = b; b = temp;
System.out.println(" 方法内: a=" + a + ", b=" + b);
}
// 实验2:数组 - 可以修改内容
static void doubleValues(int[] arr) {
for (int i = 0; i < arr.length; i++) arr[i] *= 2;
}
// 实验3:字符串 - 看似引用类型,但不可变
static void tryModifyString(String s) {
s = s + " Modified";
System.out.println(" 方法内: " + s);
}
// 实验4:对象 - 可修改属性,但不能替换对象
static void modifyBuilder(StringBuilder sb) {
sb.append(" World"); // 修改内容 - 有效
}
static void replaceBuilder(StringBuilder sb) {
sb = new StringBuilder("New Object"); // 替换引用 - 无效
System.out.println(" 方法内: " + sb);
}
// 实验5:可变参数
static int sum(int first, int... rest) {
int total = first;
for (int n : rest) total += n;
return total;
}
public static void main(String[] args) {
// 实验1
System.out.println("===== 实验1: 基本类型交换 =====");
int x = 10, y = 20;
System.out.println("调用前: x=" + x + ", y=" + y);
trySwap(x, y);
System.out.println("调用后: x=" + x + ", y=" + y + " (未改变!)");
// 实验2
System.out.println("\n===== 实验2: 数组修改 =====");
int[] arr = {1, 2, 3, 4, 5};
System.out.println("调用前: " + Arrays.toString(arr));
doubleValues(arr);
System.out.println("调用后: " + Arrays.toString(arr) + " (已改变!)");
// 实验3
System.out.println("\n===== 实验3: String不可变 =====");
String str = "Hello";
System.out.println("调用前: " + str);
tryModifyString(str);
System.out.println("调用后: " + str + " (未改变! String不可变)");
// 实验4
System.out.println("\n===== 实验4: 对象修改vs替换 =====");
StringBuilder sb = new StringBuilder("Hello");
System.out.println("调用前: " + sb);
modifyBuilder(sb);
System.out.println("修改后: " + sb + " (内容改变!)");
replaceBuilder(sb);
System.out.println("替换后: " + sb + " (引用未改变!)");
// 实验5
System.out.println("\n===== 实验5: 可变参数 =====");
System.out.println("sum(10) = " + sum(10));
System.out.println("sum(10, 20, 30) = " + sum(10, 20, 30));
System.out.println("sum(1, 2, 3, 4, 5) = " + sum(1, 2, 3, 4, 5));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 6.2.5 方法参数训练题
训练1:编写方法 void swapInts(int a, int b) 尝试交换两个整数。在 main 方法中调用后,打印原始变量的值。请解释为什么交换不成功。
训练2:编写方法 void swapArray(int[] arr, int i, int j) 交换数组中两个位置的元素。调用后验证交换是否成功,并解释为什么这次可以成功。
训练3:编写方法 int sum(int first, int... rest),计算第一个参数和可变参数的总和。测试 sum(10)、sum(10, 20, 30) 的结果。
思考:如果 Java 真的支持引用传递,void swap(int& a, int& b) 能交换两个整数。Java 不支持引用传递的设计理念是什么?(提示:思考可预测性和安全性。)
# 6.2.3 可变参数
Java 5 引入了可变参数(varargs),用 ... 表示:
public static int sum(int... nums) {
int total = 0;
for (int num : nums) {
total += num;
}
return total;
}
// 调用
System.out.println(sum(1, 2, 3)); // 6
System.out.println(sum(1, 2, 3, 4, 5)); // 15
System.out.println(sum()); // 0
2
3
4
5
6
7
8
9
10
11
12
注意:可变参数必须是方法的最后一个参数,一个方法只能有一个可变参数。
可变参数的底层原理:可变参数是编译器的语法糖。int... nums 在编译后会被转换为 int[] nums。调用 sum(1, 2, 3) 时,编译器自动创建一个数组 new int[]{1, 2, 3} 传入方法。
// 编译前
sum(1, 2, 3);
// 编译后等价于
sum(new int[]{1, 2, 3});
2
3
4
5
可变参数和数组参数的微妙区别:
// 可变参数:调用时可以直接传多个值
public static int sum(int... nums) { /* ... */ }
sum(1, 2, 3); // OK
sum(new int[]{1,2,3}); // OK
sum(); // OK(空数组)
// 数组参数:调用时必须传数组
public static int sum2(int[] nums) { /* ... */ }
// sum2(1, 2, 3); // 编译错误!
sum2(new int[]{1,2,3}); // OK
// sum2(); // 编译错误!
2
3
4
5
6
7
8
9
10
11
注意:可变参数方法不能和相同数组类型的方法重载,因为编译后它们的签名相同:
// 以下两个方法不能共存,编译错误:
// public static int sum(int... nums) { }
// public static int sum(int[] nums) { }
2
3
# 6.3 方法返回值
// 返回基本类型
public static int add(int a, int b) {
return a + b;
}
// 返回引用类型
public static int[] createArray(int size) {
return new int[size];
}
// 返回 void(无返回值)
public static void printMessage(String msg) {
System.out.println(msg);
// return; // 可以省略
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
多返回值问题:Java 方法只能返回一个值,如果需要返回多个值,常用方式有:
// 方式1:返回数组
public static int[] minAndMax(int[] arr) {
int min = arr[0], max = arr[0];
for (int n : arr) {
if (n < min) min = n;
if (n > max) max = n;
}
return new int[]{min, max};
}
// 方式2:返回对象
public class Result {
public final int min;
public final int max;
public Result(int min, int max) {
this.min = min;
this.max = max;
}
}
// 方式3:JDK 16+ 使用 Record
public record MinMax(int min, int max) {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 6.3.1 综合案例:多返回值解决方案
编写一个程序,展示 Java 方法返回值的各种用法,特别是处理"需要返回多个值"的几种常见方案。
import java.util.Arrays;
public class ReturnValueDemo {
// 方案1: 返回数组
static int[] getMinMax(int[] arr) {
int min = arr[0], max = arr[0];
for (int n : arr) {
if (n < min) min = n;
if (n > max) max = n;
}
return new int[]{min, max};
}
// 方案2: 返回自定义对象
static class Statistics {
double avg, min, max;
int count;
Statistics(double avg, double min, double max, int count) {
this.avg = avg; this.min = min; this.max = max; this.count = count;
}
}
static Statistics analyze(double[] data) {
double sum = 0, min = data[0], max = data[0];
for (double d : data) {
sum += d;
if (d < min) min = d;
if (d > max) max = d;
}
return new Statistics(sum / data.length, min, max, data.length);
}
// void方法:通过修改传入的数组/对象来"返回"结果
static void sortDescending(int[] arr) {
Arrays.sort(arr);
for (int i = 0, j = arr.length - 1; i < j; i++, j--) {
int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;
}
}
// 返回boolean表示操作是否成功
static boolean safeDivide(double a, double b, double[] result) {
if (b == 0) return false;
result[0] = a / b;
return true;
}
public static void main(String[] args) {
// 方案1: 数组返回
System.out.println("===== 返回数组 =====");
int[] data = {34, 12, 78, 5, 91, 23};
int[] minMax = getMinMax(data);
System.out.println("数据: " + Arrays.toString(data));
System.out.println("最小值: " + minMax[0] + ", 最大值: " + minMax[1]);
// 方案2: 对象返回
System.out.println("\n===== 返回对象 =====");
double[] scores = {85.5, 92.0, 78.5, 96.0, 88.5};
Statistics stats = analyze(scores);
System.out.printf("统计: 人数=%d, 平均=%.1f, 最低=%.1f, 最高=%.1f%n",
stats.count, stats.avg, stats.min, stats.max);
// void方法修改参数
System.out.println("\n===== void方法修改参数 =====");
int[] arr = {3, 1, 4, 1, 5, 9};
System.out.println("排序前: " + Arrays.toString(arr));
sortDescending(arr);
System.out.println("降序后: " + Arrays.toString(arr));
// boolean返回表示成功/失败
System.out.println("\n===== boolean返回 =====");
double[] result = new double[1];
if (safeDivide(10, 3, result)) {
System.out.printf("10 / 3 = %.4f%n", result[0]);
}
if (!safeDivide(10, 0, result)) {
System.out.println("10 / 0 = 除零错误!");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 6.3.2 方法返回值训练题
训练1:编写方法 int[] findTwoLargest(int[] arr),找出数组中最大的两个数并以数组形式返回。
思考:Java 不支持像 Go 语言那样 return a, b 返回多个值。你觉得哪种多返回值的替代方案最好?为什么?
# 6.4 方法重载
# 6.4.1 方法重载案例
方法重载(Overload)是指在同一个类中,方法名相同但参数列表不同:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
Calculator calc = new Calculator();
System.out.println(calc.add(1, 2)); // 调用 int 版本
System.out.println(calc.add(1.5, 2.5)); // 调用 double 版本
System.out.println(calc.add(1, 2, 3)); // 调用三参数版本
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 6.4.2 重载注意事项
- 参数列表必须不同(参数个数、类型或顺序不同)。
- 返回类型不同不构成重载。
- 访问修饰符不同也不构成重载。
// 以下两个方法不构成重载,会编译错误
// public int add(int a, int b) { return a + b; }
// public double add(int a, int b) { return a + b; } // 只有返回类型不同
2
3
对比 C++:C++ 也支持函数重载,规则基本相同。
# 6.4.3 重载的底层原理
疑惑:编译器如何在多个重载方法中选择正确的那个?
如果同时有
add(int, int)和add(long, long),调用add(1, 2)时,两个方法看起来都可以匹配(int 可以自动提升为 long),编译器怎么决定?
答疑:Java 的方法重载解析分为三个阶段,按优先级从高到低:
- 精确匹配:参数类型完全一致,优先选择。
add(1, 2)精确匹配add(int, int)。 - 自动类型提升:如果没有精确匹配,尝试基本类型的拓宽转换(widening)。例如
int → long → float → double。 - 自动装箱/拆箱:JDK 5+ 才有。
add(1, 2)可以匹配add(Integer, Integer)。 - 可变参数:优先级最低。
add(1, 2)匹配add(int... nums)。
论证:
public class OverloadDemo {
public static void test(int a) { System.out.println("int"); }
public static void test(long a) { System.out.println("long"); }
public static void test(Integer a) { System.out.println("Integer"); }
public static void test(int... a) { System.out.println("varargs"); }
public static void main(String[] args) {
test(1); // 输出 "int"(精确匹配优先)
test(1L); // 输出 "long"(精确匹配)
// 如果注释掉 test(int):
// test(1); // 输出 "long"(自动类型提升)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
结果展示:方法重载是编译时多态(静态绑定),编译器在编译期就确定了调用哪个方法。这与方法重写的运行时多态(动态绑定)不同。在字节码中,重载方法的调用是通过方法描述符(方法名 + 参数类型)来区分的。
# 6.4.4 方法重载训练题
训练1:编写一个 Printer 类,重载 print 方法,分别支持打印 int、double、String、int[] 四种类型。
训练2:以下代码能编译通过吗?如果能,输出什么?
public static void test(Object obj) { System.out.println("Object"); }
public static void test(String str) { System.out.println("String"); }
public static void test(Integer num) { System.out.println("Integer"); }
test(null); // 输出什么?
2
3
4
5
思考:方法重载(Overload)和方法重写(Override)有什么区别?分别在什么时期确定调用哪个方法?
# 6.5 递归
# 6.5.1 递归概念
递归是指方法调用自身。递归必须有终止条件(基准情形),否则会无限递归导致栈溢出(StackOverflowError)。
递归的底层原理:每次方法调用都会在 JVM 栈中压入一个新的栈帧,包含局部变量表、操作数栈、返回地址等。递归调用 N 次就会有 N 个栈帧同时存在。JVM 栈的大小是有限的(默认约 512KB~1MB,可通过 -Xss 参数调整),所以递归深度过大会导致 StackOverflowError。Java 不支持尾递归优化(TCO),因此即使是尾递归写法,JVM 也不会自动优化成循环。对于深层递归问题,建议改写为迭代 + 显式栈的方式。
# 6.5.2 递归案例
// 阶乘
public static long factorial(int n) {
if (n <= 1) return 1; // 终止条件
return n * factorial(n - 1); // 递归调用
}
// 斐波那契数列
public static int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
System.out.println(factorial(5)); // 120
System.out.println(fibonacci(10)); // 55
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 6.5.3 递归的优化策略
疑惑:递归这么容易栈溢出,为什么还要用它?
既然 Java 不支持尾递归优化,递归深度受限于栈大小(默认 512KB~1MB),那为什么不全部用循环?
答疑:递归的价值不在于性能,而在于表达力。有些问题天然具有递归结构(如树的遍历、分治算法、回溯搜索),用递归表达最自然。关键是要知道何时用递归、何时改写为迭代。
优化策略一:记忆化(Memoization)
// 朴素递归 fibonacci:O(2^n),大量重复计算
// fibonacci(5) 会调用 fibonacci(3) 两次,fibonacci(2) 三次
// 记忆化递归:O(n),每个值只计算一次
private static Map<Integer, Long> memo = new HashMap<>();
public static long fibMemo(int n) {
if (n <= 1) return n;
if (memo.containsKey(n)) return memo.get(n);
long result = fibMemo(n - 1) + fibMemo(n - 2);
memo.put(n, result);
return result;
}
2
3
4
5
6
7
8
9
10
11
12
优化策略二:改写为迭代
// 迭代版 fibonacci:O(n) 时间,O(1) 空间
public static long fibIterative(int n) {
if (n <= 1) return n;
long prev = 0, curr = 1;
for (int i = 2; i <= n; i++) {
long next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
2
3
4
5
6
7
8
9
10
11
优化策略三:显式栈替代系统栈
// 用显式栈模拟递归(适用于树遍历等场景)
public static void iterativePreorder(TreeNode root) {
if (root == null) return;
Deque<TreeNode> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
System.out.print(node.val + " ");
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
}
2
3
4
5
6
7
8
9
10
11
12
# 6.5.4 递归训练题
训练1:用递归实现 int power(int base, int exp),计算 base 的 exp 次方。然后优化为快速幂算法(时间复杂度从 O(n) 降到 O(log n))。
训练2:用递归实现 void printBinary(int n),打印一个正整数的二进制表示。例如 printBinary(13) 输出 1101。
思考:Java 不支持尾递归优化,但 Kotlin(同样运行在 JVM 上)通过 tailrec 关键字可以自动将尾递归转换为循环。为什么 Java 没有实现这个优化?
# 6.6 方法引用(JDK8+)
JDK 8 引入了方法引用,是 Lambda 表达式的简写:
import java.util.Arrays;
String[] names = {"Charlie", "Alice", "Bob"};
// Lambda 写法
Arrays.sort(names, (a, b) -> a.compareTo(b));
// 方法引用写法
Arrays.sort(names, String::compareTo);
System.out.println(Arrays.toString(names));
// [Alice, Bob, Charlie]
2
3
4
5
6
7
8
9
10
11
12
四种方法引用类型:
| 类型 | 语法 | Lambda 等价 |
|---|---|---|
| 静态方法引用 | Integer::parseInt | s -> Integer.parseInt(s) |
| 实例方法引用(对象) | System.out::println | x -> System.out.println(x) |
| 实例方法引用(类) | String::length | s -> s.length() |
| 构造方法引用 | ArrayList::new | () -> new ArrayList<>() |
// 各种方法引用示例
List<String> words = List.of("hello", "world", "java");
// 静态方法引用
words.stream().map(String::toUpperCase).forEach(System.out::println);
// 构造方法引用
List<StringBuilder> builders = words.stream()
.map(StringBuilder::new) // 等价于 s -> new StringBuilder(s)
.toList();
2
3
4
5
6
7
8
9
10
# 6.6.1 综合案例:方法引用转换练习
编写一个程序,展示 Lambda 表达式和四种方法引用的相互转换,帮助理解方法引用的本质。
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
public class MethodRefDemo {
public static void main(String[] args) {
List<String> names = List.of("Charlie", "alice", "Bob", "david");
// 1. 静态方法引用: 类名::静态方法
System.out.println("===== 静态方法引用 =====");
// Lambda → 方法引用
List<Integer> nums = List.of("1", "2", "3", "4");
List<Integer> parsed1 = nums.stream().map(s -> Integer.parseInt(s)).toList(); // Lambda
List<Integer> parsed2 = nums.stream().map(Integer::parseInt).toList(); // 方法引用
System.out.println("Lambda: " + parsed1);
System.out.println("方法引用: " + parsed2);
// 2. 实例方法引用(对象): 对象::实例方法
System.out.println("\n===== 对象的实例方法引用 =====");
names.forEach(s -> System.out.println(s)); // Lambda
System.out.println("--- 方法引用 ---");
names.forEach(System.out::println); // 方法引用
// 3. 实例方法引用(类): 类名::实例方法
System.out.println("\n===== 类的实例方法引用 =====");
List<String> upper1 = names.stream().map(s -> s.toUpperCase()).toList(); // Lambda
List<String> upper2 = names.stream().map(String::toUpperCase).toList(); // 方法引用
System.out.println("Lambda: " + upper1);
System.out.println("方法引用: " + upper2);
// 排序
String[] arr = names.toArray(new String[0]);
Arrays.sort(arr, String::compareToIgnoreCase); // 类的实例方法引用
System.out.println("排序后: " + Arrays.toString(arr));
// 4. 构造方法引用: 类名::new
System.out.println("\n===== 构造方法引用 =====");
List<StringBuilder> builders1 = names.stream()
.map(s -> new StringBuilder(s)).toList(); // Lambda
List<StringBuilder> builders2 = names.stream()
.map(StringBuilder::new).toList(); // 构造引用
System.out.println("Lambda: " + builders1);
System.out.println("构造引用: " + builders2);
// 对比总结
System.out.println("\n===== 四种方法引用汇总 =====");
System.out.println("1. 静态方法: Integer::parseInt ← s -> Integer.parseInt(s)");
System.out.println("2. 对象实例: System.out::println ← x -> System.out.println(x)");
System.out.println("3. 类的实例: String::toUpperCase ← s -> s.toUpperCase()");
System.out.println("4. 构造方法: StringBuilder::new ← s -> new StringBuilder(s)");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 6.6.2 方法引用训练题
训练1:将以下 Lambda 表达式改写为方法引用:
list.forEach(s -> System.out.println(s));
list.sort((a, b) -> a.compareTo(b));
list.stream().map(s -> s.length());
list.stream().map(s -> Integer.valueOf(s));
2
3
4
思考:方法引用是 Lambda 的语法糖吗?它们在字节码层面有区别吗?