Companion to the AP-Style MC & Free-Response Practice SetAP 风格选择题与自由回答题练习的解析配套
Unit 3: Class Creation第 3 单元:类的创建CSA
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)单独提示。
What does this.size = size; do in the constructor?构造方法中的 this.size = size; 做什么?
size to the instance variable size.把参数 size 赋给同名的实例变量 size。size refers to the parameter. The prefix this. reaches past the parameter and refers explicitly to the instance variable of the current object.
=: this.size → instance variable.=: bare size → parameter.size = size; (no this.) self-assigns the parameter and leaves the field at its default value.
shadow)实例变量——不加前缀的 size 指的是参数。前缀 this. 跨过参数,明确指向当前对象的实例变量。
= 左侧:this.size → 实例变量。= 右侧:裸的 size → 参数。size = size;(没有 this.),就是把参数自己赋给自己,实例变量保持默认值不变。
Which statement about a static variable is true?下列关于 static 变量的描述,哪一项是正确的?
null regardless of type.无论类型为何都被自动初始化为 null。static binds the variable to the class, not to any individual instance. All instances share the same single storage location.
ClassName.varName.0, boolean gets false, references get null) — not "null regardless of type."final.static 把变量绑定到类,而不是任何具体实例。所有实例共享同一个存储位置(类级别变量 class-level variable)。
ClassName.varName。0,boolean 为 false,引用类型为 null)—— 不是"无论什么类型都为 null"。final,否则可以重新赋值。A class with no declared constructors:没有声明任何构造方法的类:
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 常见陷阱。
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);
5 1015 1020 1020 20Box b = a, both a and b reference the same heap object. Any mutation through one is visible through the other.
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.a.n and b.n read the shared n = 20."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 之后,a 与 b 引用同一个堆上的对象(别名 aliasing)。通过任一个的修改对另一个都可见。
n = 5;a 和 b 都指向它。b.n = 10 → 共享对象的 n 现在是 10。此时读 a.n 也得到 10。a.n += b.n 读 a.n(= 10),读 b.n(也是 10 —— 同一个对象!),写 a.n = 20。a.n 与 b.n 都读共享的 n = 20。"20 20"。
陷阱 (C) "20 10" 是最常见的错误——以为 b 是 a 的独立副本。Java 没有隐式克隆;引用赋值是浅层(shallow)的。(B) "15 10" 在 += 求值时把 a.n 当成仍然是 5。
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 total;update(r) 做 total -= reading; reading = r; total += r;。
s1 = new Sensor(10); s2 = new Sensor(20); s1.update(50); s2.update(0); println(Sensor.total());
30507080total 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。
Constructor body is x = x; (no this.); new Point(7).getX()?构造方法体是 x = x;(没有 this.);new Point(7).getX() 是?
07-1x 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.
x is never touched, so it keeps its default value 0.getX() returns the instance variable → 0.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。this.x = x;。这种 bug 静悄悄发生,在初学者代码里非常常见。
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.n 与 two.n 的最终值?
1 21 9999 9999 2replace(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."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.
pass by value):对象参数传的是引用的本地副本。重新赋值参数只改变这个本地副本;通过参数修改对象本身才会影响共享对象。
replace(one):参数 b 起初与 one 别名同一对象。然后 b = new Box(99) 让 b 指向一个全新的堆对象——调用方的 one 不受影响,one.n 仍为 1。mutate(two):参数 b 与 two 引用同一对象。b.n = 99 修改这个共享对象——two.n 变为 99。"1 99"。
陷阱 (C) "99 99" 是最常见的错觉——学生以为方法能在调用方那边重新绑定引用。它做不到;要做到需要返回值或者用一个包装对象。(A) "1 2" 以为两者都没变化。(D) "99 2" 把两个方法的角色颠倒了。
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。
15203035step | 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。
inc() and dec() each return this after mutating n. int x = c.inc().inc().inc().dec().peek(); println(x + " " + c.peek());inc() 与 dec() 都在修改 n 后 return this。int x = c.inc().inc().inc().dec().peek(); println(x + " " + c.peek());
0 22 02 24 4inc() / 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()。
p == q vs p == r where r = p and q = new Pt(1,2).p == q 与 p == r,其中 r = p,q = new Pt(1,2)。
true truefalse truetrue falsefalse false== 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 == q → false.r = p copies the reference, so r points to the same object. p == r → true..equals(...); == is "do they refer to literally the same instance?"
== 比较的是引用地址,不是内容。
p 与 q 分别由两次 new Pt(...) 产生——尽管字段值相同,它们在内存里是不同的对象。p == q → false。r = p 复制引用,所以 r 指向同一个对象。p == r → true。.equals(...);== 是"是否指向同一个实例"。
drain() redeclares int level = 0; locally — final getLevel()?drain() 内部重新声明了局部 int level = 0; —— 最终 getLevel() 返回?
90100-100int 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.
level: 0 → -10. (Then disappears when drain returns.)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 都指向局部变量,而不是实例变量。
level:0 → -10(drain 返回后即消失)。level:未被触及,仍为 100。getLevel() 读实例字段 → 100。陷阱 (A) 以为修改作用到了实例变量。要真正修改实例变量,应去掉 int 声明:只写 level -= 10;(没有同名局部遮蔽时)就会指向实例变量。
public static int getCount() { return count; } where count is a non-static field.public static int getCount() { return count; },其中 count 是非静态字段。
0.能编译;返回 0。NullPointerException.运行时抛出 NullPointerException。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.
count static (one shared counter for the class — see Q5).getCount() non-static, so it has an implicit this to read from.0), but the compiler won't even let the code reach run time.
static 方法绑定到类,不绑定任何实例——所以没有隐式的 this。在它内部,对非静态字段的无前缀引用是歧义的:"究竟是哪个实例的 count?" 编译器拒绝。
count 改为 static(类共享同一个计数器——见 Q5)。getCount() 改为非静态,这样它就有隐式 this 可以读取。0),但编译器根本不让代码跑到运行时。
p.name = "Bob"; from outside Person, where name is private.在 Person 外部写 p.name = "Bob";,其中 name 是 private。
name.能编译;name 被设值。IllegalAccessException.运行时抛出 IllegalAccessException。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.
Person can read/write name freely.setName(String), ideally with validation.private at compile time, so the run-time exception class isn't involved.
private 把访问范围限制在同一个类声明内部——即使是同一包中的其他类也不能直接读写。在 Person 外部写 p.name = "Bob"; 会在编译期被拒绝。
Person 内部的代码可以自由读写 name。setName(String),最好带校验。private,根本轮不到运行时异常类登场。
After modify(x, w) assigns x = 999 and w.n = 999: println(x + " " + w.n)?modify(x, w) 里执行 x = 999 与 w.n = 999 之后,println(x + " " + w.n) 输出什么?
5 5999 9995 999999 5x: 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.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.pass by value)——但对于对象,传过去的值是引用的一个副本。
x:modify 内的参数是值 5 的副本。把这个本地副本重新赋为 999 对调用方的 x 没有任何影响。调用方的 x 仍为 5。w:modify 内的参数是引用的副本——它指向同一个 Wrap 对象。w.n = 999 透过这个引用修改共享对象。调用方看到 w.n = 999。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);
#1:alpha #2:beta #1:alpha#3:alpha #2:beta #3:alpha#3:alpha #2:beta #1:alpha#1:alpha #2:beta #3:alphaseq 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:
a → O3 → "#3:alpha"b → O2 → "#2:beta"c → O1 → "#1:alpha" (still pointing to the original)"#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():
a → O3 → "#3:alpha"b → O2 → "#2:beta"c → O1 → "#1:alpha"(仍指向最初那个)"#3:alpha #2:beta #1:alpha"。
陷阱 (B) "#3:alpha … #3:alpha" 是典型错误:以为 c 在 a 被重新赋值后"跟着 a 一起变"。不会——c 在 c = a 执行那一刻就把引用抓死了。之后再改 a 不会反过来影响 c。(D) 把陷阱反过来(误以为 a 还跟着原值走)。
Vending slot. Write a complete VendingSlot class that privately stores an item name, a unit price in cents, and a running total of money collected. The constructor VendingSlot(String itemName, int unitPrice) starts the slot with no money collected. The method int sell(int quantity) computes quantity × unit price, adds it to the running total, and returns it.自动售货机货道。编写一个完整的 VendingSlot 类,以私有方式保存商品名称、以分为单位的单价,以及已收取金额的累计值。构造方法 VendingSlot(String itemName, int unitPrice) 创建的货道初始已收金额为零。方法 int sell(int quantity) 计算 quantity × 单价,将其累加到累计值中,并返回该金额。
private fields, a constructor that assigns all three, and one method that computes a value, accumulates it, and returns it三个 private 字段;一个为全部三个字段赋初值的构造方法;以及一个「计算 → 累加 → 返回」的方法A class-design question is scored on its parts, not on cleverness. Write the header, then one field per thing the question says the object must remember, then the constructor, then the required method. Working through the list in that order is what earns the points.类的设计题按其各个组成部分评分,而非考察技巧。先写类头,再为题目要求对象记住的每一项内容各写一个字段,然后是构造方法,最后是要求的方法。按这个顺序逐项完成,就是得分之道。
public class VendingSlot
{
private String itemName;
private int unitPrice;
private int moneyCollected;
public VendingSlot(String itemName, int unitPrice)
{
this.itemName = itemName;
this.unitPrice = unitPrice;
moneyCollected = 0;
}
public int sell(int quantity)
{
int cost = quantity * unitPrice;
moneyCollected += cost;
return cost;
}
}
The question says the slot’s state must not be readable or writable from outside, so every field is private. It names three things to remember, so there are exactly three fields — one per item in that list, with a type that can hold it.题目要求货道的状态不得从外部读取或修改,因此每个字段都是 private。题目列出了需要记住的三项内容,故字段恰为三个 — 每项一个,且类型足以承载其内容。
The constructor parameters share names with the fields, so this. is required on the left-hand side of those two assignments; without it, itemName = itemName assigns the parameter to itself and the field stays null. moneyCollected has no parameter shadowing it, so it needs no this. — though writing this.moneyCollected = 0; is equally correct. Naming the parameters differently (public VendingSlot(String n, int p)) and dropping this entirely is also fine.构造方法的参数与字段同名,因此这两条赋值语句的左侧必须写 this.;否则 itemName = itemName 只是把参数赋给它自己,字段仍为 null。moneyCollected 没有被同名参数遮蔽,故无需 this. — 不过写成 this.moneyCollected = 0; 同样正确。若把参数改用不同的名字(public VendingSlot(String n, int p))而完全不用 this,也是可以的。
Setting moneyCollected = 0 explicitly is what makes “a newly created slot has collected no money” true by design rather than by accident. Java does default an int field to 0, so omitting the line still behaves correctly — but the initial value is part of what the constructor was asked to establish, and stating it costs one line.显式写出 moneyCollected = 0,使「新创建的货道尚未收取任何金额」成为有意的设计而非偶然。Java 确实会把 int 字段默认初始化为 0,因此省略该行行为上仍然正确 — 但初始值本就是构造方法被要求确立的内容之一,写明它只需一行。
In sell, the cost is computed once into a local variable, then used twice: once to update the field and once as the return value. That ordering matters. Computing it twice would work here, but naming it makes the method’s three obligations — calculate, update, return — visible in three separate lines, which is exactly how the question is scored. Note that quantity * unitPrice uses the field, not a second parameter: the price belongs to the slot, and a caller never supplies it.在 sell 中,金额只计算一次并存入局部变量,随后被使用两次:一次用于更新字段,一次作为返回值。这一顺序很重要。这里重复计算两次也能工作,但为它命名之后,方法的三项职责 — 计算、更新、返回 — 就清晰地分列在三行之中,而这恰恰就是本题的评分方式。另请注意 quantity * unitPrice 用的是字段,而非第二个参数:单价属于货道本身,调用者永远不会传入它。
| #序号 | Criterion — awarded for observable behavior评分点 — 依据可观察到的行为给分 | Pts分值 |
|---|---|---|
| 1 | Writes a correct class header declaring a class named VendingSlot写出正确的类头,声明名为 VendingSlot 的类 | 1 |
| 2 | Declares private instance variables that can hold the item name, the unit price, and the money collected, with appropriate types声明 private 实例变量,类型适当,足以保存商品名称、单价与已收金额 | 1 |
| 3 | Writes a correct constructor header taking a String parameter and an int parameter, in that order写出正确的构造方法头,依次接受一个 String 参数与一个 int 参数 | 1 |
| 4 | Constructor assigns correct initial values: both parameters to their fields, and the money collected to 0构造方法赋予正确的初始值:把两个参数分别赋给对应字段,并把已收金额置为 0 | 1 |
| 5 | Writes a correct method header public int sell(int ...)写出正确的方法头 public int sell(int ...) | 1 |
| 6 | Calculates quantity × unit price and adds it to the money-collected variable, leaving earlier sales intact计算 quantity × 单价,并将其累加到已收金额变量上,保留此前各笔交易的金额 | 1 |
| 7 | Returns that calculated cost返回上述计算所得的金额 | 1 |
| Total合计 | 7 |
private and of usable types, not on their names or declaration order. Criterion 4 is judged on the resulting state, so this. may be used or omitted as the parameter names allow, and relying on the default 0 for the money field still earns it. Criterion 6 requires accumulation: moneyCollected = cost; replaces rather than adds and does not earn it, while moneyCollected += quantity * unitPrice; does. Criterion 7 is independent of 6 — a method that returns the correct cost but forgets to accumulate earns 7 and not 6, and one that accumulates correctly but returns the running total earns 6 and not 7. Adding extra methods is neither required nor penalized.任何满足某评分点的实现均可得该分。字段与参数的命名不限。评分点 2 只看三个字段是否为 private 且类型可用,不看其名称或声明顺序。评分点 4 依据最终状态评判,故可依参数命名情况选择使用或省略 this.;依赖 int 字段默认为 0 同样可得该分。评分点 6 要求「累加」:moneyCollected = cost; 是覆盖而非累加,不得该分;moneyCollected += quantity * unitPrice; 则可以。评分点 7 与 6 相互独立 — 若返回了正确金额却忘记累加,得 7 不得 6;若累加正确却返回了累计总额,得 6 不得 7。额外增加其他方法既非必需,也不扣分。
itemName = itemName; compiles, runs, and does nothing: the parameter is assigned to itself and the field keeps its default. Either write this.itemName = itemName; or give the parameter a different name.构造方法中的自我赋值。当参数与字段同名时,itemName = itemName; 能编译、能运行,却什么也没做:参数被赋给了它自己,字段仍保持默认值。要么写成 this.itemName = itemName;,要么给参数换一个名字。moneyCollected = cost; makes the second call erase the first. The table’s third row exists to catch exactly this: after sell(2) then sell(1) the total must be 375, not 125.覆盖总额而非累加。moneyCollected = cost; 会使第二次调用抹掉第一次的结果。表中第三行正是为捕捉此错误而设:先 sell(2) 再 sell(1) 之后,总额必须是 375 而不是 125。return moneyCollected; happens to give 250 on the first call, which looks right, and then gives 375 on the second call where 125 was wanted. The method returns the cost of this sale.返回累计总额。return moneyCollected; 在第一次调用时恰好得到 250,看似正确;到第二次调用却给出 375,而所求是 125。该方法返回的是本笔交易的金额。public, or declaring them inside the constructor. public fields break the stated requirement that the state not be reachable from outside. Declaring int moneyCollected; inside the constructor creates a local variable that shadows the field and vanishes when the constructor ends, so that line initializes the local and the field keeps Java’s default — the class still compiles, and here it even behaves correctly, because an int field already defaults to 0. What is silently discarded is the constructor’s initialization itself, which only becomes visible when the field’s intended starting value is not the default.把字段声明为 public,或把它们声明在构造方法内部。public 字段违背了「状态不得从外部访问」这一明确要求。而把 int moneyCollected; 声明在构造方法内部,则会创建一个遮蔽字段、并随构造方法结束而消失的局部变量,于是这一行初始化的是局部变量,字段只保留 Java 的默认值 — 此时类仍能编译,且此处行为甚至仍然正确,因为 int 字段本就默认为 0。被悄悄丢弃的是构造方法里的这次初始化,只有当字段的预期初始值不是默认值时才会暴露出来。public void VendingSlot(String itemName, int unitPrice) is not a constructor at all — it is an ordinary method that merely shares the class’s name, so new VendingSlot("Pretzels", 125) will not compile. A constructor header has no return type, not even void.给构造方法加上返回类型。public void VendingSlot(String itemName, int unitPrice) 根本不是构造方法 — 它只是一个恰好与类同名的普通方法,因此 new VendingSlot("Pretzels", 125) 无法编译。构造方法头没有返回类型,连 void 也没有。= and += on a field that outlives the call. An instance variable is memory that persists between method calls, and the only reason to store something in one is that a later call needs what an earlier call left behind. The moment you write = where the object is supposed to be remembering, you have turned the field back into a local variable with extra steps.类的设计题是一道翻译题,而评分标准就是现成的词典。「对象必须记录 X」译为一个私有字段;「新创建的对象具有 X」译为构造方法中的一行;「该方法完成 X 并返回 Y」译为一次更新加一次返回。把题目通读一遍,先按此顺序把各个部分写下来,再去写具体逻辑,这 7 分几乎就能机械地拿到 — 这正是本题的用意所在。此处唯一需要真正思考的,是对一个生命周期长于单次调用的字段而言,= 与 += 的区别。实例变量是在多次方法调用之间持续存在的记忆;把某样东西存进去的唯一理由,就是后来的调用需要用到先前调用留下的内容。一旦在本该「记住」的地方写下 =,你就等于绕了一圈,把字段又变回了局部变量。