×

开篇引子——为什么要有方法?

我们的代码一直在 main 方法里塞,但所有逻辑都挤在一起是什么体验?

public static void main(String[] args) {
    // 1. 输入学生成绩
    System.out.println("请输入学生信息...");
    // 2. 计算总成绩  
    int sum = 0;
    // 50 行计算...
    // 3. 计算平均分
    // 20 行计算...
    // 4. 排序
    // 30 行排序...
    // 5. 打印结果
    // 10 行打印...
    // main 方法 100+ 行,看得眼睛花
}

这就是没有方法的后果——所有代码平铺在一堆,可读性差、没法复用、改一个地方要通读全文。方法就是给一段代码取个名字,需要的时候调用就行。

一、方法的定义

1.1 基本语法

[修饰符] 返回值类型 方法名([参数类型 参数名, ...]) {
    // 方法体
    return 返回值;  // 如果返回值类型不是 void,必须 return
}

来一个最精简的例子:

public class MethodDemo {
    public static void main(String[] args) {
        // 调用方法
        int result = add(10, 20);
        System.out.println("结果:" + result);  // 30
    }
    
    // 定义一个两数相加的方法
    public static int add(int a, int b) {
        return a + b;
    }
}

1.2 方法的结构拆解

组成部分说明例子
修饰符控制访问权限和特性public static
返回值类型方法返回的数据类型intStringvoid
方法名遵循小驼峰命名addgetMax
参数列表调用时传入的数据(int a, int b)
方法体花括号里的代码逻辑{ return a + b; }
return结束方法并返回值return result;

1.3 三种常见方法模式

// 1. 有参数有返回值
public static int max(int a, int b) {
    return a > b ? a : b;
}

// 2. 有参数无返回值(void)
public static void printInfo(String name, int age) {
    System.out.println("姓名:" + name + ",年龄:" + age);
    // 没有 return 也可以,有的话 return; 只是提前结束
}

// 3. 无参数无返回值
public static void showMenu() {
    System.out.println("1. 添加学生");
    System.out.println("2. 查询成绩");
    System.out.println("3. 退出系统");
}

一句话记:void 表示”只管做不返回”,其他类型都”做完了给我个结果”。

二、方法的重载(Overload)

2.1 什么是重载?

同一个类中,方法名相同,但参数列表不同。这就是重载。

最经典的重载例子:

public class OverloadDemo {
    // 两个 int 相加
    public static int add(int a, int b) {
        return a + b;
    }
    
    // 三个 int 相加
    public static int add(int a, int b, int c) {
        return a + b + c;
    }
    
    // double 相加
    public static double add(double a, double b) {
        return a + b;
    }
    
    public static void main(String[] args) {
        System.out.println(add(1, 2));        // 调用版本1
        System.out.println(add(1, 2, 3));     // 调用版本2  
        System.out.println(add(1.5, 2.5));    // 调用版本3
    }
}

2.2 重载的规则

  • 方法名必须相同——不然不叫重载
  • 参数列表必须不同——可以是类型不同、个数不同、顺序不同
  • 与返回值类型无关——只有返回值不同的话,不是重载,编译报错
  • 与参数名无关——add(int a)add(int b) 是同一个方法
// ✅ 合法重载
void print(int a) {}
void print(double a) {}         // 类型不同
void print(int a, int b) {}     // 个数不同
void print(String s, int a) {}  // 类型和顺序都不同

// ❌ 不是重载
int calc(int a) { return a; }
void calc(int b) {}  // 编译报错:与上面的方法重复

2.3 重载的实际应用

System.out.println() 本身就是重载的绝佳例子——它重载了十几个版本,接收各种类型:

System.out.println(100);        // println(int)
System.out.println(3.14);       // println(double)
System.out.println("Hello");    // println(String)
System.out.println(true);       // println(boolean)
// 全是同一个方法名,编译器根据实参自动匹配

重载让我们可以用同一个名字描述同一类操作(都是”打印”、”相加”),只是针对不同类型或数量的参数提供不同实现。这是易用性的核心。

三、方法的值传递(Pass-by-Value)

3.1 Java 只有值传递

这是 Java 新人最容易搞混的概念。网上关于”Java 到底是值传递还是引用传递”的争论可以塞满一个论坛——结论:Java 只有值传递

先看基本类型:

public static void main(String[] args) {
    int x = 10;
    change(x);
    System.out.println(x);  // 还是 10,不是 100!
}

public static void change(int a) {
    a = 100;  // 改的是 a 的副本,不影响外面的 x
}

基本类型传的是值的副本,方法内部的修改不影响原变量。

3.2 引用类型的值传递

引用类型传的是”引用的副本“,听起来绕,看代码:

public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    changeArray(arr);
    System.out.println(Arrays.toString(arr));  // [99, 2, 3] —— 变了!
}

public static void changeArray(int[] a) {
    a[0] = 99;  // 通过引用的副本修改了同一个对象
}

但如果你试图让引用指向一个新对象:

public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    newArray(arr);
    System.out.println(Arrays.toString(arr));  // 还是 [1, 2, 3]!
}

public static void newArray(int[] a) {
    a = new int[]{100, 200, 300};  // ❌ 改的是副本的指向,原数组不受影响
}

一句话总结:修改参数”指向的对象”的内容,会影响外部;修改参数”指向哪个对象”,不影响外部。

💡 当年我学这节的时候,觉得”值传递和引用传递”是个语言哲学问题。后来面试官直接问了句:Java 方法的参数能改成另一个对象吗?—— 不能。为什么?值传递。简洁明了。

四、方法的递归

4.1 递归是什么

递归就是方法自己调自己。听起来像死循环,但关键是:每次调用都让问题规模缩小,最终到达一个”可以直接解决”的终止条件。

4.2 经典案例:计算阶乘

// 5! = 5 × 4 × 3 × 2 × 1 = 120
// 数学定义:n! = n * (n-1)!, 且 0! = 1, 1! = 1

public static int factorial(int n) {
    // 终止条件(核心!没有它就会 StackOverflow)
    if (n == 0 || n == 1) {
        return 1;
    }
    // 递归步骤:问题规模缩小
    return n * factorial(n - 1);
}

// 调用
System.out.println(factorial(5));  // 120

执行过程拆解:

factorial(5)
→ 5 * factorial(4)
→ 5 * 4 * factorial(3)
→ 5 * 4 * 3 * factorial(2)
→ 5 * 4 * 3 * 2 * factorial(1)
→ 5 * 4 * 3 * 2 * 1         ← 触底反弹(return)
→ 5 * 4 * 3 * 2
→ 5 * 4 * 6
→ 5 * 24
→ 120

4.3 斐波那契数列

// 斐波那契:1, 1, 2, 3, 5, 8, 13, 21...
// 定义:f(1)=1, f(2)=1, f(n)=f(n-1)+f(n-2)

public static int fib(int n) {
    if (n == 1 || n == 2) {
        return 1;
    }
    return fib(n - 1) + fib(n - 2);
}

System.out.println(fib(6));  // 8

⚠️ 性能警告:上面这个递归版斐波那契效率极低——fib(50) 跑到天荒地老,因为大量重复计算。后面会学到用循环或记忆化递归来优化。递归不是万能的,要判断是否值得。

4.4 递归的核心三要素

  • 终止条件:必须有明确的结束条件,否则栈溢出
  • 递归步骤:每次调用都要让问题规模缩小
  • 问题能够分解:大问题能拆成小问题的子问题,且解法相同

能用递归解决的问题通常也可以用循环解决。递归的优点是代码写起来”数学感”强、清晰,缺点是性能不如循环、容易栈溢出。递归深度超过几千层就要小心了。

五、综合示例:方法改造学生成绩系统

现在我们有方法了,可以把之前的代码模块化

import java.util.Arrays;

public class ScoreSystemV2 {
    public static void main(String[] args) {
        int[] scores = {76, 88, 62, 95, 81};
        
        printArray("原始成绩", scores);
        
        int sum = sum(scores);
        double avg = avg(scores);
        int max = max(scores);
        int min = min(scores);
        
        System.out.println("总分:" + sum);
        System.out.println("平均分:" + avg);
        System.out.println("最高分:" + max);
        System.out.println("最低分:" + min);
        
        // 递归版阶乘(做个展示)
        System.out.println("5! = " + factorial(5));
    }
    
    // 工具方法
    public static void printArray(String label, int[] arr) {
        System.out.println(label + ":" + Arrays.toString(arr));
    }
    
    public static int sum(int[] arr) {
        int total = 0;
        for (int n : arr) total += n;
        return total;
    }
    
    public static double avg(int[] arr) {
        return (double) sum(arr) / arr.length;  // 复用 sum()
    }
    
    public static int max(int[] arr) {
        int max = arr[0];
        for (int n : arr) if (n > max) max = n;
        return max;
    }
    
    public static int min(int[] arr) {
        int min = arr[0];
        for (int n : arr) if (n < min) min = n;
        return min;
    }
    
    // 递归
    public static int factorial(int n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }
}

对比一下——main 方法只有 11 行,每个功能有单独的方法。这就是模块化编程的魅力:清晰、可复用、好维护。

总结

  • 方法是代码的”积木块”,有修饰符、返回值、方法名、参数列表和方法体
  • 重载让同名方法处理不同类型/数量的参数,典型如 println()
  • 值传递:Java 只有值传递。基本类型传副本,引用类型传引用副本——修改引用指向对象的内容会反映到外部
  • 递归是方法自调用,必须有终止条件。代码简洁但性能稍差,深度大时慎用

下一篇我们将做数组与方法的综合练习,刷几道实在的题目巩固这些知识点。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

作者

2248768396@qq.com

相关文章

09. 数组与方法综合练习

开篇引子 前面三篇文章讲了数组(一维、二维、...

读出全部

06. 数组基础:一维数组、遍历与常见操作

开篇引子——为什么需要数组? 想象一下:你要...

读出全部