Week 7 of 8 · Java

Shifting Gears: Java and Polymorphism

The skill was never the language.

Chapter 7 — Shifting Gears: Java and Polymorphism

“For just as the body is one and has many members, and all the members of the body, though many, are one body, so it is with Christ.” — 1 Corinthians 12:12

“Java is C++ minus the parts that hurt and plus the parts that hurt differently.” — anonymous senior engineer

This week merges Coding 1 chapters 13 and 14. In the sixteen-week book those are the Java syntax tour and Java polymorphism; here you get both in one week, because in Java they are one idea (§7.10 explains why).


Your Week at a Glance

Twelve honest hours, four sessions of about three. This is a translation week, not a new-concepts week — which is why it moves fast and why skipping reps hurts more than usual. You are not learning what a class is; you are learning to type one in a second language, cold.

#Session~HrsWhat you doCheckpoint at the end
1Syntax boot camp3Read §7.1–§7.8. In OnlineGDB, switch the language to Java and hand-type Hello, Greet, Intake, TypesDemo, EqualsTest, ArrayDemo, RosterDemo. Reps 1–6.You can type public static void main(String[] args) from memory, no typos, first try.
2The class model3Read §7.9–§7.11. Type Account, AccountDemo, ReferenceDemo; then build Vehicle, Car, Truck from scratch. Reps 7–12.You can write a Java class with private fields, a constructor, a getter, and a toString() without looking.
3Polymorphism3Read §7.12–§7.17. Type FleetDemo, FleetReport, CounterDemo, CaseFileDemo. Reps 13–18. Take the §7.20 Checkpoint cold.You pass §7.20 at 6 of 8 or better. If not, do not start the project — re-drill §7.11–§7.14.
4The project3Build P6 — Java Migration & Polymorphic Fleet (Project 6), Normal tier, start to finish. Submit the link.Your program runs from a fresh page load on someone else’s machine — test your share link in a private window.

That is twelve hours, and it covers the Normal tier only. Medium and Hard are extra credit on top — budget beyond the twelve if you want them. A clean Normal beats a broken Hard.

Coach’s Note — The biggest time sink this week is copy-paste. It feels efficient and teaches nothing. Every word of public static void main(String[] args) means something, and you will not learn what by pasting. Type it. All week.


Why This Matters

For six weeks you drove a stick shift. You called new, you remembered delete, you wrote a destructor that walked a linked list and freed every node. This week you sit down in an automatic.

Java took all the object-oriented power of C++ and hid the parts that hurt. You still write classes, inheritance hierarchies, and polymorphic calls. But memory is managed for you by a garbage collector; references are implicit and always on, so pointer syntax disappears; and there are no destructors, so there is nothing to make virtual.

Java code therefore looks like last week’s C++ with the rough edges sanded off. Some syntax simplifies; some gets fussier — Java is religious about one public class per file and about everything living inside a class. The ownership discipline you built in C++ transfers as background instinct even though you never say it out loud again.

Here is what this week proves personally: the skill was never the language. You are about to write working object-oriented code in a language you have never seen, on day one, because you already own the ideas. One warning: because the concepts are familiar, this week reads easy and types hard. Only reps close that gap.


7.1 — Getting Java in Your Browser

OnlineGDB has Java built in. No JDK install, no account beyond the free login you already have. Open onlinegdb.com, pick Java from the language dropdown, and write. The full walkthrough — files, the stdin box, the share link you submit — is in Appendix A.

Target version: Java 17 or newer; everything here works on 17, 21, and 25 alike. The one feature needing Java 16+ (pattern-matching instanceof, §7.14) is flagged with its older alternative beside it.

The one-file rule, and how to live with it

Java’s file rules are strict, and you will hit them in ten minutes:

  1. A file may contain at most one public class.
  2. If a file has a public class, the filename must exactly match it, capitalization included. public class Hello must live in Hello.java.
  3. A file may contain any number of non-public classes alongside it.

Rule 3 saves you in a browser IDE: one public class per file, not one file per class. A whole multi-class program fits in one editor buffer — this sketch shows the shape only, with the class bodies elided, so do not expect it to compile as printed:

public class Main {                 // matches the filename Main.java
    public static void main(String[] args) {
        Vehicle v = new Car(4, 1200, 5);
        v.describe();
    }
}

class Vehicle { /* ... */ }         // no 'public' — allowed to share the file
class Car extends Vehicle { /* ... */ }

That is how you will submit this week’s project; code/CaseFileDemo.java is a worked example (§7.17). The multi-file version — Vehicle.java, Car.java, Truck.java, FleetDemo.java — is what you would write on a real machine, and code/ ships both shapes.


7.2 — The Java Program Skeleton, Character by Character

code/Hello.java, the whole program:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world.");
    }
}

Actual output:

Hello, world.

public class Hello — Java code lives inside classes. No free functions, no free variables, no main outside a class; the class is the universal container. public means visible outside this file, and class names are PascalCase.

public static void main(String[] args) — Java’s int main(). Every word earns its keep:

  • public — the Java Virtual Machine (JVM), which runs your compiled program, must be able to call it from outside your class.
  • static — belongs to the class itself, not an object of it. The JVM does not construct a Hello first; it calls main on the bare class (§7.15).
  • void — returns nothing. No return 0; at the bottom.
  • main — the JVM looks for exactly this name.
  • String[] args — command-line arguments. Unused this week, but the signature must match exactly or the JVM will not find it.

System.out.println(...)System.out is an object representing standard output; println prints its argument and adds a newline. There is also .print (no newline) and .printf (format string, §7.4). That is the entire printing mechanism: no <<, no endl, no #include <iostream>. The last two braces close main and the class.

Notice the absences: no #include (Java’s core library — System, String, Math, Object — needs no import); no using namespace std;; no prototypes (Java reads the whole file before deciding what exists, so you may call a method defined below the call site); no return 0;; and no header file, ever (§7.9).

Coach’s Note — Type public static void main(String[] args) ten times right now, in ten fresh files, by hand. It is the highest-frequency line in any Java program; it should live in your fingers by Wednesday.


7.3 — The Translation Table

You already own every concept in the right-hand column. Bookmark it; Appendix C is the expanded version.

IdeaC++ (six weeks of it)Java (now)
Entry pointint main() { ... return 0; }public static void main(String[] args) { ... }
Print a linecout << "hi" << endl;System.out.println("hi");
Compare texta == ba.equals(b)the famous trap
Constantconst double MAX = 1.0;static final double MAX = 1.0;
Fixed arrayint scores[5];int[] scores = new int[5];
Growable listvector<string> v;ArrayList<String> v = new ArrayList<>();
Class declaration.h header + .cpp definitionsone .java file, bodies inline
Allocate an objectAccount* a = new Account(...);Account a = new Account(...);
Free an objectdelete a;(nothing — the garbage collector)
Destructor~Account() { ... }(none)
Method on a pointera->deposit(50);a.deposit(50);
Printable objectoperator<< overloadtoString()
Inheritclass Car : public Vehicleclass Car extends Vehicle
Call base constructor: Vehicle(w, m) init listsuper(w, m); (first statement)
Enable dynamic dispatchvirtual void describe()(nothing — always on)
Mark an overridevoid describe() override@Override public void describe()
Polymorphic collectionVehicle* fleet[3];Vehicle[] fleet = new Vehicle[3];
Runtime type checkdynamic_cast<Car*>(v)v instanceof Car

Two rows will actually bite you: a.equals(b) and the missing delete.


7.4 — The Five Types, In Java

C++JavaNotes
intintSame. 32-bit signed integer.
doubledoubleSame. 64-bit floating point.
boolbooleanFull word. true / false.
charcharSame idea, 16-bit Unicode. Single quotes: 'A'.
stringStringCapital S. A class, not a primitive. Double quotes.

Declaration looks exactly like C++: int score = 90;, String name = "Maya";. Three differences:

  1. No auto. Types are explicit. (Modern Java has var; we are not using it.)
  2. camelCase for variables and methods: isLoggedIn, not is_logged_in. Classes stay PascalCase, constants SCREAMING_SNAKE_CASE.
  3. Constants are static final: public static final double PASS_MARK = 70.0;final = cannot be reassigned, static = one copy for the class.

String is a class, and that changes three things

  • You call methods on it: name.length(), name.toUpperCase(), name.substring(1, 3), name.indexOf("y"). Note .length() with parentheses for a String, .length without for an array.
  • You compare contents with .equals(), not == (§7.6).
  • Strings are immutable. name = name + "!" builds a new string and re-points name, which is why every String method returns a new String.

substring(1, 3) returns the characters at index 1 and 2 — start inclusive, end exclusive. C++‘s substr takes a start and a length, so this is a real translation hazard.

Wrapper classes

Integer, Double, Boolean, Character are object versions of the primitives. You need them in one place this week: generic collections hold objects only, so it is ArrayList<Integer>, never ArrayList<int> (Java auto-boxes on .add(5) and auto-unboxes on .get(0)). They also carry the parsers you use constantly: Integer.parseInt("19"), Double.parseDouble("72.5").

The integer-division trap survived the trip

code/TypesDemo.java runs all of the above. Actual output:

score      = 90
weight     = 72.5
isLoggedIn = true
grade      = A
name       = Maya
PASS_MARK  = 70.0

name.length()        = 4
name.toUpperCase()   = MAYA
name.substring(1, 3) = ay
name.indexOf("y")    = 2
name + "!"           = Maya!

score / 4    = 22
score / 4.0  = 22.5

Same silent wrong answer as Chapter 1: 90 / 4 is 22, because both operands are int.

Formatted output

setprecision has a direct replacement:

System.out.printf("Cost per mile: $%.2f%n", 1.32);

%.2f = double to two places, %d = integer, %s = String, %n = newline. String.format(...) takes identical arguments but returns the string instead of printing it — exactly what you want inside toString(). Use printf for anything money-shaped: plain println on a double gives Java’s own representation, correct but not pretty (70.0 where you wanted $70.00, and 0.30000000000000004 for 0.1 + 0.2).


7.5 — Reading Input: Scanner

Java’s cin is Scanner. Unlike System.out it is not free — you import it and construct one. code/Greet.java:

import java.util.Scanner;

public class Greet {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        System.out.print("Name: ");

        String name = "friend";
        if (in.hasNextLine()) {          // ✅ guard: survives an empty input box
            name = in.nextLine();
        }

        System.out.println("Welcome to the gym, " + name + ".");
        System.out.println("Your name is " + name.length() + " characters long.");

        in.close();
    }
}

With Maya in the stdin box, actual output:

Name: Welcome to the gym, Maya.
Your name is 4 characters long.

(The prompt shares the greeting’s line because print adds no newline — same as cout << "Name: ".)

  • import java.util.Scanner; goes at the very top, above the class.
  • new Scanner(System.in) allocates and constructs as you expect. What is not back is delete.
  • in.hasNextLine() is true if a line is waiting. Not decoration: in a browser IDE it is easy to hit Run with an empty box, and an unguarded nextLine() at end-of-input throws NoSuchElementException.
  • + concatenation builds one string, and converts anything to text: "n is " + 5 gives "n is 5".
  • in.close() is the convention, not a requirement.

Reading numbers, and the trap that comes with them

Scanner has nextInt(), nextDouble(), nextBoolean(), each reading one whitespace-delimited token — and a famous trap: nextInt() consumes the number but leaves the newline behind, so the next nextLine() reads that newline and returns an empty string. Feed 19 then Marcus to

System.out.print("Age: ");
int age = in.nextInt();
System.out.print("Name: ");
String name = in.nextLine();
System.out.println("age=[" + age + "] name=[" + name + "]");

and it prints:

Age: Name: age=[19] name=[]

Nothing crashed. Nothing warned you. The fix this book uses everywhere: read every line as text, then parse it.

int age = Integer.parseInt(in.nextLine());
double weight = Double.parseDouble(in.nextLine());

One rule, no leftover newlines, and it fails loudly (NumberFormatException) instead of quietly. code/Intake.java is the full pattern with the EOF guard in a small helper. Fed Marcus, 19, 72.5:

Name: Age: Weight in kg: 
Athlete: Marcus
Age next year: 20
Weight: 72.5 kg

7.6 — == vs .equals() — The Most Famous Java Gotcha

In C++, == on a std::string compares contents. In Java, == on a String compares reference identity — same object in memory? — not same characters. Here is the body of main in code/EqualsTest.java (the class wrapper is in the file):

String a = "hello";
String b = "hello";
String c = new String("hello");

System.out.println("a == b:          " + (a == b));
System.out.println("a == c:          " + (a == c));
System.out.println("a.equals(b):     " + a.equals(b));
System.out.println("a.equals(c):     " + a.equals(c));

int x = 5;
int y = 5;
System.out.println("x == y:          " + (x == y));

Actual output:

a == b:          true
a == c:          false
a.equals(b):     true
a.equals(c):     true
x == y:          true

Stare at the first two lines. All three strings hold the same five characters, yet a == b is true and a == c is false. The compiler pools identical string literals, so a and b accidentally become one object; new String("hello") forces a fresh one. That true is a coincidence, not a rule — which is what makes the bug vicious. == on strings works often enough to look right, then fails the day the string comes from input instead of a literal.

The rule, no exceptions: primitives (int, double, boolean, char) use ==, because they have no reference to compare; objects (String, Account, Vehicle, everything else) use .equals().

Coach’s Note — The first six times you write if (name == "Maya") you will be wrong, and it will run without complaint. There is no compiler error to catch you. The only defense is a typing habit: when the thing on the left has a capital-letter type, your fingers reach for .equals(.


7.7 — References Instead of Pointers, GC Instead of delete

This is the conceptual shift; everything else follows. In Java, every variable of a class type is a reference. Always.

Account a = new Account("Maya", 100.0);
Account b = a;          // b refers to the SAME Account
b.deposit(50.0);
System.out.println(a.getBalance());   // 150.0, not 100.0

Two names, one object, no copy. Java has no value semantics for objects at all. That is exactly what Account* did last week, minus the *, &, and ->. Consequences:

  • Passing an object to a method is reference-like. No & in the parameter list; the method can modify the object and the caller sees it.
  • You cannot slice an object. Last week’s slicing bug — assigning a Car into a Vehicle value and losing the Car-ness — has no Java translation. A Vehicle variable holds a reference; the object is still a Car. The whole bug category is gone.

Primitives keep C++‘s value semantics:

public static void tryToDouble(int x) {
    x = x * 2;              // modifies the local copy only
}
int n = 5;
tryToDouble(n);             // n is still 5

There is no int& in Java: to change a number the caller owns, return the new value and reassign. code/ReferenceDemo.java demonstrates the whole rule. Actual output:

a's balance after b.deposit(50): 150.0
a == b (same object?):           true
a == c (same object?):           false
a's balance after reallyDeposit: 175.0
n after tryToDouble(n):          5
a after being set to null:       null
b still holds it:                175.0

a == c is false even though both Accounts were built with identical values — §7.6’s lesson, now on your own class.

No delete. No destructor. No leak.

a = null;   // the Account is now unreachable from 'a'

null is Java’s nullptr. When an object becomes unreachable — no reference anywhere points at it — the garbage collector eventually reclaims its memory. You do not call it and cannot schedule it.

Compare Chapter 6’s linked list, where a missing delete was a leak you had to hunt. In Java: no delete keyword, no destructors, no virtual-destructor problem, and C++-style leaks essentially impossible. (You can still pin memory by holding references you no longer need, but that is a design bug, not a bookkeeping one.) The cost is real and small: Java runs somewhat slower and occasionally pauses to collect. For essentially all software you will write, that trade is excellent.


7.8 — Arrays and ArrayList

Plain arrays

int[] scores = new int[5];          // five slots, each initialized to 0
String[] roster = new String[3];    // three slots, each initialized to null
int[] primes = {2, 3, 5, 7, 11};    // initializer list — no 'new' needed

Brackets attach to the type, not the variable name. Differences from C++ that matter:

  • The array knows its own size: scores.length — a field, no parentheses.
  • Slots are initialized for you: numbers to 0, boolean to false, object types to null. No garbage values.
  • Bounds are checked at runtime. scores[5] on a five-element array throws ArrayIndexOutOfBoundsException and stops; C++ would have shrugged and returned nonsense.
  • You never free an array.

code/ArrayDemo.java prints:

scores.length = 5
  scores[0] = 90
  scores[1] = 85
  scores[2] = 0
  scores[3] = 0
  scores[4] = 0
roster[0] before assignment = null
roster[0] after assignment  = Maya
sum of primes = 28

Note roster[0] before assignment = null. An array of objects starts full of nulls — not empty Strings, not default-constructed objects. Calling a method on an unfilled slot is the most common NullPointerException a beginner writes.

ArrayList — the resizable one

import java.util.ArrayList;

ArrayList<String> roster = new ArrayList<>();
roster.add("Maya");
System.out.println(roster.size());     // 1
System.out.println(roster.get(0));     // Maya
roster.remove("Maya");
  • <String> is a generic — “ArrayList of String”. You must name the element type.
  • .add(x), .size(), .get(i), .remove(x) — note .get(i), not [i], since Java has no operator overloading.
  • ArrayList<int> does not compile. Generics hold objects only. Use ArrayList<Integer>; autoboxing does the rest.

code/RosterDemo.java prints:

roster.size() = 3
  1. Maya
  2. Marcus
  3. Lin
After remove: 2 players.
The whole list prints itself: [Maya, Lin]
first rep count = 12

Printing an ArrayList dumps its contents readably because ArrayList has a toString() — the next section’s subject. P6’s Medium tier is cleaner with ArrayList than with fixed-size arrays.


7.9 — The Unified Class Model: No Headers, and toString()

The change that makes Java feel lighter from day one: there is no header file. One class, one file, declaration and definition together. No .h, no Account:: prefix, no include guards. code/Account.java — the Stewardship Account from P4 (Chapter 5), migrated (abridged: withdraw is written out in full in the file):

public class Account {
    private double balance;
    private String owner;

    public Account(String owner, double starting) {
        this.owner = owner;
        this.balance = (starting >= 0) ? starting : 0;
    }

    public void deposit(double amount) {
        if (amount <= 0) {
            System.out.println("Rejected: deposit must be positive.");
            return;
        }
        balance += amount;
    }

    // withdraw(double) has the same guard-clause shape: reject
    // non-positive amounts, reject overdrafts, then subtract.

    public double getBalance() { return balance; }
    public String getOwner()   { return owner; }

    @Override
    public String toString() {
        return String.format("[%s] balance: $%.2f", owner, balance);
    }
}

Against the C++ you wrote in Chapter 5:

  • public class Account — the file must be Account.java.
  • private double balance; — a field. There is no private: block label; Java puts the modifier on every single member, and forgetting it on one field means that field is not private (sidebar below).
  • The constructor is the same idea — class name, no return type — but it lives in the class body, not a .cpp file.
  • this.owner = owner;this is a reference, so it is a dot, not ->, and you need it because the parameter shadows the field.
  • Methods are access, return type, name, parameters, and the body right there. No void Account::deposit(double) scope prefix exists in the language, and guard-clause style carries over unchanged.

code/AccountDemo.java constructs new Account("Maya", 100.0), deposits 50, tries a −25 deposit and a 200 withdrawal (both rejected), withdraws 80, then prints the object, the owner, and the raw balance. Compile both files and run the one with main:

javac Account.java AccountDemo.java
java AccountDemo

Actual output:

Rejected: deposit must be positive.
Rejected: insufficient funds.
[Maya] balance: $70.00
Owner: Maya
Balance as a number: 70.0

100 + 50 − 80 = 70; the two rejected operations complained and changed nothing. Printing the Account produced the third line by calling toString(). And the Account was never deleted — no delete, no destructor, no cleanup.

toString() — Java’s answer to operator<<

In C++ you overloaded operator<<; in Java you override one method. Every Java class inherits from a universal base class called Object, which defines toString() — so every object already has one, and yours replaces it. Java calls it automatically in the two places you use constantly: System.out.println(obj) prints obj.toString(), and "text " + obj glues it in.

Skip the override and you inherit Object’s version, which prints the class name, an @, and the object’s identity hash in hex — shaped like Account@6d06d69c. Those digits differ every run and mean nothing. Seeing that in your output means you forgot to write toString(). Write one for every class.

Written asCalledWho can access it
publicpublicanyone, anywhere
protectedprotectedthe class, its subclasses, and other classes in the same package
(nothing at all)package-private, or “default”the class, and any class in the same package
privateprivateonly the class itself

The third row is the trap. Leaving the modifier off does not mean private — it means package-private, which is more open than private:

public class Account {
    private double balance;   // ✅ only Account can touch this
    String owner;             // ❌ NO modifier — any class in this package can
}

A field with no modifier looks protected at a glance. Rule for this course: always write the modifier explicitly — it tells the next reader you decided rather than forgot. You need no packages here; every file lives in the same unnamed default package, which is why an unmarked field is effectively public in our small programs. The one deliberate use is §7.17’s one-file pattern, where helper classes must be non-public.


7.10 — The Bridge: Why These Two Halves Are One Week

The sixteen-week book splits this material into a syntax week and a polymorphism week. That makes sense with sixteen weeks and a room to sit in. It makes no sense here — and not merely because we are compressing the calendar. In Java, polymorphism is not a feature you add. It is the default state of the language, already baked into the syntax you just learned.

  • §7.7: every object variable is a reference. In C++, polymorphism happened only when you deliberately used a pointer or reference, and broke silently the moment you used a value. Java gives no such choice. Half of C++‘s inheritance chapter — slicing, when to use a pointer, why value semantics ruin dispatch — does not exist here, because §7.7 removed the possibility.
  • §7.9: you wrote @Override public String toString() on a plain Account that inherits from nothing you declared. That was polymorphism. System.out.println takes an Object, calls toString(), and your version ran — dispatched at runtime through a superclass reference, exactly like a virtual call. You did it before we said the word.
  • There is no virtual keyword to learn, so there is no separate lesson in which to learn it.

So the honest structure of the week is not syntax, then polymorphism. It is: learn Java’s object model, and polymorphism arrives already assembled. The rest of the chapter is you noticing what you turned on, then picking up the four tools around it — extends, super, @Override, instanceof — plus two conveniences (static, enhanced-for). That is also why P6 has two halves: migrating encapsulated C++ into Java and building a polymorphic hierarchy are the same skill twice.


7.11 — Inheritance in Java: extends, super, @Override

The ideas are Chapter 6’s, unchanged; three keywords change. code/Vehicle.java:

public class Vehicle {
    protected int wheels;
    protected double weight;

    public Vehicle(int wheels, double weight) {
        this.wheels = wheels;
        this.weight = weight;
    }

    public void describe() {
        System.out.println("A vehicle with " + wheels + " wheels.");
    }

    public double costPerMile() {
        return weight / 1000.0;
    }

    public int getWheels() { return wheels; }

    @Override
    public String toString() {
        return String.format("%s(%d wheels, %.1f kg)",
                             getClass().getSimpleName(), wheels, weight);
    }
}

code/Car.java:

public class Car extends Vehicle {
    private int passengers;

    public Car(int wheels, double weight, int passengers) {
        super(wheels, weight);          // must be the FIRST statement
        this.passengers = passengers;
    }

    @Override
    public void describe() {
        System.out.println("A car carrying " + passengers + " passengers.");
    }

    @Override
    public double costPerMile() { return super.costPerMile() * 1.1; }

    public int getPassengers() { return passengers; }
}

code/Truck.java has the same shape with a cargoWeight field, a describe() reporting the load, and a costPerMile() that adds cargoWeight / 500.0.

1. extends replaces : public Vehicle — “a Car is a Vehicle, plus more.” Java has only public inheritance, so that is one less decision.

2. super(...) replaces the initializer list, and it must be the first statement of the constructor body — write it first in every subclass constructor you ever write. Leave it out and Java tries to insert super() for you; if the base has no no-argument constructor, you get Bug 5 of §7.18.

3. virtual disappears, because every non-static Java method is already dispatched dynamically. A cleaner default than C++‘s, and marginally slower only in benchmarks.

4. override becomes @Override, an annotation on its own line above the method. Technically optional. Write it every time. Mistype the name and the compiler refuses:

error: descrobe() in BadOverride does not override or implement a method from a supertype

Without @Override that same typo compiles perfectly, silently adding a new method nothing calls, while v.describe() on your Car runs Vehicle’s version forever. Free insurance against the most demoralizing bug in this chapter.

5. super.method() calls the base version. C++‘s Vehicle::cost_per_mile() becomes super.costPerMile(); Car’s override takes the base cost and adds 10% — Chapter 3’s composition move, applied up an inheritance chain.

One more thing hides in Vehicle.toString(): getClass().getSimpleName() asks the object what it actually is at runtime. Car and Truck never override toString(), yet a Car prints Car(...) and a Truck prints Truck(...). One inherited method, three answers, decided by the object rather than the variable’s declared type.


7.12 — Polymorphic Dispatch Through a Superclass Reference

The payoff, in four lines. This is the body of main in code/FleetDemo.java — the class wrapper is in the file:

Vehicle[] fleet = {
    new Car(4, 1200, 5),
    new Truck(6, 3500, 2000),
    new Vehicle(2, 200)
};

for (Vehicle v : fleet) {                 // "for each Vehicle v in fleet"
    v.describe();                         // the OBJECT decides which one runs
    System.out.printf("  Cost per mile: $%.2f%n", v.costPerMile());
}

double total = 0.0;
for (Vehicle v : fleet) {
    total += v.costPerMile();
}
System.out.printf("Fleet total per mile: $%.2f%n", total);

System.out.println("Slot 0 says it is a " + fleet[0]);

Build with javac Vehicle.java Car.java Truck.java FleetDemo.java, run with java FleetDemo. Actual output:

A car carrying 5 passengers.
  Cost per mile: $1.32
A truck hauling 2000 kg.
  Cost per mile: $7.50
A vehicle with 2 wheels.
  Cost per mile: $0.20
Fleet total per mile: $9.02
Slot 0 says it is a Car(4 wheels, 1200.0 kg)

The array, the slots, and the loop variable are all typed Vehicle. Yet slot 0 ran Car’s pair (1200/1000 × 1.1 = 1.32), slot 1 ran Truck’s (3500/1000 + 2000/500 = 7.50), slot 2 ran Vehicle’s (200/1000 = 0.20) — and the last line proves the object still knows what it is, through an inherited toString(), through a Vehicle-typed reference.

The declared type of the variable decides what you are allowed to call. The actual type of the object decides which version runs. That is the whole of polymorphism, in both languages. Write it on a card.

Arrays of objects

Vehicle[] f1 = { new Car(4, 1200, 5), new Truck(6, 3500, 2000) }; // initializer

Vehicle[] f2 = new Vehicle[3];             // three NULL slots
f2[0] = new Car(4, 1200, 5);               // fill them in

ArrayList<Vehicle> f3 = new ArrayList<>(); // growable
f3.add(new Car(4, 1200, 5));

The middle one bites: until you assign, every slot is null, and f2[2].describe() throws NullPointerException. Fill before you call.

Three C++ burdens vanish here: all methods dispatch dynamically by default, so you never write virtual or wonder whether the override fires; no virtual-destructor concern, since there are no destructors and no delete loop; and no slicing, since a slot holds a reference, not a value.


7.13 — The Enhanced-For Loop

for (Vehicle v : fleet) {
    v.describe();
}

Read it aloud: “for each Vehicle v in fleet.” No index, no .length, no off-by-one. It works on arrays and anything iterable, including ArrayList. When to use which:

  • Reading every element in order → enhanced-for. Almost always what you want.
  • You need the index (printing “1.”, “2.”, or comparing i to i+1) → classic for (int i = 0; i < n; i++).
  • You are replacing elements → classic for. The enhanced-for’s loop variable is a copy of the reference, so v = new Car(...) changes only the local variable, not the array slot.
  • You are adding to or removing while looping → classic for, carefully; doing it inside an enhanced-for over an ArrayList throws ConcurrentModificationException.

Note the subtlety in the third bullet: calling a method on the loop variable does affect the real object (v.describe(), v.deposit(50) work fine). What does not stick is reassigning the loop variable itself.


7.14 — instanceof and Casting (Sparingly)

Sometimes you have a Vehicle reference and must ask whether this one is actually a Car. This loop is lifted out of main in code/FleetReport.java:

for (Vehicle v : fleet) {
    if (v instanceof Car) {
        Car c = (Car) v;               // the cast is required
        System.out.println("Car with " + c.getPassengers() + " passengers.");
    } else if (v instanceof Truck) {
        Truck t = (Truck) v;
        System.out.printf("Truck with %.0f kg of cargo.%n", t.getCargoWeight());
    } else {
        System.out.println("Plain vehicle, " + v.getWheels() + " wheels.");
    }
}
  • v instanceof Car — a boolean: is the object actually a Car (or a subclass)? Java’s dynamic_cast-in-an-if.
  • (Car) v — a cast. You need it because the declared type controls what you may call: v.getPassengers() will not compile through a Vehicle variable even when the object really is a Car.

Skip the guard and cast anyway and you get a runtime crash instead:

Exception in thread "main" java.lang.ClassCastException: class Truck cannot be cast to class Car (Truck and Car are in unnamed module of loader 'app')

Java 16+ pattern matching folds the check and the cast into one move:

if (v instanceof Car c) {              // check AND declare c, in one line
    System.out.println("Car with " + c.getPassengers() + " passengers.");
}

c exists only inside the if, and only when the check passed. Prefer it on Java 16 or newer; if your compiler rejects it, use the classic form. FleetReport.java runs both loops back to back. Actual output:

--- classic instanceof + cast ---
Car with 5 passengers.
Truck with 2000 kg of cargo.
Plain vehicle, 2 wheels.
--- Java 16+ pattern matching (check + cast in one) ---
Car with 5 passengers.
Truck with 2000 kg of cargo.
Plain vehicle, 2 wheels.

Coach’s Note — Use these sparingly. Every instanceof is a question you ask at runtime that the object could have answered itself. If your loop is a chain of instanceof branches, your hierarchy is missing a method. Sometimes it is genuinely right — one subclass has data nobody else has. Often it is a design smell wearing a keyword.


7.15 — static: Class-Level Members

static marks a member as belonging to the class itself, not to any object — one copy, shared, existing before any object does. code/CounterDemo.java:

public class CounterDemo {
    private static int totalCreated = 0;    // ONE of these, shared by everybody
    private int myId;                       // one of these PER OBJECT

    public CounterDemo() {
        totalCreated++;
        myId = totalCreated;
    }

    public int getId() { return myId; }

    public static int getTotalCreated() {   // callable without any object
        return totalCreated;
    }
}

Its main prints the total before constructing anything, builds three counters, then prints each id and the total. Actual output:

Before any objects: 0
a.getId() = 1
b.getId() = 2
c.getId() = 3
Total created: 3

totalCreated is one variable for the whole class, so every constructor call advances the same counter; myId is one per object. The first line called CounterDemo.getTotalCreated() through the class name, before a single object existed. You have used other people’s statics all along — Math.sqrt(2), Integer.parseInt(s), String.format(...) — and never constructed a Math. main is static for the same reason.

Use static for utility methods that need no instance data, counters and registries shared across all instances, and constants. Do not use it for ordinary per-object state: if each object should have its own copy, it is not static.

The rule that will bite you

A static method has no object, so it cannot touch instance fields or call instance methods. There is no this inside main:

private int reps = 10;
public int getReps() { return reps; }

public static void main(String[] args) {
    System.out.println(getReps());     // ❌
}
error: non-static method getReps() cannot be referenced from a static context

Which is exactly true: whose reps? Two fixes — construct an object and call it on that (new Demo().getReps()), or make the helper static too. Helpers you call from main in the same class should be static; that is why ReferenceDemo.tryToDouble and Intake.readLine are.


7.16 — final: Shutting the Door

final means “cannot be changed,” in three flavors:

final int MAX_REPS = 20;                 // a variable that cannot be reassigned
public final void start() { ... }        // a method no subclass may override
public final class StringUtils { ... }   // a class nobody may extend

A final variable is a constant — with static at class level, the direct replacement for C++‘s const. A final method locks in behavior something else depends on. A final class must not be extended; Java’s own String is final, part of why string literals can be pooled the way §7.6 described. Rare in beginner code; reach for it on constants.


7.17 — The Case File, Rebuilt in Java

P5, in Chapter 6, was the Argument Case File: a base Argument class in C++, subclasses each overriding defend(), a case file holding them as Argument* and iterating polymorphically — plus a destructor that had to delete every owned argument. code/CaseFileDemo.java is that design in Java, deliberately written in the one-file pattern from §7.1. The listing below is abridged — the method bodies marked /* ... */ are filled in in the real file, so compile code/CaseFileDemo.java, not this excerpt:

public class CaseFileDemo {                     // public — matches the filename
    public static void main(String[] args) {
        Argument[] caseFile = {
            new Cosmological(), new Moral(),
            new Argument("Generic Argument", "Some claim.",
                         "Therefore, some conclusion.")
        };
        for (Argument a : caseFile) { a.defend(); System.out.println(); }

        System.out.println("Case file contains " + caseFile.length + " arguments:");
        for (Argument a : caseFile) { System.out.println("  - " + a.getLabel()); }
    }
}

class Argument {                                // no 'public' — shares the file
    protected String label, mainClaim, conclusion;
    Argument(String label, String claim, String conc) { /* assign fields */ }
    public void defend() { /* prints label, claim, conclusion */ }
    public String getLabel() { return label; }
}

class Cosmological extends Argument {
    Cosmological() {
        super("Cosmological Argument",
              "The universe is contingent and requires a sufficient cause.",
              "Therefore, a necessary being exists.");
    }

    @Override
    public void defend() { /* attribution, then four numbered premises */ }
}

(Moral has the same shape, citing Lewis, Mere Christianity, Book 1. The complete file, every body filled in, is in code/.) Compile the single file — javac CaseFileDemo.java — and run it. Actual output:

[Cosmological Argument] (Aquinas, Summa Theologica I, Q.2, A.3 — Third Way)
  1. Every contingent thing needs a sufficient cause.
  2. The universe is contingent.
  3. An infinite regress of contingent causes is no explanation.
  4. Therefore, there is a necessary being. We call this God.

[Moral Argument] (Lewis, Mere Christianity, Book 1)
  1. We argue as though a real standard of right exists.
  2. A real standard is not a description of behavior but a demand on it.
  3. A demand requires someone to make it.
  4. Therefore, there is a moral lawgiver.

[Generic Argument]
  Claim:      Some claim.
  Conclusion: Therefore, some conclusion.

Case file contains 3 arguments:
  - Cosmological Argument
  - Moral Argument
  - Generic Argument

Gone relative to the C++ version: Argument.h and Argument.cpp; virtual; virtual ~Argument(); Argument* args[10] with its hand-maintained count; the delete loop; every ->.

Unchanged — and this is the part that matters — the design: base class with shared fields and a default behavior, subclasses overriding one method, a collection typed to the base, a loop calling one method and letting each object answer in its own voice. That is the shape, and you now know it in two languages. The apologetic point survives too: no single argument in that array is “the” argument. They are a case file precisely because several together cover more of the question than any one alone.


7.18 — Common Bugs (Week 7 Edition)

Real messages, produced by compiling and running actual broken code. Read the first error and ignore the rest; Java cascades worse than C++.


Bug 1 — filename does not match the public class.

Wrong.java:1: error: class Hello is public, should be declared in a file named Hello.java

Fix: rename one to match the other exactly, capitalization included. If your editor’s file is Main.java, name the class Main.


Bug 2 — misspelled or out-of-scope name.

B2.java:4: error: cannot find symbol
        System.out.println(nmae);
                           ^
  symbol:   variable nmae
  location: class B2

Fix: the symbol: line names what is missing. Java is case-sensitive (Scannerscanner). Also fires when you forgot import java.util.Scanner;.


Bug 3 — calling an instance method from main.

B5.java:5: error: non-static method getReps() cannot be referenced from a static context

main is static — no object, no this. Fix: mark the helper static, or call it on a constructed object.


Bug 4 — a typo’d override, caught by @Override.

BadOverride.java:3: error: descrobe() in BadOverride does not override or implement a method from a supertype

The superclass has no matching method — misspelled name, or the parameter list differs. Fix: correct it. Note what the annotation just did: without it, this compiles cleanly and silently creates a useless method while the base version keeps running.


Bug 5 — subclass constructor without a super(...) call.

Motorcycle.java:3: error: constructor Vehicle in class Vehicle cannot be applied to given types;
    public Motorcycle(boolean hasSidecar) {
                                          ^
  required: int,double
  found:    no arguments
  reason: actual and formal argument lists differ in length

You omitted super(...), so Java tried to insert super() — and Vehicle has no no-argument constructor. The required: line says what super(...) wants. Fix: make super(wheels, weight); the first statement.


Bug 6 — generic with a primitive type.

B9.java:4: error: unexpected type
        ArrayList<int> nums = new ArrayList<>();
                  ^
  required: reference
  found:    int

Fix: ArrayList<Integer>; autoboxing handles the rest.


Bug 7 — subclass method through a superclass variable, no cast.

MissingCast.java:4: error: incompatible types: Vehicle cannot be converted to Car
        Car c = v;

Java will not silently narrow, even if the object really is a Car. Fix: if (v instanceof Car c) { ... }, or a (Car) v cast guarded by instanceof.


Bug 8 — NullPointerException at runtime.

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "roster[0]" is null
	at BadNull.main(BadNull.java:4)

You called a method on a null reference; Java names the method and the null expression. (Depending on how the file was compiled the name may appear as <local1>; the method name is always there.) Fix: the because "..." clause names the culprit. Two usual causes this week — an array of objects allocated but never filled, and a field you forgot to set in a constructor.


Bug 9 — ClassCastException at runtime.

Exception in thread "main" java.lang.ClassCastException: class Truck cannot be cast to class Car (Truck and Car are in unnamed module of loader 'app')
	at BadCast.main(BadCast.java:4)

You cast a reference to a type the object is not; it compiles fine and fails at runtime. Fix: guard with instanceof, or add a method to the base and override it so no cast is needed.


Bug 10 — NumberFormatException at runtime.

Exception in thread "main" java.lang.NumberFormatException: For input string: "nineteen"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
	at java.base/java.lang.Integer.parseInt(Integer.java:527)
	at java.base/java.lang.Integer.parseInt(Integer.java:624)
	at R4.main(R4.java:3)

Integer.parseInt got something that is not an integer, and quotes it — often a stray empty string, i.e. Bug 12 upstream. Three of those four at lines are inside Java’s own library and tell you nothing (their java.base/... line numbers also shift between Java versions, so yours may not match these exactly). Fix: read the last at line naming your file.


Bug 11 — NoSuchElementException at runtime.

Exception in thread "main" java.util.NoSuchElementException: No line found
	at java.base/java.util.Scanner.nextLine(Scanner.java:1690)
	at R5.main(R5.java:5)

You called nextLine() with no input left — in OnlineGDB, almost always an empty stdin box. Fix: fill the box before you Run, and guard with in.hasNextLine().


Bug 12 — the nextInt() / nextLine() trap. No error message at all.

Age: Name: age=[19] name=[]

nextInt() read the number and left the newline; the next nextLine() consumed it and returned empty. Fix: read every line with nextLine() and parse it, or call one extra nextLine() to flush.


Bug 13 — comparing Strings with ==. No error message at all.

a == b:          true
a == c:          false

== compared object identity: true on line 1 by accident (pooled literals), false on line 2 despite identical contents. Fix: .equals() for objects, == for primitives. A comparison right on test data and wrong on typed input is this bug.


7.19 — Reps

Three to start now. The full set — eighteen reps, each with its expected output — is in the exercises.

Rep 1. Type Hello.java from memory, without looking at §7.2. Compile and run. Repeat until the main signature comes out with zero typos first try.

Expected output:

Hello, world.

Rep 2. Write a class Player with private String name and int reps, a constructor taking both, a getter for each, and a toString() returning "Maya: 12 reps" for new Player("Maya", 12). Add a main that constructs one and prints it with System.out.println(p);.

Expected output:

Maya: 12 reps

Rep 3. Build the Vehicle/Car/Truck hierarchy from §7.11–§7.12 from scratch, then add a Motorcycle subclass with a boolean hasSidecar field. Override describe() to print "A motorcycle with a sidecar." or "A motorcycle, solo.", and costPerMile() to return super.costPerMile() * 0.6. Add new Motorcycle(2, 200, false) as slot 3 of the fleet and rerun FleetDemo.

Expected output:

A car carrying 5 passengers.
  Cost per mile: $1.32
A truck hauling 2000 kg.
  Cost per mile: $7.50
A vehicle with 2 wheels.
  Cost per mile: $0.20
A motorcycle, solo.
  Cost per mile: $0.12
Fleet total per mile: $9.14
Slot 0 says it is a Car(4 wheels, 1200.0 kg)

(The total moved from $9.02 to $9.14 because the motorcycle joined the fleet. If your total still says $9.02, you added the object to the array but the loop is not seeing it.)


7.20 — Checkpoint: Can You Do This Yet?

Close the book and every tab but a blank OnlineGDB Java file. No autocomplete, no AI, no copy-paste, no peeking. Eight items, 30 minutes.

  1. A complete, compiling program that prints one line — from memory, including the exact main signature.
  2. Read a name and an integer age from two lines with Scanner, avoiding the nextLine() trap, and print both back.
  3. A class with two private fields, a constructor setting both, one getter, and a toString() — printed with System.out.println.
  4. State the difference between == and .equals(), and which you use for int and which for String.
  5. A subclass that extends a two-argument base: correct super(...) in the right position, one @Override method, one new field with a getter.
  6. A Vehicle[] of three mixed objects, walked with enhanced-for, one method call, three different behaviors.
  7. One sentence on why you never write delete in Java, and one on what replaced it.
  8. An instanceof check that safely reaches a subclass-only method, in either form.

Pass bar: 6 of 8, cold, in 30 minutes. Items 1, 3, and 5 are all-or-nothing — it compiles or it does not. Check 4 and 7 against §7.6 and §7.7. For 2, 6, and 8, run it; if it does what you intended, it counts.

If you scored below 6, do not start the project. Re-drill: 1 → §7.2; 2 → §7.5; 3 and 5 → §7.9 and §7.11; 4 → §7.6; 6 → §7.12–§7.13; 7 → §7.7; 8 → §7.14. Retype the matching code/ file by hand, then retake this tomorrow. Half an hour now saves four hours of flailing in the project.


7.21 — When You’re Stuck (and Nobody’s in the Room)

It is Thursday, it is late, FleetDemo prints the wrong thing, and there is no hand to raise. Work this ladder in order.

1 — Read the first error, not the last. One missing brace produces nine errors. Scroll to the top, fix only the first, recompile — usually eight vanish. Java’s error volume is scarier than C++‘s and its first message is more precise.

2 — Match the message to §7.18. Every error you will realistically hit this week is there with its real text; search for the distinctive fragment (cannot find symbol, non-static, does not override). For a runtime exception the useful line is the last at line naming your file — ignore the java.base/... frames inside Java’s own library.

3 — Run the three Week 7 triage questions. Nearly every “it compiles but it’s wrong” bug this week is one of these:

  • Did I compare Strings with ==? Search the file for == ". Every hit is a bug. (§7.6)
  • Did I forget @Override? Add it to every method you meant to override and recompile; if one suddenly fails, you found the typo. (§7.11)
  • Is something null that I thought I filled? Arrays of objects start as nulls, and a field you forgot to set in a constructor stays null. (§7.8)

4 — Cut it down to nothing. New file, a main, and the one line you suspect — twelve lines, not two hundred. Print the value before and after every step.

5 — Print the type, not just the value. This week’s bugs are type bugs, and two lines answer most:

System.out.println("actual class: " + v.getClass().getSimpleName());
System.out.println("is it a Car?  " + (v instanceof Car));

If the object says Vehicle when you expected Car, the bug is in how you built the array, not in your override. If it says Car and Vehicle’s method still runs, the bug is the missing @Override from rung 3.

6 — Rubber duck it out loud. Say to the empty room: “This takes a Vehicle reference. The object is really a Car. I call describe. Java runs Vehicle’s. So Java does not think Car has a matching describe.” You usually hear the mistake mid-sentence — the bug is in an assumption you have not said out loud yet.

7 — Search the book, not the web. Most “how do I do X in Java” questions this week are a row in §7.3 (Appendix C is the expanded table), then Appendix A for anything OnlineGDB-shaped and the glossary in Appendix D.

8 — Post to the discussion board, then email the instructor. Both get this block, filled in:

Week 7 — [one-line summary, e.g. “polymorphic call runs the base method”] What I’m trying to do:Smallest code that shows it: (≤ 20 lines, or an OnlineGDB share link) Exact message or output I get: (paste all of it, unedited) What I expected instead:What I already tried: rungs 1–5, specifically …

Post even if you solve it ten minutes later, then reply to yourself with the fix — someone else is stuck on the same thing tonight, and in an asynchronous class the board is the room. For the email, add a share link you tested in a private window (one that needs your login is one nobody can help with) and subject it Accelerated Coding 1 — Week 7 — [the error text].


7.22 — This Week’s Project

You are ready for P6 — Java Migration & Polymorphic Fleet, in Project 6. Due end of Week 7.

One project, two halves mirroring this chapter — the point §7.10 made. The migration half asks you to take C++ you already shipped and rebuild it in Java. The polymorphic fleet half asks you to build a base class with subclasses that override, hold them in a collection typed to the base, and iterate. Everything you need is here: §7.9 for the class model and toString(), §7.11 for extends / super / @Override, §7.12 for the polymorphic array, §7.13 for enhanced-for, §7.14 for instanceof, §7.15 for static. Read the project document end to end first.

Three pieces of advice. Submit in the one-file shape — one public class matching the filename, package-private helpers below, like §7.17 — so your share link is a single click for whoever grades it. Migrate; do not translate word for word: the interesting part is noticing what stopped being your job, so keep a running list of every delete you do not write and every -> that became a .. Finish Normal before Medium: the tiers are extra credit stacked on a complete Normal, not alternatives to it.

Coach’s Note — In session 4, resist opening your C++ file and translating line by line. Start from a blank Java file and the class you remember. You will write it faster, and you will write it as Java — a toString() instead of a print method, an ArrayList instead of a hand-counted array — rather than C++ in a Java costume.


7.23 — Coach’s Final Word for Week 7

You did not learn to program this week. You learned new vocabulary for things you already understood — and proved something to yourself doing it.

On Monday you had never written a line of Java. By Thursday you were building inheritance hierarchies with dynamic dispatch through superclass references, which takes most people a semester. Not because Java is easy or the week was light — because you already had the ideas, and the ideas are the hard part. extends instead of : public, super(...) instead of an initializer list, . instead of ->, a garbage collector instead of a destructor: a vocabulary list, learned in four days because the concepts underneath are already yours. Chapter 1 promised that the skill is articulating a solution clearly enough that a machine can follow it — not the language you happen to type it in. Week 7 is your receipt.

One caution. This week’s ease is conditional: it is easy to read and not easy to type. If you got through by reading and nodding, you do not have it yet. Take the §7.20 Checkpoint cold. If you cannot write a Java class with a constructor and a toString() from a blank file in five minutes, drill until you can — Week 8’s final has a practical half where you write Java from nothing, on the clock, with no starter code.

One week left. Abstract classes and interfaces, then the final. See you next week.


Up next: Work every rep in the exercises, checking each against its expected output. Take the §7.20 Checkpoint cold before you build. Then open Project 6 and ship P6 — Java Migration & Polymorphic Fleet. After that, Chapter 8 — abstraction, interfaces, and the final.

Check Your Reps

Week 7 Knowledge Check

Question 1 of 6
What does this Java program print?
String s1 = "hi";
String s2 = "hi";
String s3 = new String("hi");
System.out.println((s1 == s2) + " " + (s1 == s3) + " " + s1.equals(s3));
Why: This is why == on Strings is so dangerous. Identical literals are pooled, so s1 == s2 happens to be true — but s3 was built at runtime, so s1 == s3 is false. == compares references; .equals() compares contents and is always what you want. Code that 'works' with == will betray you the moment a string is built rather than typed.
Question 2 of 6
What does this print?
static void change(int n)      { n = 99; }
static void changeArr(int[] a) { a[0] = 99; }

int x = 1;
int[] arr = {1};
change(x);
changeArr(arr);
System.out.println(x + " " + arr[0]);
Why: Java always passes by value — but for an object the value is a reference. change copies the int, so x is untouched. changeArr copies the reference, which still points at the same array, so the element really changes.
Question 3 of 6
In C++ you wrote `delete p;`. What is the Java equivalent?
Why: Java has new but no delete. This removes the leak-and-dangling-pointer class of bugs entirely, and removes your control over exactly when cleanup happens.
Question 4 of 6
What does `@Override` do?
Why: It is a safety net, not a behavior change. Without it, misspelling speak as Speak quietly creates an unrelated new method while the parent's version keeps running. Always write it.
Question 5 of 6
Which C++ concept has no Java counterpart because it simply cannot happen there?
Why: Java variables of class type hold references, never the object itself, so there is no copy to slice. Integer division truncates identically in both languages — that trap followed you across.
Question 6 of 6
Which is correct about `arr.length` and `s.length()` in Java?
Why: Swapping them is a compile error (`cannot find symbol`). It is a small inconsistency in the language that catches nearly everyone once.
YOU FINISHED. NICE WORK.