← All Units← 返回单元列表 ← Course Hub← 课程主页
A P  C O M P U T E R  S C I E N C E  A
Unit 3 · Solutions第 3 单元 · 解析

Class Creation — Solutions类的创建 —— 解析

Companion to the AP-Style MC Practice SetAP 风格选择题练习的解析配套

MEDIUM HARD AP MC

Unit 3: Class Creation3 单元:类的创建CSA



MULTIPLE CHOICEWorked Answers详细解析

Multiple Choice — Worked Answers选择题(Multiple Choice)—— 详细解析

Each item restates the prompt and choices, marks the correct letter, and gives a brief justification. Trap distractors are called out where useful.每道题重述题干与选项、标出正确答案字母,并给出简明解析;对容易混淆的干扰项(trap distractor)单独提示。

Q1MEDIUMAP MCthis Keyword

What does this.size = size; do in the constructor?构造方法中的 this.size = size; 做什么?

Answer:答案: (A)
When a parameter shadows an instance variable (same name in scope), an unqualified size refers to the parameter. The prefix this. reaches past the parameter and refers explicitly to the instance variable of the current object.
  • Left of =: this.size → instance variable.
  • Right of =: bare size → parameter.
Compare with Q6: writing size = size; (no this.) self-assigns the parameter and leaves the field at its default value.
当参数与实例变量同名时,参数会遮蔽shadow)实例变量——不加前缀的 size 指的是参数。前缀 this. 跨过参数,明确指向当前对象的实例变量
  • = 左侧:this.size → 实例变量。
  • = 右侧:裸的 size → 参数。
对比 Q6:如果只写 size = size;(没有 this.),就是把参数自己赋给自己,实例变量保持默认值不变。
Q2MEDIUMAP MCstatic Variables

Which statement about a static variable is true?下列关于 static 变量的描述,哪一项是正确的?

Answer:答案: (C)
static binds the variable to the class, not to any individual instance. All instances share the same single storage location.
  • Access from outside: ClassName.varName.
  • Default initialization follows the type (numerics get 0, boolean gets false, references get null) — not "null regardless of type."
  • Reassignment is allowed unless declared final.
Use case: counters that span all instances (see Q5), shared constants, factory helpers.
static 把变量绑定到,而不是任何具体实例。所有实例共享同一个存储位置(类级别变量 class-level variable)。
  • 外部访问:ClassName.varName
  • 默认初始化跟随类型(数值类型为 0booleanfalse,引用类型为 null)—— 不是"无论什么类型都为 null"。
  • 除非声明为 final,否则可以重新赋值。
典型用途:跨所有实例的计数器(见 Q5)、共享常量、工厂辅助方法等。
Q3MEDIUMAP MCDefault Constructor

A class with no declared constructors:没有声明任何构造方法的类:

Answer:答案: (B)
The compiler synthesizes a public no-argument constructor with an empty body — i.e. public Foo() {} — so new Foo() works and instance variables fall back to their type defaults.

Important caveat: as soon as you declare any explicit constructor (e.g. public Foo(int x)), the compiler will no longer supply the no-arg default. new Foo() then becomes a compile-time error unless you write the no-arg form yourself. This is a common AP trap.

编译器会自动合成一个公有、无参数、方法体为空的构造方法——即 public Foo() {}——所以 new Foo() 可用,实例变量取各自类型的默认值。

重要注意:只要你显式声明了任何构造方法(例如 public Foo(int x)),编译器就不再自动提供无参版本。这时 new Foo() 会编译错误,除非你自己再写一个无参构造。这是 AP 常见陷阱。

Q4HARDAP MCAliasing + compound assignment

Box a = new Box(5); Box b = a; b.n = 10; a.n += b.n; println(a.n + " " + b.n);Box a = new Box(5); Box b = a; b.n = 10; a.n += b.n; println(a.n + " " + b.n);

Answer:答案: (D)
After Box b = a, both a and b reference the same heap object. Any mutation through one is visible through the other.
  • Start: object has n = 5; both a and b point to it.
  • b.n = 10 → the shared object's n is now 10. Reading a.n here would also give 10.
  • a.n += b.n reads a.n (= 10), reads b.n (also 10 — same object!), writes a.n = 20.
  • Print: both a.n and b.n read the shared n = 20.
Output: "20 20".

Trap (C) "20 10" is the classic mistake — assumes b is an independent copy of a. Java has no implicit cloning; assignment of references is shallow. (B) "15 10" reads a.n as if it were still 5 when the += evaluates.

Box b = a 之后,ab 引用同一个堆上的对象(别名 aliasing)。通过任一个的修改对另一个都可见。
  • 初始:对象的 n = 5ab 都指向它。
  • b.n = 10 → 共享对象的 n 现在是 10。此时读 a.n 也得到 10
  • a.n += b.na.n(= 10),读 b.n(也是 10 —— 同一个对象!),写 a.n = 20
  • 打印:a.nb.n 都读共享的 n = 20
输出:"20 20"

陷阱 (C) "20 10" 是最常见的错误——以为 ba 的独立副本。Java 没有隐式克隆;引用赋值是浅层(shallow)的。(B) "15 10"+= 求值时把 a.n 当成仍然是 5

Q5HARDAP MCstatic aggregate + instance mutator

Sensor with static int total; update(r) does total -= reading; reading = r; total += r;.
s1 = new Sensor(10); s2 = new Sensor(20); s1.update(50); s2.update(0); println(Sensor.total());
Sensor 类含有 static int totalupdate(r)total -= reading; reading = r; total += r;
s1 = new Sensor(10); s2 = new Sensor(20); s1.update(50); s2.update(0); println(Sensor.total());

Answer:答案: (B)
total is class-wide; each Sensor contributes only its current reading. The mutator carefully removes the old contribution before adding the new one.
step                       | reading(s1) reading(s2)  total
new Sensor(10)             |    10           —          10
new Sensor(20)             |    10          20          30
s1.update(50):             |
  total -= reading(=10)    |    10          20          20
  reading = 50             |    50          20          20
  total += 50              |    50          20          70
s2.update(0):              |
  total -= reading(=20)    |    50          20          50
  reading = 0              |    50           0          50
  total += 0               |    50           0          50
Final total = 50.

Trap (C) 70 skips the second update entirely. (D) 80 forgets to subtract the old reading before adding the new one (treats every update as a fresh add). (A) 30 ignores all updates.

total 是类级别变量;每个 Sensor 只贡献它当前reading。修改器(mutator)在加入新值前会先减去旧值,维持总和正确。
step                        | reading(s1) reading(s2)  total
new Sensor(10)              |    10           —          10
new Sensor(20)              |    10          20          30
s1.update(50):              |
  total -= reading(=10)     |    10          20          20
  reading = 50              |    50          20          20
  total += 50               |    50          20          70
s2.update(0):               |
  total -= reading(=20)     |    50          20          50
  reading = 0               |    50           0          50
  total += 0                |    50           0          50
最终 total = 50

陷阱 (C) 70 直接漏掉了第二次 update(D) 80 忘了在加入新值前先减去旧值(把每次 update 都当成新加项)。(A) 30 完全无视所有 update

Q6HARDAP MCMissing this. Trap

Constructor body is x = x; (no this.); new Point(7).getX()?构造方法体是 x = x;(没有 this.);new Point(7).getX() 是?

Answer:答案: (A)
Inside the constructor, the parameter x shadows the instance variable x. Without the this. qualifier, both sides of x = x; refer to the parameter — it's a self-assignment of the parameter to itself.
  • The instance variable x is never touched, so it keeps its default value 0.
  • getX() returns the instance variable → 0.
Java does allow self-assignment without error, so (D) is wrong. Fix: write this.x = x; as in Q1. This bug is silent and embarrassingly common in beginner Java.
在构造方法内,参数 x 遮蔽了同名的实例变量 x。没有 this. 前缀时,x = x;两侧都指向参数——这就成了"参数自我赋值"。
  • 实例变量 x 完全没被赋值,保留默认值 0
  • getX() 返回实例变量 → 0
Java 允许自赋值(不会编译错误),所以 (D) 错误。修复方法:像 Q1 那样写 this.x = x;。这种 bug 静悄悄发生,在初学者代码里非常常见。
Q7HARDAP MCReassigning a ref param vs mutating

replace(b) { b = new Box(99); } vs mutate(b) { b.n = 99; }. After replace(one); mutate(two); — final one.n and two.n?replace(b) { b = new Box(99); }mutate(b) { b.n = 99; } 对比。执行 replace(one); mutate(two); 之后,one.ntwo.n 的最终值?

Answer:答案: (B)
Java passes references by value: the method's parameter is a local copy of the caller's reference. Reassigning the parameter only changes that local copy; mutating through the parameter affects the shared object.
  • replace(one): the parameter b initially aliases the same object as one. Then b = new Box(99) makes b point to a brand-new heap object — one in the caller is unaffected, so one.n is still 1.
  • mutate(two): the parameter b aliases two's object. b.n = 99 mutates that shared object — two.n is now 99.
Output: "1 99".

Trap (C) "99 99" is the most common misconception — students think a method can reassign references in the caller. It cannot; that would require return values or a wrapping object. (A) "1 2" assumes nothing happens to either. (D) "99 2" swaps which method does which.

Java 统一是按值传递(pass by value):对象参数传的是引用的本地副本。重新赋值参数只改变这个本地副本;通过参数修改对象本身才会影响共享对象。
  • replace(one):参数 b 起初与 one 别名同一对象。然后 b = new Box(99)b 指向一个全新的堆对象——调用方的 one 不受影响,one.n 仍为 1
  • mutate(two):参数 btwo 引用同一对象。b.n = 99 修改这个共享对象——two.n 变为 99
输出:"1 99"

陷阱 (C) "99 99" 是最常见的错觉——学生以为方法能在调用方那边重新绑定引用。它做不到;要做到需要返回值或者用一个包装对象。(A) "1 2" 以为两者都没变化。(D) "99 2" 把两个方法的角色颠倒了。

Q8HARDAP MCMutator order with conditional cap

Score s = new Score(10); s.add(20); s.cap(15); s.add(5); s.cap(50); println(s.get());cap(max) sets v = max only when v > max.Score s = new Score(10); s.add(20); s.cap(15); s.add(5); s.cap(50); println(s.get()); —— cap(max) 仅在 v > max 时把 v 设为 max

Answer:答案: (B)
Apply each mutator in order, checking the cap's guard at the time of the call.
step      | v before  cap fires?    v after
init      |    —        —             10
add(20)   |   10        —             30
cap(15)   |   30        30 > 15 ✓     15
add(5)    |   15        —             20
cap(50)   |   20        20 > 50 ✗     20
Final v = 20.

Trap (A) 15 assumes the last cap still constrains the value (it doesn't — 20 <= 50 so the cap is a no-op). (C) 30 ignores the first cap, picturing it as "set v to max" rather than "constrain v if it exceeds max." (D) 35 ignores both caps.

按顺序应用每个修改器,在调用那一刻检查 cap 的判定条件。
step      | v 调用前  cap 触发?      v 调用后
init      |    —        —             10
add(20)   |   10        —             30
cap(15)   |   30        30 > 15 ✓     15
add(5)    |   15        —             20
cap(50)   |   20        20 > 50 ✗     20
最终 v = 20

陷阱 (A) 15 以为最后那个 cap 仍然在限制值(不是——20 <= 50,所以 cap 是空操作)。(C) 30 漏掉第一个 cap,把它误解为"把 v 设为 max"而不是"当 v 超过 max 时才限制"。(D) 35 完全忽略两个 cap。

Q9HARDAP MCFluent interface (return this)

inc() and dec() each return this after mutating n. int x = c.inc().inc().inc().dec().peek(); println(x + " " + c.peek());inc()dec() 都在修改 nreturn thisint x = c.inc().inc().inc().dec().peek(); println(x + " " + c.peek());

Answer:答案: (C)
Each inc() / dec() returns this — i.e., the same Counter object on which it was called. So every link in the chain operates on the same instance c.
step            | what runs      n after
c.inc()         | n++; return c     1
.inc()          | n++; return c     2
.inc()          | n++; return c     3
.dec()          | n--; return c     2
.peek()         | return n          2  → assigned to x
Since the chain never made a copy of c, c.peek() after the chain returns the same value: 2. Output: "2 2".

Trap (A) "0 2" assumes x captures c's state at the start of the chain (it doesn't — it captures the value of .peek() at the end). (B) "2 0" reverses that misconception. (D) "4 4" assumes n keeps incrementing for each link without recognizing the dec().

每个 inc() / dec()return this——即在它身上被调用的同一个 Counter 对象。所以链式(fluent interface)中的每一环都作用在同一个实例 c 上。
step            | 运行内容          n 之后
c.inc()         | n++; return c     1
.inc()          | n++; return c     2
.inc()          | n++; return c     3
.dec()          | n--; return c     2
.peek()         | return n          2  → 赋给 x
链式调用从未给 c 做副本,所以链式结束后再调 c.peek() 仍然得到 2。输出:"2 2"

陷阱 (A) "0 2" 以为 x 抓取的是链式开始时 c 的状态(其实抓的是结尾 .peek() 的返回值)。(B) "2 0" 把上面那个误解颠倒。(D) "4 4" 以为 n 每一环都递增,忽略了 dec()

Q10HARDAP MCObject Equality

p == q vs p == r where r = p and q = new Pt(1,2).p == qp == r,其中 r = pq = new Pt(1,2)

Answer:答案: (B)
The == operator on objects compares references, not contents.
  • p and q are made by separate new Pt(...) calls — different objects in memory, regardless of identical contents. p == qfalse.
  • r = p copies the reference, so r points to the same object. p == rtrue.
Rule (Unit 1/3 carryover): for "do they have the same contents?" you'd override and use .equals(...); == is "do they refer to literally the same instance?"
对象之间的 == 比较的是引用地址,不是内容。
  • pq 分别由两次 new Pt(...) 产生——尽管字段值相同,它们在内存里是不同的对象。p == qfalse
  • r = p 复制引用,所以 r 指向同一个对象。p == rtrue
规则(从 Unit 1/3 沿用):要比较"内容是否相同"应重写并用 .equals(...)== 是"是否指向同一个实例"。
Q11HARDAP MCLocal Variable Shadows

drain() redeclares int level = 0; locally — final getLevel()?drain() 内部重新声明了局部 int level = 0; —— 最终 getLevel() 返回?

Answer:答案: (B)
The line int level = 0; declares a new local variable named level inside drain(). From that point in the method, any unqualified reference to level refers to the local, not the instance variable.
  • Local level: 0-10. (Then disappears when drain returns.)
  • Instance level: untouched, still 100.
getLevel() reads the instance field → 100. Trap (A) assumes the mutation reaches the instance variable. To actually mutate the instance, drop the int declaration: level -= 10; alone would resolve to the instance variable (with no shadowing local).
int level = 0; 这一行在 drain() 内部声明了一个新的局部变量,名字也叫 level。从这行开始到方法结束,所有不加前缀的 level 都指向局部变量,而不是实例变量。
  • 局部 level0-10drain 返回后即消失)。
  • 实例 level:未被触及,仍为 100
getLevel() 读实例字段 → 100陷阱 (A) 以为修改作用到了实例变量。要真正修改实例变量,应去掉 int 声明:只写 level -= 10;(没有同名局部遮蔽时)就会指向实例变量。
Q12HARDAP MCstatic Accessing Instance

public static int getCount() { return count; } where count is a non-static field.public static int getCount() { return count; },其中 count 是非静态字段。

Answer:答案: (C)
A static method is associated with the class, not an instance — so there is no implicit this. Inside it, an unqualified reference to a non-static field is ambiguous: "which instance's count?" The compiler refuses.
  • Exact message: non-static variable count cannot be referenced from a static context.
  • Fix #1: make count static (one shared counter for the class — see Q5).
  • Fix #2: make getCount() non-static, so it has an implicit this to read from.
Trap (B): the field has a value (0), but the compiler won't even let the code reach run time.
static 方法绑定到类,不绑定任何实例——所以没有隐式的 this。在它内部,对非静态字段的无前缀引用是歧义的:"究竟是哪个实例的 count?" 编译器拒绝。
  • 具体报错:non-static variable count cannot be referenced from a static context
  • 修复方案 1:把 count 改为 static(类共享同一个计数器——见 Q5)。
  • 修复方案 2:把 getCount() 改为非静态,这样它就有隐式 this 可以读取。
陷阱 (B):字段本身值(0),但编译器根本不让代码跑到运行时。
Q13HARDAP MCEncapsulation Access

p.name = "Bob"; from outside Person, where name is private.Person 外部写 p.name = "Bob";,其中 nameprivate

Answer:答案: (C)
private restricts access to within the same class declaration only — not even other classes in the same package can read or write it directly. Attempting p.name = "Bob"; from outside Person is rejected at compile time.
  • Same-package alone is not sufficient (that's default / package-private, which uses no modifier).
  • Same-class is sufficient — code inside Person can read/write name freely.
  • To allow external mutation, expose a mutator method like setName(String), ideally with validation.
Trap (D): Java enforces private at compile time, so the run-time exception class isn't involved.
private 把访问范围限制在同一个类声明内部——即使是同一包中的其他类也不能直接读写。在 Person 外部写 p.name = "Bob"; 会在编译期被拒绝。
  • 仅"同包"是不够的(那是默认 / 包私有访问,没有修饰符)。
  • 同类就够了——Person 内部的代码可以自由读写 name
  • 要允许外部修改,提供一个修改器方法,如 setName(String),最好带校验。
陷阱 (D):Java 在编译期就强制执行 private,根本轮不到运行时异常类登场。
Q14HARDAP MCPrimitive vs Object Params

After modify(x, w) assigns x = 999 and w.n = 999: println(x + " " + w.n)?modify(x, w) 里执行 x = 999w.n = 999 之后,println(x + " " + w.n) 输出什么?

Answer:答案: (C)
Java is uniformly pass-by-value — but for objects, the value being passed is a copy of the reference.
  • Primitive x: the parameter inside modify is a copy of the value 5. Reassigning the local copy to 999 has zero effect on the caller's x. Caller's x stays 5.
  • Object w: the parameter inside modify is a copy of the reference — it points to the same Wrap object. The dot access w.n = 999 reaches across the reference and mutates the shared object. Caller sees w.n = 999.
Distinction: reassigning the parameter vs mutating the object the parameter points to. The first never escapes; the second does.
Java 是统一的按值传递(pass by value)——但对于对象,传过去的是引用的一个副本。
  • 基本类型 xmodify 内的参数是值 5 的副本。把这个本地副本重新赋为 999 对调用方的 x 没有任何影响。调用方的 x 仍为 5
  • 对象 wmodify 内的参数是引用的副本——它指向同一个 Wrap 对象。w.n = 999 透过这个引用修改共享对象。调用方看到 w.n = 999
关键区别:重新赋值参数 vs 就地修改参数所指对象。前者永远不会逃出方法;后者会。
Q15HARDAP MCstatic seq + aliasing + toString

Tag uses id = ++seq; per instance. a = new Tag("alpha"); b = new Tag("beta"); c = a; a = new Tag("alpha"); println(a + " " + b + " " + c);Tag 每次构造时 id = ++seq;a = new Tag("alpha"); b = new Tag("beta"); c = a; a = new Tag("alpha"); println(a + " " + b + " " + c);

Answer:答案: (C)
Three things happen at once: seq increments globally, each new object gets a snapshot of seq as its id, and references can be reassigned to point to different objects on the heap. The objects themselves stay put.
step                | heap                              a points to  b points to  c points to  seq
new Tag("alpha")    | O1 {id:1, label:"alpha"}              O1            —            —          1
new Tag("beta")     | O1, O2 {id:2, label:"beta"}           O1            O2           —          2
c = a               | (no new object — just aliasing)       O1            O2           O1         2
a = new Tag("alpha")| O1, O2, O3 {id:3, label:"alpha"}      O3            O2           O1         3
At println time, the implicit toString() calls give:
  • aO3"#3:alpha"
  • bO2"#2:beta"
  • cO1"#1:alpha" (still pointing to the original)
Output: "#3:alpha #2:beta #1:alpha".

Trap (B) "#3:alpha … #3:alpha" is the classic mistake: assumes c "follows" a after reassignment. It doesn't — c captured the reference at the moment c = a ran. Reassigning a later doesn't reach back through c. (D) reverses the trap (assumes a follows the original instead).

三件事同时发生:seq 在全局递增;每个新对象把当时的 seq 抓取为自己的 id;引用可以被重新绑定到堆上的不同对象。对象本身不会被"挪动"。
step                | 堆                                a 指向        b 指向        c 指向        seq
new Tag("alpha")    | O1 {id:1, label:"alpha"}              O1            —            —          1
new Tag("beta")     | O1, O2 {id:2, label:"beta"}           O1            O2           —          2
c = a               | (没有新对象——只是别名)             O1            O2           O1         2
a = new Tag("alpha")| O1, O2, O3 {id:3, label:"alpha"}      O3            O2           O1         3
println 时隐式调用 toString()
  • aO3"#3:alpha"
  • bO2"#2:beta"
  • cO1"#1:alpha"(仍指向最初那个)
输出:"#3:alpha #2:beta #1:alpha"

陷阱 (B) "#3:alpha … #3:alpha" 是典型错误:以为 ca 被重新赋值后"跟着 a 一起变"。不会——cc = a 执行那一刻就把引用抓死了。之后再改 a 不会反过来影响 c(D) 把陷阱反过来(误以为 a 还跟着原值走)。