编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 字符串和数组
      • 流程语句
      • 函数方法
        • 6.1 概述和定义
          • 6.1.1 方法概念
          • 6.1.2 方法声明和定义
          • 6.1.3 方法分类
          • 6.1.4 静态方法和实例方法
          • 6.1.5 综合案例:字符串工具类
          • 6.1.6 方法定义训练题
        • 6.2 方法参数
          • 6.2.1 值传递
          • 6.2.2 引用类型传参
          • 6.2.4 综合案例:值传递实验室
          • 6.2.5 方法参数训练题
          • 6.2.3 可变参数
        • 6.3 方法返回值
          • 6.3.1 综合案例:多返回值解决方案
          • 6.3.2 方法返回值训练题
        • 6.4 方法重载
          • 6.4.1 方法重载案例
          • 6.4.2 重载注意事项
          • 6.4.3 重载的底层原理
          • 6.4.4 方法重载训练题
        • 6.5 递归
          • 6.5.1 递归概念
          • 6.5.2 递归案例
          • 6.5.3 递归的优化策略
          • 6.5.4 递归训练题
        • 6.6 方法引用(JDK8+)
          • 6.6.1 综合案例:方法引用转换练习
          • 6.6.2 方法引用训练题
      • 类和对象
      • 继承和多态
      • 接口和抽象类
      • 异常处理
      • 集合框架
      • IO流和File
      • 线程和锁
      • 泛型
      • 注解和反射
    • 综合案例

    • 专栏博客

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 入门教程
杨充
2026-04-07
目录

函数方法

# 06.函数方法

# 目录介绍

  • 6.1 概述和定义
    • 6.1.1 方法概念
    • 6.1.2 方法声明和定义
    • 6.1.3 方法分类
    • 6.1.4 静态方法和实例方法
    • 6.1.5 综合案例:字符串工具类
    • 6.1.6 方法定义训练题
  • 6.2 方法参数
    • 6.2.1 值传递
    • 6.2.2 引用类型传参
    • 6.2.3 可变参数
    • 6.2.4 综合案例:值传递实验室
    • 6.2.5 方法参数训练题
  • 6.3 方法返回值
    • 6.3.1 综合案例:多返回值解决方案
    • 6.3.2 方法返回值训练题
  • 6.4 方法重载
    • 6.4.1 方法重载案例
    • 6.4.2 重载注意事项
    • 6.4.3 重载的底层原理
    • 6.4.4 方法重载训练题
  • 6.5 递归
    • 6.5.1 递归概念
    • 6.5.2 递归案例
    • 6.5.3 递归的优化策略
    • 6.5.4 递归训练题
  • 6.6 方法引用(JDK8+)
    • 6.6.1 综合案例:方法引用转换练习
    • 6.6.2 方法引用训练题

# 6.1 概述和定义

# 6.1.1 方法概念

方法(Method)是 Java 中一段可重用的代码块,用于执行特定的任务。Java 中的方法必须定义在类中,不能像 C++ 那样定义独立的函数。

Java 中"函数"叫做"方法",因为所有代码都必须在类中。

# 6.1.2 方法声明和定义

// 方法定义的语法
访问修饰符 返回类型 方法名(参数列表) {
    方法体;
    return 返回值;
}
1
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));
    }
}
1
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);    // 静态方法
1
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);
1
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() + " (实例)");
    }
}
1
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 不受影响
1
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(没有改变)
}
1
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());  // 李四(没有变成王五)
}
1
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));
    }
}
1
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
1
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});
1
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();               // 编译错误!
1
2
3
4
5
6
7
8
9
10
11

注意:可变参数方法不能和相同数组类型的方法重载,因为编译后它们的签名相同:

// 以下两个方法不能共存,编译错误:
// public static int sum(int... nums) { }
// public static int sum(int[] nums) { }
1
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;  // 可以省略
}
1
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) {}
1
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 = 除零错误!");
        }
    }
}
1
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));     // 调用三参数版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 6.4.2 重载注意事项

  1. 参数列表必须不同(参数个数、类型或顺序不同)。
  2. 返回类型不同不构成重载。
  3. 访问修饰符不同也不构成重载。
// 以下两个方法不构成重载,会编译错误
// public int add(int a, int b) { return a + b; }
// public double add(int a, int b) { return a + b; }  // 只有返回类型不同
1
2
3

对比 C++:C++ 也支持函数重载,规则基本相同。

# 6.4.3 重载的底层原理

疑惑:编译器如何在多个重载方法中选择正确的那个?

如果同时有 add(int, int) 和 add(long, long),调用 add(1, 2) 时,两个方法看起来都可以匹配(int 可以自动提升为 long),编译器怎么决定?

答疑:Java 的方法重载解析分为三个阶段,按优先级从高到低:

  1. 精确匹配:参数类型完全一致,优先选择。add(1, 2) 精确匹配 add(int, int)。
  2. 自动类型提升:如果没有精确匹配,尝试基本类型的拓宽转换(widening)。例如 int → long → float → double。
  3. 自动装箱/拆箱:JDK 5+ 才有。add(1, 2) 可以匹配 add(Integer, Integer)。
  4. 可变参数:优先级最低。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"(自动类型提升)
    }
}
1
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);  // 输出什么?
1
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
}
1
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;
}
1
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;
}
1
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);
    }
}
1
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]
1
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();
1
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)");
    }
}
1
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));
1
2
3
4

思考:方法引用是 Lambda 的语法糖吗?它们在字节码层面有区别吗?

上次更新: 2026/06/10, 11:13:41
流程语句
类和对象

← 流程语句 类和对象→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式