【Java基础|Day05】Java方法详解:从定义到内存原理
文章目录
- 📚 今日学习目标
- 📦 Java方法详解:从定义到内存原理
- 一、方法是什么?
- 1.1 为什么要用方法?
- 1.2 示例
- 二、方法的定义和调用
- 2.1 方法定义
- 2.2 方法调用
- 三、如何设计一个方法?
- 四、方法的重载
- 4.1 什么是方法重载?
- 4.2 为什么需要重载?
- 五、方法的内存原理
- 5.1 方法调用的栈内存模型
- 5.2 方法参数传递的内存原理
- 六、个人梳理:Java参数传递与C++的对比分析
- 6.1 Java传递引用类型参数与C++指针传递的相似性
- 6.2 小补充:C++的引用传递
📚 今日学习目标
- 掌握方法的定义、调用与设计思路
- 理解方法重载及其应用场景
- 掌握方法调用的内存原理,特别是栈内存的执行过程
- 理解基本数据类型和引用数据类型在方法传递中的区别
📦 Java方法详解:从定义到内存原理
一、方法是什么?
方法是程序中最小的执行单位,它将重复的、具有独立功能的代码封装起来,以提高代码的维护性和复用性。
1.1 为什么要用方法?
- 避免重复:将常用代码“封装”一次,随处“调用”。
- 易于维护:功能变更时,只需修改方法内部一处代码。
- 结构清晰:将复杂程序分解为多个小方法,逻辑一目了然。
1.2 示例
- 代码示例:
❌ 不使用方法的代码(重复冗余)
public class CalculatorWithoutMethod {
public static void main(String[] args) {
// 第一组数据计算
int a1 = 10, b1 = 5;
int sum1 = a1 + b1;
int diff1 = a1 - b1;
int product1 = a1 * b1;
int quotient1 = a1 / b1;
System.out.println("第一组结果: 和=" + sum1 + ", 差=" + diff1 + ", 积=" + product1 + ", 商=" + quotient1);
// 第二组数据计算(完全重复的结构)
int a2 = 20, b2 = 4;
int sum2 = a2 + b2;
int diff2 = a2 - b2;
int product2 = a2 * b2;
int quotient2 = a2 / b2;
System.out.println("第二组结果: 和=" + sum2 + ", 差=" + diff2 + ", 积=" + product2 + ", 商=" + quotient2);
// 多组重复...
// 问题:如果要修改计算逻辑,需要修改三处!如果发现有bug,需要在三个地方都修复!
}
}
✅ 使用方法后的代码(简洁)
public class CalculatorWithMethod {
public static void main(String[] args) {
// 只需调用方法,传入不同数据即可
calculateAndPrint(10, 5, "第一组");
calculateAndPrint(20, 4, "第二组");
// 多组计算只需要简单的一行调用
...
}
// 封装成一个方法
public static void calculateAndPrint(int a, int b, String groupName) {
int sum = a + b;
int diff = a - b;
int product = a * b;
int quotient = a / b;
System.out.println(groupName + "结果: 和=" + sum + ", 差=" + diff +
", 积=" + product + ", 商=" + quotient);
}
// 如果需要修改功能逻辑,只需修改这一个方法!
}
- 更贴切的生活案例比喻:假设你每天都需要“泡咖啡”。这个流程可以封装成一个 makeCoffee() 方法,里面包含了“研磨豆子”、“加热水”、“冲泡”等步骤。每天调用一次这个方法,就能得到一杯咖啡。
二、方法的定义和调用
方法必须先定义、后调用,就像必须先制造工具,才能使用它。
2.1 方法定义
将一段功能代码封装到一个方法中
// 方法定义
public static 返回值数据类型 方法名(形参列表){
方法体;
return 返回值;
}
各部分说明:
- public:访问修饰符,表示该方法可以被其他类访问(具体的后续会详细学习)
- static:表示该方法属于类,可通过类名直接调用(具体的后续会详细学习)
- 返回值类型:如果方法需要返回结果,则需指定返回值的类型(如 int、String 等);如果不需要返回结果,则使用 void
- 方法名:遵循小驼峰命名法(如 calculateSum)
- 形式参数:方法定义时声明的参数,用于接收调用时传入的值
- 方法体:{} 内的代码,是功能的具体实现
- return:结束方法或者结束方法并返回一个值。
2.2 方法调用
使用方法名来调用执行该方法中的代码
public static void main(String[] args) {
// 方法调用
方法名(实参列表);
}
- 实际参数:调用时,真实传入的数据。其类型、顺序、数量必须与方法形式参数严格匹配。
三、如何设计一个方法?
当你要创建一个新方法时,可以遵循以下流程思考:
- 明确功能:这个方法要做什么? → 确定方法体的逻辑。
- 确定输入:做这件事需要什么? → 确定形式参数和实际参数。
- 确定输出:做完后需要给出结果吗? → 确定返回值及返回值类型。
示例:
设计一个方法,实现两个整数相加:
- 功能:两个整数相加
- 需要什么:两个 int 类型的参数
- 需要返回结果吗:需要,返回 int 类型
public static int add(int a, int b) {
return a + b;
}
四、方法的重载
4.1 什么是方法重载?
在同一个类中,允许存在多个同名方法,只要它们的参数列表不同(参数类型、参数个数、参数顺序不同)即可。
- 注意:
重载与返回值类型无关,只与参数列表有关
4.2 为什么需要重载?
当多个方法实现相似功能但参数不同时,使用重载可让方法名保持一致,提高代码的可读性。
示例:
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;
}
五、方法的内存原理
理解内存,才能真正理解程序是如何运行的。
5.1 方法调用的栈内存模型
Java 方法调用使用栈内存来管理,每个方法调用时会在栈中创建一个栈帧,存储该方法的局部变量和调用信息。栈遵循先进后出原则。
生动比喻理解栈:把栈想象成一个只有一端开口的羽毛球筒。最先放入的羽毛球(最早调用的方法),必须等它上面的所有羽毛球(后调用的方法)都拿出来后,自己才能被取出。
我们以下面代码为例,分析其内存与执行流程:

public class MethodDemo {
public static void main(String[] args) {
eat();
}
public static void eat() {
study();
System.out.println("吃饭");
sleep();
}
public static void sleep() {
System.out.println("睡觉");
}
public static void study() {
System.out.println("学习");
}
}
内存执行流程:
- main 方法入栈
- main 调用 eat(),eat 入栈
- eat 调用 study(),study 入栈
- study 执行,输出“学习”,执行完毕出栈
- eat 继续执行,输出“吃饭”
- eat 调用 sleep(),sleep 入栈
- sleep 执行,输出“睡觉”,执行完毕出栈
- eat 执行完毕出栈
- main 执行完毕出栈
整个过程输出顺序为:学习 → 吃饭 → 睡觉
5.2 方法参数传递的内存原理
由之前数组篇DAY04的内存原理学习我们可以知道:
- 基本数据类型:传递的是值的副本
- 引用数据类型:传递的是地址的副本
情况一:传递基本数据类型(传递的是“值的副本”)

public class ArgsDemo01 {
public static void main(String[] args) {
int number = 100;
System.out.println("调用前:" + number); // 输出: 100
change(number);
System.out.println("调用后:" + number); // 输出: 100 (未改变!)
}
public static void change(int number) { // 接收到的是 main 中 number 值的副本:100
number = 200;// 修改的是 change 方法自己的局部变量 number
}
}
内存分析:
- main中的number变量存储值 100
- 调用 change(number) 时,将 100 这个值的副本传给change方法的形参number
- change方法中的number被改为 200,修改的是自己的局部变量 number
- 方法调用结束,change 的栈帧销毁,其局部变量number也随之消失,不影响 main 中的 number
情况二:传递引用数据类型(传递的是“地址的副本”)

public class ArgsDemo02 {
public static void main(String[] args) {
int[] arr = {10, 20, 30};
System.out.println("调用前:" + arr[1]); // 输出: 20
change(arr);
System.out.println("调用后:" + arr[1]); // 输出: 200 (改变了!)
}
public static void change(int[] arr) {// 接收到的是 main 中 arr 存储的地址的副本
arr[1] = 200; // 通过地址副本,找到了堆中真实的数组并修改
}
}
内存分析:
- main中arr是一个引用变量,它保存着数组对象在堆内存中的地址(假设为0x001)
- 调用 change(arr) 时,传递的是地址 0x001 的副本
- change方法的形参arr也拿到了这个地址副本,因此它和main的arr指向堆中同一个数组对象
- 修改 arr[1] 即修改堆内存中的数据
- 调用结束,change栈帧销毁,但堆中的数组已被修改。main的arr因为持有原地址,所以能看到变化。
核心对比总结:
| 特性 | 基本数据类型参数 (如 int a) | 引用数据类型参数 (如 int[] arr) |
|---|---|---|
| 传递内容 | 值的副本 | 地址的副本 |
| 形参与实参关系 | 两个完全独立的变量,只是存储的数据值相等 | 两个独立的引用变量,但指向堆中同一个对象 |
| 方法内修改的影响 | 不影响原始变量 | 可以通过地址访问数据值,影响原始引用所指向的对象内容 |
六、个人梳理:Java参数传递与C++的对比分析
学习完这部分后,不难会想到C++的地址传递,会觉得逻辑很相通,于是我进一步学习分析:
6.1 Java传递引用类型参数与C++指针传递的相似性
我们先看一个Java的例子:
public static void main(String[] args) {
int[] arr={1,2};
System.out.println("实参arr:"+arr);
// 方法调用
func(arr);
System.out.println("实参arr:"+arr); // 还是原来的地址!
}
// 方法定义
public static void func(int[] arr){
int[] arr2={1,2,3};
arr=arr2; // 让形参指向新数组
System.out.println("形参arr:"+arr+" arr2"+arr2);
return ;
}
// 实参arr:[I@f6f4d33
// 形参arr:[I@23fc625e arr2[I@23fc625e ← 形参指向了新的地址
// 实参arr:[I@f6f4d33 ← 实参还是原来的地址!
对应的C++指针版本:
void func(int* p){
*p=2;
int b=1;
p=&b; // 改变指针p的指向
cout << "在func中,p指向:" << p << endl;
}
void main(){
int a=1;
int* p=&a;
cout << "调用前,p指向:" << p << endl;
func(p);
cout << "调用前,p指向:" << p << endl;
return 0;
}
在这两种情况下,虽然我们能通过地址修改对象内容,但无法通过改变形参的指向来影响实参的指向。
并且我发现这些底层逻辑相通,传递的是地址值就可以通过地址找到并修改真实的数据。
类似的,C++值传递就类似与Java传递基本类型值,本质上传递的是值的副本,是两个独立的变量,存储相同的值,所以不会相互影响。
| 特性 | Java引用类型传递 (引用类型参数) | C++指针传递 (指针参数) |
|---|---|---|
| 传递什么 | 地址的副本 | 地址的副本 |
| 形参和实参 | 两个独立引用,指向同一对象 | 两个独立指针,存相同地址 |
| 能修改对象内容吗 | ✅ 能 | ✅ 能 |
| 能改变外部指向吗 | ❌ 不能(改变形参的指向不影响实参) | ❌ 不能(改变形参指针不影响实参指针) |
| 内存中状态 | 栈上有两个引用变量,指向堆上同一对象 | 栈上有两个指针变量,指向同一地址 |
区别:
- Java 不支持基本数据类型的地址传递,所有基本类型传递的都是值副本
- C++ 拥有指针,支持基本类型的指针传递,可直接修改原始变量
6.2 小补充:C++的引用传递
我们可以知道引用的底层是指针,所以我理解引用为特殊的指针,即常量指针
int a=10;
int& b=a; // 等价于 int* const b=&a;即常量指针,这也就是为什么引用不可以更改指向的原因
b=20; // 等价于 *b=20,系统会自动帮我们解引用
关键区别:
- 初始化:引用必须初始化,指针可以后赋值
- 可否为空:引用不能为null,指针可以为nullptr
- 可否改指向:引用不能改指向(相当于int* const),指针可以随便指
- 操作方式:引用自动解引用,指针要手动*p
以上为个人学习总结,旨在梳理个人理解。如有疏漏或不当之处,欢迎指正与交流。








