Java中的方法
方法作为Java程序的基本构建块,掌握它们的使用是成为合格程序员的关键一步。现在开始探讨方法的各个方面,通过案例理解其运作机制。
方法的定义
方法是一段封装了特定功能的代码块,可以重复调用。定义方法时需要考虑五个要素:访问修饰符、返回类型、方法名、参数列表和方法体。例如:
public class MethodDemo {
// 定义一个计算两个数之和的方法
public static int addNumbers(int num1, int num2) {
int sum = num1 + num2;
return sum;
}
}
在这个简单的加法方法中,public
是访问修饰符表示公开访问,static
表示属于类而非对象,int
是返回类型,addNumbers
是方法名,括号内的int num1, int num2
是参数列表,大括号内是方法体。
方法的调用
定义了方法后,需要调用它才能执行其中的代码。对于静态方法,可以通过类名直接调用;对于实例方法,则需要通过对象实例调用。例如:
# 源文件保存为“MethodCallExample.java”
public class MethodCallExample {
public static void main(String[] args) {
// 调用静态方法
int result = MethodDemo.addNumbers(5, 3);
System.out.println("5 + 3 = " + result);
// 实例方法调用示例
Calculator calc = new Calculator();
double area = calc.calculateCircleArea(2.5);
System.out.println("半径为2.5的圆面积: " + area);
}
}
class MethodDemo {
// 定义一个计算两个数之和的方法
public static int addNumbers(int num1, int num2) {
int sum = num1 + num2;
return sum;
}
}
class Calculator {
// 实例方法计算圆面积
public double calculateCircleArea(double radius) {
return Math.PI * radius * radius;
}
}
运行结果
5 + 3 = 8
半径为2.5的圆面积: 19.634954084936208
方法重载
Java允许在同一个类中定义多个同名方法,要求它们的参数列表不同。这称为方法重载,是Java多态性的一种表现。
# 源文件保存为“OverloadDemo.java”
public class OverloadDemo {
// 重载方法示例:不同类型的参数
public void print(int num) {
System.out.println("整数: " + num);
}
public void print(double num) {
System.out.println("浮点数: " + num);
}
// 重载方法示例:不同数量的参数
public void print(String str1, String str2) {
System.out.println("字符串1: " + str1);
System.out.println("字符串2: " + str2);
}
public static void main(String[] args) {
OverloadDemo demo = new OverloadDemo();
demo.print(10); // 调用print(int)
demo.print(3.14); // 调用print(double)
demo.print("Hello", "World"); // 调用print(String, String)
}
}
运行结果
整数: 10
浮点数: 3.14
字符串1: Hello
字符串2: World
注意事项:重载方法时,仅返回类型不同不足以构成重载。比如同时定义int process()
和String process()
会导致编译错误。方法重载应该基于参数差异,而不是返回值。
变量作用域
Java中的变量作用域决定了变量的可见性和生命周期。理解作用域对编写健壮代码至关重要。
# 源文件保存为“ScopeExample.java”
public class ScopeExample {
// 类变量/静态变量 - 整个类可见
static int classVar = 100;
// 实例变量 - 每个实例独有
int instanceVar = 200;
public void demoMethod(int param) { // 参数作用域是整个方法
int localVar = 300; // 局部变量 - 仅在方法内可见
if(true) {
int blockVar = 400; // 块级变量 - 仅在if块内可见
System.out.println("块内访问所有变量:");
System.out.println(blockVar + localVar + param + instanceVar + classVar);
}
// System.out.println(blockVar); // 错误!blockVar不可见
}
public static void main(String[] args) {
ScopeExample example = new ScopeExample();
example.demoMethod(50);
// System.out.println(localVar); // 错误!localVar不可见
System.out.println("类变量: " + ScopeExample.classVar);
System.out.println("实例变量: " + example.instanceVar);
}
}
运行结果
1050
类变量: 100
实例变量: 200
注意事项:变量遮蔽是常见问题,当局部变量与实例变量同名时,局部变量会遮蔽实例变量。可以使用this
关键字访问被遮蔽的实例变量。另一个常见错误是试图在变量作用域外访问它,比如在if块外访问块内定义的变量。
案例解析
简易计算器
编写一个程序,构建一个简易计算器.
# 源文件保存为“SimpleCalculator.java”
public class SimpleCalculator {
// 类变量记录使用次数
private static int usageCount = 0;
// 实例变量存储计算结果
private double lastResult;
// 方法重载:整数加法
public int add(int a, int b) {
usageCount++;
lastResult = a + b;
return (int)lastResult;
}
// 方法重载:浮点数加法
public double add(double a, double b) {
usageCount++;
lastResult = a + b;
return lastResult;
}
// 方法重载:三个数相加
public int add(int a, int b, int c) {
return add(add(a, b), c);
}
// 获取使用次数
public static int getUsageCount() {
return usageCount;
}
// 获取最后结果
public double getLastResult() {
return lastResult;
}
public static void main(String[] args) {
SimpleCalculator calc = new SimpleCalculator();
System.out.println("5 + 7 = " + calc.add(5, 7));
System.out.println("3.2 + 4.8 = " + calc.add(3.2, 4.8));
System.out.println("2 + 3 + 4 = " + calc.add(2, 3, 4));
System.out.println("计算器使用次数: " + SimpleCalculator.getUsageCount());
System.out.println("最后计算结果: " + calc.getLastResult());
}
}
运行结果
5 + 7 = 12
3.2 + 4.8 = 8.0
2 + 3 + 4 = 9
计算器使用次数: 4
最后计算结果: 9.0
这个案例展示了类变量与实例变量的区别,方法重载的实际应用,以及如何通过方法组织代码逻辑。usageCount被所有实例共享,而lastResult是每个实例独有的。三个add方法展示了基于参数类型和数量的重载。
常见错误及解决
- 方法签名冲突:定义了两个仅返回类型不同的方法。解决方法确保重载方法的参数列表不同。
- 变量作用域混淆:在方法外使用局部变量。检查变量声明位置,确保在正确的作用域内使用。
- 静态上下文错误:在静态方法中访问实例成员。要么将方法改为实例方法,要么通过对象实例访问实例成员。
- 参数传递误解:以为修改参数会影响原始变量。Java是值传递,对于基本类型,方法内修改不会影响原始值。
- 忽略返回值:调用有返回值的方法却没有使用结果。如果不需要返回值,考虑将方法改为void返回类型。
练习题
理论题
-
下面代码有什么问题?如何修正?
public class Test { public void print(int x) { System.out.println(x); } public int print(int y) { return y * 2; } }
参考答案 两个print方法具有相同的参数列表(int),仅返回类型不同,这不是有效的重载。修正方法是修改参数列表,例如将第二个方法改为
public int print(int y, int z)
。 -
分析变量作用域:类变量、实例变量、局部变量和块级变量在内存分配和生命周期上有何不同? 参考答案 在Java中,变量作用域的关键差异总结如下
变量类型 声明位置 内存区域 生命周期 作用域 类变量<br>(静态变量) 类中 + static
修饰方法区 类加载时创建 → 程序结束销毁 整个类(所有实例共享) 实例变量<br>(成员变量) 类中(无 static
)堆内存 对象创建时产生 → 对象被GC回收时销毁 对象实例内部 局部变量 方法/构造函数内部 栈内存 方法开始执行 → 方法执行结束 声明位置到方法结束 块级变量 {}
代码块内部<br>(如if/for)栈内存 进入代码块 → 退出代码块 声明位置到代码块结束 类变量:所有实例共享同一内存空间; 不受对象实例生命周期影响 。 实例变量:个对象拥有独立副本;生命周期绑定对象,
new MyClass()
→ GC回收
局部变量 & 块级变量:共同点:存储在栈内存 + 自动销毁;局部变量生命周期 > 块级变量(方法包含多个代码块时) 本质规律:- 生命周期:类变量 > 实例变量 > 局部变量 > 块级变量
- 内存开销:实例变量(堆) > 类变量(方法区) > 局部/块级(栈)
- 共享性:类变量(共享) > 实例变量(对象独享) > 局部/块级(不可共享)
-
解释方法调用时参数传递的机制,基本类型和对象引用在传递时有何区别? 参考答案 在Java中,方法调用采值传递(Pass by Value) 机制。基本类型和对象引用的区别如下:
- 基本类型(Primitive Types):传递值的副本;方法内操作的是原始数据的独立拷贝;方法内的修改不影响原始变量
-
对象引用(Object References):传递**引用的副本**;通过副本引用修改对象状态(影响原始对象)
基本类型和对象引用在传递时区别总结表
特性 基本类型 对象引用 传递内容 实际值的拷贝(如 10
)内存地址的拷贝(如 0x1001
)内存影响 完全隔离 共享同一对象 修改效果 不影响原始数据 可修改对象状态 重新赋值效果 形参修改不影响实参(安全) 形参重定向不影响实参指向 类比 复印文件后修改复印件 给朋友家门钥匙(能改家具陈设)
实践题
-
编写一个工具类,包含以下重载方法:
- 将一个整数数组转换为字符串,用指定分隔符连接
- 将一个字符串数组转换为字符串,用默认逗号连接
- 将三个整数用"a-b-c"格式连接
参考答案
# 源文件保存为“StringUtils.java” public class StringUtils { // 方法1: 整型数组 + 自定义分隔符 public static String join(int[] array, String delimiter) { if (array == null) return ""; StringBuilder sb = new StringBuilder(); for (int i = 0; i < array.length; i++) { sb.append(array[i]); if (i < array.length - 1) { sb.append(delimiter); } } return sb.toString(); } // 方法2: 字符串数组 + 默认逗号分隔符 public static String join(String[] array) { return join(array, ","); } // 字符串数组通用方法(支持任意分隔符) public static String join(String[] array, String delimiter) { if (array == null) return ""; StringBuilder sb = new StringBuilder(); for (int i = 0; i < array.length; i++) { if (array[i] != null) { sb.append(array[i]); if (i < array.length - 1) { sb.append(delimiter); } } } return sb.toString(); } // 方法3: 三个整数 + 固定格式连接 public static String join(int a, int b, int c) { return a + "-" + b + "-" + c; } // 测试用例 public static void main(String[] args) { // 测试整数数组转换 int[] numbers = {1, 2, 3, 4}; System.out.println("整数数组测试:"); System.out.println("默认分隔符: " + join(numbers, ", ")); // 1, 2, 3, 4 System.out.println("自定义分隔符: " + join(numbers, " | ")); // 1 | 2 | 3 | 4 System.out.println("空数组: " + join(new int[0], "-")); // 空行 // 测试字符串数组转换 String[] languages = {"Java", "Python", "C++", null}; System.out.println("\n字符串数组测试:"); System.out.println("默认逗号连接: " + join(languages)); // Java,Python,C++ System.out.println("自定义分隔符: " + join(languages, " => ")); // Java => Python => C++ System.out.println("空数组: " + join(new String[0])); // 空行 System.out.println("Null数组: " + join(null)); // 空行 // 测试三整数连接 System.out.println("\n三整数连接测试:"); System.out.println("结果: " + join(10, 20, 30)); // 10-20-30 System.out.println("零值: " + join(0, -5, 100)); // 0--5-100 } }
运行结果
整数数组测试: 默认分隔符: 1, 2, 3, 4 自定义分隔符: 1 | 2 | 3 | 4 空数组: 字符串数组测试: 默认逗号连接: Java,Python,C++, 自定义分隔符: Java => Python => C++ => 空数组: Null数组: 三整数连接测试: 结果: 10-20-30 零值: 0--5-100
-
创建一个银行账户类,包含:
- 类变量记录总账户数
- 实例变量存储余额
- 存款和取款方法(考虑余额不足情况)
- 静态方法获取总账户数
参考答案
# 源文件保存为“BankAccount.java” public class BankAccount { // 类变量:记录总账户数 private static int totalAccounts = 0; // 实例变量:账户属性和余额 private final String accountNumber; private double balance; private final String accountHolder; // 构造函数:创建新账户 public BankAccount(String accountNumber, String accountHolder, double initialBalance) { this.accountNumber = accountNumber; this.accountHolder = accountHolder; this.balance = initialBalance; // 增加总账户数(类变量) totalAccounts++; } // 存款方法(带异常检查) public void deposit(double amount) { if (amount <= 0) { throw new IllegalArgumentException("存款金额必须大于零"); } balance += amount; } // 取款方法(带余额不足检查) public void withdraw(double amount) throws InsufficientFundsException { if (amount <= 0) { throw new IllegalArgumentException("取款金额必须大于零"); } if (amount > balance) { throw new InsufficientFundsException("余额不足,可用余额: " + balance); } balance -= amount; } // 获取余额 public double getBalance() { return balance; } // 获取账户信息 public String getAccountInfo() { return String.format("账号: %s | 户主: %s | 余额: $%.2f", accountNumber, accountHolder, balance); } // 静态方法:获取总账户数 public static int getTotalAccounts() { return totalAccounts; } // 自定义异常:余额不足 public static class InsufficientFundsException extends Exception { public InsufficientFundsException(String message) { super(message); } } // 测试代码 public static void main(String[] args) { try { // 创建账户 BankAccount account1 = new BankAccount("ACC001", "张三", 1000.0); BankAccount account2 = new BankAccount("ACC002", "李四", 500.0); System.out.println("--- 操作前信息 ---"); System.out.println(account1.getAccountInfo()); System.out.println(account2.getAccountInfo()); System.out.println("总账户数: " + BankAccount.getTotalAccounts()); // 执行操作 account1.deposit(200.0); account2.withdraw(150.0); System.out.println("\n--- 操作后信息 ---"); System.out.println(account1.getAccountInfo()); System.out.println(account2.getAccountInfo()); // 测试异常情况 System.out.println("\n--- 测试异常 ---"); account2.withdraw(1000.0); // 余额不足异常 } catch (InsufficientFundsException e) { System.out.println("取款失败: " + e.getMessage()); } catch (IllegalArgumentException e) { System.out.println("参数错误: " + e.getMessage()); } } }
运行结果
--- 操作前信息 --- 账号: ACC001 | 户主: 张三 | 余额: $1000.00 账号: ACC002 | 户主: 李四 | 余额: $500.00 总账户数: 2 --- 操作后信息 --- 账号: ACC001 | 户主: 张三 | 余额: $1200.00 账号: ACC002 | 户主: 李四 | 余额: $350.00 --- 测试异常 --- 取款失败: 余额不足,可用余额: 350.0
-
调试以下代码,找出作用域问题并修正:
public class ScopeProblem { int value = 10; public void print() { int value = 20; System.out.println(value); } public void printValue() { System.out.println(value); } public static void main(String[] args) { ScopeProblem sp = new ScopeProblem(); sp.print(); sp.printValue(); } }
参考答案 代码中存在变量遮蔽问题,实例变量value被局部变量value遮蔽。print()方法中的value指局部变量(输出20),printValue()中的value指实例变量(输出10)。若想访问被遮蔽的实例变量,可修改print()为:
public void print() { int value = 20; System.out.println(this.value); // 明确指定实例变量 }