Chapter 08 · Reps

Abstraction, Interfaces, and the Final — Reps

← Back to Chapter 8

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 concrete describe() printing kind() + " area = " + area().
  • Circle.javaextends Shape, one radius; area() returns Math.PI * radius * radius, kind() returns "circle".
  • Rectangle.javaextends Shape, width and height; kind() returns "rectangle".
  • ShapeDemo.javastatic double totalArea(Shape[] shapes) summing s.area(), and a main holding Shape[] shapes = { new Circle(3.0), new Rectangle(4.0, 5.0) }; that calls describe() 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.javaextends 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 topic and protected int minutes; constructor for both; abstract warmUp() and mainSet(); concrete coolDown() printing " cool down: write one sentence about " + topic; concrete run() printing "== " + topic + " (" + minutes + " min) ==" then calling warmUp(), mainSet(), coolDown() in that order.
  • TracingSessionwarmUp() prints " warm up: retype one example from the chapter"; mainSet() prints " main set: predict the output of " + (minutes / 5) + " programs".
  • BuildSessionwarmUp() prints " warm up: a three-line main that compiles"; mainSet() prints " main set: build " + (minutes / 15) + " classes, compiling after each"; and it overrides coolDown() to print " cool down: delete the feature you could not finish".
  • PlanDemo.javanew TracingSession("Part A tracing", 45) and new BuildSession("Part B build", 90) in a StudySession[]; 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.javapublic abstract class Spellcaster extends Combatant; adds protected int mana, a constructor (String name, int hp, int mana) starting with super(name, hp);, canCast(int cost) returning mana >= cost, and getMana(). It never implements takeTurn, so it stays abstract.
  • Cleric.javaextends Spellcaster, and finally implements takeTurn: if canCast(10), spend 10 mana, print name + " rebukes " + opponent.getName() + " for 9." and deal 9; else print name + " is out of mana and holds the line."
  • CasterDemo.javaCleric bede = new Cleric("Bede", 70, 25); and Cleric hilda = new Cleric("Hilda", 60, 5); held in a Combatant[], plus a report printing " " + getName() + ": HP=" + getHp() + ", alive=" + isAlive() per member. Follow the headings below: report, manas, both canCast(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 — private name, hp, maxHp (constructor sets maxHp = hp); a takeDamage(int) helper; heal(int) clamps with Math.min(maxHp, hp + amount) and prints " " + name + " heals " + amount + ". HP: " + hp; canBeHealed() returns hp < maxHp.
  • Sapling.java implements Healable — private label, height, maxHeight; heal(int) clamps at maxHeight and prints " " + label + " grows " + amount + ". height: " + height; canBeHealed() returns height < maxHeight.
  • HealDemo.javanew Hero("Maya", 100), takeDamage(30), then it plus new Sapling("Oak by the gate", 12, 20) in one ArrayList<Healable>. Two passes; each prints "pass N:" then, per element, either heal(10) if canBeHealed() 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.javanew 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.javaextends Combatant implements Healable, Burnable; constructor calls super(name, hp); takeTurn prints name + " strikes " + opponent.getName() + " for 15." and deals 15; contract methods print without leading spaces now, e.g. name + " heals " + amount + ". HP: " + hp.
  • Squire.javaextends Combatant, no interfaces; takeTurn prints name + " 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, maxCondition 100, and overrides isFullyRestored() to return condition >= 90; because it has permanent loss.
  • Chapel implements Restorable — same fields and methods, no isFullyRestored() at all.
  • main — both in one ArrayList<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.javaextends Combatant implements Healable, Burnable. takeTurn prints name + " casts fireball at " + opponent.getName() + " for 12.", deals 12, and then checks if (opponent instanceof Burnable) and burns for 3.
  • BattleDemo.java — §8.9’s driver. capabilityReport(ArrayList<Combatant>) prints per member getName() + ": HP=" + getHp() + ", alive=" + isAlive() + ", Healable=" + healable + ", Burnable=" + burnable, each flag "yes" or "no" decided by instanceof. main builds Hero("Maya", 100), Wizard("Marcus", 80), Wizard("Selene", 90); reports; deals 30 to everyone and reports; heals every Healable that canBeHealed() by 20; runs maya.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.javapublic interface Frozen { boolean isStillFrozen(); }
  • IceWarden.javaextends Combatant implements Healable, Burnable, Frozen; takeTurn prints name + " freezes " + opponent.getName() + " for 8." and deals 8; isStillFrozen() returns true; heal and burn print in Hero’s format.
  • MatrixDemo.javaSquire("Tomas", 60), Hero("Maya", 100), Wizard("Marcus", 80), IceWarden("Brigid", 90) in an ArrayList<Combatant>; deal 20 to everyone; print --- one pass over the party ---; then per member print "* " + getName() and run three checks in order — heal 10 if Healable and canBeHealed(), burn 5 if Burnable, print getName() + " is still frozen, skipping." if Frozen and isStillFrozen(). If none matched, print getName() + " 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 — private name, faith, courage; the constructor clamps both into 0–100 with Math.max(0, Math.min(100, v)); methods encourage(int) (faith up, capped at 100), discourage(int) (faith down, floored at 0), unnerve(int) (courage down, floored at 0), getName(), getFaith(), getCourage(), and hasGivenUp() returning faith <= 0.
  • Encounter.java — abstract; protected name and intensity; constructor for both; public abstract void engage(Pilgrim p);; concrete getName().
  • Trial.java — prints "[Trial: " + name + "] " + p.getName() + " faith -" + intensity, then p.discourage(intensity).
  • Companion.java — prints "[Companion: " + name + "] " + p.getName() + " faith +" + intensity, then p.encourage(intensity).
  • Temptation.java — prints "[Temptation: " + name + "] " + p.getName() + " courage -" + intensity, then p.unnerve(intensity).
  • JourneyDemo.java — a static runRoute(Pilgrim p, ArrayList<Encounter> route) that, per encounter, returns early with p.getName() + " turned back before " + e.getName() + "." if p.hasGivenUp(), else calls e.engage(p) and prints " faith=" + p.getFaith() + " courage=" + p.getCourage(). After the loop it prints p.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.

  1. Add embolden(int n) to Pilgrim: courage up, capped at 100.
  2. Strengthening.javavoid strengthen(Pilgrim p);
  3. Testing.javaint trialCost();
  4. Companion implements Strengtheningstrengthen calls p.embolden(5) and prints " (+5 courage from " + name + ")".
  5. Trial implements Testing with trialCost() returning intensity * 2; Temptation implements Testing with trialCost() returning intensity.
  6. Party.java — owns a private ArrayList<Pilgrim> members, with add(Pilgrim) and int runJourney(ArrayList<Encounter> route). The outer loop over the route prints "== " + e.getName() + " ==". The inner loop over members: if hasGivenUp(), print " " + p.getName() + " has turned back." and continue; else e.engage(p), then strengthen(p) if the encounter is Strengthening, then print " " + p.getName() + ": faith=" + p.getFaith() + " courage=" + p.getCourage(). After the inner loop, add trialCost() to a running total if the encounter is Testing. Return the total.
  7. PartyDemo.java — a party of Pilgrim("Christian", 60, 70) and Pilgrim("Mercy", 20, 50) over Trial("Slough of Despond", 25), Companion("Faithful", 20), Temptation("Vanity Fair", 30), Trial("Hill Difficulty", 15); print "total trial cost: " + total at 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:

  1. Testimony.java — abstract; protected String source; protected int weight;; constructor for both; public abstract void present();; concrete getSource() and getWeight().
  2. Citable.java — an interface with one method, String citation();.
  3. Document.javaextends Testimony implements Citable. present() prints "[Document] " + source + " weight " + weight; citation() returns "cited: " + source.
  4. Rumor.javaextends Testimony only. present() prints "[Rumor] " + source + " weight " + weight.
  5. TestimonyDemo.java — an ArrayList<Testimony> holding Document("Letter of Alpha", 8), Rumor("Market gossip", 2), Document("Court record", 9). Loop once: call present(), print " " + citation() for anything Citable, 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.