Mr.Mou @ ShiShi AP Center

AP CSA 3.8:作用域与访问

前面几课中,我们已经写过 instance variables、parameters 和 local variables。

这一课不再学习一种新的 variable,而是回答一个非常重要的问题:

一个 variable 到底在哪里“存在”,哪些代码可以使用它?

这就是 scope(作用域)。

判断 scope 时,一个非常实用的方法是:

找到 variable declaration,然后看包住它的最近一层 { }。这个 variable 通常只能在这一层范围内使用。

核心知识点

1. 第一个 AP 核心考法:哪个 variable 在哪里能用?

先看这个 class:

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

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

    public void play(int minutes)
    {
        int energyUsed = minutes * 2;

        if (energyUsed > 10)
        {
            String message = "Big workout!";
            System.out.println(message);
        }

        energy -= energyUsed;
    }
}

这里出现了 7 个 variable:

name
energy
n
e
minutes
energyUsed
message

它们并不拥有相同的 scope。

Variable 在哪里声明 Scope 可以在哪里使用
name class body class scope 整个 Pet class 内
energy class body class scope 整个 Pet class 内
n constructor parameter method scope 只在 constructor 内
e constructor parameter method scope 只在 constructor 内
minutes method parameter method scope 只在 play 内
energyUsed play method body method scope 从声明处到 play 结束
message if block block scope 只在这个 if { } 内

这一课最重要的 mental model 就是:

class
│
├── instance variables → 整个 class 看得见
│
├── constructor / method
│   ├── parameters → 只在这个 method 内
│   ├── local variables → 只在这个 method 内
│   │
│   └── if / for / while block
│       └── block variables → 只在这个 block 内

scope 越往里面,范围通常越小。

2. Class Scope:Instance Variables

Instance variables 在 class body 中声明:

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

    // methods...
}

这里:

name
energy

都有 class-level scope(class scope)。

所以同一个 class 里的 constructor 和 instance methods 都可以直接使用它们:

public Pet(String n, int e)
{
    name = n;
    energy = e;
}
public void rest()
{
    energy += 10;
}
public String toString()
{
    return name + ": " + energy;
}

这些 constructor / instance methods 都可以访问:

name
energy

因为它们是 class scope 的 instance variables。

private 会不会缩小 scope?

不会。

这是 3.8 很容易混淆的一点。

private int energy;

private 表示:

class 外部不能直接访问这个 variable。

但在 Pet class 内部,energy 仍然具有 class scope,所有 instance methods 都可以使用它。

所以要区分:

scope
→ variable 在哪些代码区域里存在 / 能被名字直接找到

access modifier
→ class 外部是否允许直接访问这个 member

它们有关联,但不是同一个概念。

3. Method Scope:Parameters 和 Local Variables

看这个 method:

public void play(int minutes)
{
    int energyUsed = minutes * 2;
    energy -= energyUsed;
}

这里有两个 local variables:

minutes
energyUsed

其中:

minutes

是 parameter variable(参数变量)。

energyUsed

是在 method body 中声明的 local variable(局部变量)。

两者都只属于:

play method

所以另一个 method 不能直接写:

public void printInfo()
{
    System.out.println(minutes);     // error
    System.out.println(energyUsed);  // error
}

Java 不会认为:

“反正它们都在 Pet class 里面,应该能用吧。”

不行。

因为这两个 variables 只存在于 play 的 scope 中。

Parameter 也是 Local Variable

这一点 AP 很喜欢考:

public Pet(String n, int e)

这里:

n
e

都是 constructor 的 local variables。

Constructor 结束以后,n 和 e 就不能再使用。

这也是为什么我们通常把参数值存进 instance variables:

name = n;
energy = e;

可以理解为:

n / e
constructor 的临时数据
        ↓
name / energy
object 长期保存的数据

4. Local Variable 不能写 public 或 private

下面这种写法是错误的:

public void play(private int minutes)
{
}

也不能这样:

public void play(int minutes)
{
    private int energyUsed = minutes * 2;
}

为什么?

public 和 private 是用来控制 class members 对外访问权限的。

但 parameter 和 local variable 本来就只存在于自己的 method / block 中,因此不能声明为:

public
private

正确写法:

public void play(int minutes)
{
    int energyUsed = minutes * 2;
}

5. Block Scope:if、for、while 里面的 Variables

Scope 还可以比一个 method 更小。

看这个例子:

public void checkEnergy()
{
    if (energy < 20)
    {
        String warning = "Low energy";
        System.out.println(warning);
    }

    System.out.println(warning);  // error
}

warning 声明在:

if (...)
{
    String warning = ...;
}

所以它只能在这个 { } 中使用。

一旦离开:

if block

warning 就超出 scope 了。

for loop 中的 variable

Unit 2 中你其实已经用了很多 block scope:

for (int i = 0; i < 5; i++)
{
    System.out.println(i);
}

i 只存在于这个 loop 中。

所以:

for (int i = 0; i < 5; i++)
{
    System.out.println(i);
}

System.out.println(i);  // error

最后一行不能编译。

可以把它想成:

for block starts
    i exists
    i exists
    i exists
for block ends
    ↓
i no longer exists

6. 最实用的判断方法:找最近的 { }

遇到 scope 题时,不要靠感觉。

先找到 variable declaration:

int score = 10;

然后问:

它被哪一层最近的 { } 包住?

例如:

public void test()
{
    int x = 10;

    if (x > 5)
    {
        int y = 20;
    }
}

对于 x:

最近的 enclosing braces
→ test() 的 { }
→ method scope

对于 y:

最近的 enclosing braces
→ if 的 { }
→ block scope

而 instance variable:

public class Pet
{
    private int energy;
}

最近的 enclosing braces 是:

class Pet { }

所以是 class scope。

7. AP 高频错误:在另一个 Method 中使用 Local Variable

看这个 class:

public class Pet
{
    private int energy;

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

    public void play(int amount)
    {
        energy = newEnergy - amount;
    }
}

为什么不能编译?

问题是:

newEnergy

声明在:

eat()

所以它只存在于 eat() 的 method scope。

play() 里面看不到它。

eat()
┌─────────────────┐
│ newEnergy       │
└─────────────────┘

play()
┌─────────────────┐
│ newEnergy ???   │ ← 不存在
└─────────────────┘

一种直接的修复方式:

public void play(int amount)
{
    energy = energy - amount;
}

这里使用 energy 没问题,因为:

energy

是 class scope 的 instance variable。

这正是 Runestone AP Practice 中非常典型的 scope violation 类型。

8. 同名 Variable:Local Variable 会“遮住” Instance Variable

这是 3.8 第二个非常重要的 AP 考点。

看这个 class:

public class Pet
{
    private int energy;

    public Pet(int e)
    {
        energy = e;
    }

    public int getEnergy()
    {
        int energy = 100;
        return energy;
    }
}

假设:

Pet p = new Pet(50);
System.out.println(p.getEnergy());

输出是什么?

100

不是:

50

为什么?

因为 getEnergy() 中又声明了一个:

int energy = 100;

现在同一个 method 中同时存在:

instance variable energy → 50
local variable energy    → 100

直接写:

energy

Java 会优先使用 local variable。

所以:

return energy;

返回的是:

100

下一课 3.9 会学习如何使用:

this

明确区分“当前 object 的 instance variable”和 local variable。这里暂时只需要记住:

同名时,在 method / constructor 中直接写 variable name,优先指向 local variable 或 parameter。

9. Scope 和 Access 不要混在一起

题目中经常会同时出现:

scope
public / private

但它们在问不同问题。

概念 核心问题 Example
scope 这个 variable 在哪里存在、哪里能使用? i 只能在 loop 中
access class 外部能不能直接访问这个 member? private int energy 不能被外部直接访问

例如:

private int energy;

energy:

scope → 整个 Pet class
access → class 外不能直接访问

所以 private 不代表只有 constructor 能看到,也不代表只有某一个 method 能看到。

同一个 class 中的其他 instance methods 都可以使用它:

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

public void play(int amount)
{
    energy -= amount;
}

10. 常见初学者错误

错误 错误代码 / 想法 问题 正确理解
在另一个 method 中使用 local variable methodB() 使用 methodA() 里的 x x 只存在于 methodA 在需要的 method 中重新计算 / 传入,或真正需要共享时使用 instance variable
在 loop 外使用 loop variable loop 后写 System.out.println(i); i 已超出 block scope 只能在 loop scope 中使用
认为 parameter 属于整个 class constructor 外继续使用 n parameter 是 local variable 需要长期保存就赋值给 instance variable
给 local variable 加 private private int total = 0; 写在 method 中 access modifier 不用于 local variable 写 int total = 0;
把 private 当成 scope 认为 private field 只能在一个 method 中用 private 控制 class 外访问 field 在 class 内仍有 class scope
同名 local variable 时误以为使用 field int energy = 100; return energy; local variable 遮住 instance variable 直接写 energy 指向 local variable
为解决 scope 问题把所有 variable 都改成 instance variable 所有临时计算都放 class level 扩大了不必要的状态 只在一个 method 用的数据应优先 local

11. 小练习(Mini Practice)

Practice 1:判断 Scope

public class Game
{
    private int score;

    public void play(int bonus)
    {
        int total = score + bonus;

        if (total > 100)
        {
            String message = "High score!";
        }
    }
}

判断下面 variables 的 scope:

score
bonus
total
message

答案:

Variable Scope
score class scope
bonus method scope
total method scope
message block scope

Practice 2:哪一行不能编译?

public void count()
{
    for (int i = 0; i < 3; i++)
    {
        int value = i * 10;
        System.out.println(value);
    }

    System.out.println(i);
}

答案:

System.out.println(i);

不能编译。

i 是 for loop 的 block variable,离开 loop 后已经超出 scope。


Practice 3:为什么报错?

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

public void showChange()
{
    System.out.println(newEnergy);
}

为什么 showChange() 不能使用 newEnergy?

答案:

newEnergy 是 eat() 中的 local variable,只存在于 eat() 的 method scope。


Practice 4:输出是什么?

public class Movie
{
    private int price;

    public Movie(int p)
    {
        price = p;
    }

    public int getPrice()
    {
        int price = 16;
        return price;
    }
}

运行:

Movie m = new Movie(25);
System.out.println(m.getPrice());

输出:

16

原因:getPrice() 中的 local variable price 遮住了 instance variable price。


Practice 5:修复 Scope Violation

public class Pet
{
    private int energy;

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

    public void play(int amount)
    {
        energy = updatedEnergy - amount;
    }
}

修复 play():

答案:

public void play(int amount)
{
    energy = energy - amount;
}

energy 是 instance variable,因此整个 Pet class 中都可以使用。

Unit 3.8 核心词汇(Vocabulary)

Vocabulary 中文理解 核心理解 / Example
scope / 作用域 variable 可以被访问或使用的代码范围 declaration 的位置决定 scope
class scope / 类作用域 整个 class 内都可使用 instance variable private int energy;
method scope / 方法作用域 只在某个 method / constructor 中使用 parameter、method local variable
block scope / 代码块作用域 只在某个 { } block 中使用 if / for 中声明的 variable
local variable / 局部变量 在 method、constructor 或 block 中声明的 variable int total = 0;
parameter variable / 参数变量 method / constructor header 中接收 argument 的 local variable play(int minutes) 中的 minutes
instance variable / 实例变量 在 class body 中声明、每个 object 自己拥有的数据 private int energy;
scope violation / 作用域错误 在 variable 的 scope 外使用它 method B 使用 method A 的 local variable
same-name variable rule / 同名变量规则 local variable / parameter 与 instance variable 同名时,前者优先 int price = 16; return price;
access modifier / 访问修饰符 控制 class member 从哪里可以直接访问 public、private