Mr.Mou @ ShiShi AP Center

AP CSA 3.5:编写方法

在 3.3 和 3.4 中,我们已经搭好了一个类的基本骨架:实例变量保存对象的数据,构造器负责初始化这些数据。

这一课继续完成第三部分:方法(methods)定义对象能做什么,也决定类外代码怎样读取或修改对象的数据。

写方法时,先问两个问题:这个方法要不要返回一个值?它需要从外面接收什么数据?

核心知识点

1. 第一个 AP 核心考法

继续使用前两课的 Pet 类:

public class Pet
{
    private String name;
    private int energy;

    public Pet(String n, int e)
    {
        name = n;
        energy = e;
    }

    public ___ getEnergy()
    {
        return energy;
    }

    public ___ feed(int amount)
    {
        energy += amount;
    }
}

两个空格应该分别填什么?

A

void, int

B

int, void

C

int, int

答案:B

先不要背格式,先看每个方法“做了什么”:

getEnergy()
    ↓
要把 energy 的值交给调用者
    ↓
需要返回 int

feed(int amount)
    ↓
只负责修改 energy
    ↓
不需要返回值 → void

所以写方法前,最重要的第一步不是想名字,而是先判断:

这个方法是“做一件事”,还是“算出 / 提供一个值”?

2. 一个方法由什么组成?

看这个方法:

public void feed(int amount)
{
    energy += amount;
}

可以拆成两部分:

public void feed(int amount)
↑      ↑    ↑      ↑
访问   返回  方法名  参数列表
权限   类型

{
    energy += amount;
}
↑
method body

方法 header 的常见结构是:

public returnType methodName(parameters)

例如:

public int getEnergy()
public void feed(int amount)
public String getName()
public boolean isTired()

方法写在 class 里面,因此 instance method 可以直接使用这个对象的实例变量:

public void feed(int amount)
{
    energy += amount;
}

这里不需要重新声明 energy,因为它已经是这个 class 的 instance variable。

从类外调用时,仍然使用 Unit 1 学过的写法:

Pet p = new Pet("Milo", 50);
p.feed(20);

Unit 1 的重点是会调用别人写好的方法;现在 3.5 的重点变成自己写出这些方法。

3. void 和 non-void:先决定有没有返回值

这是整课最核心的区别。

void method:完成动作,不返回值

public void feed(int amount)
{
    energy += amount;
}

调用:

p.feed(20);

执行后对象的 energy 发生变化,但 feed 没有把一个值返回给调用者。

常见的 void method 会:


non-void method:返回一个值

public int getEnergy()
{
    return energy;
}

因为返回的是 int,header 中必须写:

public int getEnergy()

调用后可以使用返回值:

int current = p.getEnergy();
System.out.println(p.getEnergy());

可以把过程理解成:

p.getEnergy()
      ↓
return energy;
      ↓
把一个 int 值送回调用的位置

return type 必须匹配

public String getName()
{
    return name;
}
public boolean isTired()
{
    return energy < 20;
}

return 后面可以是变量,也可以是一个计算后得到正确类型的 expression。

4. return 不只是“给答案”

在 non-void method 中,return 有两个作用:

  1. 返回一个值
  2. 立即结束当前方法

例如:

public int getEnergy()
{
    return energy;
    System.out.println("Done");
}

return energy; 执行以后,方法已经结束,所以后面的代码不会执行。

因此下面这种结构也很重要:

public boolean hasEnoughEnergy(int needed)
{
    if (energy >= needed)
    {
        return true;
    }

    return false;
}

一旦执行:

return true;

控制流程就直接回到调用这个方法的位置,不会继续执行这个方法剩下的代码。

常见 non-void 错误

public int getEnergy()
{
    System.out.println(energy);
}

这里虽然打印了 energy,但没有返回一个 int 值。

正确写法:

public int getEnergy()
{
    return energy;
}

打印(print)和返回(return)不是一回事。

5. Getter:读取 private 数据

3.3 中我们把 instance variables 设为 private:

private String name;
private int energy;

这样类外代码不能直接写:

System.out.println(p.energy);   // 不应该直接访问 private data

如果我们希望类外代码能读取 energy,可以提供一个 accessor method,平时通常叫 getter:

public int getEnergy()
{
    return energy;
}

调用:

int e = p.getEnergy();

典型 getter 的结构:

public Type getVariableName()
{
    return variableName;
}

例如:

public String getName()
{
    return name;
}
public int getEnergy()
{
    return energy;
}

getter 最重要的特点:

特征 Getter
是否返回值 ✅ 是
常见 return type 和实例变量类型相同
是否通常有参数 ❌ 没有
是否修改实例变量 通常不会

Getter 让外部代码“看见”数据,但不会直接把 private 变量变成 public。

而且不是每一个 instance variable 都必须提供 getter;是否开放读取,是 class design 的一部分。

6. Setter:修改 private 数据

如果希望类外代码能够修改某个实例变量,可以提供 mutator method,通常叫 setter。

例如:

public void setEnergy(int newEnergy)
{
    energy = newEnergy;
}

调用:

p.setEnergy(90);

数据流是:

90
↓
newEnergy
↓
energy

典型 setter 的结构:

public void setVariableName(Type newValue)
{
    variableName = newValue;
}

Getter 和 Setter 放在一起看最清楚:

  Getter Setter
目的 读取数据 修改数据
常见返回类型 实例变量的类型 void
参数 通常没有 通常有一个
例子 getEnergy() setEnergy(int e)

不要机械地给每个变量都写 setter

Runestone 特别强调:并不是所有 instance variables 都应该允许外部代码随意修改。

例如,一个对象可能允许:

p.getEnergy();

但不一定应该允许:

p.setEnergy(1000000);

更合理的设计可能是只提供有明确行为含义的方法:

p.feed(20);
p.play(10);

这样 class 可以控制自己的数据应该怎样改变。

这也是 3.3 中 data encapsulation(数据封装) 的继续应用。

7. toString():对象怎样变成可读文字

Java 中有一个非常重要的特殊方法:

public String toString()

例如:

public String toString()
{
    return name + " (energy: " + energy + ")";
}

假设:

Pet p = new Pet("Milo", 80);

下面两种写法得到相同的对象描述:

System.out.println(p.toString());
System.out.println(p);

当 println 收到一个 object 时,Java 会自动使用这个对象的 toString() 方法取得它的 String 表示。

所以:

System.out.println(p);

可以理解成:

p
↓
调用 p.toString()
↓
"Milo (energy: 80)"
↓
打印

对象和 String 使用 + 拼接时,也会使用 toString():

System.out.println("My pet: " + p);

可能输出:

My pet: Milo (energy: 80)

toString() 的固定特征

public String toString()

8. Parameters、Arguments 和 primitive pass-by-value

学生在 Unit 1 已经调用过大量带参数的方法:

str.substring(1, 4);
Math.pow(2, 3);
p.feed(20);

现在从“写方法”的角度重新看:

public void feed(int amount)
{
    energy += amount;
}

这里:

parameter(形参)
        ↓
int amount

调用:

p.feed(20);

这里的 20 是 argument(实参)。

argument → parameter
20       → amount

参数和实参必须在 个数、类型、顺序 上匹配 method header。


Primitive arguments:传进去的是值的副本

这是 AP 很重要的一点。

public void feed(int amount)
{
    energy += amount;
    amount = 0;
}

然后:

int snack = 20;
p.feed(snack);

调用时可以理解成:

snack = 20
   ↓ copy
amount = 20

方法里面执行:

amount = 0;

只改变参数变量 amount,不会把原来的 snack 改成 0。

所以方法执行结束后:

snack → 20

但这一句:

energy += amount;

确实修改了 p 对象的 instance variable energy。

因此要区分两件事:

修改 primitive parameter
→ 不会修改原来的 primitive argument

修改当前对象的 instance variable
→ 会改变这个 object 的状态

3.5 先掌握 primitive values 的这一规则;把 object reference 作为参数传递会在 3.6 单独学习。

9. 带参数、又有返回值的方法

方法不只是 getter 和 setter。

它也可以:

接收数据 → 计算 → 返回结果

例如:

public boolean hasEnoughEnergy(int needed)
{
    return energy >= needed;
}

调用:

boolean canPlay = p.hasEnoughEnergy(30);

过程是:

30
↓
needed
↓
energy >= needed
↓
boolean result
↓
return

再例如:

public int energyAfterEating(int food)
{
    return energy + food;
}

它计算一个结果并返回,但没有修改 energy 本身。

对比:

public void feed(int food)
{
    energy += food;
}

这两个方法看起来相似,但作用完全不同:

方法 修改 energy? 返回值?
energyAfterEating(int food) ❌ ✅ int
feed(int food) ✅ ❌ void

所以不要只看 method name,要读 method body 判断它真正做了什么。

10. 把一个 Pet 类完整起来

到这里,一个简单的 class 已经可以同时包含 constructor、getter、setter、普通行为方法和 toString():

public class Pet
{
    private String name;
    private int energy;

    public Pet(String n, int e)
    {
        name = n;
        energy = e;
    }

    public String getName()
    {
        return name;
    }

    public int getEnergy()
    {
        return energy;
    }

    public void setName(String newName)
    {
        name = newName;
    }

    public void feed(int amount)
    {
        energy += amount;
    }

    public boolean hasEnoughEnergy(int needed)
    {
        return energy >= needed;
    }

    public String toString()
    {
        return name + " (energy: " + energy + ")";
    }
}

外部代码可以这样使用:

Pet p = new Pet("Milo", 50);

System.out.println(p.getEnergy());
p.feed(20);
System.out.println(p.getEnergy());
System.out.println(p);

输出:

50
70
Milo (energy: 70)

现在一个 class 的三个主要部分已经连起来了:

instance variables
保存对象的数据
        ↓
constructor
设置初始状态
        ↓
methods
读取数据 / 修改状态 / 完成行为 / 返回计算结果

11. 常见初学者错误

错误 错误代码 / 想法 问题 正确理解
non-void method 没有返回值 public int getEnergy() { System.out.println(energy); } 打印不是返回 return energy;
返回类型和实际值不匹配 public int getName() { return name; } name 是 String public String getName()
只调用返回值方法,却没有使用结果 p.getEnergy(); 返回值被直接丢掉 保存、打印或放进表达式中使用
认为 return 后的代码还会执行 return x; 后继续写顺序代码 方法已经结束 return 会立即退出方法
Setter 赋值方向写反 newEnergy = energy; 没有修改 instance variable energy = newEnergy;
把 getter 写成 void public void getEnergy() Getter 应提供一个值 public int getEnergy()
认为修改 primitive parameter 会修改原变量 方法中 amount = 0 parameter 只是 argument value 的 copy 原 primitive argument 不变
每个变量机械地都写 setter 所有 private data 都允许任意修改 可能破坏 class 的设计规则 只有真正需要时才开放 setter

12. 小练习(Mini Practice)

Practice 1:Getter 还是 Setter?

private int score;

下面哪个方法可以让类外代码读取 score?

A

public void getScore()
{
    System.out.println(score);
}

B

public int getScore()
{
    return score;
}

C

public void setScore(int s)
{
    score = s;
}

答案:B

Getter 是 non-void method,需要把 instance variable 的值返回给调用者。


Practice 2:修复 method header

目标:把 additionalPeople 加到 numOfPeople 中,不需要返回值。

public int addPeople(int additionalPeople)
{
    numOfPeople += additionalPeople;
}

应该怎样修改?

答案:

public void addPeople(int additionalPeople)
{
    numOfPeople += additionalPeople;
}

这个方法的任务只是修改对象状态,因此 return type 应该是 void。


Practice 3:Primitive parameter 会不会改掉原变量?

public void usePoints(int points)
{
    points = points - 5;
}

调用:

int bonus = 20;
obj.usePoints(bonus);
System.out.println(bonus);

输出是什么?

答案:

20

bonus 的值被复制给 parameter points。方法修改的是这个副本,不是原来的 bonus。


Practice 4:toString()

public String toString()
{
    return name + ": " + energy;
}

如果:

Pet p = new Pet("Luna", 60);
System.out.println(p);

输出是什么?

答案:

Luna: 60

println 收到 object 时,会自动使用这个对象的 toString()。


Practice 5:返回还是修改?

public int futureEnergy(int food)
{
    return energy + food;
}

假设:

Pet p = new Pet("Milo", 50);
int result = p.futureEnergy(20);

执行以后:

result = ?
energy = ?

答案:

result = 70
energy = 50

这个方法只计算并返回 energy + food,并没有执行:

energy += food;

所以对象本身的 energy 没有改变。

Unit 3.5 核心词汇(Vocabulary)

Vocabulary 中文理解 核心理解 / Example
method / 方法 class 中完成特定任务的一段代码 定义对象的行为
method header / 方法头 方法第一行,说明访问权限、返回类型、名称和参数 public int getEnergy()
method body / 方法体 { } 中真正执行的代码 energy += amount;
return type / 返回类型 方法返回值的数据类型 int、String、boolean、void
void method / 无返回值方法 完成动作但不返回值的方法 public void feed(int amount)
non-void method / 有返回值方法 返回一个值的方法 public int getEnergy()
return statement / 返回语句 返回一个值并立即结束当前方法 return energy;
accessor / getter / 访问器 让外部代码获得 instance variable 的值 getEnergy()
mutator / setter / 修改器 修改 instance variable 的方法 setEnergy(int e)
toString() 返回对象的 String 描述 System.out.println(object) 会自动使用
parameter / 形参 方法定义中用来接收数据的变量 int amount
argument / 实参 调用方法时真正传进去的值 p.feed(20) 中的 20
pass by value / 值传递 parameter 得到 argument value 的副本 修改 primitive parameter 不改变原变量