Weeks
JavaWeek 4 · Abstract Classes, Interfaces, Callbacks and Iterators
Week 4 Notes

Java,
Week 4

Programming Concepts using Java, Week 4: abstract classes and interfaces, default and static interface methods, private (nested) classes, controlled interaction with objects, callbacks and iterators. Every Java example in these notes was really compiled and run.

8 chapters19 worked examples~61 min read
Abstract classInterfacedefault / staticPrivate classCallbacksIteratorsInterview prep

Chapters

  1. 01Abstract Classes and InterfacesLecture 1
  2. 02InterfacesLecture 2
  3. 03Private ClassesLecture 3
  4. 04Controlled Interaction with ObjectsLecture 4
  5. 05CallbacksLecture 5
  6. 06IteratorsLecture 6
  7. 07Interview Question BankInterview
  8. 08Week 4 Summary SheetSummary
01
Lecture 1

Abstract Classes and Interfaces

Week 3 built a class hierarchy. This lecture asks how a parent class can force its subclasses to supply a method, how that makes one function work for many types, and why Java adds interfaces to get around the single-parent rule.

1.1Abstract classes and abstract methods

Suppose every shape must be able to report its perimeter, but a plain shape has no formula for it: the formula belongs to a circle, a square, a rectangle. The parent class can declare the method without a body:

Abstract methodAn abstract method gives only the signature and the return type, followed by a semicolon: public abstract double perimeter(); The parent forces every subclass to provide a concrete implementation.
  • You cannot create objects from a class that has an abstract method: what would perimeter() do on such an object?
  • So the class that contains an abstract method must itself be declared abstract.
Shapeperimeter() // abstractabstract classCircleperimeter() { ... }Squareperimeter() { ... }Rectangleperimeter() { ... }extendsnew Shape() ✗ abstractShape s = new Circle(1) ✓
Figure 1. Shape is abstract: it names perimeter() but cannot compute it. Each subclass supplies the real code
Shape.java
public abstract class Shape {
    public abstract double perimeter();
}
Circle.java
public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double perimeter() {
        return 2 * Math.PI * radius;
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Shape[] shapearr = new Shape[3];
        double[] sizearr = new double[3];

        shapearr[0] = new Circle(1);
        shapearr[1] = new Square(2);
        shapearr[2] = new Rectangle(2, 3);

        for (int i = 0; i < 3; ++i) {
            sizearr[i] = shapearr[i].perimeter();   // each object runs its own perimeter()
            System.out.println(sizearr[i]);
        }
    }
}
Output
6.283185307179586
8.0
10.0

The classes together make one program. Shape only says what must exist; Circle, Square and Rectangle say how. The three numbers are the three perimeters (the circle's is 2πr with r = 1).

What the compiler enforces

Three rules, each one shown by a real error message.

1. An abstract class cannot be instantiated.

Main.java
public class Main {
    public static void main(String[] args) {
        Shape s = new Shape();
    }
}
Error
Main.java:3: error: Shape is abstract; cannot be instantiated
        Shape s = new Shape();
                  ^
1 error

2. A class with an abstract method must be declared abstract. Leave the word out and the compiler complains about the class itself:

Shape.java
public class Shape {
    public abstract double perimeter();
}
Error
Shape.java:1: error: Shape is not abstract and does not override abstract method perimeter() in Shape
public class Shape {
       ^
1 error

3. A concrete subclass must implement every abstract method it inherits.

Square.java
public class Square extends Shape {
    private double side;

    public Square(double side) {
        this.side = side;
    }
    // perimeter() is missing
}
Error
Square.java:1: error: Square is not abstract and does not override abstract method perimeter() in Shape
public class Square extends Shape {
       ^
1 error
A subclass may stay abstractIf a subclass implements only some of the abstract methods, it must be declared abstract too, and only a further subclass that fills in the rest can be instantiated (extra):
Square.java
public abstract class Square extends Shape {
    protected double side = 2;

    public double perimeter() {
        return 4 * side;
    }
    // area() still missing: fine, because Square is abstract too
}
Unit.java
public class Unit extends Square {
    public double area() {
        return side * side;
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Shape s = new Unit();
        System.out.println(s.perimeter() + " " + s.area());
    }
}
Output
8.0 4.0

1.2Variables of an abstract type

We cannot create an object of an abstract class, but we can still declare variables whose type is an abstract class. Such a variable can refer to an object of any concrete subclass.

Main.java
public class Main {
    public static void main(String[] args) {
        Shape s;                       // a variable of an abstract type: fine
        s = new Circle(1);             // it can refer to any concrete subclass object
        System.out.println(s.perimeter());
        System.out.println(s instanceof Circle);
    }
}
Output
6.283185307179586
true
Shape[] shapearr[0]Circleradius = 16.283...[1]Squareside = 28.0[2]Rectangle2 x 310.0for (...) sizearr[i] = shapearr[i].perimeter(); // one call, three behaviours
Figure 2. One array of Shape, three different objects: the loop makes the same call and every object answers in its own way (dynamic dispatch, Week 3)

This is exactly the Week 3 idea of polymorphism: the static type Shape decides what we may call, the real object decides which code runs.

Source slip (extra). The lecture stores the results in int sizearr[] and leaves out the semicolon after perimeter(). A perimeter is a double, and a double does not squeeze into an int without a cast (Week 2). These notes use double[] sizearr:
Main.java
public class Main {
    public static void main(String[] args) {
        Shape[] shapearr = new Shape[1];
        int[] sizearr = new int[1];
        shapearr[0] = new Circle(1);
        sizearr[0] = shapearr[0].perimeter();
    }
}
Error
Main.java:6: error: incompatible types: possible lossy conversion from double to int
        sizearr[0] = shapearr[0].perimeter();
                                          ^
1 error

1.3Abstract classes can hold more than abstract methods

An abstract class is a normal class with one restriction (no direct objects). It may have fields, constructors and fully written methods. A well-known use: write the common steps once, and leave only the part that differs as abstract. Here describe() is already complete and calls perimeter(), which each subclass supplies (extra):

Shape.java
public abstract class Shape {
    private String name;

    public Shape(String name) {                 // an abstract class can have a constructor
        this.name = name;
    }

    public abstract double perimeter();         // the part every subclass must supply

    public String describe() {                  // a normal method, already written
        return name + " with perimeter " + perimeter();
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Shape s = new Square(2);
        System.out.println(s.describe());
    }
}
Output
Square with perimeter 8.0

Two more facts, both worth knowing for an interview: an abstract class's constructor does run (when a subclass object is built, the parent part is built first) and a class can be abstract even with no abstract method at all, just to forbid direct objects.

Main.java
public class Main {
    public static void main(String[] args) {
        Shape s = new Square(2);            // an abstract class is built as part of its subclass object
        System.out.println(s.perimeter());
    }
}
Output
Shape constructor
Square constructor
8.0
Base.java
public abstract class Base {
    public void hello() {
        System.out.println("hello from Base");
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Base b = new Base();
    }
}
Error
Main.java:3: error: Base is abstract; cannot be instantiated
        Base b = new Base();
                 ^
1 error

1.4Generic functions through an abstract class

Abstract classes can also describe a capability rather than a thing. Suppose we want one sorting function for any kind of object, as long as two objects can be compared. The only thing sorting needs is a way to ask "is this one smaller?":

Myclasscmp() on sizeCirclecmp() on radiusEmployeecmp() on salaryComparablecmp(Comparable s)-1 / 0 / 1quicksort(Comparable[] a)compares witha[i].cmp(a[j])every class that extends (or later implements) Comparable can be sorted
Figure 3. quicksort is written once, against Comparable; anything that extends Comparable can be sorted
  • cmp returns -1 if this < s, 0 if equal, 1 if this > s.
  • Inside quicksort, to compare a[i] and a[j] we write a[i].cmp(a[j]), whatever the real class is.

The class that wants to be sortable extends Comparable and defines cmp. The lecture says to compare this.size with ((Myclass) s).size: the cast is needed to reach size, because s is typed Comparable and a Comparable has no size. The instanceof test first makes the cast safe (Week 3). A complete, running version:

Comparable.java
public abstract class Comparable {
    public abstract int cmp(Comparable s);
    // return -1 if this < s
    // return 0  if this == s
    // return 1  if this > s
}
SortFunctions.java
public class SortFunctions {
    public static void quicksort(Comparable[] a) {
        quicksort(a, 0, a.length - 1);
    }

    private static void quicksort(Comparable[] a, int lo, int hi) {
        if (lo >= hi) return;
        Comparable pivot = a[hi];
        int p = lo;
        for (int i = lo; i < hi; ++i) {
            if (a[i].cmp(pivot) < 0) {         // compare with cmp(), nothing else
                Comparable t = a[i]; a[i] = a[p]; a[p] = t;
                ++p;
            }
        }
        Comparable t = a[p]; a[p] = a[hi]; a[hi] = t;
        quicksort(a, lo, p - 1);
        quicksort(a, p + 1, hi);
    }
}
Myclass.java
public class Myclass extends Comparable {
    private double size;          // quantity used for comparison

    public Myclass(double size) {
        this.size = size;
    }

    public double getSize() {
        return size;
    }

    public int cmp(Comparable s) {
        if (s instanceof Myclass) {
            double other = ((Myclass) s).size;     // cast, to reach s.size
            if (this.size < other) return -1;
            if (this.size > other) return 1;
            return 0;
        }
        return 0;
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Myclass[] a = { new Myclass(3.5), new Myclass(1.25), new Myclass(9), new Myclass(2) };
        SortFunctions.quicksort(a);
        for (Myclass m : a) {
            System.out.println(m.getSize());
        }
    }
}
Output
1.25
2.0
3.5
9.0
Source slip (extra). The lecture's cmp is only a skeleton: it shows the if and then comments. Written as is, it does not compile, because the method promises an int on every path:
Myclass.java
public class Myclass extends Comparable {
    private double size;

    public int cmp(Comparable s) {
        if (s instanceof Myclass) {
            // compare this.size and ((Myclass) s).size
        }
    }
}
Error
Myclass.java:8: error: missing return statement
    }
    ^
1 error
Extra: the real Comparable. Java already has an interface called java.lang.Comparable, with a method named compareTo. The lecture defines its own Comparable with cmp to teach the idea. A class in your own package takes precedence over java.lang, so both exist side by side:
Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(Comparable.class.getName());
        System.out.println(java.lang.Comparable.class.getName());
        System.out.println("abc".compareTo("abd"));   // the real one: compareTo, not cmp
    }
}
Output
Comparable
java.lang.Comparable
-1

1.5Multiple inheritance: interfaces

Here is the catch. Circle already extends Shape, and Java allows only one parent class (Week 3: the diamond problem). How can a Circle also be a Comparable? The answer is to make Comparable an interface:

InterfaceAn interface is an abstract class with no concrete components: every method is abstract. A class that uses it is said to implement it, and a class can extend only one class but implement any number of interfaces.
Shapeperimeter()abstract classComparablecmp(Comparable s)interfacePrintableprint()interfaceCircleextends Shape implementsComparable, Printableextends: only ONEimplements: ANY number
Figure 4. Circle extends one class (solid arrow) and implements any number of interfaces (dashed arrows)
Comparable.java
public interface Comparable {
    public abstract int cmp(Comparable s);
}
Circle.java
public class Circle extends Shape implements Comparable {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double perimeter() {
        return 2 * Math.PI * radius;
    }

    public int cmp(Comparable s) {
        Circle other = (Circle) s;
        return Double.compare(this.perimeter(), other.perimeter());
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Circle[] c = { new Circle(3), new Circle(1), new Circle(2) };
        SortFunctions.quicksort(c);                 // a Circle[] is a Comparable[]
        for (Circle x : c) {
            System.out.println(x.perimeter());
        }
    }
}
Output
6.283185307179586
12.566370614359172
18.84955592153876
Source wording (extra). The lecture says "a class that extends an interface is said to implement it". In Java source you write implements, not extends, for an interface (extending an interface is something an interface does to another interface).

Why this is safe: two abstract methods with the same name cannot contradict each other, because neither has code. Two classes could (the diamond), which is why Java keeps classes to one parent. Trying to extend two classes is a syntax error:

Circle.java
public class Circle extends Shape, Comparable {
}
Error
Circle.java:1: error: '{' expected
public class Circle extends Shape, Comparable {
                                 ^
1 error

And several interfaces are fine; the one object is then a Shape, a Comparable and a Printable at once, so it can be passed to any code that wants any of those types:

Circle.java
public class Circle extends Shape implements Comparable, Printable {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double perimeter() {
        return 2 * Math.PI * radius;
    }

    public int cmp(Comparable s) {
        return Double.compare(perimeter(), ((Circle) s).perimeter());
    }

    public void print() {
        System.out.println("Circle, perimeter " + perimeter());
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Circle c = new Circle(1);
        Shape s = c;          // a Circle is a Shape
        Comparable k = c;     // ... and a Comparable
        Printable p = c;      // ... and a Printable
        p.print();
        System.out.println(s instanceof Comparable);
    }
}
Output
Circle, perimeter 6.283185307179586
true

1.6Lecture summary

  • Use the class hierarchy to group related classes together.
  • An abstract method in a parent forces each subclass to implement it sensibly.
  • Any class with an abstract method is itself abstract; no objects can be created from it, but variables of that type are fine.
  • Abstract classes can describe capabilities, which allows generic functions such as one quicksort for everything Comparable.
  • An interface is an abstract class with no concrete components. A class extends one class but implements any number of interfaces.
Next · Lecture 2Interfaces
02
Lecture 2

Interfaces

The previous lecture ended with the definition of an interface. This one looks at what interfaces are for, at the two kinds of method Java later allowed inside them, and at the old multiple-inheritance conflict that those additions brought back.

2.1An interface is a purely abstract class

  • All methods are abstract. They are implicitly public abstract, so writing int cmp(Comparable s); is enough.
  • A class implements an interface by providing concrete code for each abstract function.
  • Classes can implement multiple interfaces. Since the functions are abstract there is no contradictory inheritance: nothing to pick between.

If a class says it implements an interface but leaves a method out, the compiler refuses, exactly as for an abstract class:

Circle.java
public class Circle implements Comparable {
}
Error
Circle.java:1: error: Circle is not abstract and does not override abstract method cmp(Comparable) in Comparable
public class Circle implements Comparable {
       ^
1 error

And because interface methods are public, the implementing method must be public too. Leaving the word out gives the method package access, which is a weaker access than the interface promised:

Circle.java
public class Circle implements Comparable {
    int cmp(Comparable s) {          // package access: weaker than public
        return 0;
    }
}
Error
Circle.java:2: error: cmp(Comparable) in Circle cannot implement cmp(Comparable) in Comparable
    int cmp(Comparable s) {          // package access: weaker than public
        ^
  attempting to assign weaker access privileges; was public
1 error

2.2Describing the relevant slice of a class

  • Interfaces describe relevant aspects of a class. The abstract functions describe a specific "slice" of capabilities.
  • Another class only needs to know about these capabilities, nothing else. This is exposing limited capabilities.
EmployeeString namedouble salarydouble bonus(float p)String getName()int cmp(Comparable s)quicksort(Comparable[] a)needs only ONE slice:cmp() // can I compare?everything else is irrelevantAn interface describes a slice of what a class can do.
Figure 5. quicksort needs one slice of an Employee: can it be compared? Everything else about Employee is irrelevant to it

Take the generic quicksort for any datatype that supports comparison. We express that capability by making the argument type Comparable[]. That is the only information quicksort needs about the underlying type. Now the very same quicksort sorts employees by salary and plain numbers wrapped in a class, with no change to it:

Main.java
public class Main {
    public static void main(String[] args) {
        Employee[] e = { new Employee("Ravi", 2000), new Employee("Asha", 1000), new Employee("Meena", 1500) };
        SortFunctions.quicksort(e);
        for (Employee x : e) System.out.println(x.getName());

        Dnum[] d = { new Dnum(30), new Dnum(10), new Dnum(20) };
        SortFunctions.quicksort(d);                  // same function, a completely different type
        for (Dnum x : d) System.out.println(x.value);
    }
}
Output
Asha
Meena
Ravi
10
20
30
A limit of interfaces. An interface lists the methods that must be present, but it cannot express the intended behaviour of cmp (return -1, 0 or 1). That promise lives only in a comment, and every implementer has to honour it by hand.

What else an interface can contain (extra)

Interface fields are always constants (public static final), so they need a value and can never change. They are shared by every implementer:

Limits.java
public interface Limits {
    int MAX = 100;                  // implicitly public static final
}
Main.java
public class Main implements Limits {
    public static void main(String[] args) {
        System.out.println(MAX);
        System.out.println(Limits.MAX);
    }
}
Output
100
100
Main.java
public class Main implements Limits {
    public static void main(String[] args) {
        MAX = 200;
    }
}
Error
Main.java:3: error: cannot assign a value to static final variable MAX
        MAX = 200;
        ^
1 error
Counter.java
public interface Counter {
    int count;                    // no value: not allowed, every field is a constant
}
Error
Counter.java:2: error: = expected
    int count;                    // no value: not allowed, every field is a constant
             ^
1 error

And like an abstract class, an interface has no objects of its own:

Main.java
public class Main {
    public static void main(String[] args) {
        Comparable c = new Comparable();
    }
}
Error
Main.java:3: error: Comparable is abstract; cannot be instantiated
        Comparable c = new Comparable();
                       ^
1 error

2.3Adding methods to interfaces

Newer versions of Java extended interfaces to allow concrete functions to be added. There are two kinds.

Static functions
  • Cannot access instance variables (they belong to no object).
  • Invoke them directly or with the interface name: Comparable.cmpdoc().
Default functions
  • Give a default implementation for some functions.
  • The class may override them.
  • Invoke like a normal method, on an object: a[i].cmp(a[j]).

Static function in an interface

Comparable.java
public interface Comparable {
    public abstract int cmp(Comparable s);

    public static String cmpdoc() {
        String s;
        s = "Return -1 if this < s, ";
        s += "0 if this == s, ";
        s += "+1 if this > s.";
        return s;
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(Comparable.cmpdoc());
    }
}
Output
Return -1 if this < s, 0 if this == s, +1 if this > s.

A static interface method belongs to the interface only. It is not inherited by the implementing class, and cannot be called through an object either (extra, checked by running):

Main.java
public class Main {
    public static void main(String[] args) {
        Dnum d = new Dnum();
        System.out.println(d.cmpdoc());
    }
}
Error
Main.java:4: error: cannot find symbol
        System.out.println(d.cmpdoc());
                            ^
  symbol:   method cmpdoc()
  location: variable d of type Dnum
1 error
Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(Dnum.cmpdoc());
    }
}
Error
Main.java:3: error: cannot find symbol
        System.out.println(Dnum.cmpdoc());
                               ^
  symbol:   method cmpdoc()
  location: class Dnum
1 error

Default function in an interface

A class that does nothing gets the default. A class that wants something better overrides it. Calling it is the same either way:

Comparable.java
public interface Comparable {
    public default int cmp(Comparable s) {
        return 0;                       // default: everything is "equal"
    }
}
Dnum.java
public class Dnum implements Comparable {
    public int value;

    public Dnum(int value) {
        this.value = value;
    }

    @Override
    public int cmp(Comparable s) {        // overrides the default
        return Integer.compare(value, ((Dnum) s).value);
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Comparable p = new Plain();
        System.out.println(p.cmp(new Plain()));            // the default runs

        Comparable a = new Dnum(1);
        System.out.println(a.cmp(new Dnum(5)));            // the override runs
    }
}
Output
0
-1

2.4Dealing with conflicts

The old problem of multiple inheritance returns. Once interfaces carry code, two of them can offer different versions of the same method.

  • Conflict between static/default methods: the subclass must provide a fresh implementation.
  • Conflict between a class and an interface: the method inherited from the class wins. This was motivated by reverse compatibility.
two default methodsI1hello()I2hello()C implements I1, I2must write its ownhello()class and interfacePersongetName(): "No name"Designationdefault getName()Employee extends Personimplements Designationclass wins: "No name"
Figure 6. Left: two defaults collide, so the class must decide. Right: a class method beats an interface default

Two interfaces. With nothing written, the compiler refuses:

C.java
public class C implements I1, I2 {
}
Error
C.java:1: error: types I1 and I2 are incompatible;
public class C implements I1, I2 {
       ^
  class C inherits unrelated defaults for hello() from types I1 and I2
1 error

The class must supply a fresh implementation. Inside it, you may still choose one of the inherited versions by naming the interface before super:

C.java
public class C implements I1, I2 {
    @Override
    public String hello() {                       // a fresh implementation
        return "C picks: " + I1.super.hello();    // or write something brand new
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(new C().hello());
    }
}
Output
C picks: from I1

A class and an interface. Employee inherits from the class Person and implements Designation. Both have getName(). The one inherited from the class wins, with no error and no choice to make:

Person.java
public class Person {
    public String getName() {
        return "No name";
    }
}
Designation.java
public interface Designation {
    public default String getName() {
        return "No designation";
    }
}
Employee.java
public class Employee extends Person implements Designation {
}
Main.java
public class Main {
    public static void main(String[] args) {
        Employee e = new Employee();
        System.out.println(e.getName());
        Designation d = e;                       // through the interface type: still the class's method
        System.out.println(d.getName());
    }
}
Output
No name
No name
Why "class wins"

Old code written before Java 8 had classes with methods but no defaults. When defaults were added to widely used interfaces, a rule that class beats interface kept all that old code compiling and behaving as before. This is the lecture's point about the pitfalls of extending a language while maintaining compatibility.

A real example of the same idea: forEach was added to the standard library as a default method of Iterable, so every existing list gained it without a single class being rewritten (extra):

Main.java
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("Asha");
        names.add("Ravi");
        names.forEach(n -> System.out.println("hello " + n));    // forEach is a default method of Iterable
    }
}
Output
hello Asha
hello Ravi

2.5Lecture summary

  • Interfaces express abstract capabilities, in terms of methods that must be present.
  • They cannot specify the intended behaviour of those functions.
  • Java later allowed concrete functions: static (no instance variables) and default (may be overridden).
  • This reintroduces the conflict in multiple inheritance: the subclass must resolve it with a fresh implementation; for a class versus an interface, the class wins.
Next · Lecture 3Private Classes
03
Lecture 3

Private Classes

Until now a class was either public (anyone may use it) or hidden inside its file. This lecture shows a third option: a class inside another class, visible only to the class that holds it. It is encapsulation applied one level deeper.

3.1Nested objects

  • An instance variable can be of a user defined type. Employee uses Date for the joining date.
  • Date is a public class, also available to other classes: that is sensible, because everyone has dates.
  • So the question is: when could a private class make sense?
Employee.java
public class Employee {
    private String name;
    private double salary;
    private Date joinDate;              // a nested object: an instance variable of a user defined type

    public Employee(String name, double salary, Date joinDate) {
        this.name = name;
        this.salary = salary;
        this.joinDate = joinDate;
    }

    public String toString() {
        return name + " joined on " + joinDate;
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Employee e = new Employee("Asha", 1000, new Date(14, 1, 2022));
        System.out.println(e);
    }
}
Output
Asha joined on 14-1-2022

3.2A linked list and its Node

A LinkedList is built out of Node objects: each node holds a value and a pointer to the next node. The lecture first makes Node a public class. Why should Node be public? Nobody using a list wants to deal with nodes at all.

  • We may want to enhance Node later, with a prev field, to get a doubly linked list.
  • That does not affect the interface of LinkedList: insert and head stay the same. But if Node is public, other code may already depend on it.

With a public Node any outside code can build nodes, read their fields and even tie them into a loop. The list has no say:

Node.java
public class Node {
    public Object data;
    public Node next;

    public Node(Object data) {
        this.data = data;
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList list = new LinkedList();
        list.insert("a");
        list.insert("b");
        list.insert("c");
        System.out.println(list.head());     // the most recently inserted comes out first
        System.out.println(list.head());
        System.out.println(list.size());

        Node loose = new Node("anyone can build a node");   // Node is public: other code can use it
        loose.next = loose;                                 // ... and even tie it into a loop
        System.out.println(loose.next.data);
    }
}
Output
c
b
1
anyone can build a node
Source slip (extra). The lecture's head() returns the first value and removes it (first = first.next), so it behaves like a "pop", yet it never reduces size. The listings here add size--. The lecture also elides insert; here it puts the new node at the front, which is why the newest value comes out first.

3.3Make Node a private class

Private classA private class is a class declared inside another class with the private modifier. It is also called an inner class. Objects of the private class can see the private components of the enclosing class (and the enclosing class can see theirs).
LinkedListprivate Node first; private int size;private class Node (inside LinkedList)data "c"nextdata "b"nextdata "a"nextnullfirst points at the newest node; the last next is nullother codeinsert(x), head()size()LinkedList.Node✗ private access
Figure 7. Node lives inside LinkedList: the list works with nodes freely, while code outside can only use insert, head and size
LinkedList.java
public class LinkedList {
    private class Node {
        private Object data;
        private Node next;

        private Node(Object data) {
            this.data = data;
        }
    }

    private int size;
    private Node first;

    public Object head() {
        Object returnval = null;
        if (first != null) {
            returnval = first.data;      // the outer class may read Node's private fields
            first = first.next;
            size--;
        }
        return returnval;
    }

    public void insert(Object newData) {
        Node n = new Node(newData);
        n.next = first;
        first = n;
        size++;
    }

    public int size() {
        return size;
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList list = new LinkedList();
        list.insert("a");
        list.insert("b");
        list.insert("c");
        System.out.println(list.head());
        System.out.println(list.head());
        System.out.println(list.size());
    }
}
Output
c
b
1

Notice what the code does. head() reads first.data and first.next, which are private fields of Node, and it compiles: nesting gives the outer class that access. Outside code, on the other hand, cannot even name the type:

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList.Node n = null;
    }
}
Error
Main.java:3: error: LinkedList.Node has private access in LinkedList
        LinkedList.Node n = null;
                  ^
1 error

The same visibility works the other way round. Here the private inner class changes a private field of the enclosing object, and the notation Counter.this names that enclosing object explicitly:

Counter.java
public class Counter {
    private int count = 0;                       // private to Counter

    private class Step {
        void bump() {
            count++;                              // the inner class reaches the outer private field
            System.out.println("count is now " + Counter.this.count);
        }
    }

    public void run() {
        Step s = new Step();
        s.bump();
        s.bump();
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        new Counter().run();
    }
}
Output
count is now 1
count is now 2

Rules worth knowing (extra, all checked by running)

  • Only a nested class can be private. A top-level class is public or package-private; private on it is an error.
Node.java
private class Node {
}
Error
Node.java:1: error: modifier private not allowed here
private class Node {
        ^
1 error
  • Inner versus static nested. An ordinary inner class is tied to one object of the outer class (it can read x below). A static nested class is not tied to any outer object, so it is the lighter choice for something like Node that never needs the outer object.
Outer.java
public class Outer {
    private int x = 7;

    private class Inner {                 // ordinary inner class: tied to one Outer object
        int twice() { return 2 * x; }
    }

    private static class Plain {           // static nested class: no Outer object needed
        int five() { return 5; }
    }

    public void show() {
        Inner a = new Inner();
        Plain b = new Plain();
        System.out.println(a.twice());
        System.out.println(b.five());
    }
}
Output
14
5

From outside, an accessible inner class needs an outer object to be built, written outer.new Inner(), and inside it Outer.this.name reaches the outer field even when the inner class has a field of the same name:

Main.java
public class Main {
    public static void main(String[] args) {
        Outer o = new Outer();
        Outer.Inner in = o.new Inner();             // an inner object needs an outer object
        in.show();
    }
}
Output
inner
inner
outer

3.4Lecture summary

  • An object can have nested objects as instance variables.
  • In some situations the structure of these nested objects need not be exposed.
  • Private classes give an additional degree of data encapsulation.
  • Combine private classes with interfaces to give controlled access to the state of an object: the next lecture.
Next · Lecture 4Controlled Interaction with Objects
04
Lecture 4

Controlled Interaction with Objects

Encapsulation is a key principle of object oriented programming. This lecture goes from the simple case, a date that must stay valid, to a harder one: a railway booking system that must control who may ask what, and how often, by combining a private class with an interface.

4.1Manipulating objects: accessors and mutators

  • Internal data is private. Access to it is regulated through public methods.
  • Accessor methods read (getDay, getMonth, getYear). Mutator methods change (setDay, setMonth, setYear).
  • Regulating access lets us ensure data integrity.

Why not simply a setter for every field? Because a date is a whole: whether the day is legal depends on the month and the year. With three independent setters the object can pass through, and even end in, a state that was never valid:

Main.java
public class Main {
    public static void main(String[] args) {
        Date d = new Date();
        d.setDay(31);
        d.setMonth(1);
        d.setYear(2024);          // 31 January: fine
        d.setMonth(4);            // now it says 31 April, and nothing stopped us
        System.out.println(d);
    }
}
Output
31-4-2024
The lecture's fix

Update the data as a whole, rather than individual components. One method setDate(d, m, y) receives all three values and validates the day-month-year combination before changing anything:

Date.java
public class Date {
    private int day, month, year;

    public int getDay()   { return day; }
    public int getMonth() { return month; }
    public int getYear()  { return year; }

    private static boolean leap(int y) {
        return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
    }

    private static int daysIn(int m, int y) {
        switch (m) {
            case 4: case 6: case 9: case 11: return 30;
            case 2: return leap(y) ? 29 : 28;
            default: return 31;
        }
    }

    // update the date as a whole, not field by field
    public void setDate(int d, int m, int y) {
        if (m < 1 || m > 12 || d < 1 || d > daysIn(m, y)) {
            throw new IllegalArgumentException("invalid date " + d + "-" + m + "-" + y);
        }
        day = d;
        month = m;
        year = y;
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Date d = new Date();
        d.setDate(29, 2, 2024);                       // 2024 is a leap year
        System.out.println(d.getDay() + "-" + d.getMonth() + "-" + d.getYear());
        d.setDate(31, 4, 2024);                       // April has 30 days
    }
}
Error
29-2-2024
Exception in thread "main" java.lang.IllegalArgumentException: invalid date 31-4-2024
	at Date.setDate(Date.java:23)
	at Main.main(Main.java:6)

The lecture leaves the validation as a comment ("validate the d-m-y combination"). Above it is real: months run 1 to 12 and a day may not exceed the length of that month, with February 29 allowed only in a leap year (divisible by 4, but by 400 if it is a century).

4.2Interaction with state: the railway query

A booking object stores train reservation information. It can answer: how many seats are available on train number trainno on date d? To control spamming by bots, the user must log in before querying, so the query must be connected to the user's logged-in status.

  1. The first version has getStatus(trainno, d) directly on RailwayBooking. Anyone can call it, logged in or not. Nothing links a query to a user.
  2. Use objects. On login the user receives an object that can make a query. Only a successful login yields one, and the object is created from a private class that can look up railwayDB.

Step 1: login returns an object of a private class

The lecture's login only returns inside the if. What happens when the password is wrong? Java insists on an answer for every path:

RailwayBooking.java
public class RailwayBooking {
    private BookingDB railwayDB = new BookingDB();

    private class QueryObject {
        public int getStatus(int trainno, Date d) {
            return railwayDB.seatsOn(trainno, d);
        }
    }

    private boolean validLogin(String u, String p) {
        return u.equals("asha") && p.equals("secret");
    }

    public QueryObject login(String u, String p) {
        QueryObject qobj;
        if (validLogin(u, p)) {
            qobj = new QueryObject();
            return qobj;
        }
    }
}
Error
RailwayBooking.java:20: error: missing return statement
    }
    ^
1 error

With return null; added for a failed login, the code compiles. But there is a deeper problem. The method is public, yet its return type QueryObject is private. A caller receives an object whose class it is not allowed to use:

Main.java
public class Main {
    public static void main(String[] args) {
        RailwayBooking rb = new RailwayBooking();
        int seats = rb.login("asha", "secret").getStatus(12627, new Date(14, 1, 2022));
    }
}
Error
Main.java:4: error: RailwayBooking.QueryObject.getStatus(int,Date) is defined in an inaccessible class or interface
        int seats = rb.login("asha", "secret").getStatus(12627, new Date(14, 1, 2022));
                                              ^
1 error

This is the problem the lecture poses: how does the user know the capabilities of the private class QueryObject?

Step 2: describe the capabilities with an interface

  • An interface QIF describes the capabilities of the object returned on login.
  • QueryObject implements QIF, and login is declared to return QIF.
  • The caller never needs to know the class name: the type it sees is public, the class behind it is private.
userholds aQIF handleRailwayBookingpublic QIF login(u, p)private class QueryObjectimplements QIFnumQueries = 0final QLIM = 2getStatus(train, d)BookingDBrailwayDBloginreturns the object,typed QIFThe class is private, but its capabilities are public through QIF.
Figure 8. login gives the user a handle typed QIF; the private QueryObject behind it keeps the counter and reaches the database
QIF.java
public interface QIF {
    public abstract int getStatus(int trainno, Date d);
}
RailwayBooking.java
public class RailwayBooking {
    private BookingDB railwayDB = new BookingDB();

    private boolean validLogin(String u, String p) {
        return u.equals("asha") && p.equals("secret");
    }

    public QIF login(String u, String p) {          // the caller only ever sees the interface
        if (validLogin(u, p)) {
            return new QueryObject();
        }
        return null;
    }

    private class QueryObject implements QIF {
        public int getStatus(int trainno, Date d) {
            return railwayDB.seatsOn(trainno, d);
        }
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        RailwayBooking rb = new RailwayBooking();
        QIF q = rb.login("asha", "secret");
        System.out.println(q.getStatus(12627, new Date(14, 1, 2022)));
        System.out.println(rb.login("asha", "wrong"));      // a failed login gives no object at all
    }
}
Output
27
null

4.3Limiting the number of queries per login

The query object so far allows an unlimited number of queries. To limit them, maintain a counter as instance variables of the object returned on login. The object remembers the state of the interaction:

Source slip (extra). The lecture declares private final int QLIM; and never gives it a value. A final field must be assigned exactly once, normally in the constructor, so the compiler refuses:
RailwayBooking.java
public class RailwayBooking {
    private BookingDB railwayDB = new BookingDB();

    public QIF login(String u, String p) {
        return new QueryObject();
    }

    private class QueryObject implements QIF {
        private int numQueries;
        private final int QLIM;               // never given a value

        public int getStatus(int trainno, Date d) {
            return 0;
        }
    }
}
Error
RailwayBooking.java:10: error: variable QLIM not initialized in the default constructor
        private final int QLIM;               // never given a value
                          ^
1 error

The fixed version gives QLIM its value in a constructor (the limit is 2 per login here) and answers -1 once the quota is used up. Every login creates a new QueryObject, so each user has an independent counter:

RailwayBooking.java
public class RailwayBooking {
    private BookingDB railwayDB = new BookingDB();

    private boolean validLogin(String u, String p) {
        return u.equals("asha") || u.equals("ravi");
    }

    public QIF login(String u, String p) {
        if (validLogin(u, p)) {
            return new QueryObject(2);                 // each login gets its own object, limit 2
        }
        return null;
    }

    private class QueryObject implements QIF {
        private int numQueries;
        private final int QLIM;

        private QueryObject(int limit) {
            QLIM = limit;
        }

        public int getStatus(int trainno, Date d) {
            if (numQueries < QLIM) {
                numQueries++;                          // remember the state of the interaction
                return railwayDB.seatsOn(trainno, d);
            }
            return -1;                                 // quota used up
        }
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        RailwayBooking rb = new RailwayBooking();
        Date d = new Date(14, 1, 2022);

        QIF asha = rb.login("asha", "x");
        QIF ravi = rb.login("ravi", "x");

        System.out.println(asha.getStatus(12627, d));
        System.out.println(asha.getStatus(12627, d));
        System.out.println(asha.getStatus(12627, d));     // third query: refused
        System.out.println(ravi.getStatus(12628, d));     // Ravi has his own counter
    }
}
Output
27
27
-1
28
The design in one line

Private class for the code and the state, public interface for what the outside may do, and one object per interaction. The same shape appears again in the next lectures (the timer owner, the iterator).

4.4Lecture summary

  • We can provide controlled access to an object by combining private classes with interfaces.
  • External interaction happens through an object of the private class; its capabilities are known through a public interface.
  • The object can maintain instance variables to track the state of the interaction.
Next · Lecture 5Callbacks
05
Lecture 5

Callbacks

Sometimes an object starts another piece of work and carries on with its own. When the other work finishes it must tell the starter. That "call me back" arrangement is a callback, and the lecture builds it step by step to show why interfaces make it generic.

5.1Implementing a call-back facility

  • MyClass m creates a Timer t and starts t to run in parallel.
  • m continues to run. (We will see later how to invoke parallel execution in Java.)
  • When the time limit expires, t notifies m. Assume MyClass has a function timerDone() for this.
MyClass mTimer tnew Timer(this)owner = mt.start()runs in parallelm carries onwith other workwaits for thetime limittimerDone()owner.timerDone()notify
Figure 9. The timer is created and started by MyClass, runs in parallel, and calls timerDone() on its owner when the time limit expires
The key design question

Timer t should know whom to notify. So MyClass m passes its own identity (this) when it creates the Timer, and the Timer stores it as its owner.

Version 1: a Timer written for MyClass

The interface Runnable indicates that Timer can run in parallel. The Timer is specific to MyClass: its owner field has the type MyClass.

Source slip (extra). The lecture's Timer says implements Runnable but defines a method called start(). Runnable asks for a method named run(), so as written the class is rejected:
Timer.java
public class Timer implements Runnable {
    private MyClass owner;

    public Timer(MyClass o) {
        owner = o;
    }

    public void start() {
        owner.timerDone();
    }
}
Error
Timer.java:1: error: Timer is not abstract and does not override abstract method run() in Runnable
public class Timer implements Runnable {
       ^
1 error

A working version needs run() to do the waiting and the notifying. To really run the timer in parallel we hand it to a Thread and call start() on the thread (the lecture defers this to a later week, so treat it as an extra). join() only makes the example wait for the timer so that the printed order is the same every time:

MyClass.java
public class MyClass {
    public void f() throws InterruptedException {
        Timer t = new Timer(this);                 // this object created t
        Thread th = new Thread(t);                 // (extra) a thread so that t runs in parallel
        th.start();                                // start t
        System.out.println("f: timer started, f carries on");
        th.join();                                 // (extra) wait, so the output order is fixed
    }

    public void timerDone() {
        System.out.println("MyClass.timerDone() called");
    }
}
Timer.java
public class Timer implements Runnable {
    // Timer can be invoked in parallel
    private MyClass owner;

    public Timer(MyClass o) {
        owner = o;                                 // creator
    }

    public void run() {
        try {
            Thread.sleep(100);                     // the "time limit"
        } catch (InterruptedException e) { }
        owner.timerDone();                         // notify the owner
    }
}
Main.java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        new MyClass().f();
    }
}
Output
f: timer started, f carries on
MyClass.timerDone() called

Version 2: a generic Timer with an Object owner

The Timer is specific to MyClass, but we need a generic timer. Using the Java class hierarchy: make the constructor parameter of type Object, compatible with all caller types (every class is an Object). The price: we must cast the owner back to MyClass to call timerDone().

Timer.java
public class Timer implements Runnable {
    private Object owner;                          // any object at all

    public Timer(Object o) {
        owner = o;
    }

    public void run() {
        try { Thread.sleep(100); } catch (InterruptedException e) { }
        ((MyClass) owner).timerDone();             // cast the owner back to MyClass
    }
}
Main.java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        new MyClass().f();                         // fine: the owner really is a MyClass

        Thread th = new Thread(new Timer(new Other()));   // compiles, because any Object is accepted
        th.start();
        th.join();
    }
}
Error
f: timer started, f carries on
MyClass.timerDone() called
Exception in thread "Thread-1" java.lang.ClassCastException: class Other cannot be cast to class MyClass (Other and MyClass are in unnamed module of loader 'app')
	at Timer.run(Timer.java:10)
	at java.base/java.lang.Thread.run(Thread.java:1583)

That is the weakness. The compiler accepted new Timer(new Other()) because any object is an Object, yet the cast (MyClass) owner fails when the timer fires: a run-time error, found late (Week 3: a bad cast is a ClassCastException). Note too that the error happens inside the timer's own thread, so main does not see an exception at all.

Version 3: use an interface

  • Define an interface for the callback: TimerOwner with the one abstract method timerDone().
  • Modify MyClass to implement TimerOwner.
  • Modify Timer so that its owner is of type TimerOwner.
Timer(MyClass o)v1only works for MyClass✗ not genericTimer(Object o)v2((MyClass) owner).timerDone()⚠ cast can fail at run timeTimer(TimerOwner o)v3owner.timerDone()✓ anyone with the capability
Figure 10. The owner's type, version by version: too specific, too loose, just right
TimerOwner.java
public interface TimerOwner {
    public abstract void timerDone();
}
Timer.java
public class Timer implements Runnable {
    private TimerOwner owner;

    public Timer(TimerOwner o) {
        owner = o;
    }

    public void run() {
        try { Thread.sleep(100); } catch (InterruptedException e) { }
        owner.timerDone();                         // no cast, no guessing the class
    }
}
MyClass.java
public class MyClass implements TimerOwner {
    public void f() throws InterruptedException {
        Timer t = new Timer(this);
        Thread th = new Thread(t);
        th.start();
        System.out.println("f: timer started, f carries on");
        th.join();
    }

    public void timerDone() {
        System.out.println("MyClass.timerDone() called");
    }
}
Main.java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        new MyClass().f();

        // owner need not be the object that created the Timer
        Thread th = new Thread(new Timer(new Other()));
        th.start();
        th.join();
    }
}
Output
f: timer started, f carries on
MyClass.timerDone() called
Other.timerDone() called

No cast and no failing at run time: new Timer(x) compiles only if x really has the capability of being notified. And, as the lecture says, the owner need not be the object that created the Timer: in the second half of main the notified object Other is a completely different one.

Even shorter (extra)

Because TimerOwner has a single abstract method, a Java lambda can stand in for an object that implements it, and an anonymous class is the longer way to the same thing. Callbacks like this are how event handlers (a button click) are written in Java.

Main.java
public class Main {
    public static void main(String[] args) throws InterruptedException {
        // TimerOwner has one abstract method, so a lambda can stand in for the object
        Thread th = new Thread(new Timer(() -> System.out.println("done, said a lambda")));
        th.start();
        th.join();
    }
}
Output
done, said a lambda
Main.java
public class Main {
    public static void main(String[] args) {
        TimerOwner o = new TimerOwner() {                  // an object of an unnamed class
            public void timerDone() {
                System.out.println("done, said an anonymous class");
            }
        };
        o.timerDone();
    }
}
Output
done, said an anonymous class

5.2Lecture summary

  • Callbacks are useful when we spawn a class in parallel: the spawned object notifies the owner when it is done.
  • It can notify some other object too: the owner in Timer need not be the object that created it.
  • Interfaces make the callback generic: the owner has to have the capability to be notified, and nothing more.
Next · Lecture 6Iterators
06
Lecture 6

Iterators

The last lecture of the week is another case of interaction with state: walking through every element of a list without knowing how the list is stored.

6.1A linear list, two implementations

A generic linear list of objects: the internal implementation may vary. One implementation keeps the objects in an array:

LinearList.java
public class LinearList {
    // Array implementation
    private int limit = 100;
    private Object[] data = new Object[limit];
    private int size;

    public LinearList() { size = 0; }

    public void append(Object o) {
        data[size++] = o;
    }
}

Another keeps them as a linked list of nodes. Its append walks to the last node and attaches a new one. The lecture's loop, for (m = head; m.next != null; m = m.next) {}, ends on the last node. But in a list that is still empty, head is null, so m.next fails on the very first append:

Main.java
public class Main {
    public static void main(String[] args) {
        LinearList l = new LinearList();
        l.append("first");
    }
}
Error
Exception in thread "main" java.lang.NullPointerException: Cannot read field "next" because "<local2>" is null
	at LinearList.append(LinearList.java:9)
	at Main.main(Main.java:4)
Reading the message. The exception names the variable <local2> because plain javac does not keep local variable names in the class file. Compile with javac -g (IDEs do) and it says "m". The cause is the same: m is null. The listings below handle the empty list with its own case.

6.2The wish: loop over every element

We want a loop that runs through all the values in a linear list. If the list is an array with public access we could write:

Array.java
int i;
for (i = 0; i < data.length; ++i) {
    ...  // do something with data[i]
}

For a linked list with public access:

List.java
Node m;
for (m = head; m != null; m = m.next) {
    ...  // do something with m.data
}
Source slip (extra). The lecture writes m !+ null, a typo for m != null.

But we do not have public access, and we do not know which implementation is in use. We need an abstraction:

Iterator idea
Start at the beginning of the list
while (there is a next element) {
    get the next element;
    do something with it
}

6.3The Iterator interface

Encapsulate this functionality in an interface called Iterator:

Iterator.java
public interface Iterator {
    public abstract boolean has_next();
    public abstract Object get_next();
}
LinearList.java
public class LinearList {
    private Node head;
    private int size;

    public LinearList() { size = 0; }

    public void append(Object o) {
        Node n = new Node(o);
        if (head == null) {                      // the empty list needs its own case
            head = n;
        } else {
            Node m = head;
            while (m.next != null) m = m.next;
            m.next = n;
        }
        size++;
    }

    private class Node {
        private Object data;
        private Node next;

        private Node(Object data) {
            this.data = data;
        }
    }

    private class Iter implements Iterator {
        private Node position;                   // remembers where this iterator is

        public Iter() {
            position = head;
        }

        public boolean has_next() {
            return position != null;
        }

        public Object get_next() {
            Object o = position.data;
            position = position.next;
            return o;
        }
    }

    // export a fresh iterator
    public Iterator get_iterator() {
        Iter it = new Iter();
        return it;
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        LinearList l = new LinearList();
        l.append("A");
        l.append("B");
        l.append("C");

        Object o;
        Iterator i = l.get_iterator();
        while (i.has_next()) {
            o = i.get_next();
            System.out.println(o);
        }
    }
}
Output
A
B
C

How do we implement Iterator in LinearList? We need a pointer to remember the position of the iterator. The lecture also asks how nested loops are handled: two loops over the same list need two independent positions.

Why the position cannot live in the list itself

If the list keeps one position pointer, the inner loop uses up the pointer the outer loop was relying on. Expected nine pairs, we get two:

Main.java
public class Main {
    public static void main(String[] args) {
        Bad l = new Bad();
        while (l.hasNext()) {
            String oi = l.next();
            // the inner loop uses the same pointer, so it eats the rest of the list
            while (l.hasNext()) {
                System.out.println(oi + "," + l.next());
            }
        }
    }
}
Output
A,B
A,C
The solution

Create an Iterator object and export it. Every call to get_iterator() returns a fresh object that carries its own position. The class of that object is a private class that implements the public interface: this is the Lecture 3 + Lecture 4 pattern again.

data "A"nextdata "B"nextdata "C"nextnullheadi (outer)j (inner)each iterator object keeps its OWN position in the list
Figure 11. Two iterator objects walk the same list independently: each remembers its own node
client codewhile (i.has_next()) i.get_next()interface Iteratorhas_next() get_next()Iter over an arrayint positionIter over a linked listNode position
Figure 12. The client only sees Iterator; how the position is kept (an index, a node) is the private class's business
  • Iter's definition depends on the linear list: for the linked list it holds a Node, for the array it holds an index. The client code is identical:
LinearList.java
public class LinearList {
    private int limit = 100;
    private Object[] data = new Object[limit];
    private int size;

    public LinearList() { size = 0; }

    public void append(Object o) {
        data[size++] = o;
    }

    private class Iter implements Iterator {
        private int position = 0;                // an index this time, not a node

        public boolean has_next() {
            return position < size;
        }

        public Object get_next() {
            return data[position++];
        }
    }

    public Iterator get_iterator() {
        return new Iter();
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        LinearList l = new LinearList();
        l.append("A");
        l.append("B");
        l.append("C");

        Iterator i = l.get_iterator();          // exactly the same client code as before
        while (i.has_next()) {
            System.out.println(i.get_next());
        }
    }
}
Output
A
B
C

For nested loops, acquire multiple iterators. A new one for every pass of the outer loop:

Main.java
public class Main {
    public static void main(String[] args) {
        LinearList l = new LinearList();
        l.append("A");
        l.append("B");
        l.append("C");

        Object oi, oj;
        Iterator i, j;

        i = l.get_iterator();
        while (i.has_next()) {
            oi = i.get_next();
            j = l.get_iterator();               // a fresh iterator for every pass of the outer loop
            while (j.has_next()) {
                oj = j.get_next();
                System.out.println(oi + "," + oj);
            }
        }
    }
}
Output
A,A
A,B
A,C
B,A
B,B
B,C
C,A
C,B
C,C
Source slip (extra). The lecture's nested array loop has for (int j = 0; i < data.length; ++j): the inner condition tests i where it means j. Since i never changes inside, j runs off the end of the array:
Main.java
public class Main {
    public static void main(String[] args) {
        String[] data = { "A", "B", "C" };
        for (int i = 0; i < data.length; ++i) {
            for (int j = 0; i < data.length; ++j) {       // the lecture's typo: i, not j
                System.out.println(data[i] + "," + data[j]);
            }
        }
    }
}
Error
A,A
A,B
A,C
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at Main.main(Main.java:6)

6.4The for-each loop uses an iterator

The newer Java for over lists implicitly constructs and uses an iterator:

ForEach.java
for (type x : a) {
    do something with x;
}
  • For your own class to work in this loop, it must implement the standard interface java.lang.Iterable, whose one method iterator() returns a java.util.Iterator (with the standard names hasNext and next, not the lecture's has_next and get_next). This is an extra, checked by running:
LinearList.java
import java.util.Iterator;

public class LinearList implements Iterable<Object> {
    private Object[] data = new Object[100];
    private int size;

    public void append(Object o) {
        data[size++] = o;
    }

    public Iterator<Object> iterator() {            // the one method Iterable asks for
        return new Iterator<Object>() {
            private int position = 0;

            public boolean hasNext() {
                return position < size;
            }

            public Object next() {
                return data[position++];
            }
        };
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        LinearList l = new LinearList();
        l.append("A");
        l.append("B");
        l.append("C");

        for (Object x : l) {                         // the compiler builds the iterator loop for us
            System.out.println(x);
        }
    }
}
Output
A
B
C

For a standard list, the loop and the explicit iterator are the same thing; the compiler writes the second from the first:

Main.java
import java.util.ArrayList;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("Asha");
        names.add("Ravi");

        for (String s : names) {                     // what you write
            System.out.println(s);
        }

        Iterator<String> it = names.iterator();      // what it means
        while (it.hasNext()) {
            String s = it.next();
            System.out.println(s);
        }
    }
}
Output
Asha
Ravi
Asha
Ravi

A trap: changing the list during the loop (extra)

An iterator remembers a position in a list. If the list is changed behind its back, the position may no longer mean anything, so the standard iterators check and fail fast:

Main.java
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("Asha");
        names.add("Ravi");
        names.add("Meena");

        for (String s : names) {
            if (s.equals("Asha")) {
                names.remove(s);                     // changing the list while an iterator is running
            }
        }
    }
}
Error
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
	at Main.main(Main.java:10)

The safe way to remove while iterating is to ask the iterator itself:

Main.java
import java.util.ArrayList;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> names = new ArrayList<>();
        names.add("Asha");
        names.add("Ravi");
        names.add("Meena");

        Iterator<String> it = names.iterator();
        while (it.hasNext()) {
            if (it.next().equals("Asha")) {
                it.remove();                         // the iterator itself may remove safely
            }
        }
        System.out.println(names);
    }
}
Output
[Ravi, Meena]

6.5Lecture summary

  • Iterators are another example of interaction with state.
  • Each iterator needs to remember its position in the list.
  • Export an object with a pre-specified interface to handle the interaction.
  • The new Java for over lists implicitly constructs and uses an iterator.
Next · InterviewInterview Question Bank
07
Interview

Interview Question Bank

What is likely to be asked from Week 4, with 30-second answers, deeper explanations, a lecture-or-extra tag on each question, and code you can run.

Week 4 is where interviewers test design thinking: abstract class versus interface, hiding implementation behind a private class and an interface, callbacks and iterators. Every answer starts with a 30-second version. Each question is tagged From the lecture or Extra.

1Definition

One clear sentence: what it is.

2Tiny example

Three or four lines of code.

3The failure it prevents

What goes wrong without it.

7.1Interview heat-map: what is most likely

Very likely7 topics
Abstract class: what it is, can it be instantiated"Can you create an object of an abstract class?"Notes 1.1
Abstract class vs interface"Abstract class or interface: which one, and why?"Notes 1.3, 1.5, 2.1
Default-method conflicts and "class wins""Two interfaces, same default method: what now?"Notes 2.4
Inner classes: why private"What is an inner class, and why make it private?"Notes 3.2, 3.3
Returning an interface from login"Why does login return QIF instead of QueryObject?"Notes 4.2
Callbacks with interfaces"What is a callback? Show one with an interface."Notes 5.1
Iterators"What is an iterator? Why not use the list's internals?"Notes 6.1-6.3
Likely10 topics
Why many interfaces but one class"Why can a class implement many interfaces but extend only one class?"Notes 1.5, 2.4
Default and static interface methods"What are default methods and why were they added?"Notes 2.3, 2.4
Abstract class details: constructor, no abstract methods"Can an abstract class have a constructor? No abstract method at all?"Notes 1.3
Comparable in practice"How do you sort your own objects in Java?"Notes 1.4
Inner vs static nested class"Inner class or static nested class?"Notes 3.3
Encapsulation: validation and consistent state"Why not a setter for every field?"Notes 4.1
Object plus cast versus interface"Why not take Object and cast it?"Notes 5.1
Nested loops over one list"How do nested loops work with iterators?"Notes 6.3
for-each and Iterable"How does for-each work? What must a class provide?"Notes 6.4
ConcurrentModificationException"Why does removing inside for-each throw?"Notes 6.4
Sometimes2 topics
Returning null on failure"Is returning null a good design?"Notes 4.2
Fixing the lecture's linked-list append"Why does append crash on an empty list?"Notes 6.1

7.2Abstract classes and interfaces

Interview 1What is an abstract class, and can you create an object of it?
Lecture 1Very likelyNotes 1.1From the lecture

Think of Shape and perimeter().

Main.java
Shape s = new Shape();   // Shape is abstract
Compiler says
Main.java:3: error: Shape is abstract; cannot be instantiated
Answer
Say this first (30 seconds)

An abstract class has at least one method with no body (an abstract method) that forces every subclass to supply the code. You cannot create objects of it (the compiler refuses), but you can declare variables of its type and let them refer to objects of concrete subclasses.

Go deeper
  • A class containing an abstract method must itself be declared abstract; a subclass that does not implement all of them is abstract too.
  • Why bother? It lets one array or one function handle many kinds of object (polymorphism) while still guaranteeing each kind has the method.
Follow-up they may askCan an abstract class have a constructor and ordinary methods?
Interview 2Abstract class versus interface: what is the difference and when do you use each?
Lectures 1-2Very likelyNotes 1.3, 2.1From the lecture

Compare them on state, constructors, and how many a class can have.

Abstract classInterface
Methodsabstract and concrete mixedabstract; plus default and static (Java 8+)
Fieldsany, including changing stateonly constants (public static final)
Constructoryes (runs for subclass objects)no
How many per classone (extends)any number (implements)
Says"is a kind of", with shared code and state"can do", a capability
Answer
Say this first (30 seconds)

Use an abstract class for a family of related classes that share code or state (Shape). Use an interface for a capability that unrelated classes can have (Comparable). A class can extend one class but implement many interfaces.

Go deeper
  • Interfaces avoid the diamond problem because (originally) nothing in them has code to contradict.
  • Since Java 8 interfaces can hold code (default, static), so the line has blurred; the state and constructor rows still separate them.
Follow-up they may askWhich one would you choose for a Stack and a Queue that a Deque must satisfy both?
Interview 3Why can a class implement many interfaces but extend only one class?
Lecture 1LikelyNotes 1.5, 2.4From the lecture

This is the diamond problem from Week 3.

Circle.java
public class Circle extends Shape, Comparable { }
Compiler says
Circle.java:1: error: '{' expected
Answer
Say this first (30 seconds)

If two parent classes both define f() with code, a subclass would not know which to use. Interface methods were abstract, so there was nothing to contradict; the class just writes one implementation. Hence one parent class, any number of interfaces.

Go deeper
  • Default methods brought the conflict back in a smaller form, so Java adds rules: write your own version, or the class wins over an interface.
Follow-up they may askWhat exactly happens if two interfaces have the same default method?
Interview 4What are default and static methods in an interface, and why were they added?
Lecture 2LikelyNotes 2.3, 2.4From the lecture

Java 8 changed what an interface may hold.

Comparable.java
public interface Comparable {
    public default int cmp(Comparable s) { return 0; }
    public static String cmpdoc() { return "doc"; }
}
Answer
Say this first (30 seconds)

A default method gives an interface method a body that implementing classes inherit and may override. A static method belongs to the interface itself and is called Comparable.cmpdoc(); it cannot touch instance variables. They were added so that existing interfaces could grow (for example forEach on Iterable) without breaking every class that implements them.

Go deeper
  • Static interface methods are not inherited: Dnum.cmpdoc() and d.cmpdoc() are both "cannot find symbol".
  • A default method may call the interface's own abstract methods, but it has no instance variables to read: an interface holds no object state.
Follow-up they may askHow do you call the default method of one particular interface from your class?
Interview 5Two interfaces have the same default method. What happens? What if a superclass has it as well?
Lecture 2Very likelyNotes 2.4From the lecture

Think of I1.hello(), I2.hello() and Person.getName().

C.java
public class C implements I1, I2 { }
Compiler says
C.java:1: error: types I1 and I2 are incompatible;
class C inherits unrelated defaults for hello() from types I1 and I2
Answer
Say this first (30 seconds)

Two competing defaults: the compiler refuses, and the class must override the method (it may call one of them with I1.super.hello()). A method from a superclass against an interface default: the class wins, silently.

Go deeper
  • The class-wins rule exists for reverse compatibility: old code that relied on a class method must not change meaning when an interface later gains a default.
Follow-up they may askWhat does Designation d = e; d.getName() print for the Employee example?
Interview 6Can an abstract class have a constructor, fields and ordinary methods? Can it have no abstract methods at all?
Lecture 1LikelyNotes 1.3Extra

Yes to all, with one restriction.

Prints (new Square(2))
Shape constructor
Square constructor
8.0
Answer
Say this first (30 seconds)

Yes. An abstract class is a normal class that cannot be instantiated directly. Its constructor runs as the first step of building a subclass object (the parent part is built first), and it can hold state and finished methods. It may also be declared abstract with no abstract method, just to forbid direct objects.

Go deeper
  • Pattern: finished method that calls an abstract one (describe() calls perimeter()): the template method.
Follow-up they may askIs the constructor of the abstract class called when you write new Square(2)?
Interview 7How do you sort your own objects in Java? What is Comparable, and what is Comparator?
Lecture 1LikelyNotes 1.4Extra

The lecture writes its own Comparable with cmp; Java has a real one.

Main.java
Arrays.sort(words);                                   // natural order
Arrays.sort(words, Comparator.comparingInt(String::length));   // another order
Prints
[banana, fig, kiwi, pear]
[fig, kiwi, pear, banana]
Answer
Say this first (30 seconds)

The class implements java.lang.Comparable<T> and its compareTo method (the real version of the lecture's cmp); sorting then follows that natural order. To sort a different way without changing the class, pass a Comparator.

Go deeper
  • Both are the same idea as the lecture: a capability expressed as an interface, so one sort function serves every type.
Follow-up they may askWhat must compareTo return, and why is "-1/0/1" only a convention?

7.3Private classes and controlled access

Interview 8What is an inner (private nested) class, and why would you make Node private inside LinkedList?
Lecture 3Very likelyNotes 3.2, 3.3From the lecture

Think about who should know that a list is made of nodes.

LinkedList.java
public class LinkedList {
    private class Node {
        private Object data;
        private Node next;
    }
    private Node first;
    ...
}
Outside code says
Main.java:3: error: LinkedList.Node has private access in LinkedList
Answer
Say this first (30 seconds)

A class declared inside another class. Marked private, only the enclosing class can use it, while the two can still read each other's private members. Making Node private hides the implementation: the list can change (for example to a doubly linked list) without any outside code noticing.

Go deeper
  • Only nested classes can be private; a top-level private class is a compile error.
Follow-up they may askWhat is the difference between an inner class and a static nested class?
Interview 9What is the difference between an inner class and a static nested class?
Lecture 3LikelyNotes 3.3Extra

One needs an outer object, the other does not.

Prints (Outer.show())
14
5
Answer
Say this first (30 seconds)

An ordinary inner class is tied to one object of the outer class and can use its fields (Outer.this.x). A static nested class is not tied to any outer object, so it holds no hidden reference to one. For a helper like Node that never needs the outer object, private static class is the leaner choice.

Go deeper
  • Creating an inner object from outside needs an outer one: outer.new Inner().
Follow-up they may askWhy can that hidden reference cause a memory leak?
Interview 10Why does login return the interface QIF instead of the private class QueryObject?
Lecture 4Very likelyNotes 4.2From the lecture

A public method that returns a private type.

Compiler says
Main.java:4: error: RailwayBooking.QueryObject.getStatus(int,Date) is defined in an inaccessible class or interface
Answer
Say this first (30 seconds)

The class is private, so the caller may not use it, even though the object is in its hands (the compiler reports an inaccessible class). The public interface QIF names what the object can do. So the caller gets the capabilities without the class, and you stay free to change the class.

Go deeper
  • Same pattern as the iterator (Iter behind Iterator) and the timer owner.
Follow-up they may askHow do you limit each user to two queries?
Interview 11Why not provide a setter for every field? How do you keep an object valid?
Lecture 4LikelyNotes 4.1From the lecture

Think about a date: day, month, year.

Prints (three setters)
31-4-2024
Output of setDate(...)
29-2-2024
Exception in thread "main" java.lang.IllegalArgumentException: invalid date 31-4-2024
	at Date.setDate(Date.java:23)
	at Main.main(Main.java:6)
Answer
Say this first (30 seconds)

Because fields can be related: whether a day is legal depends on the month and the year. Independent setters allow impossible states (31 April). Provide one method that takes all the parts (setDate(d, m, y)), validates the combination, and only then changes the object.

Go deeper
  • Rejecting bad input with an exception (here IllegalArgumentException) stops the caller from carrying on with a wrong date.
Follow-up they may askWhich is better on a failed login: null or an exception?
Interview 12The lecture's login hands back nothing on a failed login. Is returning null a good design?
Lecture 4SometimesNotes 4.2Extra

What does the caller do with the result?

Prints (a good login, then a wrong password)
27
null
Answer
Say this first (30 seconds)

It works, but it pushes a hidden duty on every caller: forget the null check and the next call throws a NullPointerException. Better options are to throw a clear exception on failure, or to return an Optional so that the type itself says "might be empty".

Go deeper
  • This is an extra: the lecture only sketches the login and focuses on the capabilities.
Follow-up they may askWhat does Optional add over null?

7.4Callbacks and iterators

Interview 13What is a callback? Implement one using an interface.
Lecture 5Very likelyNotes 5.1From the lecture

A timer that tells its creator when time is up.

TimerOwner.java
public interface TimerOwner {
    public abstract void timerDone();
}
Prints (MyClass, then Other)
f: timer started, f carries on
MyClass.timerDone() called
Other.timerDone() called
Answer
Say this first (30 seconds)

A callback is a call from a spawned object back to its owner when the work is done. The worker is given a reference to an interface (TimerOwner) that declares the notification method, so it needs no knowledge of the owner's class: anything with timerDone() will do, and it need not even be the object that created the Timer.

Go deeper
  • The same shape drives GUI event listeners ("on click, call me").
  • One-method interfaces can be written as a lambda: new Timer(() -> ...).
Follow-up they may askWhy not just take an Object and cast?
Interview 14A generic Timer(Object o) accepts everything. Why is that worse than Timer(TimerOwner o)?
Lecture 5LikelyNotes 5.1Extra

Compare when each mistake is found.

Run time says
Exception in thread "Thread-1" java.lang.ClassCastException: class Other cannot be cast to class MyClass (Other and MyClass are in unnamed module of loader 'app')
Answer
Say this first (30 seconds)

With Object the compiler accepts any argument, so passing a wrong kind of object is found only at run time, when the cast (MyClass) owner throws a ClassCastException. With an interface type the same mistake is a compile error, found before the program runs.

Go deeper
  • Rule of thumb: make wrong code impossible to compile rather than hoping to catch it in testing.
Follow-up they may askWhere did the exception go in the timer's thread?
Interview 15What is an iterator, and why not loop over the list's internal array or nodes directly?
Lecture 6Very likelyNotes 6.1-6.3From the lecture

The same loop has to work for an array list and a linked list.

Main.java
Iterator i = l.get_iterator();
while (i.has_next()) {
    Object o = i.get_next();
}
Prints
A
B
C
Answer
Say this first (30 seconds)

An iterator is an object that walks a collection one element at a time, through two operations: is there a next one? and give me the next one. The list hands out the iterator and keeps its internals private, so the client loop does not depend on how the list is stored, and the storage can change freely.

Go deeper
  • The iterator remembers a position: a node for a linked list, an index for an array.
Follow-up they may askHow do you run two loops over the same list at once?
Interview 16How do you handle nested loops over the same list with iterators?
Lecture 6LikelyNotes 6.3From the lecture

What goes wrong if the list itself keeps the position?

Prints (one shared pointer: wrong)
A,B
A,C
Prints (two iterators: all 9 pairs)
A,A  A,B  A,C  B,A  B,B  B,C  C,A  C,B  C,C
Answer
Say this first (30 seconds)

Ask the list for a fresh iterator for every loop: j = l.get_iterator() inside the outer loop. Each iterator object holds its own position. If the position were a field of the list, the inner loop would use up the outer loop's place.

Go deeper
  • Two iterators on one list is exactly what "export a fresh object" buys you.
Follow-up they may askHow does the for-each loop get its iterator?
Interview 17How does the for-each loop work, and what must your own class provide to be used in it?
Lecture 6LikelyNotes 6.4From the lecture

"The new Java for over lists implicitly constructs and uses an iterator."

Prints (the loop, then the explicit iterator)
Asha  Ravi  Asha  Ravi
Answer
Say this first (30 seconds)

The compiler rewrites for (T x : c) into a loop over c.iterator() using hasNext() and next(). Your class only has to implement Iterable<T>, whose single method iterator() returns a java.util.Iterator<T>. Arrays are handled separately.

Go deeper
  • The lecture's own Iterator (has_next, get_next) is a teaching version; the real interface uses hasNext and next (and an optional remove).
Follow-up they may askWhat is the difference between Iterable and Iterator?
Interview 18Why does removing an element inside a for-each loop throw an exception, and how do you remove safely?
Lecture 6LikelyNotes 6.4Extra

An iterator remembers a position in a list.

Run time says
Exception in thread "main" java.util.ConcurrentModificationException
Prints (it.remove())
[Ravi, Meena]
Answer
Say this first (30 seconds)

The loop is an iterator in disguise. If the list is changed behind its back (names.remove(s)), its remembered position may be wrong, so the standard iterators detect it and fail fast with ConcurrentModificationException. Remove through the iterator (it.remove()) or collect and remove after the loop.

Go deeper
  • Extra: it is a safety net, not a guarantee for threads.
Follow-up they may askWhat does "fail-fast" mean?
Interview 19The lecture's linked-list append loops for (m = head; m.next != null; m = m.next). Where does it break?
Lecture 6SometimesNotes 6.1Extra

Run it on a list with nothing in it.

LinearList.java
for (m = head; m.next != null; m = m.next) {}   // head is null on an empty list
Run time says
Exception in thread "main" java.lang.NullPointerException: Cannot read field "next" because "<local2>" is null
Answer
Say this first (30 seconds)

On an empty list head is null, so m.next dereferences null and throws a NullPointerException. The empty list is a special case: if head == null make the new node the head, otherwise walk to the last node.

Go deeper
  • Habit: for every list or tree code, test empty, one element, many.
Follow-up they may askHow would you make append O(1)?
Next · SummaryWeek 4 Summary Sheet
08
Summary

Week 4 Summary Sheet

Week 4 on one page: a lecture-by-lecture glance, key terms, the pairs to tell apart, the errors you can now read, a checklist and corrections to the source notes.

8.1Week 4 at a glance

L1Abstract classes and interfaces

abstract methods force subclasses; no objects, but variables of that type. Generic quicksort(Comparable[]). An interface is purely abstract: extend one class, implement many.

L2Interfaces

A slice of capabilities. Static methods (not inherited) and default methods (may override). Conflicts: write your own; class wins over an interface.

L3Private classes

A class nested inside another, marked private: hides the implementation (Node in LinkedList). Inner and outer see each other's private members.

L4Controlled interaction

Private class + public interface: login returns a QIF. The object keeps state (numQueries). Update a date as a whole.

L5Callbacks

A spawned object calls its owner back. Timer(MyClass) is too specific, Timer(Object) needs a cast, Timer(TimerOwner) is generic and safe.

L6Iterators

An exported object with has_next / get_next remembers a position. One iterator per loop. For-each builds an iterator for you.

8.2Key terms, lecture by lecture

L1 · Abstract classes
abstract method
Signature without a body; subclasses must implement it.
abstract class
Class with an abstract method (or declared so); cannot be instantiated.
Comparable
Capability: can be compared with another of its kind.
interface
Purely abstract class; implemented, not extended.
L2 · Interfaces
implements
A class provides the code for an interface's methods.
static method
Belongs to the interface; no instance variables; not inherited.
default method
Interface method with a body; may be overridden.
class wins
A superclass method beats an interface default.
L3 · Private classes
nested class
A class declared inside another class.
inner class
A non-static nested class, tied to an outer object.
static nested
Not tied to an outer object.
Outer.this
The enclosing object, from inside an inner class.
L4 · Controlled interaction
accessor / mutator
Method that reads / changes private data.
data integrity
The object stays in a valid state.
QIF
The public interface behind which the private query object hides.
final field
Assigned exactly once, e.g. in the constructor.
L5 · Callbacks
callback
A call from a spawned object back to its owner.
Runnable
Interface of things that can run in parallel; asks for run().
owner
The object notified; typed as the interface TimerOwner.
L6 · Iterators
Iterator
Interface with has_next() and get_next().
position
What an iterator remembers: node or index.
Iterable
Standard interface that lets for-each work (extra).
fail-fast
Standard iterators throw if the list changes under them (extra).

8.3The pairs you must tell apart

Abstract classstate + code, extend one
VS
Interfacecapability, implement many
extendsone class
VS
implementsany number of interfaces
abstract methodno body
VS
default methodhas a body, may override
static (interface)on the interface, not inherited
VS
defaulton the object, inherited
Class winsclass method vs interface default
VS
Conflicttwo defaults: must override
Inner classtied to an outer object
VS
static nestedindependent
private classhidden from outsiders
VS
public interfacewhat outsiders may call
Setter per fieldcan break validity
VS
setDate(d, m, y)validates the whole
Timer(Object)cast, fails at run time
VS
Timer(TimerOwner)checked at compile time
one iteratorone position
VS
one per loopnested loops work
for-eachuses an iterator for you
VS
it.remove()safe removal

8.4Messages and errors you can now read

What went wrongWhat you will seeWhen
new on an abstract classerror: Shape is abstract; cannot be instantiatedcompile time
abstract method in a non-abstract class, or a missing implementationerror: Square is not abstract and does not override abstract method perimeter() in Shapecompile time
interface method implemented with weaker accessattempting to assign weaker access privileges; was publiccompile time
extending two classeserror: '{' expectedcompile time
two interfaces with the same defaulterror: types I1 and I2 are incompatible;compile time
static interface method through the class or an objecterror: cannot find symbolcompile time
assigning an interface constanterror: cannot assign a value to static final variable MAXcompile time
interface field without a valueerror: = expectedcompile time
method with no return on some patherror: missing return statementcompile time
double stored in an interror: incompatible types: possible lossy conversion from double to intcompile time
outside code naming a private nested classerror: LinkedList.Node has private access in LinkedListcompile time
calling through a private class typeerror: ... is defined in an inaccessible class or interfacecompile time
top-level class marked privateerror: modifier private not allowed herecompile time
final field never given a valueerror: variable QLIM not initialized in the default constructorcompile time
class says implements Runnable but has no run()error: Timer is not abstract and does not override abstract method run() in Runnablecompile time
cast of an owner to the wrong classjava.lang.ClassCastException: class Other cannot be cast to class MyClassrun time
invalid datejava.lang.IllegalArgumentException: invalid date 31-4-2024run time
appending to an empty linked list with the lecture's loopjava.lang.NullPointerException: Cannot read field "next" because "<local2>" is nullrun time
inner loop tests the wrong variablejava.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3run time
removing from a list inside for-eachjava.util.ConcurrentModificationExceptionrun time

8.5Last-minute checklist

L1 · Abstract classes
  • abstract method: no body, forces subclasses
  • abstract class: no objects, variables fine
  • generic code against Comparable
  • one parent class, many interfaces
L2 · Interfaces
  • methods are public abstract
  • static: not inherited; default: may override
  • two defaults: override yourself
  • class beats interface
L3 · Private classes
  • nested class hides the implementation
  • inner and outer see each other's privates
  • top-level class cannot be private
  • static nested if no outer object is needed
L4 · Controlled interaction
  • private data, public methods
  • update a date as a whole, validate
  • login returns the interface, not the class
  • object keeps its own counter
L5 · Callbacks
  • owner passes this to the worker
  • worker calls owner.timerDone()
  • type the owner as an interface
  • owner need not be the creator
L6 · Iterators
  • iterator object remembers a position
  • one fresh iterator per loop
  • private Iter, public Iterator
  • for-each = iterator behind the scenes

8.6Corrections made to the source notes

WhereThe source saysCorrect
Lecture 1, the loopsizearr[i] = shapearr[i].perimeter() into an int array, no semicolondouble[] sizearr and a semicolon (checked by running)
Lecture 1, cmponly the if and commentsmust return an int on every path
Lecture 1, interfaces"a class that extends an interface"a class implements an interface
Lecture 3, head()removes the first node but keeps sizesize--
Lecture 4, loginreturns only if the login is validneeds return null; (checked by running)
Lecture 4, QLIMprivate final int QLIM; with no valueassign it in the constructor (checked by running)
Lecture 5, Timerimplements Runnable with a method start()needs run() (checked by running)
Lecture 6, loopsm !+ null, i < data.length in the inner loopm != null, j < data.length
Lecture 6, linked appendwalks from head with no empty casehandle head == null (NullPointerException, checked by running)
Revision overview
Abstractabstract methods
no objects, variables ok
generic Comparable
Interfacesimplements many
static, default
class wins
Private classesnested, hidden
see each other's private
inner vs static
Week 4Abstraction in Java
Controlled accessprivate class + interface
QIF, counter state
validate as a whole
Callbacksowner notified
TimerOwner interface
no cast needed
Iteratorsexport an iterator object
one per loop
for-each uses it
Revision: all of Week 4 on one page
Back to top