Week 5: Inheritance and Polymorphism II

Polymorphism, Interfaces, and Cellular Automata · 11.05.2026

Slides

Download Week 5 Slides (PDF) →

Code Examples

Three reference packages. The first is the W4 abstract Geometry hierarchy carried forward as the starter for Task 1 (add Drawable + Comparable). The second is the in-class Game of Life template you fill in during the lecture. The third is the cached-length PolyLine — the worked example for class invariants.

Code/Week5/src/at/ac/univie/gis/week5/
├── geometry/
│   ├── Geometry.java                   abstract base with label + abstract getArea()
│   ├── Circle.java                     concrete subclass — Task 1 adds Drawable + Comparable
│   └── Rectangle.java                  concrete subclass — Task 1 adds Drawable + Comparable
├── gameoflife/
│   └── GameOfLifeInClassTemplate.java  10×10 boolean[][], main loop wired; you fill in tick/neighbours/toString
└── polyline/
    ├── Point.java                      2D point with distanceTo
    ├── PolyLine.java                   has-a ArrayList<Point>, cached length kept in sync by add()
    └── Main.java                       small driver showing the cached length stays consistent

1. geometry/ — starter for Task 1 (interfaces on top of an abstract base)

Geometry.java — The same abstract base class from W4. Holds the shared label field and the abstract getArea() contract. Keep this file as it is — the parent type for shared state. The new capabilities (Drawable, Comparable) belong on the subclasses, as interfaces.

package at.ac.univie.gis.week5.geometry;

public abstract class Geometry {

    private String label;

    public Geometry(String label) {
        this.label = label;
    }

    public String getLabel() {
        return label;
    }

    /**
     * Abstract method - no body, just a contract.
     * Each subclass fills this in with its own area formula.
     */
    public abstract double getArea();

    /**
     * A concrete method on an abstract class is allowed.
     * It can call the abstract method - the call resolves to the
     * subclass implementation at runtime (dynamic dispatch).
     */
    public String describe() {
        return label + " has area " + getArea();
    }
}

Circle.java & Rectangle.java — Two concrete subclasses, each with its own area formula. Task 1 asks you to add implements Drawable, Comparable<Geometry> here: extends Geometry stays for the shared state, and the two interfaces add the new capabilities. This is slide 13 in code form — single inheritance for state, multiple interfaces for capabilities.

package at.ac.univie.gis.week5.geometry;

public class Circle extends Geometry {

    private double radius;

    public Circle(String label, double radius) {
        super(label);
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}
package at.ac.univie.gis.week5.geometry;

public class Rectangle extends Geometry {

    private double width;
    private double height;

    public Rectangle(String label, double width, double height) {
        super(label);
        this.width = width;
        this.height = height;
    }

    @Override
    public double getArea() {
        return width * height;
    }
}

2. gameoflife/ — the in-class template

GameOfLifeInClassTemplate.java — A 10×10 boolean[][] seeded with a small starter pattern, plus a main loop that prints / ticks / sleeps. Three methods are stubbed for you to fill in. The point of the exercise is the copy-and-swap pattern in tick(): a fresh nextGame array is allocated, every cell’s next state is computed from the current game, then a single assignment commits the new generation. This is the same pattern Task 3 asks you to apply to the SIR step.

package at.ac.univie.gis.week5.gameoflife;

/**
 * Conway's Game of Life - Template for in-class exercise.
 *
 * Rules (to implement in tick()):
 * 1. Any live cell with fewer than 2 live neighbors dies (underpopulation).
 * 2. Any live cell with 2 or 3 live neighbors survives.
 * 3. Any live cell with more than 3 live neighbors dies (overpopulation).
 * 4. Any dead cell with exactly 3 live neighbors becomes alive (reproduction).
 *
 * Students should implement: tick(), neighbours(), and toString().
 */
public class GameOfLifeInClassTemplate {

    // The game grid: true = alive, false = dead
    private boolean[][] game =
            {
                    {false, false, false, false, false, false, false, false, false, false},
                    {false, false, false, true,  true,  false, false, false, false, false},
                    {false, false, false, true,  true,  false, false, false, false, false},
                    {false, false, false, false, false, false, false, false, false, false},
                    {false, false, false, false, false, false, false, false, false, false},
                    {false, false, false, true,  true,  false, true,  false, false, false},
                    {false, true,  true,  true,  false, false, false, false, false, false},
                    {false, true,  false, false, false, true,  false, false, false, false},
                    {false, false, false, false, true,  false, false, false, false, false},
                    {false, false, false, false, false, false, false, false, false, false}
            };

    public GameOfLifeInClassTemplate() {
        // Constructor - add initialization code if needed
    }

    /**
     * Advances the simulation by one step, applying the Game of Life rules.
     * IMPORTANT: All cells must be updated simultaneously, so we write to a new array
     * and then replace the old one (otherwise earlier updates would affect later ones).
     */
    public void tick() {
        boolean[][] nextGame = new boolean[game.length][game[0].length];
        // TODO: Apply the four rules using neighbours() to fill nextGame, then set game = nextGame
    }

    /**
     * Counts the number of live neighbors around cell (i, j).
     * Uses Queen's adjacency (8 surrounding cells). Must handle edge cases at grid boundaries.
     */
    public int neighbours(int i, int j) {
        int count = 0;
        // TODO: Check all 8 surrounding cells, incrementing count for each live neighbor
        return count;
    }

    /**
     * Returns a string representation of the grid for console output.
     * For example, use '#' for alive and '.' for dead cells.
     */
    @Override
    public String toString() {
        // TODO: Build a string representation of the grid
        return "";
    }

    /**
     * Main loop: prints the grid, advances one tick, waits, and repeats forever.
     */
    public static void main(String[] args) throws InterruptedException {
        GameOfLifeInClassTemplate game1 = new GameOfLifeInClassTemplate();
        while (true) {
            System.out.println(game1);
            game1.tick();
            Thread.sleep(500); // Pause between frames (increase to 2000ms for debugging)
        }
    }
}

3. polyline/ — class invariants on a cached field

PolyLine.java — The ArrayList<Point> is private, and a cached length field is kept in sync by add(): the new segment’s distance is added in the same step as the point itself. Because the list is private, no outside code can append behind PolyLine’s back — that’s what lets the class promise “my stored length always equals the actual sum of segment lengths.” That promise is the class invariant; private fields plus a single update gate are what make it stick. computeLength() exists as a from-scratch fallback after bulk modifications; getLength() is a cheap field read.

package at.ac.univie.gis.week5.polyline;

import java.util.ArrayList;

public class PolyLine {

    private ArrayList<Point> pointlist;
    private double length = 0;

    public PolyLine() {
        pointlist = new ArrayList<Point>();
        // Initialize length to ensure getLength() returns 0 even before any points are added
        computeLength();
    }

    /**
     * Adds a point to the polyline and updates the total length.
     * This is why encapsulation matters: without this method, a user could add points
     * directly to the list and getLength() would return a stale (incorrect) value.
     */
    public boolean add(Point e) {
        if (!pointlist.isEmpty()) {
            // Add the distance from the last point to the new point
            length += pointlist.get(pointlist.size() - 1).distanceTo(e);
        }
        return pointlist.add(e);
    }

    public Point get(int index) {
        return pointlist.get(index);
    }

    public int size() {
        return pointlist.size();
    }

    /**
     * Recomputes the total length from scratch by summing distances between consecutive points.
     * Normally not needed since add() keeps length up-to-date, but useful after modifications.
     */
    public double computeLength() {
        length = 0; // Reset before summing - the loop adds to length
        for (int i = 0; i < size() - 1; i++) {
            length += Math.sqrt(
                    Math.pow((get(i).getX() - get(i + 1).getX()), 2) +
                    Math.pow((get(i).getY() - get(i + 1).getY()), 2));
        }
        return length;
    }

    /**
     * Returns the current total length of the polyline.
     * Always up-to-date because add() updates it incrementally.
     */
    public double getLength() {
        return length;
    }
}

Point.java — A plain 2D point with distanceTo. Same shape as the W3/W4 Point, lifted into the W5 package alongside PolyLine.

package at.ac.univie.gis.week5.polyline;

public class Point {

    private double x, y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "Point{x=" + x + ", y=" + y + '}';
    }

    public double distanceTo(Point other) {
        return Math.sqrt(Math.pow(x - other.getX(), 2) + Math.pow(y - other.getY(), 2));
    }

    public double getX() { return x; }
    public void setX(double x) { this.x = x; }
    public double getY() { return y; }
    public void setY(double y) { this.y = y; }
}

Main.java — A small driver. Adds points one by one, then prints both getLength() (the cached value) and computeLength() (the from-scratch recomputation) so you can see the invariant holding: the two numbers agree.

package at.ac.univie.gis.week5.polyline;

public class Main {
    public static void main(String[] args) {

        // Create an empty polyline
        PolyLine poly = new PolyLine();
        System.out.println("Empty polyline's length: " + poly.getLength());

        // A 1-point polyline has length 0 (no segments yet)
        // Whether a 1-point polyline should be allowed is a design decision
        poly.add(new Point(5, 5));
        System.out.println("One-point polyline's length: " + poly.getLength());

        // Adding more points creates line segments, increasing the total length
        poly.add(new Point(6, 6));
        poly.add(new Point(8, 7));
        poly.add(new Point(9, 10));

        // add() keeps the length up-to-date; computeLength() recalculates from scratch
        // Both should return the same result
        System.out.println("Polyline's length: " + poly.computeLength());
    }
}

Assignment

Week 5 Assignment

Reading: Chapters on interfaces and 2D arrays.

Coding: Three small tasks — interface practice, finish the Game of Life template, fix the SIR step with copy-and-swap.

  • Drawable + Comparable on Geometry. In Code/Week5/geometry/, declare a small Drawable interface with one method String draw() returning a one-line text representation. Make Circle and Rectangle implement both Drawable and Comparable<Geometry> (compareTo orders by area). In a small main, build a List<Geometry> mixing circles and rectangles, sort with Collections.sort(list), and print each via draw(). This is the design rule from today’s lecture in code: one parent for shared state, multiple interfaces for capabilities.
  • Finish the Game of Life template. Open gameoflife/GameOfLifeInClassTemplate and fill in the three TODO methods — tick() with copy-and-swap, neighbours() with edge guards (out-of-bounds neighbours count as dead), and a toString() using # for alive and . for dead. Test on the blinker. The 2D + copy-and-swap pattern you write here is the foundation for the W8–W10 mandatory SIR cellular-automaton assignment.
  • Fix the SIR step with copy-and-swap. Refactor your W4 Code/Week4/sir/Main.java SIRstep so every person’s next state is computed from the population’s current state, then applied at the end. The cleanest shape is a parallel int[] nextStates aligned with population: compute next states for everyone first, then walk the array once and apply. Same pattern as Game of Life, just on a 1D list of Person instead of a 2D grid.

Submission: Upload FirstNameLastNameW5.zip with your .java files
Deadline: Sunday at 5:00 PM (Vienna Time) — extended to right before Week 7 so you have time to prepare for the midterm.