Chapter 8 — Reps
Conditioning, not grading. Type, compile, run. AI off.
Last conditioning before the final, so the set is bigger and the back half is exam prep. Reps 1–12 drill this week; Reps 13–18 are cumulative, and each says which half of the exam it prepares.
Every rep ships with the exact output the finished program prints. That is your grader. Match it character for character and you got it; a difference is the bug, and finding it is the rep.
Two habits: one folder per rep (Java gets unhappy when two Hero.java files share a directory), and do them in order — Rep 8 reuses Rep 5’s Combatant, Rep 12 reuses Rep 11’s classes, Rep 17 starts from Rep 16.
Reps 1–5: Abstract Classes
Rep 1 — Shape
Four files, from scratch. Don’t open code/ until you’ve finished and compared.
Shape.java— abstract;public abstract double area();,public abstract String kind();, and a concretedescribe()printingkind() + " area = " + area().Circle.java—extends Shape, oneradius;area()returnsMath.PI * radius * radius,kind()returns"circle".Rectangle.java—extends Shape,widthandheight;kind()returns"rectangle".ShapeDemo.java—static double totalArea(Shape[] shapes)summings.area(), and amainholdingShape[] shapes = { new Circle(3.0), new Rectangle(4.0, 5.0) };that callsdescribe()on each, then prints"total area = " + totalArea(shapes).
Build all four, run java ShapeDemo.
Expected output:
circle area = 28.274333882308138
rectangle area = 20.0
total area = 48.27433388230814
The long decimals are double being honest about π (§8.3). If your last digits differ, you computed area some other way.
Rep 2 — Bug drill: new Shape()
Keep Rep 1’s Shape.java. Add exactly this beside it and compile with javac Shape.java BreakShape.java.
public class BreakShape {
public static void main(String[] args) {
Shape s = new Shape();
s.describe();
}
}
Expected output (a compile error — nothing runs):
BreakShape.java:3: error: Shape is abstract; cannot be instantiated
Shape s = new Shape();
^
1 error
Wording drifts slightly between Java versions; the file, line number, and caret don’t. Say the reason out loud before moving on: new Shape() would build an object whose method table has a hole in it (§8.2). Delete the file.
Rep 3 — Bug drill: the subclass with a hole
In Rep 1’s folder, add Triangle.java — extends Shape, fields base and height, implementing only kind(). Compile.
Expected output:
Triangle.java:1: error: Triangle is not abstract and does not override abstract method area() in Shape
public class Triangle extends Shape {
^
1 error
That message is a to-do list, not a complaint (§8.15). Now add area() returning 0.5 * base * height and put new Triangle(6.0, 4.0) third in the Shape[].
Expected output:
circle area = 28.274333882308138
rectangle area = 20.0
triangle area = 12.0
total area = 60.27433388230814
Notice what you did not change: describe(), totalArea(), the loop. New behavior arrived by writing a new class.
Rep 4 — The template method
Fresh folder. §8.3’s pattern, and the strongest argument for abstract classes over interfaces.
StudySession.java— abstract;protected String topicandprotected int minutes; constructor for both; abstractwarmUp()andmainSet(); concretecoolDown()printing" cool down: write one sentence about " + topic; concreterun()printing"== " + topic + " (" + minutes + " min) =="then callingwarmUp(),mainSet(),coolDown()in that order.TracingSession—warmUp()prints" warm up: retype one example from the chapter";mainSet()prints" main set: predict the output of " + (minutes / 5) + " programs".BuildSession—warmUp()prints" warm up: a three-line main that compiles";mainSet()prints" main set: build " + (minutes / 15) + " classes, compiling after each"; and it overridescoolDown()to print" cool down: delete the feature you could not finish".PlanDemo.java—new TracingSession("Part A tracing", 45)andnew BuildSession("Part B build", 90)in aStudySession[];run()each.
Expected output:
== Part A tracing (45 min) ==
warm up: retype one example from the chapter
main set: predict the output of 9 programs
cool down: write one sentence about Part A tracing
== Part B build (90 min) ==
warm up: a three-line main that compiles
main set: build 6 classes, compiling after each
cool down: delete the feature you could not finish
run() is finished code calling two methods with no bodies, and no subclass can scramble the order. (45 / 5 and 90 / 15 are integer division. Chapter 1’s trap never retires.)
Rep 5 — The abstract middle layer
Fresh folder. Keep it — Reps 8, 11, and 12 all reuse Combatant.java.
Type Combatant.java exactly as §8.1 gives it. Then add a layer in the middle:
Spellcaster.java—public abstract class Spellcaster extends Combatant; addsprotected int mana, a constructor(String name, int hp, int mana)starting withsuper(name, hp);,canCast(int cost)returningmana >= cost, andgetMana(). It never implementstakeTurn, so it stays abstract.Cleric.java—extends Spellcaster, and finally implementstakeTurn: ifcanCast(10), spend 10 mana, printname + " rebukes " + opponent.getName() + " for 9."and deal 9; else printname + " is out of mana and holds the line."CasterDemo.java—Cleric bede = new Cleric("Bede", 70, 25);andCleric hilda = new Cleric("Hilda", 60, 5);held in aCombatant[], plus areportprinting" " + getName() + ": HP=" + getHp() + ", alive=" + isAlive()per member. Follow the headings below: report, manas, bothcanCast(10)answers, three turns (bede→hilda,hilda→bede,bede→hilda), report, manas.
Expected output:
start:
Bede: HP=70, alive=true
Hilda: HP=60, alive=true
mana: Bede=25, Hilda=5
canCast(10)? Bede=true, Hilda=false
turns:
Bede rebukes Hilda for 9.
Hilda is out of mana and holds the line.
Bede rebukes Hilda for 9.
end:
Bede: HP=70, alive=true
Hilda: HP=42, alive=true
mana: Bede=5, Hilda=5
Bede’s HP is right at both ends because Combatant’s constructor ran, through two super(...) calls. Abstract classes have constructors (§8.2).
Now prove the middle layer is really abstract: in a throwaway file, write Spellcaster g = new Spellcaster("Gale", 60, 20); and compile.
Expected output:
BreakCaster.java:3: error: Spellcaster is abstract; cannot be instantiated
Spellcaster g = new Spellcaster("Gale", 60, 20);
^
1 error
Spellcaster implemented nothing new, so the hole it inherited is still a hole. Delete that file.
Reps 6–9: Interfaces
Rep 6 — One contract, two unrelated classes
Fresh folder. Write Healable.java exactly as §8.4 gives it — two signatures, no bodies, no public on the methods. Then two classes that share no base class at all:
Hero.java implements Healable— privatename,hp,maxHp(constructor setsmaxHp = hp); atakeDamage(int)helper;heal(int)clamps withMath.min(maxHp, hp + amount)and prints" " + name + " heals " + amount + ". HP: " + hp;canBeHealed()returnshp < maxHp.Sapling.java implements Healable— privatelabel,height,maxHeight;heal(int)clamps atmaxHeightand prints" " + label + " grows " + amount + ". height: " + height;canBeHealed()returnsheight < maxHeight.HealDemo.java—new Hero("Maya", 100),takeDamage(30), then it plusnew Sapling("Oak by the gate", 12, 20)in oneArrayList<Healable>. Two passes; each prints"pass N:"then, per element, eitherheal(10)ifcanBeHealed()or" nothing to do".
Expected output:
pass 1:
Maya heals 10. HP: 80
Oak by the gate grows 10. height: 20
pass 2:
Maya heals 10. HP: 90
nothing to do
That loop never mentions Hero or Sapling. It knows two method names, because those are the only two things the contract guarantees.
Rep 7 — Two contracts, one class
Same folder. Add Burnable.java with a single void burn(int amount); and change Hero to implements Healable, Burnable. burn(int) floors at 0 and prints " " + name + " is burned for " + amount + ". HP: " + hp.
TwoContractsDemo.java — new Hero("Maya", 100), takeDamage(30), then hold the same object through each interface: Healable h = maya; h.heal(15); under the heading held as Healable:, then Burnable b = maya; b.burn(20); under held as Burnable:, then print "canBeHealed=" + maya.canBeHealed().
Expected output:
held as Healable:
Maya heals 15. HP: 85
held as Burnable:
Maya is burned for 20. HP: 65
canBeHealed=true
One object, two contracts, two type-safe views. A class extends one class and implements as many interfaces as it likes (§8.5).
Before leaving, break it once: delete the word public from Sapling’s heal and compile.
Expected output:
Sapling.java:4: error: heal(int) in Sapling cannot implement heal(int) in Healable
void heal(int amount) { height += amount; }
^
attempting to assign weaker access privileges; was public
1 error
Interface methods are implicitly public and your implementation may not narrow that. Put it back.
Rep 8 — Bug drill: calling through the base type
Fresh folder. Copy in Rep 5’s Combatant.java and Rep 7’s Healable.java and Burnable.java, then do the refactor the last two reps set up — let the base own the state.
Hero.java—extends Combatant implements Healable, Burnable; constructor callssuper(name, hp);takeTurnprintsname + " strikes " + opponent.getName() + " for 15."and deals 15; contract methods print without leading spaces now, e.g.name + " heals " + amount + ". HP: " + hp.Squire.java—extends Combatant, no interfaces;takeTurnprintsname + " raises a shield in front of " + opponent.getName() + "."
Now the drill. Put new Hero("Maya", 100) in an ArrayList<Combatant> and, inside for (Combatant c : party), call c.heal(10);. Compile.
Expected output:
BreakCall.java:8: error: cannot find symbol
c.heal(10);
^
symbol: method heal(int)
location: variable c of type Combatant
1 error
The object is a Hero and can heal. The compiler judges by the declared type. Now try the obvious dodge — Combatant c = new Hero("Maya", 100); Healable h = c; — and compile that.
Expected output:
BreakAssign.java:4: error: incompatible types: Combatant cannot be converted to Healable
Healable h = c;
^
1 error
Java widens automatically and never narrows for you. The fix is §8.5’s idiom: ask, then cast. Write CapabilityFix.java — a party of new Hero("Maya", 100) and new Squire("Tomas", 60), receiveDamage(25) to everyone, then a loop that either calls ((Healable) c).heal(10) when c instanceof Healable or prints c.getName() + " cannot be healed. HP: " + c.getHp().
Expected output:
Maya heals 10. HP: 85
Tomas cannot be healed. HP: 35
Memorize those two errors. They are the most common Week 8 failures and both say the same thing: you are calling through a type that never made that promise.
Rep 9 — Default methods
Fresh folder. Type Restorable.java from §8.7 — four abstract methods plus default boolean isFullyRestored() { return getCondition() >= getMaxCondition(); }.
Then RestorableDemo.java, holding two non-public helper classes in the same file (legal when only one class is public):
Manuscript implements Restorable— clamps condition to 0–100,maxCondition100, and overridesisFullyRestored()toreturn condition >= 90;because it has permanent loss.Chapel implements Restorable— same fields and methods, noisFullyRestored()at all.main— both in oneArrayList<Restorable>:new Manuscript("Fragment 7", 65),new Chapel("Village Chapel", 55). Report,restore(30)on everything, report,restore(20), report. Each line is" " + getLabel() + ": " + getCondition() + "/litman-books/" + getMaxCondition() + " fullyRestored=" + isFullyRestored().
Expected output:
start:
Fragment 7: 65/100 fullyRestored=false
Village Chapel: 55/100 fullyRestored=false
after one pass of 30:
Fragment 7: 95/100 fullyRestored=true
Village Chapel: 85/100 fullyRestored=false
after a second pass of 20:
Fragment 7: 100/100 fullyRestored=true
Village Chapel: 100/100 fullyRestored=true
Read the middle block twice. Same question, same loop — one object answers with the default, one with its override, and the loop never learns which.
Reps 10–12: Abstraction, Assembled
Rep 10 — Comparable and Comparator
Fresh folder. Witness.java implements Comparable<Witness> — private name and century, getters, compareTo returning Integer.compare(this.century, other.century).
WitnessSort.java — an ArrayList<Witness> of Alpha 4, Beta 2, Gamma 5, Delta 1, Epsilon 3, in that order (placeholder names, not a scholarly citation). Print it as entered, then after Collections.sort(list), then after list.sort(...) with an anonymous Comparator<Witness> comparing getName(). Each element prints as " " + getName() + " (century " + getCentury() + ")".
Expected output:
as entered:
Alpha (century 4)
Beta (century 2)
Gamma (century 5)
Delta (century 1)
Epsilon (century 3)
sorted by compareTo (century):
Delta (century 1)
Beta (century 2)
Epsilon (century 3)
Alpha (century 4)
Gamma (century 5)
sorted by a Comparator (name):
Alpha (century 4)
Beta (century 2)
Delta (century 1)
Epsilon (century 3)
Gamma (century 5)
One type, two orderings, zero changes to Witness (§8.8).
Rep 11 — The six-file build
Fresh folder, and the big one. Reuse Rep 5’s Combatant.java and Rep 8’s Healable.java, Burnable.java, Hero.java. Add:
Wizard.java—extends Combatant implements Healable, Burnable.takeTurnprintsname + " casts fireball at " + opponent.getName() + " for 12.", deals 12, and then checksif (opponent instanceof Burnable)and burns for 3.BattleDemo.java— §8.9’s driver.capabilityReport(ArrayList<Combatant>)prints per membergetName() + ": HP=" + getHp() + ", alive=" + isAlive() + ", Healable=" + healable + ", Burnable=" + burnable, each flag"yes"or"no"decided byinstanceof.mainbuildsHero("Maya", 100),Wizard("Marcus", 80),Wizard("Selene", 90); reports; deals 30 to everyone and reports; heals everyHealablethatcanBeHealed()by 20; runsmaya.takeTurn(marcus),marcus.takeTurn(selene),selene.takeTurn(maya); reports again. Headings as shown.
javac *.java, then java BattleDemo.
Expected output:
--- capabilities ---
Maya: HP=100, alive=true, Healable=yes, Burnable=no
Marcus: HP=80, alive=true, Healable=yes, Burnable=yes
Selene: HP=90, alive=true, Healable=yes, Burnable=yes
--- everyone takes 30 ---
Maya: HP=70, alive=true, Healable=yes, Burnable=no
Marcus: HP=50, alive=true, Healable=yes, Burnable=yes
Selene: HP=60, alive=true, Healable=yes, Burnable=yes
--- heal whoever is Healable and needs it ---
Maya heals 20. HP: 90
Marcus heals 20. HP: 70
Selene heals 20. HP: 80
--- turns ---
Maya strikes Marcus for 15.
Marcus casts fireball at Selene for 12.
Selene is burned for 3. HP: 65
Selene casts fireball at Maya for 12.
--- final ---
Maya: HP=78, alive=true, Healable=yes, Burnable=no
Marcus: HP=55, alive=true, Healable=yes, Burnable=yes
Selene: HP=65, alive=true, Healable=yes, Burnable=yes
The most important line in that transcript is the one that isn’t there. Marcus’s fireball on Selene printed a burn line; Selene’s on Maya did not. Same method, same call site — Selene implements Burnable and Maya doesn’t.
Rep 12 — The capability matrix
Fresh folder; reuse Combatant, Healable, Burnable, Hero, Wizard, Squire from Reps 8 and 11. Add:
Frozen.java—public interface Frozen { boolean isStillFrozen(); }IceWarden.java—extends Combatant implements Healable, Burnable, Frozen;takeTurnprintsname + " freezes " + opponent.getName() + " for 8."and deals 8;isStillFrozen()returnstrue;healandburnprint inHero’s format.MatrixDemo.java—Squire("Tomas", 60),Hero("Maya", 100),Wizard("Marcus", 80),IceWarden("Brigid", 90)in anArrayList<Combatant>; deal 20 to everyone; print--- one pass over the party ---; then per member print"* " + getName()and run three checks in order — heal 10 ifHealableandcanBeHealed(), burn 5 ifBurnable, printgetName() + " is still frozen, skipping."ifFrozenandisStillFrozen(). If none matched, printgetName() + " has no optional capabilities."
Your four combatant classes now implement zero, one, two, and three interfaces.
Expected output:
--- one pass over the party ---
* Tomas
Tomas has no optional capabilities.
* Maya
Maya heals 10. HP: 90
* Marcus
Marcus heals 10. HP: 70
Marcus is burned for 5. HP: 65
* Brigid
Brigid heals 10. HP: 80
Brigid is burned for 5. HP: 75
Brigid is still frozen, skipping.
One loop, four behaviors, no class name anywhere in it. Inheritance answered “what is it?”; interfaces answered “what can it do?”; the driver only asked the second (§8.6).
Reps 13–18: Cumulative Review Drills
The final is two parts (§8.19). Part A is timed code reading; Part B is a four-hour build from blank. These six split the same way. Do them in order; the last three are timed.
Rep 13 — Trace by hand first, Java (Part A)
The highest-yield drill in the chapter. Do it in this order or you get nothing from it. Type the file. Do not compile yet. On paper, go line by line and write every line you think it prints. Then compile, run, and compare.
import java.util.ArrayList;
interface Marked {
String mark();
}
abstract class Step {
protected String label;
protected int cost;
public Step(String label, int cost) {
this.label = label;
this.cost = cost;
}
public abstract void apply(Walker w);
public String getLabel() { return label; }
}
class Climb extends Step implements Marked {
public Climb(String label, int cost) { super(label, cost); }
@Override
public void apply(Walker w) { w.spend(cost * 2); }
@Override
public String mark() { return "climbed " + label; }
}
class Rest extends Step {
public Rest(String label, int cost) { super(label, cost); }
@Override
public void apply(Walker w) { w.recover(cost / 3); }
}
class Walker {
private String name;
private int energy;
public Walker(String name, int energy) {
this.name = name;
this.energy = Math.max(0, Math.min(100, energy));
}
public void spend(int n) { energy = Math.max(0, energy - n); }
public void recover(int n) { energy = Math.min(100, energy + n); }
public int getEnergy() { return energy; }
public String getName() { return name; }
public boolean isDone() { return energy <= 0; }
}
public class TraceMe {
public static void main(String[] args) {
Walker w = new Walker("Ruth", 120);
ArrayList<Step> route = new ArrayList<>();
route.add(new Climb("Hill Difficulty", 20));
route.add(new Rest("House Beautiful", 40));
route.add(new Climb("Valley Wall", 25));
route.add(new Rest("Arbor", 10));
route.add(new Climb("Last Ridge", 30));
for (Step s : route) {
if (w.isDone()) {
System.out.println(w.getName() + " stopped before " + s.getLabel() + ".");
break;
}
s.apply(w);
if (s instanceof Marked) {
System.out.println(" " + ((Marked) s).mark());
}
System.out.println(s.getLabel() + " -> energy " + w.getEnergy());
}
System.out.println("final: " + w.getEnergy());
}
}
Expected output:
climbed Hill Difficulty
Hill Difficulty -> energy 60
House Beautiful -> energy 73
climbed Valley Wall
Valley Wall -> energy 23
Arbor -> energy 26
climbed Last Ridge
Last Ridge -> energy 0
final: 0
Four traps, and Part A is built out of exactly these. (a) Ruth is constructed with 120 and starts at 100 — the class clamps its own state. (b) 40 / 3 is 13 and 10 / 3 is 3. (c) The last climb costs 60 against 26 energy and lands on 0, not −34. (d) The stopped before line never prints — the guard runs at the top of an iteration, and there is no iteration after the last one. If you predicted that line, you learned it today instead of on exam day.
Rep 14 — Trace by hand first, C++ (Part A)
Same protocol: paper first, then g++ -std=c++17 -Wall -Wextra and run. Part A sets C++ from Weeks 1–6 beside Java from Weeks 7–8 on purpose, so switch languages cold, as the exam will make you.
#include <iostream>
#include <string>
using namespace std;
struct Claim {
string text;
int weight;
};
void boost(int& w, int amount) {
w += amount;
}
int strongest_index(const Claim claims[], int count) {
if (count <= 0) {
return -1;
}
int best = 0;
for (int i = 1; i < count; i++) {
if (claims[i].weight > claims[best].weight) {
best = i;
}
}
return best;
}
int main() {
Claim claims[3] = {
{"design", 7},
{"morality", 9},
{"resurrection", 9}
};
int total = 0;
for (int i = 0; i < 3; i++) {
total += claims[i].weight;
}
cout << "total = " << total << endl;
cout << "mean = " << total / 3 << endl;
cout << "rem = " << total % 3 << endl;
boost(claims[0].weight, 5);
cout << "after boost: " << claims[0].weight << endl;
int best = strongest_index(claims, 3);
cout << "strongest = " << claims[best].text << endl;
string s = "the resurrection accounts";
cout << "find(res) = " << s.find("res") << endl;
cout << "npos? " << (s.find("zzz") == string::npos) << endl;
cout << "substr(4,12) = " << s.substr(4, 12) << endl;
return 0;
}
Expected output:
total = 25
mean = 8
rem = 1
after boost: 12
strongest = design
find(res) = 4
npos? 1
substr(4,12) = resurrection
Check four of those. 25 / 3 is 8. boost took int&, so it really changed the struct inside the array — which is why strongest is design, the claim that started weakest. find returns an index, and a failed find is compared against string::npos, never -1. And a bool prints as 1, not true, without boolalpha — a difference from Java worth having in your fingers.
Rep 15 — One program, two languages (Part A)
Type this, compile it, run it, keep the output.
#include <iostream>
#include <string>
using namespace std;
class Codex {
protected:
string name;
int century;
public:
Codex(string n, int c) : name(n), century(c) {}
virtual ~Codex() {}
virtual string describe() const {
return name + " (century " + to_string(century) + ")";
}
};
class Fragment : public Codex {
private:
int lines;
public:
Fragment(string n, int c, int l) : Codex(n, c), lines(l) {}
string describe() const override {
return Codex::describe() + " - fragment, " + to_string(lines) + " lines";
}
};
int main() {
// Sample data for the exercise, not a scholarly citation.
Codex* shelf[3];
shelf[0] = new Codex("Codex Alpha", 4);
shelf[1] = new Fragment("Fragment Beta", 2, 7);
shelf[2] = new Fragment("Fragment Gamma", 3, 104);
for (int i = 0; i < 3; i++) {
cout << shelf[i]->describe() << endl;
}
for (int i = 0; i < 3; i++) {
delete shelf[i];
}
return 0;
}
Now translate it to Java in three files — Codex.java, Fragment.java, and a ShelfDemo.java holding the three objects in an ArrayList<Codex> and printing describe() in an enhanced-for — without looking anything up. Five decisions, each a Chapter 7 fact: virtual disappears, the virtual destructor disappears, Codex::describe() becomes super.describe(), Codex* becomes a plain reference, and the delete loop disappears entirely.
Expected output — both programs, identical:
Codex Alpha (century 4)
Fragment Beta (century 2) - fragment, 7 lines
Fragment Gamma (century 3) - fragment, 104 lines
Two syntaxes, one idea — the whole §8.10 argument, and the reason a cumulative exam over two languages is fair.
Rep 16 — Build from blank: the journey (Part B)
Timer: 45 minutes. Blank folder, no notes beyond this book, AI off. Part B’s shape at about a third of its size. Print formats are given exactly, because that is how you check yourself.
Pilgrim.java— privatename,faith,courage; the constructor clamps both into 0–100 withMath.max(0, Math.min(100, v)); methodsencourage(int)(faith up, capped at 100),discourage(int)(faith down, floored at 0),unnerve(int)(courage down, floored at 0),getName(),getFaith(),getCourage(), andhasGivenUp()returningfaith <= 0.Encounter.java— abstract;protectednameandintensity; constructor for both;public abstract void engage(Pilgrim p);; concretegetName().Trial.java— prints"[Trial: " + name + "] " + p.getName() + " faith -" + intensity, thenp.discourage(intensity).Companion.java— prints"[Companion: " + name + "] " + p.getName() + " faith +" + intensity, thenp.encourage(intensity).Temptation.java— prints"[Temptation: " + name + "] " + p.getName() + " courage -" + intensity, thenp.unnerve(intensity).JourneyDemo.java— a staticrunRoute(Pilgrim p, ArrayList<Encounter> route)that, per encounter, returns early withp.getName() + " turned back before " + e.getName() + "."ifp.hasGivenUp(), else callse.engage(p)and prints" faith=" + p.getFaith() + " courage=" + p.getCourage(). After the loop it printsp.getName() + " reached the gate with faith=" + p.getFaith() + " courage=" + p.getCourage() + "."
main runs two routes. First new Pilgrim("Christian", 60, 70) over Trial("Slough of Despond", 25), Companion("Faithful", 20), Temptation("Vanity Fair", 30), Trial("Hill Difficulty", 15), Companion("Hopeful", 10). Then a line of ---. Then new Pilgrim("Pliable", 20, 40) over just Trial("Slough of Despond", 25) and Companion("Faithful", 20).
Expected output:
[Trial: Slough of Despond] Christian faith -25
faith=35 courage=70
[Companion: Faithful] Christian faith +20
faith=55 courage=70
[Temptation: Vanity Fair] Christian courage -30
faith=55 courage=40
[Trial: Hill Difficulty] Christian faith -15
faith=40 courage=40
[Companion: Hopeful] Christian faith +10
faith=50 courage=40
Christian reached the gate with faith=50 courage=40.
---
[Trial: Slough of Despond] Pliable faith -25
faith=0 courage=40
Pliable turned back before Faithful.
Ship that inside 45 minutes with no help and Part B’s Normal tier is a fair fight. If it took 90, do a second variant tonight with different stats and a fourth encounter type. Variety is the rep.
Rep 17 — Build from blank: capabilities and a container (Part B)
Timer: 75 minutes. Start from your Rep 16 folder. This adds Medium tier (interfaces) and Hard tier (a container class) — Patterns 3 and 5 from §8.12.
- Add
embolden(int n)toPilgrim: courage up, capped at 100. Strengthening.java—void strengthen(Pilgrim p);Testing.java—int trialCost();Companion implements Strengthening—strengthencallsp.embolden(5)and prints" (+5 courage from " + name + ")".Trial implements TestingwithtrialCost()returningintensity * 2;Temptation implements TestingwithtrialCost()returningintensity.Party.java— owns aprivate ArrayList<Pilgrim> members, withadd(Pilgrim)andint runJourney(ArrayList<Encounter> route). The outer loop over the route prints"== " + e.getName() + " ==". The inner loop over members: ifhasGivenUp(), print" " + p.getName() + " has turned back."andcontinue; elsee.engage(p), thenstrengthen(p)if the encounter isStrengthening, then print" " + p.getName() + ": faith=" + p.getFaith() + " courage=" + p.getCourage(). After the inner loop, addtrialCost()to a running total if the encounter isTesting. Return the total.PartyDemo.java— a party ofPilgrim("Christian", 60, 70)andPilgrim("Mercy", 20, 50)overTrial("Slough of Despond", 25),Companion("Faithful", 20),Temptation("Vanity Fair", 30),Trial("Hill Difficulty", 15); print"total trial cost: " + totalat the end.
Expected output:
== Slough of Despond ==
[Trial: Slough of Despond] Christian faith -25
Christian: faith=35 courage=70
[Trial: Slough of Despond] Mercy faith -25
Mercy: faith=0 courage=50
== Faithful ==
[Companion: Faithful] Christian faith +20
(+5 courage from Faithful)
Christian: faith=55 courage=75
Mercy has turned back.
== Vanity Fair ==
[Temptation: Vanity Fair] Christian courage -30
Christian: faith=55 courage=45
Mercy has turned back.
== Hill Difficulty ==
[Trial: Hill Difficulty] Christian faith -15
Christian: faith=40 courage=45
Mercy has turned back.
total trial cost: 110
Check the total by hand — 50 + 30 + 30 — and note that Companion contributes nothing, because it never signed the Testing contract. Note also that Party never names Trial, Companion, or Temptation, only Encounter and the two interfaces. Add a fourth encounter type tomorrow and Party does not change by one character.
Rep 18 — The timed rehearsal (Part B)
The last drill before the exam, and it has its own protocol printed at the top of the file. Follow it exactly.
Open code/PracticeFinal.java. Read only the prompt block. Close the file — actually close it. Open a blank project, set a timer for 90 minutes, and solve the prompt from nothing with the AI off. When the timer ends, finished or not, reopen the file and compare against the reference solution underneath. Different subject, same shape as Part B: you are rehearsing the shape, not the answer, which is why reading the solution first feels like studying and is not (§8.14).
Expected output of the reference solution (javac PracticeFinal.java, then java PracticeFinal):
[Challenge: Pop quiz] -20 confidence
Maya confidence: 30
[Encouragement: Written feedback] +10 confidence
(extra +5 boost from Written feedback)
Maya confidence: 45
[Challenge: Critique] -25 confidence
Maya confidence: 20
[Encouragement: Mentor note] +20 confidence
(extra +5 boost from Mentor note)
Maya confidence: 45
[Challenge: Final question] -15 confidence
Maya confidence: 30
Maya completed the session with confidence 30.
Your wording may differ — the prompt fixes the behavior, not the punctuation. The numbers must not. If your Maya ends anywhere but 30, walk the arithmetic: 50, −20, +10 then +5, −25, +20 then +5, −15. A drifting number means a clamp is in the wrong place or a stretch-goal encourage fired where it shouldn’t.
Done? One Last Thing.
Blank folder. Twenty minutes. No notes, no book, no earlier rep open. This is §8.17’s checkpoint with a compiler attached: produce it cold and you can write the spine of Part B cold.
From memory, write:
Testimony.java— abstract;protected String source; protected int weight;; constructor for both;public abstract void present();; concretegetSource()andgetWeight().Citable.java— an interface with one method,String citation();.Document.java—extends Testimony implements Citable.present()prints"[Document] " + source + " weight " + weight;citation()returns"cited: " + source.Rumor.java—extends Testimonyonly.present()prints"[Rumor] " + source + " weight " + weight.TestimonyDemo.java— anArrayList<Testimony>holdingDocument("Letter of Alpha", 8),Rumor("Market gossip", 2),Document("Court record", 9). Loop once: callpresent(), print" " + citation()for anythingCitable, accumulate the weights, then print"total weight = " + total.
Expected output:
[Document] Letter of Alpha weight 8
cited: Letter of Alpha
[Rumor] Market gossip weight 2
[Document] Court record weight 9
cited: Court record
total weight = 19
Abstract base, interface capability, polymorphic collection, one loop, an accumulator from Chapter 2, and a getWeight() the loop trusts because the base declared it. If it compiled the first time, you have the move.
You are ready for the final.
Up next: the §8.17 Checkpoint, then Final Part A — Code Reading & Tracing (50 points, auto-graded) and Final Part B — Pilgrim’s Journey Engine (100 points, four focused hours, Java), both in Canvas. Show up rested. Trust your training. Submit something honest.