EIE3320 考前最后练手 · 校园二手市场(Campus Marketplace)

一句话:用一条 7 阶段的流水线,把 HW1–HW5 的重难点各走一遍,全程只用控制台(不碰 GUI),90–150 分钟。 配套:eie3320 homework 精读(每题拆解)、eie3320 课件精读(ch11/ch14 逐页)。 用法:每个阶段都有逐字期望输出。写完就编译运行、跟期望输出对齐——对齐了才进下一阶段。

0. 怎么练(先读这一节)

0.1 三条规则

规则说明
不许看答案本文不含实现代码,只给「必须有哪些成员」+「期望输出」+ 分层提示。卡住时按 §11 从 L1 逐级看到 L3。
一次只推进一个阶段每个阶段都能独立编译运行。跑不通就别往下走——后面的阶段都建立在前面之上。
对齐输出,不对齐就改期望输出是从真实运行结果导出的,逐字符可信。用 §10.3 的自动比对命令,别用肉眼看。

0.2 三档练法(按剩余时间选)

档时间做什么适合
全量2–2.5 hS0 → S7 全走一遍考前一天完整过一遍
主线60–75 min只做 S4 + S5 + S6(继承 / 多态 / 集合),S1–S3 的类直接抄本文的成员表只想补最可能考崩的地方
速查25 min只读 §2 覆盖矩阵 + §12 报错速查 + §11 提示,不动手进考场前最后 20 分钟

不想手抄驱动? vault 的 SYS/SYS2000-asset-media/eie3320-final-practice/eie3320-final-practice-starter.zip 里是 7 个驱动文件(Step1.java … Step6.java、TestMarket.java)+ 7 份 expected/stepN.txt + 一份 README.md。解压到任意目录后直接 javac,你只需要新建那 9 个领域类。 想看完整分步代码? vault 的 SYS/SYS2000-asset-media/eie3320-final-practice/eie3320-final-practice-solution.zip 里是 starter/ + step1/–step7/ + expected/ + build.py;每步都可直接编译运行,用来对照。

0.3 为什么用「校园二手市场」这个题目

因为它和 HW4 的 dome 项目同构但不同题:同样有「父类 + 三个子类 + 一个集合管理器 + 三个搜索」,但字段(价格、时间、卖家、运费)全是新的。抄不动,只能真的理解——这正是考前练手要的。


1. 项目全貌

1.1 十个文件

文件阶段职责一句话
Owner.javaS1值对象卖家:姓名 + 电话,不可变
Money.javaS1工具类Money.format(4300) → "$4,300.00"
Listing.javaS2 → S4核心父类S2 写成具体类;S4 改成 abstract
Basket.javaS3定长容器5 个槽位的数组 + 实际条数
Shippable.javaS4接口只有「能寄」的子类才实现
TextBook.javaS4子类 1课程 + 版次;不实现 Shippable
Device.javaS4子类 2品牌 + 保修;实现 Shippable
Furniture.javaS4子类 3尺寸 + 重量;实现 Shippable
Market.javaS5 → S6集合管理器ArrayList<Listing> + 三搜索 + 遍历删除 + 排序
Step1..Step6.java / TestMarket.java各阶段验收驱动照抄,不许改

1.2 类关系

                      Listing  «abstract»
      -id  -title  -price  -owner  -postedHour  -postedMinute
      +postedTime()   +slot()   +toString()   +print()   +compareTo(Listing)
      +category()  «abstract»          +detail()  «abstract»
                              ▲
                    extends   │
        ┌─────────────────────┼─────────────────────────┐
        │                     │                         │
   TextBook                Device                   Furniture
   -course                 -brand                   -widthCm
   -edition                -warrantyMonths          -depthCm
        │                      │ implements             -weightKg
        │                      ▼                        │
        │                 Shippable ◀───────────────────┘
        │                 +shippingFee()
        │
   不实现 Shippable → print() 里显示 self-pickup

三个要点,写完 S4 就该能自己讲出来:

  1. print() 是父类的具体方法,却调用了子类的 category() / detail() —— 模板方法,HW4 Q2 的 super.print() 的升级版。
  2. Shippable 只有两个子类实现 —— 接口表达「能力」,不是「是什么」(ch14 p.36–38)。
  3. Listing 数组能装三种子类 —— 抽象类作数组元素类型(ch14 p.17)。

1.3 终点:TestMarket 的完整输出

这就是 S7 要复现的东西(细节见 §10):

== 1. list() ==
ID   CATEGORY   TITLE                    PRICE      OWNER              POSTED
---- ---------- ------------------------ ---------- ------------------ ------------
#1   TextBook   EIE3320 Lecture Notes    $120.00    Chan Tai Man       2:31 p.m.
#2   Device     iPad Air 11              $4,300.00  Lee Siu Ming       9:05 a.m.
#3   Furniture  Study Desk               $480.00    Chan Tai Man       9:47 p.m.
#4   TextBook   Calculus 8e              $260.00    Lee Siu Ming       8:00 a.m.
#5   Device     Mechanical Keyboard      $350.00    Chan Tai Man       11:59 p.m.

(完整版 54 行输出在 §10.2。)


2. 考点覆盖矩阵(先看这张,知道自己在补什么)

作业 / 考点本项目哪一步具体在哪
HW1 Q1 金字塔对齐S5list() 的 printf 列宽
HW1 Q2 / 与 %S2postedTime() 的 hour % 12
HW1 Q3 写方法、每行 N 个S3Basket.print() 一条一行
HW1 Q4 switch 分组穿透S2slot() 的 case 5..11 → morning
HW2 Q1b 12 小时制 + a.m./p.m.S2postedTime()
HW2 Q2 构造器 + double 字段S4三个子类的构造器
HW2 Q3 对象协作(共享引用)S2 / S5Listing 持有 Owner;Market 持有 List
HW3 Q1 数组 + 条数≠容量S3Basket.add() 的 count >= items.length
HW3 Q1 toString 委派S3Basket.print() 里 items[i] 直接拼接
HW4 Q1→Q2 抽父类S4Listing 从具体类改成 abstract
HW4 Q2 super(...) / 覆盖S4三个子类构造器首行、两个抽象方法
HW4 Q3 三种遍历S5 / S6下标(totalValue)、for-each(list)、Iterator(removeByOwner)
HW5 Q1a equals 精确搜索S5searchByTitle
HW5 Q1c Pattern.find 子串搜索S5searchByTitlePattern
HW5 Q1d 任意字段搜索S5searchByPattern(靠父类 toString)
HW5 Q2 遍历 + 过滤S6removeByOwner / shippableItems
Ch14 抽象类 + 接口S4abstract category/detail、Shippable
Ch14 接口作返回类型S6ArrayList<Shippable> shippableItems()
Ch11 ComparableS6Listing implements Comparable<Listing>
Ch10 不可变对象S1Owner 全 final 字段、无 setter

本项目故意不覆盖(练手时间有限,这两块去作业精读里补):

没覆盖去哪补
HW4 Q4 Iterator vs get() 的性能对比作业精读 §5.4(LinkedList + 下标是 O(n²))
HW4 Q5 HashMap作业精读 §5.5;本项目用 Comparable + Collections.sort 替代了「换一种集合」的练习
GUI / 事件驱动那是 Lecture 7–8,不在本次范围

3. S0 · 环境自检(5 分钟)

目标:确认 javac 能编译、java 能运行、控制台编码不乱码。

S0.1 三行命令

javac -version
java -version

期望看到 javac 21.x 一类版本号(19、17 都够用)。两个命令都找不到 → 装 JDK 或检查 JAVA_HOME。

S0.2 建目录并跑一个 Hello

mkdir C:\Temp\market; cd C:\Temp\market
@'
public class Hello {
    public static void main(String[] args) {
        System.out.println("ok");
    }
}
'@ | Out-File -Encoding utf8 Hello.java
javac Hello.java; java Hello

期望输出 ok。

S0.3 验收清单

  • javac -version 有版本号
  • java Hello 打印 ok
  • 目录里出现了 Hello.class(说明 javac 真的在工作)

BlueJ 用户:可以完全在 BlueJ 里做,但 §10.2 的自动比对需要命令行。建议还是用命令行练——考试环境也是命令行/IDE 混着的。 IDE 用户(Eclipse / IntelliJ / VS Code):新建一个 default package 的项目,所有 .java 平铺在同一个目录里。不要建 package,否则 javac *.java 的路径会变。


4. S1 · Owner + Money(10 分钟)

目标:把「值对象」和「格式化」两件小事做对。看着简单,但 $1,234.50 这一行能刷掉一半人。

4.1 必须实现的成员

类成员要求
OwnerOwner(String name, String phone)两个字段都 private final
getName() / getPhone()只读
toString()返回 "Chan Tai Man (6123 4567)"
MoneyMoney 构造器private,且类声明为 final(工具类不让实例化)
static String format(double)返回 "$" + 带千分位、两位小数

4.2 硬约束

  • Owner 不许有 setName() / setPhone()(ch10 不可变对象)。
  • Money 不许自己写循环拼小数——用 String.format。
  • 千分位必须有:1234.5 → $1,234.50(不是 $1234.50)。

4.3 驱动 Step1.java(照抄,不许改)

public class Step1
{
    public static void main(String[] args)
    {
        Owner chan = new Owner("Chan Tai Man", "6123 4567");
        Owner lee = new Owner("Lee Siu Ming", "9876 5432");
 
        System.out.println(chan);
        System.out.println(lee);
        System.out.println(chan.getName() + " / " + chan.getPhone());
 
        System.out.println(Money.format(120));
        System.out.println(Money.format(1234.5));
        System.out.println(Money.format(0));
        System.out.println(Money.format(5510));
        System.out.println(Money.format(0.005));
    }
}

4.4 期望输出(逐字)

Chan Tai Man (6123 4567)
Lee Siu Ming (9876 5432)
Chan Tai Man / 6123 4567
$120.00
$1,234.50
$0.00
$5,510.00
$0.01

4.5 验收清单

  • 前 3 行完全一致(含括号、空格、斜杠两侧各一个空格)
  • 1234.5 → $1,234.50(千分位)
  • 0 → $0.00(补两位)
  • 0.005 → $0.01(不是 $0.00,也不是 $0.005)

4.6 三个坑

坑现象原因
忘了千分位$1234.50用了 %.2f 而不是 %,.2f
1234 传成 int编译不过format(int) 与 format(double) 是两个方法,int 实参能自动拓宽,没问题——但如果你把参数写成 int 就丢失小数了
toString 写成 return name;第 1 行只有名字println(对象) 调的是 toString(),不是字段

$0.01 这一条是故意的:String.format("%,.2f", 0.005) 走的是 HALF_UP,而 0.005 在二进制里其实略小于 0.005……能得 $0.01 说明你用的是 String.format(它按十进制字符串舍入)。用 Math.round 手工算反而可能得 $0.00。不要在这上面纠结,对齐输出即可。


5. S2 · Listing:12 小时制 + switch 时段(20 分钟)

目标:这一阶段把 HW2 Q1b 的 12 小时制 和 HW1 Q4 的 switch 分组 一次写完。此时 Listing 还是具体类(能 new),S4 才会把它改成 abstract。

5.1 必须实现的成员

成员要求
private static int count静态计数器,每创建一条就 ++
private final int id构造器里 this.id = ++count(先自增再赋值,所以 id 从 1 开始)
private final String title / double price / Owner owner三个基本字段
private final int postedHour / postedMinute发布时间(24 小时制,0..23 与 0..59)
Listing(String title, double price, Owner owner, int hour, int minute)唯一构造器
getId() / getCount()(static) / getTitle() / getPrice() / getOwner()getter
String postedTime()12 小时制,见 5.2
String slot()用 switch 返回时段,见 5.3
String toString()title + " (" + Money.format(price) + ") by " + owner.getName()
void print()四行块,见 5.4

5.2 postedTime() 的规则(HW2 Q1b 的核心)

内部 postedHourpostedTime()
012:xx a.m.
1–111:xx a.m. … 11:xx a.m.
1212:xx p.m.
13–231:xx p.m. … 11:xx p.m.
  • 小时不补零(9:05,不是 09:05)。
  • 分钟必须两位(9:05,不是 9:5)。
  • 后缀是 a.m. / p.m.,带两个点,前面一个空格。

5.3 slot() 的分组(HW1 Q4 的 switch 穿透)

小时返回
5, 6, 7, 8, 9, 10, 11morning
12, 13, 14, 15, 16afternoon
17, 18, 19, 20evening
其余(21, 22, 23, 0, 1, 2, 3, 4)night

必须用 switch,且用「多个 case 标签共享一段语句 + 最后 return」的写法(不要每个 case 都写 break)。这是 HW1 Q4 那道 daysInMonth 的同款动作。

5.4 print() 的格式

#1 [Listing] EIE3320 Notes
   price  : $120.00
   owner  : Chan Tai Man (6123 4567)
   posted : 2:31 p.m. (afternoon)
  • 第 1 行:"#" + id + " [Listing] " + title([Listing] 是字面量,S4 会换成 category())。
  • 后 3 行都以三个空格开头,字段名后补空格对齐到 7 列再跟 :。 即 price :、 owner :、 posted :。
  • owner 那行直接拼 owner 对象(靠它的 toString())。

5.5 驱动 Step2.java

public class Step2
{
    public static void main(String[] args)
    {
        Owner chan = new Owner("Chan Tai Man", "6123 4567");
 
        Listing a = new Listing("EIE3320 Notes", 120.0, chan, 14, 31);
        Listing b = new Listing("iPad Air 11", 4300.0, chan, 9, 5);
        Listing c = new Listing("Midnight Snack", 12.5, chan, 0, 5);
        Listing d = new Listing("Late Deal", 8.0, chan, 23, 59);
        Listing e = new Listing("Noon Deal", 15.0, chan, 12, 0);
        Listing[] all = {a, b, c, d, e};
 
        for (Listing item : all)
        {
            System.out.println("#" + item.getId() + "  " + item.postedTime()
                    + "  ->  " + item.slot());
        }
        System.out.println("count = " + Listing.getCount());
        System.out.println();
        a.print();
    }
}

5.6 期望输出

#1  2:31 p.m.  ->  afternoon
#2  9:05 a.m.  ->  morning
#3  12:05 a.m.  ->  night
#4  11:59 p.m.  ->  night
#5  12:00 p.m.  ->  afternoon
count = 5

#1 [Listing] EIE3320 Notes
   price  : $120.00
   owner  : Chan Tai Man (6123 4567)
   posted : 2:31 p.m. (afternoon)

5.7 验收清单

  • 五个拐点全对:0:05 → 12:05 a.m.、9:05 → 9:05 a.m.、12:00 → 12:00 p.m.、14:31 → 2:31 p.m.、23:59 → 11:59 p.m.
  • 小时无前导零、分钟有前导零
  • slot() 中 21..23 与 0..4 都落到 night(23:59 那条就是验证)
  • count = 5、id 是 1..5(注意 ++count 的位置)
  • print() 三行缩进与 [Listing] 字面量一致

5.8 三个坑

坑现象原因
12:05 a.m. 写成 0:05 a.m.第 3 行不对忘了 h12 == 0 → 12
2:31 p.m. 写成 02:31 p.m.第 1 行不对用了 %02d 格式化小时(只需要给分钟补零)
id 从 0 开始#0写成了 this.id = count++(后置自增)

6. S3 · Basket:定长数组 + 条数 ≠ 容量(15 分钟)

目标:把 HW3 Q1 那个 NullPointerException 陷阱再踩一次、再解决一次。

6.1 必须实现的成员

成员要求
private final Listing[] items长度固定 5:new Listing[5]
private int count初始 0,表示实际装了几件
boolean add(Listing item)装得下 → 放进 items[count]、count++、返回 true;装不下 → 返回 false(不许扩容、不许抛异常)
int size()返回 count(不是 items.length)
double total()求和前 count 个
void print()一条一行 + 一行汇总,见 6.3

6.2 硬约束

  • 循环上限只能用 count。写 items.length 会打出 5 行之后继续访问 null → NullPointerException(add 满 5 件后第 6 件被拒,数组里就有 null)。
  • 不许用 ArrayList——这一阶段就是要练「数组 + 条数」这套手工管理。

6.3 输出格式

1: A ($10.00) by Chan Tai Man
...
items = 5, total = $150.00
  • 序号从 1 开始(不是 0)——(i + 1) + ": "。
  • 每条后面直接拼 items[i] 对象(自动调 toString())——这就是 HW3 要求的「必须调用 toString」。
  • 最后一行:"items = " + count + ", total = " + Money.format(total())。

6.4 驱动 Step3.java

public class Step3
{
    public static void main(String[] args)
    {
        Owner chan = new Owner("Chan Tai Man", "6123 4567");
        Basket basket = new Basket();
 
        String[] names = {"A", "B", "C", "D", "E", "F"};
        double[] prices = {10.0, 20.0, 30.0, 40.0, 50.0, 60.0};
 
        for (int i = 0; i < names.length; i++)
        {
            Listing item = new Listing(names[i], prices[i], chan, 10, 0);
            boolean ok = basket.add(item);
            System.out.println("add " + names[i] + " -> " + ok);
        }
 
        System.out.println("size = " + basket.size());
        basket.print();
    }
}

6.5 期望输出

add A -> true
add B -> true
add C -> true
add D -> true
add E -> true
add F -> false
size = 5
1: A ($10.00) by Chan Tai Man
2: B ($20.00) by Chan Tai Man
3: C ($30.00) by Chan Tai Man
4: D ($40.00) by Chan Tai Man
5: E ($50.00) by Chan Tai Man
items = 5, total = $150.00

6.6 验收清单

  • 只有前 5 个 true,第 6 个 false
  • 没有异常(如果抛 NullPointerException,说明 print()/total() 用了 items.length)
  • size = 5 而不是 size = 5 之后又冒出空行/空条目
  • total = $150.00(只算 5 件,第 6 件没进去)

6.7 一句话总结

容量是 items.length,条数是 count。凡两者并存,遍历与求和一律用条数。


7. S4 · 继承 + 抽象类 + 接口(25 分钟,最重要的一步)

目标:把 S2 的 Listing 从「一个能干活的类」改造成「一个规定契约的抽象父类」,再加三个子类和一个接口。这一步同时吃掉 HW4 Q1→Q2 与 Ch14 的抽象类 / 接口。

7.1 动手前先想 30 秒

S2 的 Listing 里,print() 打印的是 [Listing]。现在要打印 [TextBook] / [Device] / [Furniture]——这个字符串只能由子类决定,但 print() 又只有一份(写在父类里)。

这就是 ch14 p.6 那个洞:父类需要一个「有名字、没实现」的方法。答案是:

public abstract String category();

7.2 要改的 Listing

改动内容
类声明public abstract class Listing
新增抽象方法 1public abstract String category(); —— 返回 "TextBook" / "Device" / "Furniture"
新增抽象方法 2public abstract String detail(); —— 一行补充信息
改 toString()见 7.3
改 print()见 7.4
其他字段 / 构造器 / postedTime() / slot() / 全部 getter一行都不许动

注意:Listing 一旦 abstract,S2 / S3 的 Step2.java、Step3.java 里的 new Listing(...) 就编译不过了——这是预期的,它们已经完成使命,删掉或改名即可(例:Step2.java.bak)。

7.3 新的 toString()(一句话可搜索形式)

[TextBook] EIE3320 Lecture Notes ($120.00) by Chan Tai Man | 6123 4567 | Course: EIE3320, ed.3

拼接顺序:"[" + category() + "] " + title + " (" + Money.format(price) + ") by " + owner.getName() + " | " + owner.getPhone() + " | " + detail()

为什么要把 detail() 塞进 toString():S5 的 searchByPattern("Apple") 要能靠 toString() 命中「品牌」这种子类才有的字段。父类看不见子类字段,但父类能调 toString()——而 toString() 又是动态绑定的。这一条是 HW5 Q1d 的全部秘密。

7.4 新的 print()(模板方法 + 接口判断)

#1 [TextBook] EIE3320 Lecture Notes
   price  : $120.00
   owner  : Chan Tai Man (6123 4567)
   posted : 2:31 p.m. (afternoon)
   detail : Course: EIE3320, ed.3
   ship   : self-pickup
  • 第 1 行把 [Listing] 换成 category()。
  • 第 5 行 detail : 后拼 detail()。
  • 第 6 行分两种:
    • 若 this instanceof Shippable → 转型后调 shippingFee(),输出 ship : $63.00
    • 否则 → ship : self-pickup

关键字都在这一行里:this instanceof 接口 + (接口) this 转型。ch14 p.25–28 的 object instanceof Edible 同款。

7.5 Shippable 接口

public interface Shippable
{
    double shippingFee();
}
  • 接口里不写 public abstract(省略即默认,p.29)。
  • 只有 Device 和 Furniture 实现它,TextBook 不实现——「能力」不是「身份」。

7.6 三个子类

子类新增字段category()detail()shippingFee()
TextBookString course、int edition"TextBook""Course: " + course + ", ed." + edition— (不实现接口)
DeviceString brand、int warrantyMonths"Device""Brand: " + brand + ", warranty " + warrantyMonths + " mo"20 + 0.01 * getPrice()
Furnituredouble widthCm、double depthCm、double weightKg"Furniture""Size: " + widthCm + " x " + depthCm + " cm, " + weightKg + " kg"weightKg * 5.0

构造器签名(顺序必须一致,Step4 就是这么调的):

子类构造器
TextBook(String title, double price, Owner owner, int hour, int minute, String course, int edition)
Device(String title, double price, Owner owner, int hour, int minute, String brand, int warrantyMonths)
Furniture(String title, double price, Owner owner, int hour, int minute, double widthCm, double depthCm, double weightKg)

7.7 三条硬约束(每条都对应一个考点)

  1. 每个子类构造器第一行必须是 super(title, price, owner, hour, minute); —— 因为 Listing 只有带参构造器,隐式 super() 找不到目标(ch11 p.37 的 Apple/Fruit 报错就是没写)。
  2. category() / detail() 的可见性必须是 public —— 接口/抽象方法默认 public,写成包可见会报 attempting to assign weaker access privileges。
  3. print() 里不许写 if (category().equals("Device")) —— 判断「能不能寄」只能靠 instanceof Shippable,不能靠字符串。用字符串判断等于把多态退化成 switch。

7.8 驱动 Step4.java

public class Step4
{
    public static void main(String[] args)
    {
        Owner chan = new Owner("Chan Tai Man", "6123 4567");
        Owner lee = new Owner("Lee Siu Ming", "9876 5432");
 
        Listing[] items = {
            new TextBook("EIE3320 Lecture Notes", 120.0, chan, 14, 31, "EIE3320", 3),
            new Device("iPad Air 11", 4300.0, lee, 9, 5, "Apple", 12),
            new Furniture("Study Desk", 480.0, chan, 21, 47, 120.0, 60.0, 18.0)
        };
 
        for (Listing item : items)
        {
            item.print();
            System.out.println();
        }
 
        System.out.println("-- one-line form (toString) --");
        for (Listing item : items)
        {
            System.out.println(item);
        }
    }
}

注意 Listing[] items = { new TextBook(...), new Device(...), new Furniture(...) }; —— 抽象类型的数组装三种子类(ch14 p.17)。

7.9 期望输出

#1 [TextBook] EIE3320 Lecture Notes
   price  : $120.00
   owner  : Chan Tai Man (6123 4567)
   posted : 2:31 p.m. (afternoon)
   detail : Course: EIE3320, ed.3
   ship   : self-pickup

#2 [Device] iPad Air 11
   price  : $4,300.00
   owner  : Lee Siu Ming (9876 5432)
   posted : 9:05 a.m. (morning)
   detail : Brand: Apple, warranty 12 mo
   ship   : $63.00

#3 [Furniture] Study Desk
   price  : $480.00
   owner  : Chan Tai Man (6123 4567)
   posted : 9:47 p.m. (night)
   detail : Size: 120.0 x 60.0 cm, 18.0 kg
   ship   : $90.00

-- one-line form (toString) --
[TextBook] EIE3320 Lecture Notes ($120.00) by Chan Tai Man | 6123 4567 | Course: EIE3320, ed.3
[Device] iPad Air 11 ($4,300.00) by Lee Siu Ming | 9876 5432 | Brand: Apple, warranty 12 mo
[Furniture] Study Desk ($480.00) by Chan Tai Man | 6123 4567 | Size: 120.0 x 60.0 cm, 18.0 kg

7.10 验收清单

  • TextBook 显示 self-pickup,另外两个显示运费(这是接口有没有接对的分水岭)
  • 运费数值:$63.00(20 + 43)、$90.00(18 × 5)
  • posted 行的时段:21:47 → night(因为 slot() 只把 17–20 算 evening)
  • 一行形式的 toString() 三个 | 分隔符齐全、detail() 在最末
  • 把 TextBook 的 detail() 临时删掉 → 应立刻编译失败(TextBook is not abstract and does not override abstract method detail())。看到这个报错说明抽象方法真的生效了

7.11 四个坑

坑报错 / 现象原因
忘了 super(...)constructor Listing in class Listing cannot be applied to given types父类只有带参构造器
抽象方法写成 protectedattempting to assign weaker access privileges覆盖/实现不能降可见性(ch11 p.75)
shippingFee() 写成 intDevice is not abstract and does not override abstract method shippingFee()(返回类型不匹配)接口声明是 double,实现必须同为 double
print() 里 this.shippingFee()cannot find symbol: method shippingFee()Listing 没有这个方法;必须先 instanceof + 转型

8. S5 · Market:集合 + 对齐表 + 三个搜索(25 分钟)

目标:第一次拥有「一个类管一堆对象」。三个搜索是本阶段的主体——它们分别对应 HW5 Q1a / Q1c / Q1d。

8.1 必须实现的成员

成员要求
private final ArrayList<Listing> listings泛型写全:new ArrayList<Listing>()
void add(Listing item)直接 listings.add(item)
int size()listings.size()
double totalValue()用下标 for 累加(for (int i = 0; i < listings.size(); i++))
void list()对齐表格,见 8.2
Listing searchByTitle(String title)精确匹配 title,用 equals;找不到返回 null
Listing searchByTitlePattern(String pat)title 包含 pat;用 Pattern + Matcher.find()
Listing searchByPattern(String pat)任意字段包含 pat;对 toString() 做 find()

8.2 list() 的表头与列宽(照抄这四行)

System.out.printf("%-4s %-10s %-24s %-10s %-18s %s%n",
        "ID", "CATEGORY", "TITLE", "PRICE", "OWNER", "POSTED");
System.out.println("---- ---------- ------------------------ ---------- "
        + "------------------ ------------");
for (Listing item : listings)
{
    System.out.printf("%-4s %-10s %-24s %-10s %-18s %s%n",
            "#" + item.getId(), item.category(), item.getTitle(),
            Money.format(item.getPrice()), item.getOwner().getName(),
            item.postedTime());
}

六个列的宽度:4 / 10 / 24 / 10 / 18 / 不限。表头与数据行必须用同一个格式串,否则列会错开。

8.3 三个搜索的区别(这一页要能默写)

方法匹配对象用什么大小写典型调用 → 结果
searchByTitletitle 整串String.equals敏感"iPad Air 11" → 命中;"ipad air 11" → null
searchByTitlePatterntitle 任意位置Pattern.compile(pat).matcher(title).find()敏感"Desk" → Study Desk
searchByPattern整条 toString()同上,但对象换成 item.toString()敏感"Apple" → iPad Air 11(品牌字段)

三个判断:

  1. find() 不是 matches()。matches() 要求整串匹配,"Desk" 就永远匹配不到 "Study Desk"。
  2. searchByPattern 只能靠 toString()。父类看不见 brand / course / weightKg,但这些字段都被子类写进了 detail(),而 detail() 被拼进了 toString()——所以「搜得到」是 S4 那个设计的回报。
  3. equals 不是 ==。== 比引用,换成动态拼接出来的标题就会失配。

8.4 驱动 Step5.java

public class Step5
{
    public static void main(String[] args)
    {
        Market market = buildMarket();
 
        market.list();
        System.out.println("size = " + market.size());
        System.out.println("total = " + Money.format(market.totalValue()));
        System.out.println();
 
        show("searchByTitle(\"iPad Air 11\")", market.searchByTitle("iPad Air 11"));
        show("searchByTitle(\"ipad air 11\")", market.searchByTitle("ipad air 11"));
        show("searchByTitlePattern(\"Desk\")", market.searchByTitlePattern("Desk"));
        show("searchByTitlePattern(\"e\")", market.searchByTitlePattern("e"));
        show("searchByPattern(\"Apple\")", market.searchByPattern("Apple"));
        show("searchByPattern(\"EIE3320\")", market.searchByPattern("EIE3320"));
        show("searchByPattern(\"Nintendo\")", market.searchByPattern("Nintendo"));
    }
 
    private static void show(String call, Listing result)
    {
        if (result == null)
        {
            System.out.println(call + "  ->  null");
        }
        else
        {
            System.out.println(call + "  ->  " + result.getTitle());
        }
    }
 
    static Market buildMarket()
    {
        Owner chan = new Owner("Chan Tai Man", "6123 4567");
        Owner lee = new Owner("Lee Siu Ming", "9876 5432");
 
        Market market = new Market();
        market.add(new TextBook("EIE3320 Lecture Notes", 120.0, chan, 14, 31, "EIE3320", 3));
        market.add(new Device("iPad Air 11", 4300.0, lee, 9, 5, "Apple", 12));
        market.add(new Furniture("Study Desk", 480.0, chan, 21, 47, 120.0, 60.0, 18.0));
        market.add(new TextBook("Calculus 8e", 260.0, lee, 8, 0, "AMA1120", 8));
        market.add(new Device("Mechanical Keyboard", 350.0, chan, 23, 59, "Keychron", 6));
        return market;
    }
}

buildMarket() 是 static 且包可见,S6 会直接复用它(Step5.buildMarket()),所以不要把它改成 private,也不要改方法名。

8.5 期望输出

ID   CATEGORY   TITLE                    PRICE      OWNER              POSTED
---- ---------- ------------------------ ---------- ------------------ ------------
#1   TextBook   EIE3320 Lecture Notes    $120.00    Chan Tai Man       2:31 p.m.
#2   Device     iPad Air 11              $4,300.00  Lee Siu Ming       9:05 a.m.
#3   Furniture  Study Desk               $480.00    Chan Tai Man       9:47 p.m.
#4   TextBook   Calculus 8e              $260.00    Lee Siu Ming       8:00 a.m.
#5   Device     Mechanical Keyboard      $350.00    Chan Tai Man       11:59 p.m.
size = 5
total = $5,510.00

searchByTitle("iPad Air 11")  ->  iPad Air 11
searchByTitle("ipad air 11")  ->  null
searchByTitlePattern("Desk")  ->  Study Desk
searchByTitlePattern("e")  ->  EIE3320 Lecture Notes
searchByPattern("Apple")  ->  iPad Air 11
searchByPattern("EIE3320")  ->  EIE3320 Lecture Notes
searchByPattern("Nintendo")  ->  null

8.6 验收清单

  • 表格六列全部对齐($4,300.00 那行最容易把 PRICE 列挤歪)
  • total = $5,510.00(120 + 4300 + 480 + 260 + 350)
  • "ipad air 11" → null(大小写敏感,证明用了 equals)
  • searchByPattern("Apple") → iPad Air 11(证明 toString() 真的把 detail() 拼进去了)
  • searchByPattern("Nintendo") → null(找不到要返回 null,不能抛异常、不能返回空对象)

8.7 三个坑

坑现象原因
searchByPattern 搜不到 Apple返回 nulltoString() 没拼 detail()(S4 漏了),或搜的是 title 而不是 toString()
searchByTitlePattern("Desk") 返回 null找不到 Study Desk用了 matches() 而不是 find()
表格错列$4,300.00LeePRICE 列宽写成 %-9s($4,300.00 是 9 字符,正好顶满、没有间隔)

9. S6 · Iterator + Comparable + 排序(20 分钟)

目标:把 HW4 Q3(边遍历边删)、HW4 Q6(Comparable + Collections.sort)、Ch14(接口作返回类型) 各练一遍。

9.1 要给 Listing 补的两处

改动内容
类声明public abstract class Listing implements Comparable<Listing>
新增方法public int compareTo(Listing other) —— 先比 price,相等再比 id

compareTo 的返回约定:小于 → 负数、相等 → 0、大于 → 正数。所以:

if (price < other.price) return -1;
if (price > other.price) return 1;
return Integer.compare(id, other.id);

9.2 要给 Market 加的三个方法

方法要求
int removeByOwner(String ownerName)用 Iterator 遍历,命中就 iterator.remove(),返回删掉几条
ArrayList<Shippable> shippableItems()遍历 listings,instanceof Shippable 的转型后加进新列表,返回它
void sortByPrice()Collections.sort(listings);(一行)

9.3 为什么删除必须用 Iterator

错误写法(很多人会这么写):

for (int i = 0; i < listings.size(); i++)
{
    if (listings.get(i).getOwner().getName().equals(ownerName))
    {
        listings.remove(i);     // 删完之后 i 已经指向下一个元素,会漏删
    }
}

删除后后面的元素整体前移,i++ 又跳过一个,于是连续两条同属主时会漏删一条。本项目的数据正好是「Lee 的两条不相邻」——所以这个 bug 不会在本数据上暴露,但换成相邻两条就会。

正确写法(Iterator 的 remove() 是唯一安全方式):

Iterator<Listing> iterator = listings.iterator();
while (iterator.hasNext())
{
    Listing item = iterator.next();
    if (item.getOwner().getName().equals(ownerName))
    {
        iterator.remove();
        removed++;
    }
}

连带一个坑:for (Listing item : listings) 里调 listings.remove(item) 会抛 ConcurrentModificationException。增强 for 只能读,不能删。

9.4 为什么 shippableItems() 的返回类型是 ArrayList<Shippable>

因为元素的声明类型是接口,不是 Listing。这样调用方拿到的每个元素都能直接调 shippingFee(),不需要再 instanceof:

ArrayList<Shippable> shippable = market.shippableItems();
for (Shippable item : shippable)
{
    item.shippingFee();      // 直接调,无需转型
}

这正是 Ch14 p.32「接口也是类型」 的用处,也是 HW5 Q1 返回 Item 的同构写法(那边返回父类,这边返回接口)。

9.5 驱动 Step6.java

import java.util.ArrayList;
 
public class Step6
{
    public static void main(String[] args)
    {
        Market market = Step5.buildMarket();
 
        System.out.println("before: size = " + market.size());
        market.list();
        System.out.println();
 
        int removed = market.removeByOwner("Lee Siu Ming");
        System.out.println("removeByOwner(\"Lee Siu Ming\") -> removed = " + removed);
        market.list();
        System.out.println("size = " + market.size()
                + ", total = " + Money.format(market.totalValue()));
        System.out.println();
 
        ArrayList<Shippable> shippable = market.shippableItems();
        System.out.println("shippable = " + shippable.size());
        for (Shippable item : shippable)
        {
            System.out.println("  fee = " + Money.format(item.shippingFee()));
        }
        System.out.println();
 
        market.sortByPrice();
        System.out.println("after sortByPrice():");
        market.list();
    }
}

Step6 依赖 Step5.java(调 Step5.buildMarket()),所以这个阶段 Step5.java 必须留在同一目录里。

9.6 期望输出

before: size = 5
ID   CATEGORY   TITLE                    PRICE      OWNER              POSTED
---- ---------- ------------------------ ---------- ------------------ ------------
#1   TextBook   EIE3320 Lecture Notes    $120.00    Chan Tai Man       2:31 p.m.
#2   Device     iPad Air 11              $4,300.00  Lee Siu Ming       9:05 a.m.
#3   Furniture  Study Desk               $480.00    Chan Tai Man       9:47 p.m.
#4   TextBook   Calculus 8e              $260.00    Lee Siu Ming       8:00 a.m.
#5   Device     Mechanical Keyboard      $350.00    Chan Tai Man       11:59 p.m.

removeByOwner("Lee Siu Ming") -> removed = 2
ID   CATEGORY   TITLE                    PRICE      OWNER              POSTED
---- ---------- ------------------------ ---------- ------------------ ------------
#1   TextBook   EIE3320 Lecture Notes    $120.00    Chan Tai Man       2:31 p.m.
#3   Furniture  Study Desk               $480.00    Chan Tai Man       9:47 p.m.
#5   Device     Mechanical Keyboard      $350.00    Chan Tai Man       11:59 p.m.
size = 3, total = $950.00

shippable = 2
  fee = $90.00
  fee = $23.50

after sortByPrice():
ID   CATEGORY   TITLE                    PRICE      OWNER              POSTED
---- ---------- ------------------------ ---------- ------------------ ------------
#1   TextBook   EIE3320 Lecture Notes    $120.00    Chan Tai Man       2:31 p.m.
#5   Device     Mechanical Keyboard      $350.00    Chan Tai Man       11:59 p.m.
#3   Furniture  Study Desk               $480.00    Chan Tai Man       9:47 p.m.

9.7 验收清单

  • removed = 2(Lee 的两条)
  • 删完 size = 3、total = $950.00(120 + 480 + 350)
  • id 不变:剩下的是 #1 #3 #5,不是重新编号成 #1 #2 #3
  • shippable = 2,两条运费 $90.00(桌子)与 $23.50(键盘:20 + 3.5)——TextBook 不在里面
  • 排序后是 $120 → $350 → $480(升序),id 顺序变成 #1 #5 #3

9.8 三个坑

坑现象原因
排序后 #1 #3 #5 → 变成 #1 #2 #3id 变了id 是构造时分配的,排序不改变 id;如果变了,说明你把 id 存成了「列表下标」
用下标删除偶发漏删见 9.3
Collections.sort(listings) 编译不过no suitable method found for sort(ArrayList<Listing>)Listing 没写 implements Comparable<Listing>

10. S7 · 终局验收(15 分钟)

目标:用完整驱动跑一遍,把五份作业的考点一次性串起来。

10.1 驱动 TestMarket.java(这是最终判分器,照抄不许改)

import java.util.ArrayList;
 
public class TestMarket
{
    public static void main(String[] args)
    {
        Owner chan = new Owner("Chan Tai Man", "6123 4567");
        Owner lee = new Owner("Lee Siu Ming", "9876 5432");
 
        Market market = new Market();
        market.add(new TextBook("EIE3320 Lecture Notes", 120.0, chan, 14, 31, "EIE3320", 3));
        market.add(new Device("iPad Air 11", 4300.0, lee, 9, 5, "Apple", 12));
        market.add(new Furniture("Study Desk", 480.0, chan, 21, 47, 120.0, 60.0, 18.0));
        market.add(new TextBook("Calculus 8e", 260.0, lee, 8, 0, "AMA1120", 8));
        market.add(new Device("Mechanical Keyboard", 350.0, chan, 23, 59, "Keychron", 6));
 
        section("1. list()");
        market.list();
 
        section("2. totals");
        System.out.println("size        = " + market.size());
        System.out.println("totalValue  = " + Money.format(market.totalValue()));
        System.out.println("count(static) = " + Listing.getCount());
 
        section("3. searches");
        report("searchByTitle(\"iPad Air 11\")", market.searchByTitle("iPad Air 11"));
        report("searchByTitle(\"ipad air 11\")", market.searchByTitle("ipad air 11"));
        report("searchByTitlePattern(\"Desk\")", market.searchByTitlePattern("Desk"));
        report("searchByPattern(\"Apple\")", market.searchByPattern("Apple"));
        report("searchByPattern(\"Nintendo\")", market.searchByPattern("Nintendo"));
 
        section("4. basket");
        Basket small = new Basket();
        String[] names = {"pen", "ruler", "eraser", "clip", "glue", "tape"};
        for (int i = 0; i < names.length; i++)
        {
            boolean ok = small.add(new TextBook(names[i], 10.0 + i, chan, 10, 0, "EIE3320", 1));
            System.out.println("add " + names[i] + " -> " + ok);
        }
        small.print();
 
        section("5. ship + remove + sort");
        ArrayList<Shippable> shippable = market.shippableItems();
        System.out.println("shippable = " + shippable.size());
        for (Shippable item : shippable)
        {
            System.out.println("  fee = " + Money.format(item.shippingFee()));
        }
        System.out.println("removed = " + market.removeByOwner("Lee Siu Ming"));
        market.sortByPrice();
        market.list();
 
        section("6. detail block of the cheapest listing");
        market.searchByTitlePattern("").print();
    }
 
    private static void section(String title)
    {
        System.out.println();
        System.out.println("== " + title + " ==");
    }
 
    private static void report(String call, Listing result)
    {
        System.out.println(call + "  ->  "
                + (result == null ? "null" : result.getTitle()));
    }
}

两个彩蛋,跑完再回看:

  • 第 2 节的 count(static) = 5:Listing.count 是类的静态计数器,与 market 里有几条无关。S4 之后 TextBook/Device/Furniture 都继承它、共用同一个 count(ch8 p.56–58)。
  • 第 6 节的 searchByTitlePattern(""):空字符串模式能匹配任何标题(find() 对空模式恒为真),于是返回第一条——这就是「先排序再取最便宜」的偷懒写法。

10.2 期望输出(54 行 + 前置空行,逐字)


== 1. list() ==
ID   CATEGORY   TITLE                    PRICE      OWNER              POSTED
---- ---------- ------------------------ ---------- ------------------ ------------
#1   TextBook   EIE3320 Lecture Notes    $120.00    Chan Tai Man       2:31 p.m.
#2   Device     iPad Air 11              $4,300.00  Lee Siu Ming       9:05 a.m.
#3   Furniture  Study Desk               $480.00    Chan Tai Man       9:47 p.m.
#4   TextBook   Calculus 8e              $260.00    Lee Siu Ming       8:00 a.m.
#5   Device     Mechanical Keyboard      $350.00    Chan Tai Man       11:59 p.m.

== 2. totals ==
size        = 5
totalValue  = $5,510.00
count(static) = 5

== 3. searches ==
searchByTitle("iPad Air 11")  ->  iPad Air 11
searchByTitle("ipad air 11")  ->  null
searchByTitlePattern("Desk")  ->  Study Desk
searchByPattern("Apple")  ->  iPad Air 11
searchByPattern("Nintendo")  ->  null

== 4. basket ==
add pen -> true
add ruler -> true
add eraser -> true
add clip -> true
add glue -> true
add tape -> false
1: [TextBook] pen ($10.00) by Chan Tai Man | 6123 4567 | Course: EIE3320, ed.1
2: [TextBook] ruler ($11.00) by Chan Tai Man | 6123 4567 | Course: EIE3320, ed.1
3: [TextBook] eraser ($12.00) by Chan Tai Man | 6123 4567 | Course: EIE3320, ed.1
4: [TextBook] clip ($13.00) by Chan Tai Man | 6123 4567 | Course: EIE3320, ed.1
5: [TextBook] glue ($14.00) by Chan Tai Man | 6123 4567 | Course: EIE3320, ed.1
items = 5, total = $60.00

== 5. ship + remove + sort ==
shippable = 3
  fee = $63.00
  fee = $90.00
  fee = $23.50
removed = 2
ID   CATEGORY   TITLE                    PRICE      OWNER              POSTED
---- ---------- ------------------------ ---------- ------------------ ------------
#1   TextBook   EIE3320 Lecture Notes    $120.00    Chan Tai Man       2:31 p.m.
#5   Device     Mechanical Keyboard      $350.00    Chan Tai Man       11:59 p.m.
#3   Furniture  Study Desk               $480.00    Chan Tai Man       9:47 p.m.

== 6. detail block of the cheapest listing ==
#1 [TextBook] EIE3320 Lecture Notes
   price  : $120.00
   owner  : Chan Tai Man (6123 4567)
   posted : 2:31 p.m. (afternoon)
   detail : Course: EIE3320, ed.3
   ship   : self-pickup

10.3 自动比对(不要用肉眼)

把期望输出存成 expected\step7.txt(或任意名字),然后:

javac -encoding UTF-8 *.java
java TestMarket > mine.txt
Compare-Object (Get-Content expected\step7.txt) (Get-Content mine.txt) -SyncWindow 0

没有任何输出 = 完全一致。 有输出时每条会是 <=(期望里有、你没有)或 =>(你多出来),定位很快。

Compare-Object 默认忽略行尾空格,这对本题是好事(编辑器会吃掉行尾空格)。但行首空格、列间空格不会被忽略——所以缩进和表格对齐照样能测出来。

10.4 最终验收清单(六条全过 = 可以进考场)

  • 六节标题都在,顺序对(== 1. list() == … == 6. detail block … ==)
  • 第 1 节 5 行数据,六列对齐,$4,300.00 不挤压 OWNER 列
  • 第 3 节五个搜索结果与期望完全一致(尤其 "ipad air 11" → null 与 "Apple" → iPad Air 11)
  • 第 4 节第 6 个 add tape -> false,且 items = 5, total = $60.00(没有异常)
  • 第 5 节 shippable = 3;删完后 id 是 #1 #5 #3;排序后价格升序
  • 第 6 节 ship : self-pickup(TextBook 不实现接口)

11. 卡住了怎么办(分层提示,从 L1 开始看)

建议:每卡住一次,只看一级。看完还想不通再看下一级。三级都看完基本等于答案。

S1 没跑对

S2 的 12 小时制不对

S2 的 slot() 报「missing return statement」

S3 抛 NullPointerException

S4 编译报「is not abstract and does not override abstract method」

S4 运费不显示 / 显示成 self-pickup

S5 表格错列

S5 searchByPattern("Apple") 找不到

S6 排序不生效 / 编译报 sort

S6 删除漏了一条


12. 常见编译错误速查(按报错原文找)

报错(原文片段)几乎总是修法
constructor X in class Y cannot be applied to given types子类构造器首行没写 super(...),或参数不对对齐父类构造器的参数表
X is not abstract and does not override abstract method m()子类漏实现抽象方法 / 接口方法补方法,或把子类也声明 abstract
cannot find symbol: method getXxx()忘了写 getter / 大小写拼错补 getter;注意 getTitle 不是 gettitle
cannot find symbol: class ArrayList忘了 import java.util.ArrayList;加 import(String/Math 不用,它们在 java.lang)
incompatible types: Object cannot be converted to Listing裸 ArrayList / 裸 Iterator 没写泛型ArrayList<Listing> / Iterator<Listing>
incompatible types: possible lossy conversion from double to int把 double 赋给 int要么改类型,要么显式 (int)
attempting to assign weaker access privileges实现接口/覆盖抽象方法时写了 protected 或包可见一律 public
missing return statement有分支没 return补 default: return ...;
non-static variable listings cannot be referenced from a static context在 main 里直接用了实例字段/实例方法先 new 对象再调;或把方法改 static
class X is public, should be declared in a file named X.java文件名与 public class 名不一致文件名严格等于类名(大小写也算)
reached end of file while parsing少一个大括号 }数 {};用 IDE 的自动缩进(Auto layout)定位
variable price might not have been initializedfinal 字段没在构造器里赋值构造器覆盖全部分支,或声明时给初值
Exception ... ConcurrentModificationException(运行时)增强 for 里删元素改用 Iterator.remove()
Exception ... ClassCastException(运行时)转型前没 instanceof先判断再转

13. 时间预算与二刷建议

13.1 单次通刷(约 2 小时 15 分)

阶段预算超时说明
S0 环境5 min超过 10 分钟说明环境有问题,先解决环境
S1 Owner/Money10 min千分位想不出来就直接看 L3
S2 Listing20 min12 小时制是重点,慢一点值得
S3 Basket15 min这阶段纯复习 HW3,应该最快
S4 继承 + 抽象 + 接口25 min最该花时间的 25 分钟
S5 Market + 三搜索25 min表格对齐耗时可跳过(抄 §8.2)
S6 Iterator / Comparable20 mincompareTo 想不清就看 L3
S7 终局15 min对齐输出 + 逐条核对清单

13.2 二刷(只重做四个「手会生」的点)

考前第二次不要重写全部,只做这四个,每个 10 分钟:

  1. postedTime() —— 12 小时制,写 5 个拐点自测(0:05、11:59、12:00、14:31、23:59)。
  2. print() 的 instanceof Shippable —— 接口 + 转型 + 运费。
  3. 三个搜索的差别 —— 在纸上写三行伪代码:equals / title.find / toString.find。
  4. Iterator.remove() —— 默写那四行 while。

13.3 从「能跑」到「能考」:把代码翻译成答案

这个项目不只练手,它的每个部分都能直接变成考试答案:

考试可能问用本项目的哪段回答
「抽象类和接口的区别」Listing 有字段/构造器/具体方法(抽象类);Shippable 只有签名(接口)
「为什么需要 abstract 方法」print() 一份实现 + category() 由子类决定
「多态的实际用处」market.list() 一行 item.category() 处理三种类型
「向下转型为什么必要」print() 里不转型就调不到 shippingFee()
「== 和 equals」searchByTitle 必须 equals
「Iterator 的优势」removeByOwner 边遍历边删
「为什么 ArrayList<Listing> 不用强转」S5 里 listings.get(i).getPrice() 直接可用
「静态成员属于谁」Listing.getCount() 统计三种子类共创建了多少条

14. 自测:合上代码,回答这 8 个问题

  1. Listing 为什么必须是 abstract?如果去掉 abstract 只留两个抽象方法会怎样?
  2. print() 里为什么不能写 if (category().equals("Device"))?
  3. TextBook 为什么不实现 Shippable?如果强行让它实现,shippingFee() 该怎么写才合理?
  4. searchByPattern("Apple") 能命中,靠的是哪三件事串起来?(提示:字段私有 → detail() → toString() → 动态绑定)
  5. removeByOwner 用下标删会漏删,为什么?举一个会漏的具体数据。
  6. sortByPrice() 之后 #3 排到了最后,为什么 id 没变?
  7. Step2 与 Step3 在 S4 之后为什么编译不过?
  8. 如果把 Market 的 ArrayList<Listing> 换成 Listing[],add 方法要改什么?(提示:回到 S3)

第 8 题的答案就是「为什么标准库要用 ArrayList」——数组要自己扩容、自己维护条数,而 ArrayList 替你做了。这也是 ch11 p.88 那四条痛点的由来。


链接

配套精读

课堂笔记

素材

文件说明
EIE3320 HW 1..5五份作业原件;本项目的每阶段都对应其中一段
dome-v2 / auction 项目与本项目同构的原始 BlueJ 工程,可对照看结构