我会从最简单、最直接的方式讲起,然后逐步介绍更高级和灵活的方式,并提供完整的代码示例。

核心思想
在 Java 中,一个类要调用另一个类的函数,必须遵循以下步骤:
- 创建对象(实例化):要调用一个非静态(
static)的方法,你必须先创建该类的对象(实例),对象就是这个类在内存中的一个“具体代表”。 - 通过对象调用方法:使用
对象名.方法名()的语法来调用目标类中的方法。
直接调用(最常见)
这是最基础、最直接的方式,一个类创建另一个类的对象,然后调用其公共方法。
示例场景
假设我们有一个 Calculator 类,它提供一些数学计算功能,然后我们有一个 Main 类,作为程序的入口,它使用 Calculator 来进行计算。
步骤 1: 创建被调用的类 (Calculator.java)
这个类包含一些我们想要调用的方法。

// Calculator.java
public class Calculator {
// 这是一个公共方法,可以被其他类访问
public int add(int a, int b) {
System.out.println("Calculator: 正在执行加法...");
return a + b;
}
// 另一个公共方法
public double divide(double x, double y) {
System.out.println("Calculator: 正在执行除法...");
if (y == 0) {
System.out.println("错误:除数不能为零!");
return Double.NaN; // 返回“不是一个数字”
}
return x / y;
}
}
关键点:
public关键字表示这个方法可以被任何其他类访问。- 这些方法不是
static的,所以我们必须先创建Calculator的对象才能调用它们。
步骤 2: 创建调用类 (Main.java)
这个类会创建 Calculator 的对象并使用它。
// Main.java
public class Main {
public static void main(String[] args) {
// 1. 创建 Calculator 类的对象
// 这就像在现实生活中拿出一个计算器
Calculator myCalculator = new Calculator();
// 2. 通过对象调用 Calculator 类的方法
int sum = myCalculator.add(10, 5);
System.out.println("Main: 计算结果是 " + sum);
System.out.println("--------------------");
double result = myCalculator.divide(20.0, 4.0);
System.out.println("Main: 计算结果是 " + result);
System.out.println("--------------------");
myCalculator.divide(10.0, 0.0); // 测试除数为零的情况
}
}
关键点:
Calculator myCalculator = new Calculator();这行代码做了两件事:new Calculator(): 在内存中分配空间,创建一个新的Calculator对象。Calculator myCalculator: 声明一个名为myCalculator的Calculator类型的变量,让它指向新创建的对象。
myCalculator.add(10, 5): 使用 操作符,通过myCalculator这个对象来访问并执行add方法。
运行结果
Calculator: 正在执行加法...
Main: 计算结果是 15
--------------------
Calculator: 正在执行除法...
Main: 计算结果是 5.0
--------------------
Calculator: 正在执行除法...
错误:除数不能为零!
调用静态方法(无需创建对象)
如果一个方法被声明为 static,它就属于这个类本身,而不是属于任何一个特定的对象,调用静态方法时不需要创建对象。

修改 Calculator.java
我们添加一个静态方法 multiply。
// Calculator.java
public class Calculator {
// ... (之前的 add 和 divide 方法保持不变) ...
// 这是一个静态方法,属于 Calculator 类本身
public static int multiply(int a, int b) {
System.out.println("Calculator.multiply: 正在执行乘法..."); // 静态方法不能直接访问非静态成员
return a * b;
}
}
在 Main.java 中调用静态方法
// Main.java
public class Main {
public static void main(String[] args) {
// 调用静态方法,直接使用类名,不需要创建对象
int product = Calculator.multiply(6, 7);
System.out.println("Main: 计算结果是 " + product);
// 你也可以通过对象调用,但不推荐,这会引起混淆
// Calculator myCalculator = new Calculator();
// int product2 = myCalculator.multiply(6, 7);
}
}
关键点:
- 调用静态方法使用
类名.方法名()的语法。 - 静态方法通常用于工具类或与类状态无关的操作。
通过构造函数传递对象(更高级)
一个类需要长期持有另一个类的对象引用,而不是临时创建一个用完就扔,这时可以通过构造函数将对象传递进来。
示例场景
一个 Teacher 类需要知道他教的 Student 的信息。
步骤 1: 创建被依赖的类 (Student.java)
// Student.java
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
步骤 2: 创建依赖的类 (Teacher.java)
Teacher 类在创建时就需要一个 Student 对象。
// Teacher.java
public class Teacher {
private Student student; // Teacher 类持有一个 Student 对象的引用
// 构造函数,接收一个 Student 对象
public Teacher(Student student) {
this.student = student;
System.out.println("Teacher: 我开始教 " + student.getName() + " 了。");
}
// Teacher 的方法,可以调用它持有的 Student 对象的方法
public void doSomethingWithStudent() {
System.out.println("Teacher: " + student.getName() + " 同学,请回答问题!");
}
}
步骤 3: 在 Main.java 中组装它们
// Main.java
public class Main {
public static void main(String[] args) {
// 1. 先创建被依赖的对象
Student zhangSan = new Student("张三");
// 2. 将这个对象作为参数,传递给 Teacher 的构造函数
Teacher liTeacher = new Teacher(zhangSan);
// 3. Teacher 现在可以调用它内部持有的 Student 对象的方法
liTeacher.doSomethingWithStudent();
}
}
运行结果:
Teacher: 我开始教 张三 了。
Teacher: 张三 同学,请回答问题!
这种方式在复杂程序中非常常见,是“依赖注入”思想的基础。
接口回调(最灵活)
这是面向接口编程的核心思想,调用方不关心具体是哪个类实现了功能,只关心它实现了某个接口,这使得代码非常灵活,易于扩展和替换。
示例场景
一个 Button(按钮)被点击后,需要执行某个操作,这个操作的具体内容是由使用者决定的。
步骤 1: 定义一个接口 (ClickListener.java)
接口定义了一个“契约”,规定了必须实现哪些方法。
// ClickListener.java
public interface ClickListener {
// 当按钮被点击时,必须调用这个方法
void onClick();
}
步骤 2: 创建实现接口的类 (EmailSender.java 和 SmsSender.java)
这两个类都实现了 ClickListener 接口,但它们的行为完全不同。
// EmailSender.java
public class EmailSender implements ClickListener {
@Override
public void onClick() {
System.out.println("EmailSender: 发送一封邮件!");
}
}
// SmsSender.java
public class SmsSender implements ClickListener {
@Override
public void onClick() {
System.out.println("SmsSender: 发送一条短信!");
}
}
步骤 3: 创建调用方 (Button.java)
Button 类持有一个 ClickListener 类型的引用,它不关心这个引用具体指向 EmailSender 还是 SmsSender。
// Button.java
public class Button {
private ClickListener listener; // 持有接口类型的引用
// 设置点击监听器
public void setClickListener(ClickListener listener) {
this.listener = listener;
}
// 模拟按钮被点击
public void click() {
System.out.println("Button: 按钮被点击了!");
if (listener != null) {
listener.onClick(); // 调用接口的方法,具体执行哪个类的实现,取决于 set 的是谁
}
}
}
步骤 4: 在 Main.java 中使用
// Main.java
public class Main {
public static void main(String[] args) {
Button sendButton = new Button();
// 场景一:设置邮件发送器为点击监听器
System.out.println("--- 场景一:发送邮件 ---");
sendButton.setClickListener(new EmailSender());
sendButton.click();
// 场景二:轻松切换到短信发送器
System.out.println("\n--- 场景二:发送短信 ---");
sendButton.setClickListener(new SmsSender());
sendButton.click();
}
}
运行结果:
--- 场景一:发送邮件 ---
Button: 按钮被点击了!
EmailSender: 发送一封邮件!
--- 场景二:发送短信 ---
Button: 按钮被点击了!
SmsSender: 发送一条短信!
| 方式 | 适用场景 | 如何调用 | 优点 | 缺点 |
|---|---|---|---|---|
| 直接调用 | 最常见的情况,一个类需要临时使用另一个类的功能。 | ClassName obj = new ClassName();obj.method(); |
简单直观。 | 耦合度相对较高。 |
| 静态方法调用 | 工具类方法、与类实例无关的操作。 | ClassName.staticMethod(); |
无需创建对象,节省内存。 | 过度使用会破坏面向对象特性,难以继承和覆盖。 |
| 构造函数传递 | 一个类的生命周期依赖于另一个类。 | new DependentClass(new Dependency()); |
关系明确,利于管理依赖。 | 可能导致构造函数参数过多。 |
| 接口回调 | 需要解耦、提高灵活性和可扩展性的场景(如事件监听、策略模式)。 | button.setClickListener(new SomeListener());button.click(); |
低耦合、高内聚,是优秀设计的标志。 | 需要额外定义接口,增加了代码的复杂性。 |
对于初学者来说,掌握方式一(直接调用)和方式二(静态方法调用)是最重要的,随着经验的增长,你会越来越多地使用方式三和方式四来构建更健壮、更灵活的系统。
