Mr.Mou @ ShiShi AP Center

AP CSA 3.7:类(static)变量与方法

前面几课中,我们写的大多数变量和方法都属于某一个具体 object。

这一课学习 static:有些数据和行为不属于某一个 object,而属于整个 class。

其实你已经见过很多次了:

Math.random();
Math.sqrt(25);

这里没有先创建 Math object,而是直接通过 class name 调用 method。

对于 class 的 variables / methods:instance member 属于具体 object;static member 属于整个 class。所有 objects 共享同一份 static variable。

核心知识点

1. 第一个 AP 核心考法:到底有几份数据?

先看这个 class:

public class Pet
{
    private String name;
    private static int petCount = 0;

    public Pet(String n)
    {
        name = n;
        petCount++;
    }

    public static int getPetCount()
    {
        return petCount;
    }
}

然后运行:

Pet p1 = new Pet("Milo");
Pet p2 = new Pet("Luna");
Pet p3 = new Pet("Pico");

System.out.println(Pet.getPetCount());

输出是什么?

A

1

B

2

C

3

答案:C

为什么?

关键在这里:

private static int petCount = 0;

petCount 是 class variable(类变量)。

整个 Pet class 只有一份 petCount:

              Pet class
          petCount = 3
               ▲
        ┌──────┼──────┐
        │      │      │
      p1      p2      p3
    "Milo"  "Luna"  "Pico"

三个 objects 各自有自己的 name:

p1.name → "Milo"
p2.name → "Luna"
p3.name → "Pico"

但它们共享同一个:

Pet.petCount

这就是 3.7 最重要的区别:

instance data 是“一人一份”;static data 是“全班共享一份”。

2. Instance Member 和 Static Member

Java class 中的 variable 和 method 都可以分成两类。

类型 属于谁 是否写 static 常见调用 / 使用方式
instance variable 某个 object 否 通过该 object 的 instance method 使用
class variable 整个 class 是 ClassName.variableName(如果是 public)
instance method 某个 object 否 objectName.methodName()
class method 整个 class 是 ClassName.methodName()

例如:

public class Pet
{
    private String name;          // instance variable
    private static int petCount;  // class variable

    public void printName()       // instance method
    {
        System.out.println(name);
    }

    public static int getPetCount() // class method
    {
        return petCount;
    }
}

最简单的判断方法就是先问:

这个成员描述的是“某一个 object”,还是“整个 class”?

如果每只 Pet 都应该有不同的名字:

private String name;

它应该是 instance variable。

如果所有 Pet 需要共同维护“已经创建了多少只 Pet”:

private static int petCount;

它应该是 class variable。

3. Class Method(static method)

Class method 也叫 static method。它可以是 public,也可以是 private。

基本语法:

public static returnType methodName(parameters)
{
    // method body
}

注意 static 的位置:

public static int getPetCount()
       ↑

它通常放在 access modifier(public / private)之后、return type 之前。

例如:

public static int getPetCount()
{
    return petCount;
}

调用:

Pet.getPetCount();

如果另一个 static method 也在同一个 class 中,可以直接用 method name 调用:

public static void printPetCount()
{
    System.out.println(getPetCount());
}

这种写法你在 Unit 1 已经见过很多次:

Math.random();
Math.sqrt(25);

Math.random() 和 Math.sqrt() 都是 class methods,所以不需要:

new Math()

就可以直接通过 class name 调用。

main 也是一个 familiar example:

public static void main(String[] args)

它也是 static method。

4. Static Method 最重要的限制

这一条是 3.7 最常考、也最容易出错的地方。

看这个 class:

public class Pet
{
    private String name;
    private static int petCount = 0;

    public void printName()
    {
        System.out.println(name);
    }

    public static void printCount()
    {
        System.out.println(petCount);
    }
}

这里:

printCount()

可以直接使用:

petCount

因为两者都是 static。

但是下面这种写法不行:

public static void printSomething()
{
    System.out.println(name);  // error
}

为什么?

因为 name 属于某一个 Pet object。

但 printSomething() 属于整个 Pet class。

当 Java 执行:

Pet.printSomething();

它根本不知道你想访问:

p1.name ?
p2.name ?
p3.name ?

所以 static method 不能直接访问 instance variable。

同理,它也不能直接调用 instance method:

public static void test()
{
    printName();  // error
}

因为 printName() 必须针对某一个具体 Pet object 执行。

可以记成:

static context
    ↓
知道整个 class 的共享数据
    ↓
不知道“当前是哪一个 object”

5. 如果 Static Method 拿到了一个 Object 呢?

Static method 不能直接访问 instance data,并不代表它永远不能使用 object。

如果 method 收到一个 object reference:

public static void showPet(Pet p)
{
    p.printName();
}

这是合法的。

调用:

Pet p1 = new Pet("Milo");
Pet.showPet(p1);

输出:

Milo

原因是现在 static method 知道你指的是哪个 object:

p → p1 object

所以它可以通过:

p.printName();

调用这个具体 object 的 instance method。

这就是 Runestone 总结里的关键规则:

static method 不能直接访问 instance members;但如果得到某个 object,就可以通过那个 object 来访问。

6. Class Variable(static variable)

Class variable 使用 static 声明:

private static int petCount = 0;

它属于 class,而不是某一个 object。

为什么 counter 很适合用 static?

继续看:

public Pet(String n)
{
    name = n;
    petCount++;
}

每调用一次 constructor:

new Pet(...)

共享的 petCount 就增加 1。

例如:

Pet p1 = new Pet("Milo");   // petCount = 1
Pet p2 = new Pet("Luna");   // petCount = 2
Pet p3 = new Pet("Pico");   // petCount = 3

这正是 class variable 常见的用途:

统计创建了多少个 objects
记录所有 objects 中的最大值
记录所有 objects 中的最小值
维护跨 objects 共享的数据

Runestone 也用最大温度作为例子。

例如:

public class Temperature
{
    private double temperature;
    public static double maxTemp = 0;

    public Temperature(double t)
    {
        temperature = t;

        if (t > maxTemp)
        {
            maxTemp = t;
        }
    }
}

执行:

Temperature t1 = new Temperature(75);
Temperature t2 = new Temperature(100);
Temperature t3 = new Temperature(65);

最终:

Temperature.maxTemp → 100.0

不是 65。

因为 maxTemp 是一份持续共享的数据,而不是每个 Temperature object 自己保存一个最大值。

7. public static Variable 如何访问?

如果 class variable 是 public:

public static int petCount;

class 外部可以使用:

Pet.petCount

也就是:

ClassName.variableName

这和 static method 的调用非常像:

Pet.getPetCount();

对比:

ClassName.variableName
ClassName.methodName()

原因一样:它们都属于 class。

8. final:这个 Variable 不能再改

3.7 的最后一个关键字是:

final

例如:

final double PI = 3.14;

一旦赋值之后,就不能再写:

PI = 4.2;  // error

Java 会报错。

如果一个值应该作为整个 class 共享的 constant(常量),经常会看到:

public static final double PI = 3.14;

三个关键词分别表示:

public → class 外部可以访问
static → 属于整个 class
final  → 不能被重新赋值

Java 的常量名称传统上使用全部大写:

MAX_SPEED
MAX_SCORE
PI

你其实早就用过一个真正的例子:

Math.PI

可以把它理解成一个属于 Math class 的公开常量。

9. 把 3.7 放进一个完整 Class

现在把这一课最重要的内容放在一起:

public class Pet
{
    // 每个 Pet object 自己有一份
    private String name;
    private int energy;

    // 整个 Pet class 共享一份
    private static int petCount = 0;

    // class constant
    public static final int MAX_ENERGY = 100;

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

    // instance method
    public void feed()
    {
        if (energy < MAX_ENERGY)
        {
            energy++;
        }
    }

    // instance method
    public String getName()
    {
        return name;
    }

    // class method
    public static int getPetCount()
    {
        return petCount;
    }
}

创建 objects:

Pet p1 = new Pet("Milo", 80);
Pet p2 = new Pet("Luna", 90);

现在:

p1.name      → "Milo"
p1.energy    → 80

p2.name      → "Luna"
p2.energy    → 90

Pet.petCount → 2

Pet.MAX_ENERGY → 100

调用 instance method:

p1.feed();

调用 class method:

Pet.getPetCount();

访问 public class constant:

Pet.MAX_ENERGY

这一张图可以把 3.7 的结构全部串起来:

Pet class
│
├── shared by the whole class
│   ├── static petCount
│   ├── static getPetCount()
│   └── static final MAX_ENERGY
│
├── p1 object
│   ├── name = "Milo"
│   └── energy = 80
│
└── p2 object
    ├── name = "Luna"
    └── energy = 90

10. 常见初学者错误

错误理解 / 写法 实际情况
“每个 object 都有自己的 static variable” ❌ static variable 属于 class,所有 objects 共享一份
“static method 就是普通 instance method 的另一种写法” ❌ static method 属于 class,不针对某个具体 object
在 static method 里直接写 System.out.println(name); ❌ name 是 instance variable,没有指定是哪一个 object
在 static method 中直接调用 printName(); ❌ instance method 需要一个具体 object
“static method 完全不能使用 object” ❌ 如果拿到 object reference,可以通过这个 object 调用 instance method
用 p1.getPetCount() 来理解 static method ⚠️ Runestone 说明 Java 可以通过 object 调 static method,但核心理解仍是它属于 class;AP 学习时优先识别 Pet.getPetCount()
“每创建一个 object,static counter 又从 0 开始” ❌ static counter 是共享的一份数据,会继续累积
“final 就是变量永远没有值” ❌ final variable 可以赋值,但之后不能重新赋值
final double pi = 3.14; 必须报错 ❌ 合法;只是常量传统上使用大写名称,如 PI
PI = 4.2; 在 PI 是 final 时合法 ❌ final variable 不能重新赋值

11. 小练习(Mini Practice)

Practice 1:Static Counter

public class Game
{
    private static int count = 0;

    public Game()
    {
        count++;
    }

    public static int getCount()
    {
        return count;
    }
}

执行:

Game g1 = new Game();
Game g2 = new Game();
Game g3 = new Game();
Game g4 = new Game();

System.out.println(Game.getCount());

输出是什么?

答案:

4

count 是 class variable,四次 constructor call 都更新同一份变量。


Practice 2:哪一行会出错?

public class Student
{
    private String name;
    private static int studentCount = 0;

    public static void printInfo()
    {
        System.out.println(studentCount); // Line 1
        System.out.println(name);         // Line 2
    }
}

答案:Line 2。

studentCount 是 static variable,可以由 static method 直接访问。

name 是 instance variable,但这里没有指定某个 Student object。


Practice 3:修复 Static Method

下面的方法不能编译:

public static void showName()
{
    System.out.println(name);
}

如果希望传入一个 Pet object 并打印它的名字,可以怎样修改?

一种正确写法:

public static void showName(Pet p)
{
    System.out.println(p.getName());
}

现在 static method 知道应该访问哪个 object。


Practice 4:最大值

public class Score
{
    public static int maxScore = 0;

    public Score(int s)
    {
        if (s > maxScore)
        {
            maxScore = s;
        }
    }
}

执行:

Score a = new Score(72);
Score b = new Score(95);
Score c = new Score(88);

System.out.println(Score.maxScore);

输出是什么?

答案:

95

第三个 object 的 88 不会替换已经保存的 95。


Practice 5:final

下面哪一行会导致错误?

public static final int MAX_LEVEL = 100;

System.out.println(MAX_LEVEL); // A
int x = MAX_LEVEL;             // B
MAX_LEVEL = 200;               // C

答案:C。

final variable 已经被赋值为 100,不能再重新赋值。


Practice 6:调用方式

假设:

public class Pet
{
    private static int petCount = 0;

    public void feed()
    {
        // ...
    }

    public static int getPetCount()
    {
        return petCount;
    }
}

下面哪一种最能体现两种 method 的归属?

p1.feed();
Pet.getPetCount();

答案:这两行正好体现核心区别。

instance method → object.method()
class method    → ClassName.method()

Unit 3.7 核心词汇(Vocabulary)

Vocabulary 中文理解 核心理解 / Example
static / 静态 表示成员属于整个 class private static int count;
class method / 类方法 属于 class 的 method,也叫 static method Math.random()
instance method / 实例方法 针对某个 object 执行的 method p1.feed()
class variable / 类变量 属于整个 class、所有 objects 共享的 variable static int petCount
instance variable / 实例变量 每个 object 自己保存的数据 private String name;
shared / 共享 多个 objects 使用同一份 class variable 所有 Pet 共享 petCount
static context / 静态环境 正在 static method 中执行代码 不能直接访问某个 object 的 instance variable
final / 最终变量 赋值后不能重新赋值的 variable final double PI = 3.14;
constant / 常量 不应改变的固定值 通常使用大写名称,如 PI
public static final 公开、属于 class、且不能修改 Math.PI