Skip to main content
Object Oriented Design, Analysis and Programming

Interfaces, Nested Interfaces, Inner Classes and Exception Handling

Published: 2026-08-19
Level: postgraduate
Audience: Postgraduate students in Object Oriented Design, Analysis and Programming

Prerequisite Knowledge

This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.

Previously Covered in This Subject

  • Inheritance — covered in Lecture 1: Object-Oriented Analysis and Design
  • Interfaces — covered in Lecture 1: Object-Oriented Analysis and Design
  • Static Methods, Overloading and Inheritance Interaction — covered in Lecture 17: Constructors, Static Members, this, final and Software Development Life Cycle
  • Exception Handling with try and catch — covered in Lecture 19: Packages, Input-Output Streams and File Handling in Java

# Interfaces, Nested Interfaces, Inner Classes and Exception Handling

21.1 Interfaces — The Blueprint of a Class

21.1.1 What an Interface Is and the Blueprint Analogy

Hook: Why would Java let you write a type that has no code inside it — and then force every class that touches it to write that code for you? That tension is the whole idea of an interface.

An interface (a blueprint of a class), written with the keyword interface, is a type that declares what a class must do without saying how it does it. The professor's anchoring picture — carry it through the whole lecture — is an architect's blueprint: it lists every room a house must have, but it leaves wall colour, flooring, and furniture to the builder.

Mapping the analogy explicitly:

  • Blueprint sheet = interface named, say, Printable or abc.
  • Room label on the sheet = method declaration such as void print(); — a promise that a room exists.
  • Built house = concrete class such as Trial or BankAccount that supplies walls and wiring — the method body in curly braces { ... }.
  • Building inspector = compiler: it checks that every room on the sheet appears in the house, with exactly the signature listed.

Where the analogy breaks: a real blueprint can be vague ("a bedroom about 12 ft by 14 ft"). A Java interface is exact: the signature void print(); fixes return type, name, and parameter list. The builder cannot substitute void print(String s).

This contract rule is absolute. If you write class Trial implements Printable, you promise to supply a body for every method Printable declares. Miss one and the compiler refuses to compile — the same strictness you saw earlier with an abstract class (a class that contains at least one declaration without a body and forces a subclass to define it), but an interface is syntactically lighter and intentionally incomplete: it was designed to be pure contract, not partial implementation.

Intuition — everyday picture: Think of a restaurant menu as an interface. The menu lists dish names (orderPizza(), orderCoffee()) but no recipe. Every kitchen that "implements" the menu must be able to cook each dish; different kitchens can cook the same dish differently. The customer orders through the menu type, not caring which kitchen fulfilled it. The analogy breaks where a menu can list optional dishes — in a classic interface, every listed method is compulsory.

A tiny concrete declaration makes the idea tangible:

interface abc {
    void print();
    void show();
}

abc lists two rooms. No curly-brace bodies appear — each line ends with ;. Any class that later writes implements abc must, in that class body, open void print() { ... } and void show() { ... } and fill them. The interface forces the contract: if you say you implement me, you must implement all of me.

The same blueprint can be implemented in many different classes, each giving its own body. This one-to-many relationship is the source of polymorphism you met in Icon or Callback examples: unrelated classes honour the same contract, so client code can call through the interface reference without knowing which builder built the house.

This blueprint idea maps directly to Java API design where teams publish an interface and many independent implementations conform to it — frameworks publish List or Stack as contracts and let ArrayList, LinkedList, FixedStack, DynStack all supply their own bodies.

21.1.2 Abstract Methods and Static Constants

Two phrases define the member model of a classic interface. They are worth naming precisely because the exam tests them.

First, its methods are abstract methods — declared without a body. An abstract method (a method header with no implementation) consists of a return type, a name, a parameter list, and a terminating semicolon. You write void print(); and you stop. The curly-brace body lives later, in the implementing class. The word abstract here means "body intentionally missing — to be supplied by someone else."

Second, its data members are static constants. Unpack each word on first use:

  • static (belongs to the type itself rather than to any single object) — you refer to it by the name of the type, as in Printable.min or SharedConstants.NO, not by new Printable().min.
  • constant (a value that, once initialized, never changes) — in Java you achieve this with the keyword final (cannot be reassigned after initialization).

So an interface field such as int min = 5; is not a per-object variable you mutate later; it is a shared, type-level constant whose value stays at 5 for every use. Under the hood the compiler treats it as one copy shared by all code, not one per object.

Formal shape of a classic interface (before Java 8 defaults):

[public] interface InterfaceName {
    // abstract method: no body, ends with ;
    returnType methodName(parameterList);
    // constant: implicitly public static final
    type CONSTANT_NAME = value;
}
  • Every method listed here is implicitly public abstract (visible everywhere, body missing).
  • Every field listed here is implicitly public static final (visible everywhere, one copy, never changes) and must be initialized where declared.
  • No instance variables (per-object fields) and no constructor are allowed — the blueprint has no house to store them in.

A concrete illustration with both kinds of members:

interface SharedConstants {
    int NO = 0;          // constant
    int YES = 1;         // constant
    void callback(int p); // abstract method
}

NO and YES are shared constants. callback is an abstract declaration. A class Question implements SharedConstants or Client implements Callback does not declare its own NO — it inherits the constant as if it had defined public static final int NO = 0; itself, and it must supply public void callback(int p) { ... }.

21.1.3 Compiler-Added Modifiers — public, static, final and public abstract

Writing int min = 5; inside an interface looks minimal, but what the compiler sees is fully qualified. This is an ease-of-use facility: you may omit the modifiers, the compiler inserts them for you. The verbal description preserved from the lecture is exact — for data members the compiler adds the keywords public static final, and for methods the compiler adds public abstract.

Concretely, when you write inside an interface named Printable:

interface Printable {
    int min = 5;
    void print();
}

the compiler interprets it as:

interface Printable {
    public static final int min = 5;
    public abstract void print();
}

What each inserted keyword means — one line per keyword:

  • public on a field or method — the member should be accessible anywhere, because an interface is a public contract; hiding it would defeat the purpose.
  • static on a field — the field should be referred to by the name of the type (Printable.min), not by an object reference, because there is no per-object state in an interface.
  • final on a field — the variable, once given 5, is converted to a constant and must not change; reassignment is a compile error.
  • public on a method — the declaration is publicly visible to every implementer.
  • abstract on a method — the method is intentionally body-less; the semicolon terminates it, and the body is supplied later by the implementing class.

You never need to type these keywords when you author the interface, but they are present after compilation — you can verify with javap -c Printable and you will see public static final and public abstract in the bytecode listing. Writing them explicitly is legal but redundant; omitting them is idiomatic.

The design choice connects to the taxonomy rule that interface members are inherently public: a blueprint hidden in a drawer is useless. The same rule forces a nested interface declared inside another interface to be public — a point revisited in section 21.5.

21.1.4 Why Interfaces Have No Instance Variables and How Objects Are Created

Interfaces are syntactically similar to classes — both use curly braces { }, both list members — but they lack instance variables (per-object fields that each object carries separately, such as private int data or private int balance). The intuition the professor stressed: just as you cannot create a direct object of an abstract class with new AbstractClass(), you cannot write new Printable() as a freestanding construction.

Why not? An instance variable belongs to an object — it needs memory per object, a constructor to initialize it, and state to maintain. If you could fabricate an interface object directly by writing the interface name on both sides of the constructor call (Printable p = new Printable();), you would be pretending the incomplete blueprint is a complete house. There is no body for print(), no storage for per-object state, no constructor to run.

What you can do is create an object through a reference to an existing concrete class that implements the interface, and refer to it through the interface type. The dynamically looked-up method then runs the implementing class's body.

interface Callback {
    void callback(int param);
}
class Client implements Callback {
    public void callback(int p) {
        System.out.println("callback called with " + p);
    }
}
Callback c = new Client(); // legal: Client object, Callback reference
c.callback(42);            // dispatches to Client's body at runtime

c is declared as Callback but points to a Client object. The call c.callback(42) is resolved at runtime to Client.callback. The same Printable blueprint can likewise be implemented in many different classes, each giving its own body to the declared methods — exactly the one-interface-multiple-implementations pattern shown with FixedStack/DynStack or Client/AnotherClient.

Scope — when the classic interface model applies and when it breaks:

  • Applies: You need a pure contract across unrelated hierarchies (UI callbacks, Icon, Comparable, collection interfaces), or you need multiple inheritance of type without state — a class can implement many interfaces where it can extend only one class.
  • Breaks / shifts after Java 8: The statement "no bodies in interfaces" was absolute before JDK 8. After JDK 8, an interface may carry default and static methods with bodies, and after JDK 9, private helpers. Those bodies are explicitly marked and do not carry per-object instance state. The defining difference remains: a class can maintain per-object state via instance variables; an interface cannot.
  • What goes wrong if violated: Trying new Printable() gives cannot instantiate the type Printable. Forgetting a method body in the implementer gives class must implement inherited abstract method or is not abstract and does not override abstract method.

Visual intuition — picture a class hierarchy diagram with Throwable at the top, branching to Exception and Error, and a separate, parallel hierarchy for interfaces: a dashed box labelled Printable (no instance fields, dashed methods print()) with solid arrows implements pointing from two solid class boxes Client and AnotherClient, each containing a filled print() body and a per-object field like size. The x-axis is "completeness" (dashed = incomplete, solid = complete); the y-axis is number of implementations. The takeaway: one dashed contract fans out to many solid implementers; the inspector arrow (compiler) enforces coverage.

Pitfalls — traps beginners fall into on this concept:

  • Treating int min = 5; as a mutable per-object field. It is public static final — one copy, never changes. Writing min++ or this.min = 7 either fails to compile (cannot assign to final) or silently refers to a different, shadowing field if you redeclare. Always access as Printable.min.
  • Forgetting public on the implementing method. Interface methods are public abstract; the implementing body must be public void print() { ... }, not package-private void print(). Omitting public gives attempting to assign weaker access — the professor's most-flagged compile error for novices.
  • Trying new Interface() or omitting all method bodies without abstract on the class. Either the class must implement every declaration, or the class itself must be declared abstract and defer the job (see abstract class Incomplete implements Callback).
  • Confusing extends and implements. class B extends A is class-to-class; class C implements Printable is class-to-interface; interface Showable extends Printable is interface-to-interface. Writing interface Showable implements Printable is a compile error.

Recap — An interface is a blueprint: it lists public abstract methods (no bodies) and public static final constants (one shared, never-changing copy). The compiler inserts those modifiers for you. You cannot new the interface itself; you new a concrete class and refer to it through the interface type, with runtime dispatch picking the right body. Bridge — The next section turns this contract idea into the strict keyword taxonomy (extends vs implements vs interface extends interface) and shows how the "no instance state" property lets Java allow multiple interface inheritance where multiple class inheritance is forbidden.

Real-world and domain placement — Outside the lecture hall, every major Java framework publishes interfaces as stable contracts: java.util.List with many implementations (ArrayList, LinkedList, Vector), JDBC's Connection and Statement implemented differently by each database vendor, and the IntStack example you will extend with defaults. The blueprint contract lets teams evolve implementations independently; the compiler's public static final / public abstract insertion is what makes the published constants and method promises uniform across all those implementations.

21.2 Class-Interface Relationships and Multiple Inheritance

21.2.1 The Three Relationships — extends, implements and interface extends interface

Hook: In Java you can say a class "is-a" class, a class "can-do" an interface, and an interface "extends" another interface — but mix up the verb and the compiler stops you cold. Three keywords, three distinct relationships, no interchange.

Three distinct keywords govern how types relate, and the professor made the taxonomy strict because the compiler enforces it character-for-character.

  • A class extends another class. You write class B extends A for ordinary inheritance of state and behavior. B inherits fields and method bodies from A, may override them, and there is an is-a relationship: every B is an A.
  • A class implements an interface. You write class XYZ implements ABC where ABC is an interface. The word implements signals you are taking on the interface contract — you must supply bodies for every public abstract method ABC declares, each declared public in the class.
  • An interface extends another interface. You write interface Showable extends Printable. This is the interface-to-interface line: inheritance of contracts, not of bodies. The child interface accumulates all method declarations of its parent(s) and may add more.

Note the vocabulary carefully: a class implements an interface, but an interface extends another interface; an interface never says it implements another interface. Writing interface Showable implements Printable is a compile error. Similarly, a class never "implements" a class — class B implements A where A is a class is also an error.

General form (from the reference docs) captures the multiplicity:

class ClassName [extends Superclass] [implements Interface1 [, Interface2 ...]] { ... }
interface InterfaceName [extends SuperInterface1 [, SuperInterface2 ...]] { ... }

A class may have at most one extends (single class inheritance) but many implements separated by commas. An interface may extend many interfaces at once, also comma-separated.

The three relationships at a glance — memorize the keyword and what is inherited:

  • class B extends A — inherits state (fields) + bodies (method implementations). Single parent only.
  • class C implements P, Q — inherits contracts (abstract declarations + static constants). Many parents allowed because no per-object state ambiguity.
  • interface R extends P, Q — inherits contracts from other interfaces. Many parents allowed; implementing class must ultimately fulfil the union of all declarations.

This taxonomy is strict and the compiler enforces it — wrong keyword choice is not a style issue but a cannot find symbol or 'implements' expected error that stops the build.

21.2.2 Worked Example — Bank, BankAccount and CheckingAccount Hierarchy

Setup: Define an interface Bank with two methods deductP() and withdraw(), both declared as void return type without bodies. No data members are present in this illustration. Think of Bank as a capability contract: any bank must be able to deduct penalty and allow withdrawal, but the regulation does not dictate how.

interface Bank {
    void deductP();   // public abstract, ends with ;
    void withdraw();  // public abstract, ends with ;
}

Step 1 — Implementing class. Create class BankAccount implements Bank. Because Bank is an interface, the methods deductP and withdraw are abstract. It becomes a necessity to define them inside BankAccount. You open curly braces after each and provide logic — whatever fee-deduction or withdrawal logic you choose. The mere presence of implements Bank forces both definitions or the program complains (BankAccount is not abstract and does not override abstract method).

class BankAccount implements Bank {
    private double balance;

    public void deductP() {
        balance -= 5.0; // example fee logic
        System.out.println("deducted penalty, balance = " + balance);
    }
    public void withdraw() {
        balance -= 100.0;
        System.out.println("withdrew, balance = " + balance);
    }
}

Note public is required on both bodies — the interface declarations are public abstract, so the implementer cannot weaken visibility.

Step 2 — Simultaneous inheritance and implementation. Create class CheckingAccount extends BankAccount implements Bank. Two keywords appear on the same header, and their order matters: extends first, then implements.

class CheckingAccount extends BankAccount implements Bank {
    public void deductP() {
        System.out.println("checking deduct with overdraft check");
    }
    // withdraw inherited from BankAccount if not overridden
}

What each keyword does here:

  • extends BankAccount means CheckingAccount inherits everything from BankAccount, including any implementations it already gave for deductP and withdraw, and can override them as part of the inheritance hierarchy. It inherits the balance field, the ready-made bodies, and the is-a relationship: every CheckingAccount is a BankAccount.
  • implements Bank means it also directly commits to the Bank contract again — redundant in this specific hierarchy (since BankAccount already satisfied it) but legal and pedagogically important: it states the intent that CheckingAccount itself directly honours Bank, not merely transitively through its parent.

Even if CheckingAccount chooses not to explicitly override the inherited deductP/withdraw, the compiler still demands that those methods be available — but they are, via inheritance. If the class provides new bodies, those bodies satisfy both the parent-class inheritance and the interface contract at once. The lesson the professor repeated for emphasis: in Java you may inherit a class with extends and implement an interface with implements on the same class simultaneously. This dual facility is the idiomatic way to give a class both state inheritance and capability contracts:

class MyClass extends SuperClass implements Interface1, Interface2 { ... }

Trace — constructing via the Bank type:

Bank b1 = new BankAccount();      // Bank reference, BankAccount object
b1.deductP();   // dispatches to BankAccount.deductP  → "deducted penalty..."
b1.withdraw();  // dispatches to BankAccount.withdraw → "withdrew..."

Bank b2 = new CheckingAccount(); // Bank reference, CheckingAccount object
b2.deductP();   // dispatches to CheckingAccount.deductP → "checking deduct..."
b2.withdraw();  // inherits BankAccount.withdraw if not overridden

// Direct construction also legal:
CheckingAccount c = new CheckingAccount();
c.deductP(); c.withdraw();

Sense-check: Bank b = new Bank(); would still be illegal — Bank is an interface, no new Bank() is allowed. You always new the concrete class on the right, refer through the interface on the left if you want polymorphism.

21.2.3 Why Java Forbids Multiple Class Inheritance but Allows Multiple Interface Implementation

Intuition — why one is forbidden and the other allowed: Imagine inheriting from two complete houses, each with its own plumbing already installed differently — which pipes do you keep when they disagree? Now imagine inheriting from two blueprints that only list room names and promise no plumbing yet — there is nothing to disagree about until you, the builder, supply the plumbing yourself. The first is class multiple inheritance (forbidden); the second is interface multiple inheritance (allowed).

Java does not support multiple inheritance directly via classes. You cannot write class C extends A, B to inherit two or more concrete classes at once — the compiler rejects the comma after extends with cannot inherit from multiple classes.

The reason preserved in explanation: a class is complete — it carries defined state (instance fields) and method bodies — and inheriting two complete classes would create ambiguity about which body to use. If A and B each define void show() { ... } differently and C extends A, B inherits both, the call new C().show() has two competing bodies with equal claim. This is the classic diamond problem with state and behaviour.

An interface, however, is partial in its classic form: it carries only declarations (public abstract methods, public static final constants) and no per-object defined bodies and no per-object instance fields. Because it is not a complete class — the plumbing is not yet installed — the language permits you to combine several of them. There is no competing body to choose between when both Printable and Showable merely declare void show();. That is how multiple inheritance is achieved through interfaces: a class can implement more than one interface (class Trial implements Printable, Showable), and an interface can extend more than one interface (interface C extends A, B), without the compiler complaining.

Scope — when this clean story needs qualification:

  • Assumption: Classic interfaces with only abstract declarations. Under that assumption, class Trial implements P, Q where both declare void show(); needs only one show() body in Trial — it satisfies both.
  • After Java 8: An interface may carry default and static methods with bodies. If two interfaces each supply a default void show() { ... } with different bodies, a class Trial implements P, Q that omits show() now has two competing default bodies. The compiler no longer accepts silence — you must override show() in Trial and explicitly delegate, e.g. P.super.show(); Q.super.show();. The body exists but is explicitly marked and resolved via qualified super calls, which still avoids the classic diamond ambiguity by forcing the implementer to choose.
  • What breaks if violated: class C extends A, B does not compile. class T implements P, Q compiles only if you resolve duplicate default bodies; otherwise you get class T inherits unrelated defaults for show() from types P and Q.

The professor's re-explanation tied this directly to the public static final / public abstract model: because interface members imply no per-object instance state and (classically) no bodies, combining them introduces no state conflict — unlike combining two classes each with private int balance and divergent withdraw() logic.

Visual intuition — sketch two diagrams side-by-side. Left: two solid house icons A and B, each with filled pipes, converging with a big red X on C extends A, B — axes are "parent completeness" (solid = complete) vs "ambiguity" (high). Right: two dashed blueprint icons Printable and Showable (dashed outlines, no pipes), converging with a green check on class Trial implements Printable, Showable — one solid house Trial below supplying the single show() body. A dashed arrow labelled "default show() with body" pointing to a warning triangle marks the post-Java-8 special case where you must add InterfaceName.super.show(). Takeaway in one sentence: multiple blueprints are combinable because they are incomplete; multiple houses are not.

Pitfalls — where students stumble on this taxonomy:

  • Writing class C extends A, B and expecting it to compile. It never does in Java. If you need capabilities from both A and B, extract them as interfaces IA and IB and write class C implements IA, IB, or use composition (C has-an A and a B).
  • Thinking implements inherits instance fields. It does not — interfaces have no instance variables. Trial implements Printable.Showable does not inherit print() from an outer class Printable; that needs extends Showable.Printable (see section 21.5). Mixing the verb predicts the wrong member set.
  • Supplying only one of several interface methods and forgetting the class is then abstract. class Incomplete implements Callback { void show(){} } without callback must be abstract class Incomplete implements Callback, otherwise does not override abstract method.
  • Duplicating the implements keyword. Write class Trial implements Printable, Showable (one implements, comma-separated), not class Trial implements Printable implements Showable.

21.2.4 Worked Example — Printable and Showable Implemented Together

Setup: Two interfaces share method names. This is a distinct example from the bank example — intentionally minimal to isolate the multiple-inheritance mechanics.

interface Printable { void print(); void show(); }
interface Showable  { void print(); void show(); }

Both declare print and show with only declarations (public abstract, no bodies). No constants are present.

Construction: class Trial implements Printable, Showable using a single implements clause listing both interfaces separated by a comma. The order Printable, Showable versus Showable, Printable does not matter.

Obligation: Because Trial implements both, it must provide bodies for print and show. The bodies reside only here, each marked public:

class Trial implements Printable, Showable {
    public void print() { System.out.println("within print"); }
    public void show()  { System.out.println("within show"); }
}

Crucial clarification the professor belaboured: the name show appears in both interfaces, but neither interface contains a body for it. There is only one show definition in Trial, and it satisfies both interface declarations simultaneously. No disambiguation of the declaration source is needed at this stage because declarations have no bodies to choose between. The method signature match is exact — return type, name, parameter list — and that single body counts for all interfaces that declared it. The same holds for print.

Driver and output — tiny concrete trace:

Trial p = new Trial();
p.print(); // → within print   (Trial.print supplies body for both Printable.print and Showable.print)
p.show();  // → within show    (Trial.show supplies body for both declarations)

Call via interface references also works and dispatches to the same single body:

Printable pr = new Trial(); pr.show(); // → within show
Showable  sh = new Trial(); sh.show(); // → within show  (same body)

Sense-check: If Trial had omitted show(), the compiler would report Trial is not abstract and does not override abstract method show() in Showable (and similarly for Printable) — one missing body violates two contracts at once, but one supplied body satisfies both.

Comparison — sibling concepts side-by-side: Class multiple inheritance (extends two classes) is forbidden because of competing bodies and state; interface multiple inheritance (implements many interfaces) is allowed because there are no competing bodies in the classic model. When to pick which? Never write extends A, B — extract capability contracts as interfaces. Use implements when a class must be "printable and showable and sortable"; use extends when a class is a specialization of exactly one parent's state and behaviour (CheckingAccount is a BankAccount).

Q: If two interfaces declare the same method name, which interface does the implementing class's definition refer to? A: Neither declaration carries a body, so there is no conflict to resolve. The single definition in the implementing class satisfies every interface that declared that signature. You provide one body and it counts for all. This holds because classic interface methods are public abstract with no implementation. The moment both interfaces supply a default body for the same signature, silence is no longer allowed — the class must override and delegate explicitly via Printable.super.show() / Showable.super.show() (see section 21.4), which is precisely the disambiguation that declarations alone do not need.

Recap — Three verbs, three meanings: class extends class (inherit state + bodies, single parent), class implements interface(s) (take on contracts, many allowed, each with public bodies), interface extends interface(s) (accumulate contracts, many allowed). The Bank → BankAccount → CheckingAccount chain shows extends and implements coexisting; the Printable + Showable → Trial example shows one body satisfying two declaration-only contracts. Bridge — Classic interfaces avoid body conflicts — but Java 8 added default methods with bodies to solve the evolution problem, which reintroduces a controlled form of that conflict and forces explicit resolution; that is the focus of the next two sections.

Exam note: Be ready to write class CheckingAccount extends BankAccount implements Bank and class Trial implements Printable, Showable from memory, to explain in one sentence why class C extends A, B is illegal while class C implements P, Q is legal (complete vs partial, state vs no state), and to predict within print / within show for the driver above.

Real-world — This pattern is how a single service class in production conforms to several capability contracts at once, e.g. class ReportService implements Printable, Exportable, Auditable or a UI widget class MyIcon implements Icon, Comparable<MyIcon>. The one-body-satisfies-both-declarations rule keeps such multi-contract adoption lightweight until default bodies are involved.

21.3 Default Methods in Interfaces

21.3.1 The Evolution Problem Default Methods Solve

Hook: You publish an interface used by 10,000 jars. A year later you need to add one method. Do you force 10,000 teams to edit their code, or is there a way to add the method without breaking anyone?

In the classic model, every method declaration in an interface must be defined in every class that implements it. That creates a painful evolution problem: if a published interface needs a new method, every existing implementation must be edited to add the new body or it stops compiling. The change is source-incompatible — the compiler demands the missing body.

A concrete story: IntStack is interface IntStack { void push(int item); int pop(); } and FixedStack and DynStack both implement it across many codebases. Adding void clear(); with the classic rule would mean FixedStack and DynStack each fail with does not override abstract method clear() until someone adds a body. For a widely used library interface (Collection, List, Stream), that would be catastrophic.

To overcome this, after Java 8, default methods were introduced. A default method (a method inside an interface that carries its own implementation and does not force every implementing class to override it) supplies a fallback body directly in the interface. The keyword default marks that fallback.

Why the name "default"? Because it is the body used by default when the implementing class supplies none. The professor stressed the motivation as library evolution without breakage, and as optional functionality: a method such as remove() or clear() may be meaningful for modifiable sequences but not for non-modifiable ones. Supplying a default that does nothing or throws UnsupportedOperationException lets non-modifiable implementers ignore it without writing a dummy skeleton implementation.

Intuition — everyday picture: Think of a rental agreement template. Classic clauses are mandatory — every tenant must write a response. A default clause comes pre-filled with a sensible fallback ("tenant will not paint walls without permission — otherwise walls remain as-is"). A particular lease may override the clause with its own wording; if it leaves the clause untouched, the pre-filled wording applies. The agreement does not break just because a new pre-filled clause was added.

Mapping: interface = template, implementing class = signed lease, default method = pre-filled clause, overriding class method = custom clause. Break point: unlike a paper template, a default method can be invoked via InterfaceName.super.method() for disambiguation when two templates conflict — paper has no such mechanism.

The defining difference between interface and class still holds: even with defaults, an interface cannot maintain per-object state (no instance variables). It can specify behaviour fallback, but it cannot store that behaviour's state. So default is a special-purpose escape hatch for evolution, not a license to turn interfaces into classes.

21.3.2 Syntax of Default Methods — the default Keyword

You create a default method by prefixing the method header with the keyword default and then supplying a body in curly braces — unlike a classic declaration which terminates with ;. The placement matters: default comes before the return type, after any public (which is implicit).

Classic declaration vs default method — side-by-side in the same interface:

interface Printable {
    void print(); // classic: public abstract, ends with ;
    default void show() {
        System.out.println("within show of Printable");
    }
}
  • print()abstract declaration (no body, must be supplied by implementer).
  • default void show() { ... }default method (has body in the interface, supply by implementer is optional). If the implementer supplies its own show(), that body wins (class implementation takes priority over interface default). If it does not, the interface's default body is used.
  • A default method is implicitly public; writing public default void show() is legal but default already implies public.
  • You may also have static and (since JDK 9) private methods with bodies in an interface — each with its own rules, covered in section 21.4.

A more practical evolution example from the reference docs — adding clear() to a published stack interface without breaking preexisting stacks:

interface IntStack {
    void push(int item); // store an item
    int pop();           // retrieve an item
    // Because clear() has a default, it need not be
    // implemented by a preexisting class that uses IntStack.
    default void clear() {
        System.out.println("clear() not implemented.");
    }
}

In real-world code the default would throw rather than print:

default void clear() {
    throw new UnsupportedOperationException("clear not supported");
}

This preserves the rule that existing classes (FixedStack compiled before clear() existed) continue to work unchanged until someone actually calls clear().

Notice the contrast deliberately preserved in the lecture: print is simply declared and terminated, while show is declared with default and accompanied by a body. The default keyword is what makes that fallback legal — without it, a body in an interface would be a compile error under the pre-JDK-8 interpretation (interface abstract methods cannot have a body).

A class may mix these: one interface can have both classic abstract declarations (compulsory) and default methods (optional), plus static helpers. The compiler enforces the distinction.

21.3.3 Worked Example — Printable with a Default show Method

Context: Printable has one abstract declaration void print(); and one default method default void show() with a body as above. This is the canonical minimal example that isolates the new behaviour.

interface Printable {
    void print(); // abstract, compulsory
    default void show() {
        System.out.println("within show of Printable");
    }
}

Implementing class — intentionally omits show:

class Trial implements Printable {
    public void print() {
        System.out.println("within print");
    }
    // no show implementation at all — legal because show is default
}

Key points line by line:

  • class Trial implements Printable promises the compiler it will satisfy Printable. The compiler checks: print() is abstract → must be present — it is, with public void print() { ... }, so that promise is met. show() is default → presence is optional — the compiler allows omission.
  • The class defines only print. It intentionally omits show. The program does not complain — Trial is concrete and not abstract — which is the whole point of default. Before JDK 8 this would have forced Trial to be abstract or to supply show.
  • The public on print() is still required (see section 21.1 pitfalls).

Walkthrough — does this compile?

  • Interface declares 2 members: print (abstract) + show (default with body).
  • Class supplies 1 body: print with public.
  • Compiler verdict: Trial is valid concrete class. One abstract fulfilled, one default inherited as fallback. No error.
  • If you had added a second abstract void display(); to Printable without default, Trial would immediately fail until you added public void display() { ... }.

21.3.4 Calling Behavior When a Class Omits the Default Implementation

Driver in a class with main:

Trial p = new Trial();
p.print(); // direct control to Trial's print definition
p.show();  // searches Trial for show, finds none, falls back to Printable's default show

Resolution order — precisely what the professor traced:

  1. p.show() looks first for a definition inside Trial. Resolution is by runtime type: p is a Trial object, so the VM checks Trial.show.
  2. When none is found, control falls back to the interface and executes the default implementation provided there — within show of Printable is printed.
  3. The keyword default is what makes that fallback legal: it tells the compiler "this interface method has a body that may be used as fallback."
  4. If a class does supply show, its version overrides the default — class priority beats interface default (the first rule in the conflict resolution list). So:
class Trial2 implements Printable {
    public void print() { System.out.println("within print"); }
    public void show()  { System.out.println("my own show"); }
}
Trial2 t2 = new Trial2(); t2.show(); // → my own show  (Trial2 wins over Printable default)

Output trace — both calls in one run:

Trial p = new Trial();
p.print(); // → within print               (direct, class body)
p.show();  // → within show of Printable   (no class body, default fallback)

Trial2 t2 = new Trial2();
t2.show(); // → my own show                (class body overrides default)

Sense-check: p.show() does not give cannot find symbol — it resolves to the default. Removing default from the interface would make Trial fail to compile for missing show; adding it back restores the fallback.

The same dispatch story holds through an interface reference — polymorphism applies:

Printable pr = new Trial();
pr.print(); // → within print  (Trial)
pr.show();  // → within show of Printable (default, no Trial override)

The lecture used this to emphasize that default methods preserve late binding: the VM still selects by actual object type, preferring class body over default.

Scope — when defaults apply and what they cannot do:

  • Applies: Evolution of published interfaces (IntStack.clear(), Collection.forEach, List.sort) where you want to add capability without breaking existing implementers; optional operations where some implementations will leave the default (often throw new UnsupportedOperationException(...)) and only new or modifiable implementations override.
  • Does not: Replace class state. A default method cannot read or write per-object instance fields of the interface (there are none). It can call other abstract methods of the same interface — e.g., a default clear() that loops calling pop() — and will dispatch to the implementing class's pop(). It cannot carry a private per-object cache.
  • What breaks if violated: Removing default from a previously default method silently reintroduces the classic breakage: every implementer fails to compile. Adding default without considering the UnsupportedOperationException vs sensible default semantics can hide bugs where callers expect meaningful work but get a do-nothing print.

Picture a stack of three layers: bottom — dashed interface Printable with print() (red, abstract, no body) and show() (green, default with filled body). Middle — optional solid class Trial that may or may not supply its own show (transparent vs solid green box). Top — arrow p.show() dropping down, hitting the class box first; if the box is transparent (no body), the arrow passes through to the green default below — labelled "fallback". X-axis is "lookup order" (class first, then interface), y-axis is "presence". One-sentence takeaway: calls check the class first; only when the class is silent does the interface's default speak.

Pitfalls — mistakes that specifically involve default vs classic:

  • Forgetting default before the body in the interface. Writing void show() { ... } without default gives interface abstract methods cannot have a body (pre-JDK-8 mental model). The keyword must be present.
  • Omitting public on the overriding method in the class. Even though the default is public, the overriding show() in Trial must be public void show() — not package-private — otherwise attempting to assign weaker access.
  • Expecting the default to be inherited like a class method you can call via super.show(). Inside the implementing class, plain super.show() refers to superclass, not interface default. The correct form for interface default delegation is InterfaceName.super.method() (see section 21.4), and it is only legal inside the implementing class that declares implements InterfaceName.
  • Providing a do-nothing default that silently hides an unsupported case. The reference docs advise throwing UnsupportedOperationException as the default for truly optional operations, so callers learn at runtime that clear() is not supported rather than seeing a misleading print and assuming it worked.

21.3.5 Student Questions and Answers

Q: If I add a new declared method to an interface, do all existing classes break? A: With classic abstract declarations, yes — each implementing class must add the new body, or be declared abstract, otherwise you get does not override abstract method. With a default method, no — you give the new method a body directly inside the interface itself (prefix with default and supply { ... }), and existing classes inherit that fallback automatically without any edit. That is precisely why default methods were added: to let interfaces evolve without breaking preexisting implementations. The fallback is used only when the class omits the method; if a new or updated class later supplies its own show(), that class body takes priority over the default. For a real example, IntStack grew a default void clear() { System.out.println("clear() not implemented."); } without forcing FixedStack or DynStack to change — they keep compiling, and clear() prints the not-implemented message until someone overrides it with a real loop.

Comparison to cement the distinction: classic void print(); ends with ; and mandates a class body; default void show() { ... } ends with { ... } and permits omission. One is compulsory, one is fallback.

Recap — The evolution problem: adding a classic abstract method breaks all implementers. The Java 8 fix: mark the new method default and give it a body in the interface; implementers inherit it as fallback unless they override — class wins over default. Bridge — default solves the one-interface case, but what about a class that implements two interfaces each with a default for the same signature? And what about utility methods that belong to the interface type itself rather than any object? Those are the static helpers and diamond-conflict rules of the next section.

Exam note: Expect to (a) define interface Printable { void print(); default void show(){...} }, (b) state that class Trial implements Printable { public void print(){...} } compiles without show, and (c) trace p.show() to the default fallback, versus a Trial2 that does override show and shadows it.

Real-world — Library maintainers use default methods to add new capabilities to long-published interfaces without breaking thousands of downstream implementations. Java's own Collection interface added default boolean removeIf(...), default Stream<E> stream(), and default void forEach(...); List added default void sort(...). Each was shipped as a default so code compiled against older JDKs kept compiling while newer implementers could override for better performance.

21.4 Static Methods in Interfaces and Default Methods Under Multiple Inheritance

21.4.1 Static Methods Inside Interfaces

Just as you can have default methods with bodies, you can have static methods with bodies inside an interface (added alongside defaults in JDK 8). A static method (a type-level method — a class method — whose invocation is tied to the type name, not to an object instance) never dispatches on an object; it is resolved by the type that owns it.

Syntax: prefix the method header with static, then supply the body in curly braces. Like defaults, static interface methods are implicitly public when written without an access modifier.

interface Printable {
    void print(); // abstract, instance-level, compulsory
    static void show() {
        System.out.println("within static show");
    }
}

What static in an interface means — contrast with default:

  • default void show() { ... } — instance-level fallback. Called via an object (p.show()), subject to override (class wins), falls back to the interface if the class omits it, and participates in interface-multiple-inheritance diamond resolution.
  • static void show() { ... } — type-level helper. Called by interface name (Printable.show()), cannot be overridden by an implementing class in the usual polymorphic sense, not inherited as an instance method, and never participates in a InterfaceName.super diamond.

Both are definitions with bodies inside the interface — permitted alongside classic abstract declarations — but they live in different namespaces: one on objects, one on the type itself.

The interesting point the professor emphasized: this is a definition inside the interface, permitted alongside default methods, and it behaves like any other static member (like public static final constants) — one copy owned by the type. You would put here helpers or factories that conceptually belong to the interface namespace rather than any single implementation, e.g. a static Printable.createDefault() or a validator Printable.isValid(String s).

Since JDK 9 you can also write private and private static helper methods inside interfaces — visible only within the same interface — to share code between multiple defaults/statics without exposing it to implementers. The lecture focuses on the public static form most relevant for the exam.

21.4.2 Worked Example — Calling a Static Interface Method by Interface Name

Construction: class Trial implements Printable { public void print(){ System.out.println("within print"); } } The class ends after print; no show body is present — there is nothing to add, because show is static and lives on the interface type, not on the implementing class's instance.

interface Printable {
    void print(); // abstract
    static void show() {
        System.out.println("within static show");
    }
}
class Trial implements Printable {
    public void print() {
        System.out.println("within print");
    }
    // no show — static show is not an instance method to implement
}

Driver in another class (e.g., a client with main):

Trial t = new Trial();
t.print();          // instance call, resolved to Trial.print → within print
Printable.show();   // static call by interface name → within static show

Why not t.show()? Because show is static and lives on the interface type Printable itself — analogous to calling a static class method by ClassName.method(). You call a static interface method by the name of the type that owns it, not through an object reference. Writing t.show() where show is static will either fail (cannot make static reference to non-static confusion in older idioms) or, if the compiler permits it with a warning, be resolved as Printable.show() at compile time — but the idiomatic and exam-expected form is Printable.show(). The lecture flagged this as an exam note: expect a question asking how to invoke a static interface method; answer is by interface name.

Contrast — static vs instance dispatch in one program:

interface Printable {
    void print();
    default void instanceShow() { System.out.println("default instance"); }
    static void staticShow()    { System.out.println("static"); }
}
class Trial implements Printable {
    public void print() {}
    public void instanceShow() { System.out.println("overridden instance"); }
}
Trial t = new Trial();
t.print();              // → (Trial body)
t.instanceShow();       // → overridden instance  (instance dispatch, class wins)
Printable.staticShow(); // → static                (type dispatch, no override possible)
// t.staticShow();       // ← not idiomatic; exam expects Printable.staticShow()

Sense-check: Trial.staticShow() is not the right call if staticShow is declared in Printable — the static belongs to Printable, not to Trial. Even t.staticShow() would be flagged as poor style by tools.

For print, which is an instance-level abstract method given a body in Trial, you always call via the object t.print() and it dispatches polymorphically. For show which is static on the interface, you always call via the interface name Printable.show() and it never dispatches polymorphically.

21.4.3 Two Default show Methods — the Conflict Scenario

Now combine default methods with multiple inheritance. Two interfaces each provide their own default show — each with a body, each with the same signature — to set up the diamond where the same signature has two competing default bodies.

interface Printable {
    void print(); // still abstract
    default void show() { System.out.println("within show of Printable"); }
}
interface Showable {
    void print(); // still abstract
    default void show() { System.out.println("within show of Showable"); }
}

So print is simply declared in both (no body, no conflict — one body later satisfies both), while show is defined in both as a default method, each with its own body. This is the crucial shift from the earlier single-default example: previously there was at most one body to fall back to, so omission was allowed. Now there are two equally valid fallbacks for the same signature, and silence is no longer disambiguation.

This sets up the classic default-method diamond — interfaces P and Q both default show(), class Trial implements P, Q sits at the tip. The question becomes: which default would t.show() pick? The answer is: the compiler refuses to pick — it requires the class to choose explicitly.

Scope — when conflict arises and when it does not:

  • No conflict: Both P and Q declare void show(); (abstract, no body) — one show() body in Trial satisfies both (section 21.2).
  • Conflict: Both P and Q declare default void show() { ... } with bodies — class T implements P, Q {} with no show() gives inherits unrelated defaults for show() from types P and Q and fails to compile.
  • No conflict via override: class T implements P, Q { public void show(){...} } overrides both defaults — class body wins, no ambiguity.
  • Even class alone wins: If T had a single interface parent plus a class parent that also supplies show(), the class body's priority resolves it without qualified super.

What breaks if violated: leaving Trial empty (class Trial implements Printable, Showable { public void print()... } with no show) now yields a compile error instead of a silent fallback, which is the compiler's way of forcing you to acknowledge the diamond.

Visual — draw the diamond: top nothing, middle layer two dashed boxes Printable and Showable each containing a green default show() box with different text; bottom solid box Trial implements Printable, Showable with a red warning triangle on show labelled "two defaults → must override". Arrows from Trial.super.show go up separately to Printable.super.show and Showable.super.show. Takeaway: two green defaults at the same level cannot both be the fallback — the tip must write its own body.

21.4.4 Worked Example — Resolving Conflicts with InterfaceName.super

Construction: class Trial implements Printable, Showable where both supply default void show() as above.

If Trial omits any show, the compiler cannot choose between the two defaults and will complain (see scope). You must resolve explicitly by overriding show() in Trial and, inside that overriding method, explicitly delegating to whichever default(s) you want via qualified super calls.

class Trial implements Printable, Showable {
    public void print() { System.out.println("within print"); }
    public void show() {
        Printable.super.show(); // invoke Printable's default
        Showable.super.show();  // invoke Showable's default
    }
}

Inside Trial.show, the two lines Printable.super.show() and Showable.super.show() each invoke one interface's default. The qualifier InterfaceName.super is the explicit disambiguator, borrowing the familiar super.method() concept from class inheritance where super.method() calls a parent's version. Because show exists in both interfaces, you prefix with the interface name, then a dot, then super, then a dot, then the method name. This is the prescribed Java syntax for reaching an interface default from the implementing class — and it is only legal inside Trial that declares implements Printable, Showable.

You are free to choose: call only one default, call both in sequence (as above), or supply an entirely new body without delegating at all. The cousin example from the reference docs — IntStack evolving with default void clear() — would have Trial override clear() and perhaps just not delegate if the default's "not implemented" print is unwanted.

Driver and line-by-line tracing — the exam trace:

Trial t = new Trial();
t.print(); // → within print
t.show();  // enters Trial.show, hits two delegations

Trace for t.show():

  1. t.show() enters Trial.show (class body takes priority over both defaults — no dispatch to a single default).
  2. Printable.super.show(); executes the body from Printable → prints within show of Printable.
  3. Showable.super.show(); executes the body from Showable → prints within show of Showable.
  4. Method returns; program continues.

If the header had been class Trial implements Printable, Showable { public void show(){ Printable.super.show(); } } only within show of Printable would print. If you wrote an entirely new print, e.g. public void show(){ System.out.println("my show"); }, only my show prints — you choose the semantics.

Alternative the professor flagged as not valid: super.show() (without interface qualifier) inside Trial — there is no class superclass with show; you must write Printable.super.show() or Showable.super.show(). And Printable.show() would try to call a static, not a default instance delegation — wrong namespace.

Rules for the diamond summarized from the reference:

  1. Class implementation always takes priority over interface default. So a public void show() in Trial beats both defaults unconditionally.
  2. If a class implements two interfaces with the same default signature and omits its own body, compilation fails — you must override.
  3. Inside the overriding body, InterfaceName.super.method() reaches that interface's default; this qualified super is only legal in the class that directly implements the interface.

A second real example — Alpha and Beta both default void reset() — the same rules apply; MyClass implements Alpha, Beta must public void reset(){ Alpha.super.reset(); } or its own body (page 249 of the companion text).

Driver restated for the lecture's two-line output case:

Trial t = new Trial();
t.print(); // System.out.println within print
t.show();  // first executes Printable's show, then Showable's show via the two super calls

21.4.5 Student Questions and Answers

Q: When both interfaces have a default show, does t.show() automatically pick one? A: No. With two competing defaults the compiler requires you to override show in the implementing class and explicitly delegate via Printable.super.show() and Showable.super.show() as needed. Silence is not disambiguation here.

The lecture's re-explanation made the distinction from the earlier declaration-only case stark: when both interfaces merely declare void show(); (no body), one show() body in Trial satisfies both — no disambiguation needed. Once both define default void show() { ... } with a body, there are two competing implementations of equal rank. The compiler will not guess; you must write public void show() { ... } in Trial. Inside that method you choose: Printable.super.show() to run the Printable version, Showable.super.show() for the other, both in sequence if you want both, or neither if you provide entirely fresh logic. Plain super.show() is illegal here — you need the qualified InterfaceName.super form, and it only works inside Trial that implements the interface.

Recap — static void show() in an interface is a type-level helper called as Printable.show() — never via t.show() on the object. Two default void show() bodies at the same level create a diamond that silence cannot resolve: you must @Override show() in the class and delegate explicitly with Printable.super.show() / Showable.super.show() (or supply a fresh body). Bridge — Defaults and statics resolved how interfaces can carry behaviour — but interfaces can also be nested inside other types, with their own access rules, static nature, and qualified names; nesting is next.

Exam note: Expect to (a) call a static interface method as Printable.show() and state why t.show() is wrong, (b) write the conflict resolver class Trial implements Printable, Showable { public void print(){...} public void show(){ Printable.super.show(); Showable.super.show(); } } from memory, and (c) explain why the same resolver is unnecessary when both interfaces only declare void show();.

Real-world — This pattern appears in Java's own collections and streams where interfaces evolved with defaults like Collection.forEach, List.sort, Collection.removeIf, and stream defaults. A class that implements two library interfaces that happened to add the same default name hits exactly this diamond; explicit InterfaceName.super delegation is how service adapters in large codebases resolve library-evolution diamond problems without renaming.

21.5 Nested Interfaces

21.5.1 Definition and Access Rules

Hook: What if a contract only makes sense inside another contract — can you declare an interface inside an interface, or an interface inside a class?

Nesting means one entity inside another — you open the braces { } of an outer type and declare a second type inside. A nested interface is an interface declared within another interface or within another class. The phrase means exactly that lexical containment.

A minimal skeleton:

interface Outer {
    void outerMethod();
    interface Inner {      // nested interface
        void innerMethod();
    }
}
class OuterClass {
    interface Inner {      // nested interface inside a class
        void innerMethod();
    }
}

Core rules — the professor listed them as non-negotiable and examination-frequent:

  • A nested interface cannot be accessed directly by its simple name alone when you are outside the outer type; it is referred to by the outer type name qualified with a dot: Outer.Inner. So outside Printable, the inner Showable is Printable.Showable, and outside a class A the member interface is A.NestedIF. This matches Outer.Inner and A.NestedIF in the companion docs example.
  • If declared inside an interface, the nested interface must be public. It should be available publicly because interface members are inherently public (see section 21.1). Writing private interface Showable inside interface Printable is illegal. The illustration used Printable as outer and Showable as inner — both implicitly public.
  • If declared inside a class, the nested interface can have any access modifier, because a class may control visibility more flexibly per the language rules: public, protected, private, or default (package) are all legal for an interface member of a class. You choose whether outside code may see it.
  • Nested interfaces are implicitly declared as static. This point was made explicitly as part of the nesting rules: even without writing static, the inner interface is a type-level member that does not require an outer instance to exist. Writing interface Outer { static interface Inner { ... } } is redundant — the static is implied.

These rules parallel member-class rules but with the interface-specific public-inside-interface constraint.

21.5.2 Implicit static Nature and Access-Modifier Constraints

The implicit static means the inner interface does not require an instance of the outer type to exist; it is a type-level member, like a static constant. You do not need new Outer() to refer to Outer.Inner — you just write the qualified name.

Practically, this means Outer.Inner is loaded with Outer as a static member, not tied to any particular object. That is why you can implements Outer.Inner without holding an Outer instance, analogous to accessing Printable.min without constructing Printable.

The access-modifier distinction is worth belabouring because it is a frequent exam distractor:

  • Inside an interface, you cannot hide the inner interface as private — it must be public (implicitly, even if you omit the keyword), because interfaces are about public contracts. Declaring interface Outer { private interface Inner { ... } } fails with illegal combination of modifiers.
  • Inside a class, you may write public interface Inner, private interface Inner, protected interface Inner, or default interface Inner, depending on whether you want outside code to see it. A private inner interface is only visible within the outer class and is used for internal callbacks that should not leak.

Decision table — nested interface access:

Outer is interface  → Inner must be public (implicitly), implicitly static
                    reference as Outer.Inner
Outer is class      → Inner may be public / protected / private / default, implicitly static
                    reference as Outer.Inner; visibility follows the modifier
  • Qualified name is always Outer.Inner from outside, regardless of where it is nested.
  • Plain Inner alone outside the outer type does not compile — cannot find symbol: Inner — because the simple name is not in scope.

Only inner interfaces and inner classes can be static; you cannot write static interface Outer at the top level, for the same reason you cannot write static class Outer at top level — static only makes sense for a member, and a top-level type has no enclosing member to be static with respect to.

21.5.3 Worked Example — Interface Within Interface — Outer vs Inner

Outer vs inner illustration — the canonical pair the lecture dissected case-by-case:

interface Printable {
    void print();
    interface Showable {
        void show();
    }
}

Here Printable is outer, Showable is inner (nesting of interfaces). Printable declares print (compulsory if you implement outer); Showable declares show (compulsory only if you implement inner). The inner is implicitly public static.

Case 1 — Implementing only the outer.

class Trial implements Printable {
    public void print() { System.out.println("within print"); }
    public void show()  { System.out.println("within show of Trial"); }
}

In driver, Trial p = new Trial(); p.print(); p.show(); works. Why can p.show() be called? Note carefully — and the lecture flagged this as the examinable subtlety — this show in Trial is a local method of Trial itself, not the inner interface's show. Nowhere in this variant does the class header mention Showable. So this show should not be confused with the show declared in Showable; it is just a coincidentally same-named ordinary method that Trial happens to have. The compiler does not link it to Printable.Showable.

Critical follow-up question addressed in detail: What happens when the implementation of show is removed from Trial in this case?

If you delete public void show(){...} from Trial in this case, nothing is going to happen and no compilation complaint is raised. The outer interface Printable only declares print; it does not have access to the inner interface Showable — nesting is lexical containment, not inheritance. Because Trial is declared as implements Printable, print becomes compulsory to define, but Showable is never referenced, so its show is not compulsory. The show you saw in Trial was a local addition, and removing it simply means the corresponding p.show() call in main must also be removed; otherwise you call a non-existent method on Trial. No interface contract is violated. The inner Showable sits untouched.

Case 1 — trace with and without internal show:

// With Trial's own show
class Trial implements Printable {
    public void print(){ System.out.println("within print"); }
    public void show(){ System.out.println("within show of Trial"); } // not from interface
}
Trial p = new Trial(); p.print(); // → within print
p.show();              // → within show of Trial (local method, not interface obligation)

// After deleting show from Trial — still compiles
class Trial implements Printable {
    public void print(){ System.out.println("within print"); }
}
Trial p = new Trial(); p.print(); // → within print  (only contract)
 // p.show(); // ← now compile error: Trial has no show() at all — local method gone

Sense-check: Printable.Showable was never mentioned in implements, so compiler never checked for show.

Case 2 — Implementing the inner.

class Trial implements Printable.Showable {
    public void show() { System.out.println("within show"); }
}

Now the header says Printable.Showable — the inner interface qualified by the outer. This says explicitly: I am taking on the inner contract, not the outer one. Therefore show, which is declared inside Showable, becomes compulsory to define, and the body is provided accordingly. Note print() is not required now — you did not implement Printable. Driver:

Trial t = new Trial();
t.show(); // → within show — delegates to Trial's show which satisfies Printable.Showable
// t.print(); // ← error: Trial has no print — outer not implemented

If you omit the implementation of show here, you get a compilation error: Trial is not abstract and does not override abstract method show() in Printable.Showable — the class is explicitly implements Printable.Showable and therefore must define show. This contrast between Case 1 and Case 2 is the core lesson: implementing the outer does not pull in the inner; implementing the inner must be stated as Outer.Inner.

Scope — implementing outer vs inner vs both:

  • implements Printable — must define print() only. Printable.Showable.show() is irrelevant; a local show() is optional and unrelated.
  • implements Printable.Showable — must define show() only. print() is irrelevant.
  • implements Printable, Printable.Showable — must define both print() and show() — union of outer and inner contracts, comma-separated as usual.

What breaks if confused: writing implements Showable alone outside Printable gives cannot find symbol: class Showable — you must qualify. Writing implements Printable.Showable and calling t.print() fails because print was never promised.

Picture the containment as Russian nesting dolls: the outer doll labelled Printable (contains print()), a smaller doll inside labelled Showable (contains show()). An arrow implements Printable points only to the outer doll — it does not reach inside. An arrow implements Printable.Showable threads through the outer wall to point directly at the inner doll. X-axis is nesting depth, y-axis is obligation. Takeaway: Outer and Outer.Inner are distinct contracts; you name the one you take on.

Pitfalls — nested-interface traps:

  • Writing implements Showable instead of Printable.Showable from outside. Bare Showable is not on scope path — the inner name is only visible as Outer.Inner when you are outside Outer. Inside Printable itself you could say Showable, but never outside.
  • Thinking implements Printable automatically brings Printable.Showable with it. It does not — the inner is not inherited by implementing the outer. You must list each contract you intend to honour.
  • Adding a local show() to Trial implements Printable and believing it satisfies Printable.Showable. It does not — the compiler checks the implements list, not mere name coincidence. Only implements Printable.Showable links the body to the inner's abstract declaration.

21.5.4 Worked Example — Interface Within Class and Class Within Interface

Interface within class — the next permutation, swapping which kind of type is outer:

class Printable {
    public void print() { System.out.println("within print of outer class"); }
    interface Showable {
        void show(); // implicitly public abstract, implicitly static
    }
}
class Trial implements Printable.Showable {
    public void show() { System.out.println("within show"); }
}

How to implement: refer via Printable.Showable using the outer class name followed by inner interface name — same Outer.Inner qualified rule. Then show must be defined (it is the inner interface's abstract method). Driver:

Trial t = new Trial();
t.show();  // → within show — satisfies Printable.Showable
// t.print(); // DOES NOT WORK — compile error

Why t.print() fails: print belongs to the outer class Printable, not to the inner interface Showable. Trial implements only Printable.Showable, the inner interface, so it inherits the contract of show but does not inherit the instance method print of the outer class. To call print, you need an object of Printable itself (Printable pr = new Printable(); pr.print();), or you must extend the outer class, not implement its inner interface. Because this is a clean case of implementing an inner interface, Trial can only guarantee show. The qualified name matters more than the coincidence that print and show live in the same file.

The access-modifier flexibility appears here: because Showable lives inside a class, its declaration could be public interface Showable, protected interface Showable, private interface Showable, or default — controlling whether Trial outside the package can see Printable.Showable at all.

Class within interface — the opposite nesting (the mirror image):

interface Showable {
    void show(); // abstract
    class Printable {
        public void print() { System.out.println("within print"); }
    }
}
class Trial extends Showable.Printable {
    public void show() { System.out.println("within show"); } // not overriding yet
}

Key vocabulary shift the professor highlighted: Printable is now a class inside an interface. A class is inherited, not implemented. So you write extends Showable.Printable using extends (because you are inheriting a class) and qualify with the outer interface name — Showable.Printable — exactly the same Outer.Inner dot rule, but with extends instead of implements because the inner type is a class.

Inside a class-inside-interface, the inner class is implicitly public static in the same way the inner interface was — it does not require an outer instance. Its print() is a concrete instance method, inherited normally.

Driver:

Trial t = new Trial();
t.show();  // → within show  (defined in Trial — but see next case for obligation)
t.print(); // → within print (inherited from Showable.Printable, callable via derived object)

Output: first within show, then within print. No error occurs when calling print via a Trial object because inheritance makes parent class methods available on the child object — Trial is-a Showable.Printable, so print is inherited like any extends parent. This contrasts with the previous case where you could not call print via Trial because you were implementing an interface, not extending a class — you only acquired a contract, not a concrete parent body.

Side-by-side — implements inner interface vs extends inner class:

// Interface inside class — implements
class Printable { interface Showable { void show(); } }
class Trial implements Printable.Showable {
    public void show(){ System.out.println("show"); }
}
Trial t = new Trial(); t.show(); // ok
// t.print(); // fails — not inherited, only Showable was implemented

// Class inside interface — extends
interface Showable { class Printable { public void print(){...} } }
class Trial2 extends Showable.Printable { }
Trial2 t2 = new Trial2(); t2.print(); // ok — inherited via extends
// t2.show(); // fails unless Trial2 also implements Showable — see next case

Sense-check: implements never gives you a concrete method for free; extends does.

21.5.5 Worked Example — Combined Inheritance and Implementation

Variant where both are needed — the capstone that forces you to use extends and implements together because the inner class and the outer interface each contribute something distinct. Starting from the previous Showable with its inner Printable and its own show declaration, consider a scenario where the method is renamed to showOne inside the interface to create a naming gap that separates the two contributions:

interface Showable {
    void showOne(); // interface's own abstract method
    class Printable {
        public void print(){ System.out.println("within print"); }
    }
}
class Trial extends Showable.Printable implements Showable {
    public void showOne() { System.out.println("within showOne"); }
    // print is inherited from Showable.Printable and may be overridden or left as is
}

Now the header uses both extends Showable.Printable (to inherit the inner class Printable and its concrete print()) and implements Showable (to take the interface's contract showOne()). The two keywords sit together: extends first, then implements, exactly the general form class ... extends ... implements ....

  • For print, which is a concrete method of the inner class, overriding is optional. Trial inherits print() automatically via extends; it may @Override public void print(){ ... } with its own body if desired, but leaving it out is valid — the inherited body satisfies callers. This is class inheritance behaviour.
  • For showOne, which is declared in the interface Showable as public abstract, a definition is compulsory because Trial states implements Showable and showOne is the interface's promise. If you omit showOne, the compiler complains: class Trial should implement the method showOne (or is not abstract and does not override abstract method showOne()). This is interface implementation behaviour.

This reinforces the fundamental divide the lecture hammered: inherited concrete methods may be overridden at will (or left as-inherited); interface-declared methods must be defined or the compiler refuses to compile.

Driver for this combined case:

Trial t = new Trial();
t.showOne(); // → within showOne (compulsory body supplied)
t.print();   // → within print  (inherited, or overridden body if you overrode)

If Trial had also overridden print with its own print, t.print() would dispatch to Trial's override — normal class overriding with polymorphic dispatch.

The professor used this to underscore that extends vs implements is not cosmetic — it predicts which members you get for free and which you must supply. A common exam mistake is to write implements Showable.Printable when Printable is a class — that fails with Printable is not an interface.

21.5.6 Student Questions and Answers

Q: Do I write implements Printable.Showable or implements Showable when Showable is nested inside Printable? A: You must qualify with the outer: implements Printable.Showable. A nested interface cannot be accessed directly by its simple inner name from outside; it is referred to by the outer interface or outer class name, dot, inner name.

The rule is lexical, not optional: outside Printable, the simple name Showable is not on the search path. Inside Printable itself you could write interface Showable and refer to Showable, but any code outside the outer type must use the qualified Outer.Inner. The same holds when the outer is a class — class Printable { interface Showable {...} } is implements Printable.Showable. And when the inner is a class rather than an interface, you still qualify — extends Showable.Printable — but you use extends because a class is inherited, never implemented. Forgetting the qualifier gives cannot find symbol: class Showable; using implements for a class inner gives interface expected.

Recap — Nesting means Outer { Inner }. From outside, always Outer.Inner — never bare Inner. An inner interface inside an interface is implicitly public static and must be public; inside a class it may be any visibility and is still implicitly static. implements Outer.Inner takes a contract; extends Outer.Inner inherits a class — choose the keyword by what the inner type is, not the outer. Combined extends Showable.Printable implements Showable is the idiom when you need both an inherited concrete method and a compulsory interface method. Bridge — Nesting so far was interface-inside-type. The same outer/inner hierarchy exists for classes: inner classes that behave like members, with private-access rights, special Outer.Inner in = o.new Inner() construction, and static-nested variants — next.

Exam note: Be ready to write all four variants from memory: (a) interface Printable { interface Showable{void show();}} with implements Printable vs implements Printable.Showable and state when removal of show() errors; (b) class Printable { interface Showable... } with implements Printable.Showable and that t.print() fails; (c) interface Showable { class Printable{...}} with extends Showable.Printable and that t.print() succeeds; (d) combined extends Showable.Printable implements Showable with showOne.

Real-world — Nested interfaces of the form Outer.Inner model scoped contracts — for example, a NetworkService interface exposing an inner Callback or Handler, or a Map interface exposing Map.Entry as a nested interface. The implicit static avoids needing a NetworkService instance just to refer to the callback type, and the public vs private choice lets the outer class hide internal callback contracts.

21.6 Nested Classes — Member Inner and Static Inner Classes

21.6.1 The Nesting Analogy — A Class as a Member of Another Class

Hook: If an interface can live inside a type, can a class live inside a class — and what special power does that inner class get for being "part of the family"?

A nested class is a class declared inside another class, just as an interface can be nested. Think of the same outer/inner hierarchy you just saw for interfaces, but now both levels are classes. If you have class ABC { class XYZ { } }, then XYZ is an inner class of ABC. The professor's first principle: because XYZ itself is like a member of ABC — similar to a data member or a method — it can directly access whatever members the outer class owns, including private ones. That insider access is the reason nested classes exist.

Mapping the analogy:

  • Outer class ABC = house.
  • Inner class XYZ = a person who lives in the house and is on the household register (a member).
  • Data members of outer = rooms and valuables inside the house.
  • Because the person is a member, they have a key to every room — even private rooms.

Where the analogy breaks: a house member still cannot be static-detached from the house without losing that key intimacy — a static nested class (below) is like a registered address that shares the house name but lives elsewhere and keeps no key to private bedrooms.

21.6.2 Types of Nested Classes at a Glance

The four nested-class kinds — roadmap for what follows:

  • Member inner class (non-static inner class) — an inner class that behaves like a data member of the outer class. Lives with an outer instance; can access all outer members including private; needs o.new Inner() to construct. This is the main workhorse.
  • Anonymous inner class — a class with no name, created inline at a new expression (new Outer() { ... }). One object, one use, typically to override a single method or implement an interface on the spot. Covered deeply in section 21.7.
  • Local inner class — a class created within a method definition, whose scope is the bracket { } of that method. Visible only inside the method. Also in section 21.7.
  • Static nested class — an inner class prefixed with static (static class Inner). Like a static data member: no implicit outer instance, can access static outer members only (including private static), not instance members; constructed as new Outer.Inner() without an outer object.

These labels are not interchangeable — the constructor syntax, access privilege, and file artifact each differ, and the exam will ask you to match syntax to kind.

Use this as a checklist. The next subsections work each type with a full implementation; anonymous and local are deferred to section 21.7 where their one-off and method-scoped nature is isolated.

21.6.3 Member Inner Class — Syntax, Access and Object Creation

Worked example — the canonical illustration the lecture built live:

class Outer {
    private int data = 10;
    class Inner {
        void show() {
            System.out.println(data); // accesses outer's private field
        }
    }
}

Observe: Inner is inside Outer. Its show method accesses data, a private field of Outer. This is not a visibility violation because Inner is like a member of Outer; as a member it can access all data members of its outer, even private ones. The compiler implements this by giving Inner a hidden reference to its enclosing Outer instance (often named Outer.this), and in bytecode it generates synthetic accessor methods — which is why you can even inspect synthetic bridges with javap -p.

General rule: a member inner class can access every outer member — private, protected, default, public, instance or static — as if it were a method of Outer.

Driver class syntax for instantiation — the three-point rule the professor drilled:

Outer o = new Outer();
Outer.Inner in = o.new Inner();
in.show(); // → 10

Construction anatomy — three points, exact syntax:

  1. Create an instance of the outer class first: Outer o = new Outer(); — because the inner logically lives with that outer instance.
  2. Declare the inner type with the qualified name Outer.Inner — same Outer.Inner dot rule as for nested interfaces.
  3. Initialize with o.new Inner() — you must take a reference to the outer object o and use the new operator qualified by that reference. You do not write simply new Inner() or new Outer.Inner() in isolation for a non-static inner. The o.new prefix tells the VM which outer instance the inner should be tied to.

Variations that fail or mean something else:

  • new Inner() alone — cannot find symbol: class Inner when outside Outer.
  • new Outer.Inner() alone — legal only for a static nested class, not for a member inner class.
  • Outer.Inner in = new Outer().new Inner(); — also legal (anonymous outer instance), but o.new Inner() with a named o is clearer for exams.

From inside Outer itself (e.g., inside an Outer method) you may write Inner in = new Inner(); or this.new Inner(); — the outer this is implicit.

Tiny trace — two outer instances, two inner instances:

Outer o1 = new Outer(); // data = 10
Outer o2 = new Outer(); // data = 10 (separate house, same initial value)
Outer.Inner in1 = o1.new Inner(); // tied to o1
Outer.Inner in2 = o2.new Inner(); // tied to o2
in1.show(); // → 10 from o1's data
in2.show(); // → 10 from o2's data
// if Outer had method setData(int v) that changed this.data, each inner would see its own outer's update

Sense-check: in1 silently holds a reference to o1; in2 to o2. If you serialized or printed in1, the hidden Outer.this keeps o1 alive — a memory implication often asked.

21.6.4 Compiler Artifacts and Private-Member Access

When the compiler sees a member inner class, it creates two class files: Outer.class and Outer\\$Inner.class (outer name, dollar sign, inner name, dot class). This file split is how the JVM represents the nesting — there is no "class inside a file" at bytecode level; each is its own .class whose name encodes the nesting with \\$. The companion text T6 Chapter 9 and T2 Chapter 6 both call this out.

You can see it yourself:

javac Outer.java
ls
Outer.class
Outer\\$Inner.class

In a stack trace, frames from inner code appear as Outer\\$Inner.show(Outer.java:6) — the \\$ tells you it was an inner class. In a build directory, the two files are side-by-side; the dollar is not a filesystem quirk but the JVM's canonical encoding.

The instance of the outer must be created first because the inner holds a hidden reference to the outer (the Outer.this pointer). Since the inner has that reference, it can access all data members of the outer, including private ones, as demonstrated above with data. This also explains why a member inner instance keeps its outer alive for garbage collection — the inner cannot be collected while something holds the inner, because the inner transitively holds the outer.

Real-world: This file naming is visible when you inspect build directories and explains why you see \\$ in stack traces for inner classes. Tooling that filters or instruments classes (ProGuard, JaCoCo, coverage reports) must handle \\$ names explicitly; misconfigured filters that ignore \\$ may silently skip inner classes.

Visual — picture a house diagram: a large rectangle Outer containing a field private data = 10 and a smaller rectangle Inner with an arrow Outer.this pointing back to the house's wall. Outside, two .class files are drawn: Outer.class and Outer\\$Inner.class linked by that same \\$ arrow. Below, the construction syntax Outer o = new Outer(); Outer.Inner in = o.new Inner(); is annotated: o births Inner via o.new. X-axis is containment (outer contains inner), y-axis is bytecode split (one source, two class files). Takeaway: one source file, two class files, hidden link enabling private access.

Scope — when member-inner intimacy applies:

  • Applies: Inner is non-static, tied to an o. Inner.show() may freely read/write Outer instance and static members, call its private methods, even access Outer.this.data shadowed by a local data.
  • Does not: A static nested class (next) — no Outer.this, so no instance access.
  • What breaks if you detach: Forgetting the outer instance and trying new Outer.Inner() for a non-static inner gives an enclosing instance that contains Outer.Inner is required — you omitted the o. qualifier.

21.6.5 Static Nested Class — Capabilities and Limitations

Add the keyword static to an inner class — it becomes a fundamentally different creature:

class Outer {
    static int sData = 20;
    int iData = 10;
    static class Inner {
        void show() {
            System.out.println(sData);   // ok — static outer field
            // System.out.println(iData); // NOT allowed — no Outer.this
        }
    }
}

Rules — the professor listed them as a compact contract:

  • A static nested class is created inside a class and is itself prefixed with static — exactly static class Inner. It is still "nested" (declared inside Outer) but not an "inner" in the family-key sense; it is a type-level member, like a static field.
  • It can access static data members of the outer class, including private static members (sData), because statics belong to the type, not to any instance. It accesses them as Outer.sData or directly sData.
  • It cannot access non-static (instance) members or instance methods of the outer (iData, private int data, instance void print()), because there is no implicit outer instance attached — there is no Outer.this to look through. Trying System.out.println(iData) gives non-static variable iData cannot be referenced from a static context.
  • Only inner classes can be prefixed with static; you cannot write static class Outer at the top level — the language allows static only for members, and an inner class is a member while a top-level class is not (modifier static not allowed here at class level).

Object creation follows the qualified form without needing an outer instance — like a static field access:

Outer.Inner in = new Outer.Inner();
in.show(); // → 20
// No Outer o = new Outer(); needed before

Or, from inside Outer, simply Inner in = new Inner(); since Inner is in scope, but the canonical external form is new Outer.Inner().

Comparison — member inner versus static nested at a glance:

Member inner:     class Outer { class Inner { ... } }
                  Outer o = new Outer();
                  Outer.Inner in = o.new Inner();   // needs o
                  Inner can access outer instance + static

Static nested:    class Outer { static class Inner { ... } }
                  Outer.Inner in = new Outer.Inner(); // no o needed
                  Inner can access only outer static

Two traces — what compiles and what does not:

// Member inner — instance access allowed
class Outer { private int data = 10; class Inner { void show(){ System.out.println(data); } } }
Outer o = new Outer(); Outer.Inner in = o.new Inner(); in.show(); // → 10

// Static nested — instance access forbidden
class Outer2 {
    static int sData = 20; int iData = 10;
    static class Inner { void show(){ System.out.println(sData); } } // ok
    // static class Bad { void show(){ System.out.println(iData); }} // compile error
}
Outer2.Inner in2 = new Outer2.Inner(); in2.show(); // → 20

Sense-check: static in front of Inner detaches the \\$ class file as well — there will still be Outer\\$Inner.class on disk, but its internal synthetic field is static, not an instance this\\$0.

When to pick which? Use a member inner class when the inner logically needs intimate access to per-object state — classic example: an Iterator inner class that must read the enclosing ArrayList's private elementData and size. Use a static nested class when the helper is logically grouped with Outer for namespace reasons but does not need an outer instance — e.g., Map.Entry (an entry does not need the map instance), or a Builder nested inside the class it builds, or a utility holder that only touches private static configuration.

Pitfalls — member vs static nesting confusion:

  • Using new Outer.Inner() for a non-static inner. Gives an enclosing instance is required. For non-static, you need o.new Inner().
  • Trying to mark a top-level class static. static class Outer { } at file scope is illegal — static only for members.
  • Accessing iData from a static class Inner. The missing Outer.this makes instance access impossible; pass an explicit Outer reference to the static inner's constructor if you need per-object data: static class Inner { void show(Outer o){ System.out.println(o.iData); } }.
  • Over-retaining the outer via a long-lived inner. A non-static Inner instance captured in a collection or listener keeps its Outer.this alive, potentially leaking memory. If the inner does not need outer state, make it static to break the hidden reference.

Recap — class Outer { class Inner { } } is a member inner class: like a field, holds Outer.this, accesses private instance+static, built as o.new Inner(), compiled to Outer\\$Inner.class. class Outer { static class Inner { } } is a static nested class: type-level member, no Outer.this, accesses only static (including private static), built as new Outer.Inner(), top-level cannot be static. Bridge — Member and static nests cover named classes that live for many uses. For one-off, unnamed work — implementing an interface or subclassing once at the call site — and for helpers confined to a single method, the anonymous and local inner forms take over.

Exam note: Expect to (a) write Outer o = new Outer(); Outer.Inner in = o.new Inner(); in.show(); vs Outer.Inner in = new Outer.Inner();, (b) name the two files Outer.class and Outer\\$Inner.class, and (c) state which outer members each kind can touch (private instance allowed for member inner only; private static for both).

Real-world — Member inner classes are the idiom for tight coupling such as an ArrayList's Itr iterator that needs private access to its enclosing collection's internal array and count, or a GUI component's inner listener that manipulates the outer panel's private fields. Static nested classes group logically related utilities — Map.Entry, Outer.Builder — that need access only to static configuration without holding an outer instance.

21.7 Anonymous and Local Inner Classes

21.7.1 Anonymous Inner Classes — A Class With No Name

Hook: What if you need to override a method exactly once and never again — is it worth giving that subclass a name and a file?

An anonymous inner class (a class with no name) is a class that has no identifier between new and the opening brace {. Because it has no name, you create only a single object of it, typically at the exact point where you need to override a method or implement an interface inline — often as an argument or right-hand side of an assignment.

The syntax the lecture described precisely: it looks like the invocation of a constructor, except that a class definition is contained within the following block in curly braces. You start where a normal new Outer() or new SomeInterface() would go, you append an opening brace {, you supply the method bodies you need, you close the brace }, and you terminate the whole expression with a semicolon ;. The block between the braces is the class body, and it has no name between new and the brace.

Skeleton:

Outer o = new Outer() {
    // anonymous class body: override one or more methods
    void show() { System.out.println("anonymous impl"); }
}; // ← semicolon ends the assignment that contains the class definition

Or with an interface:

Outer o = new Outer() { // Outer is interface
    public void show() { ... }
};

Key characteristics to internalize:

  • The name of the actual class created is decided by the compiler; it is anonymous to you as the user, though the compiler assigns an internal synthetic name manifest as EnclosingClass\\$1.class, \\$2, etc., visible in build directories.
  • The anonymous class extends the outer class (or implements the interface) and gives an implementation for the declared abstract method(s); the object of the anonymous class is referred to by the variable of the outer type (Outer o) or directly as an argument.
  • This is useful when a method or interface is to be overridden or implemented just once and does not warrant a full named subclass in its own file — classic one-off callbacks, GUI listeners, or test doubles.

Intuition — everyday picture: Think of a sticky note vs a printed form. A named subclass is a printed form with a title you file and reuse (class MyCallback extends Callback). An anonymous class is a sticky note you scribble at the desk where you need it, stick it once, and never file — it does one job and has no title. You can still call the methods through the type written on the desk (Callback), but the note itself has no reusable name.

Mapping: new Type() { ... } = desk position + sticky note body, outer type = desk label, single o reference = the hand holding the note. Break point: a sticky note could in theory add a new scribble only you can see (void extra(){} inside the note); an anonymous class can add extra() but outside code cannot see it through Callback — the hand's type determines the interface.

21.7.2 Worked Example — Anonymous Class Extending an Abstract Class

Setup: An abstract outer class with an abstract method and a concrete method — this is the lecture's first anonymous case, choosing an abstract class as the base to show that the anonymous class can both override the abstract and inherit the concrete:

abstract class Outer {
    abstract void show();
    void print() { System.out.println("within print"); }
}

Anonymous construction in another class Test inside main — the critical line the professor asked you to read character-by-character:

public class Test {
    public static void main(String[] args) {
        Outer o = new Outer() {
            void show() {
                System.out.println("within show of anonymous");
            }
        }; // semicolon terminates the anonymous class expression
        o.show();
        o.print();
    }
}

Reading Outer o = new Outer() { ... };:

  • You appear to invoke the constructor of Outer (new Outer()), but because Outer is abstract, you cannot instantiate it bare — new Outer() alone would give Outer is abstract; cannot be instantiated.
  • The trailing { void show(){...} } defines an unnamed subclass of Outer that supplies show, and o refers to the single instance of that unnamed subclass. The compiler invents a name like Test\\$1 that extends Outer.
  • The anonymous class started at the { after new Outer() and ended at the } before the ;. The ; belongs to the assignment statement, not to the class — forgetting it gives ';' expected.
  • Calls o.show() and o.print() both work because o holds a reference to the anonymous subclass which inherits the concrete print method from Outer and supplies show. o.print()within print (inherited), o.show()within show of anonymous (overridden).

Driver trace — line by line:

Outer o = new Outer() {                    // create unnamed subclass of Outer, single instance
    void show(){ System.out.println("within show of anonymous"); }
};                                         // assignment ends

o.show();  // dispatches to anonymous show → within show of anonymous
o.print(); // dispatches to inherited Outer.print → within print

// In the file system:
Outer.class        // abstract base
Test.class         // enclosing class
Test\\$1.class       // anonymous subclass of Outer (synthetic name)

Sense-check: o instanceof Outer is true — the anonymous instance is-an Outer. The type Outer itself is still abstract; only the unnamed child is instantiable.

Visual — draw Outer as a dashed abstract box with show() dashed (abstract) and print() solid (concrete). An arrow labelled new Outer(){show()} spawns a small solid unnamed box below it (Test\\$1) containing show solid; a dashed inheritance arrow points back up to Outer and the print box is shown as inherited. A variable arrow o : Outer points to the unnamed box. X-axis is time (definition site), y-axis is type (abstract vs concrete). Takeaway: definition and instantiation happen together at one line.

21.7.3 Worked Example — Anonymous Class Implementing an Interface

The same mechanism works with an interface — just as powerful, with identical syntax, only the base type is interface instead of abstract class. Replace the abstract class with:

interface Outer {
    void show(); // public abstract, implicitly
}

In Test.main — note the public now required on the implementing method because interface methods are public abstract:

public class Test {
    public static void main(String[] args) {
        Outer o = new Outer() {
            public void show() {
                System.out.println("within show of anonymous interface impl");
            }
        }; // anonymous class ends at }, ; ends the assignment
        o.show(); // → within show of anonymous interface impl
    }
}

Again, you create an object of the outer entity (here an interface) and at the same time you define the method inside an anonymous class body that has no name. The class started at { after new Outer() and ended at }, with no name present. The object o can call show via the interface reference, dispatched to the anonymous body.

General anonymous syntax — class vs interface:

// Anonymous extending abstract class:
OuterClass o = new OuterClass([args]) {
    // override / implement abstract methods; may call super / access outer
};

// Anonymous implementing interface:
OuterInterface o = new OuterInterface() {
    // implement every abstract method, each must be public
};
  • In both cases, (args) goes to the superclass constructor; for an interface the () is empty.
  • Inside the braces you may also have instance initializer blocks, extra fields, and (bounded) access to final or effectively-final locals — the access-to-enclosing-scope privilege discussed in T2 Chapter 4.

A telling detail from the companion text — anonymous is a special case of inner class, and final matters for captured locals. If main has final JTextField textField and the anonymous ActionListener does textField.setText("Hello"), the inner method can access the enclosing variable only because it is final (pre-JDK 8 rule) or effectively final (JDK 8+). The lecture instance used a non-GUI analogue, but the scoping principle is identical.

Two instantiations in one method — each is a different anonymous class instance:

interface Outer { void show(); }

Outer o1 = new Outer() { public void show(){ System.out.println("first"); } };
Outer o2 = new Outer() { public void show(){ System.out.println("second"); } };
o1.show(); // → first
o2.show(); // → second
// On disk: Test\\$1.class and Test\\$2.class — two distinct synthetic names

Even if the bodies look identical, each new Outer(){...} expression creates a distinct anonymous class type.

21.7.4 Limitations of Anonymous Classes

Three limitations were highlighted — exam-distractor candidates because they discriminate anonymous from named inner classes:

  • Synthetic name: The name of the actual class created is decided by the compiler; it is anonymous to you as the user, though the compiler assigns an internal synthetic name manifest as EnclosingClass\\$1.class style (or Outer\\$1.class within a test file). You never write that name in source, but you will see it in .class listings, debugger frames, and coverage reports. The numbering increments with each anonymous declaration in the file.
  • Single inheritance and outer-typed reference: The anonymous class extends the outer class (or implements the interface) and gives an implementation for the declared method(s); the object of the anonymous class is referred to by the variable of the outer type (Outer o). This means o is typed as Outer, not as the anonymous type — the anonymous type has no name to declare a variable of that type. Accessing through o sees only what Outer exposes. This is the same hidden-Outer.this and capture semantics as member inner classes, applied inline.
  • No additional visible methods: An anonymous class cannot usefully expose additional methods beyond those visible through the outer reference because it is accessed using the reference to the outer type. If you add a fresh method void extra(){ System.out.println("extra"); } inside the anonymous body, you cannot call o.extra() because the static type of o is Outer, which knows nothing about extra. The compiler checks the declared type of o, not the runtime synthetic type. The extra method exists inside the synthetic class but is unreachable through Outer — it is essentially dead unless called internally or via reflection.
Outer o = new Outer() {
    void show(){ System.out.println("show"); }
    void extra(){ System.out.println("extra"); } // exists, but...
};
o.show();  // ok — Outer declares show
// o.extra(); // compile error: cannot find symbol: method extra()

Consequence: anonymous classes are limited to overriding or implementing what the outer type already declares — they cannot extend the API visible through o. If you need an extra method to be callable from outside, use a named inner or top-level class with its own type.

A fourth implicit limitation the professor noted in passing: anonymous classes cannot have an explicit named constructor — they have no name to write one with. You can only supply an instance initializer { ... } or call the superclass constructor arguments inside new Outer(args), but not public Anonymous(){...}.

Scope — when anonymous is the right tool and when it is not:

  • Use anonymous when: The override/implementation is one-off, short (a few lines), and has no extra state or helpers beyond the outer interface. Classic one-shot is a GUI ActionListener, a Comparator inline, or a tiny Callback test stub.
  • Avoid anonymous when: You need the same behaviour in multiple places (duplicate bodies), you need extra public methods, you need a constructor, or the body is long enough to hurt readability — then a named static nested class or a lambda (for functional interfaces) is cleaner. Over-nesting anonymous classes also produces \\$1, \\$2 noise and confuses debuggers.

Visual — picture Outer o as a hand labelled Outer holding a sticky note. The note has show() written and a second scribble extra() crossed out with an arrow o.extra() blocked by a wall labelled "Outer has no extra()". X-axis is reference type visibility, y-axis is implementation. Takeaway: body exists on the note but only Outer's ink is visible through the hand.

Pitfalls — anonymous traps:

  • Trying o.extra() after defining extra() inside the anonymous body. The body compiles but the call through Outer fails — static type, not runtime type, controls access.
  • Forgetting the terminating ; after } in Outer o = new Outer(){...};. The }; is two tokens: } ends the class, ; ends the assignment. Omitting ; gives a cryptic ';' expected inside a larger expression.
  • Calling new Interface() without {...}. Bare new Outer() where Outer is an interface gives interface cannot be instantiated; the { ... } is what makes it an anonymous implementing class.
  • Needing a constructor or complex init. Anonymous has none — use an instance initializer { data = 5; } or just write a named static class MyImpl implements Outer with a real constructor.
  • Capturing a non-final local pre-JDK 8. for (int i=0; i<3; i++) { Outer o = new Outer(){ void show(){ System.out.println(i); } }; } would fail unless i were made final / effectively final (the MouseListener with textField example relies on final JTextField textField).

21.7.5 Local Inner Classes — Classes Inside Methods

Purpose: A local inner class confines a helper class to the single method that needs it, reducing namespace pollution and tying lifetime to the method activation — a smaller scope than any named inner class.

Definition: A local inner class is a class created within the method definition of the outer class. Its scope is the bracket { } of the method in which you create it — exactly like a local variable's scope. Outside that method's braces, the type does not exist.

Illustration — the lecture's minimal trace:

class Outer {
    void outerMethod() {
        class Inner {
            void show() { System.out.println("inside local inner"); }
        }
        Inner i = new Inner(); // must be inside the same method
        i.show();              // → inside local inner
    }
}

So you have Outer, its method outerMethod, and inside that method you declare class Inner — no public, no Outer. qualifier needed, just class Inner. Object creation new Inner() and the call i.show() must occur within the same method scope; you cannot create Inner outside outerMethod because the type is invisible there — cannot find symbol: class Inner if you try new Inner() in main.

When you create an object of Outer and call outerMethod(), that method instantiates its own local Inner and exercises it:

Outer o = new Outer();
o.outerMethod(); // → inside local inner

You cannot directly call the inner's method via an outer object without going through the enclosing method, because again the inner type is lexically confined to the method. The path Outer o = new Outer(); Outer.Inner in = ... does not work — Inner is not Outer.Inner; it is only Inner within outerMethod's block.

Rules the professor stressed alongside local inners:

  • A local inner class may access outer class instance fields and methods (via Outer.this) and static members, plus final / effectively-final locals and parameters of the enclosing method. It cannot access a non-final mutable local that changes afterwards (the same capture rule as anonymous classes, rooted in the compiler copying values).
  • It cannot have public/private/protected before class Inner — local classes are like local variables, they have no access modifier.
  • It cannot be static as static class Inner inside a method — local classes are inherently tied to the method activation, not to the type.
  • Compiled form: Outer\\$1Inner.class or Outer\\$1\\$Inner.class depending on nesting — another \\$ on disk, but now numbered by method scope. The file artifacts are part of what the exam flagged as "know the dollar naming".

Comparison — local vs anonymous vs member:

Member inner:   class Outer { class Inner { void show(){} } }     // in class scope, needs o.new
Anonymous:      Outer o = new Outer(){ void show(){} };            // no name, one object, at new
Local:          void outerMethod(){ class Inner{ void show(){} }   // in method scope, new only there
                    Inner i = new Inner(); i.show(); }
Static nested:  class Outer { static class Inner{} }               // type member, no Outer.this

Scope — when local is ideal:

  • Use local when: The helper class is meaningful only inside one method (e.g., a comparator or validator used only within outerMethod's loop), and you want to avoid polluting the class namespace with a helper no other method should see.
  • Do not: Use local as a general-purpose replacement for a member inner when other methods also need the helper — then a member inner or static nested is appropriate.
  • Capture limitation: Like anonymous, a local class capturing a local int x requires x to be final / effectively final. Mutating x after the class declaration but before instantiation will break compilation.

Visual — draw Outer as an outer rectangle, outerMethod() as a smaller rounded rectangle inside it, Inner drawn inside that rounded rectangle with arrows pointing only within it. An arrow from main trying to new Inner() from outside the rounded rectangle is blocked by a wall labelled "scope = method braces". Takeaway: Inner lives and dies within outerMethod's { }.

Recap — Anonymous class: new Outer(){ void show(){...} }; — no name, one object, compiler names it \\$1, accessed through Outer, cannot expose extra() beyond Outer. Local class: void outerMethod(){ class Inner{ void show(){...}} Inner i = new Inner(); i.show(); } — visible only inside the method braces, cannot be Outer.Inner, cannot be static.

Bridge — All nesting forms (interfaces and classes, member/static/anonymous/local) shared the rules of qualified names, implicit static, and member privilege. The lecture now pivots from structural nesting to behavioural contracts at runtime — how Java prevents abnormal termination when risky code fails — the try-catch-finally, throw/throws model.

Exam note: Be able to (a) identify { ... } after new Outer() or new OuterInterface() as the anonymous class body and state the required ;, (b) state that o.extra() is illegal because Outer has no extra(), (c) state that Inner in outerMethod cannot be created outside that method, and (d) sketch Outer\\$1.class vs Outer\\$Inner.class vs Outer\\$1Inner.class naming.

Real-world — Anonymous inner classes for one-off overrides were the idiom for event listeners and callbacks before lambdas — button.addActionListener(new ActionListener(){ public void actionPerformed(...){...}}) and Collections.sort(list, new Comparator<Country>(){...}). Local inner classes reduce namespace pollution when a method needs a small helper (a validator, a parser state) that should never be exposed to the rest of the class.

21.8 Exception Handling — Fundamentals and the Try-Catch-Finally Model

21.8.1 What an Exception Is — Compile-Time vs Runtime Errors

Hook: The compiler promises to catch missing semicolons before you run — but who catches a division by zero that only appears when a user types 0 at runtime?

When you run a program you may experience errors. The professor organized them into two broad categories that predict who is responsible for finding them.

  • Compile-time errors (checked by the compiler before execution, also called static errors) — the compiler discovers these before any line runs. Examples the lecture used: missing semicolon, misspelling an identifier (prtinln for println), using a variable without declaration, or mismatched types in an assignment that the compiler can prove wrong. You discover these immediately when you compile; the program refuses to run — javac reports the file and line and you fix it. No runtime behaviour is involved.
  • Runtime errors (not detected at compile time, also called dynamic errors) — the compiler cannot know at compile time that a user will supply 0 as a divisor, or 12 elements for a size-10 array, or a string "hello" where Integer.parseInt expects digits, or that the console will fail to respond to readLine. These errors surface only while the program runs and may cause abnormal termination — the method stops abruptly, a default stack trace prints, and no user-friendly explanation appears. This runtime failure situation is what is called an exception (an object that represents an abnormal condition at runtime).

Formal terms — first-use definitions:

  • An exception — an object (an instance of class Throwable or its subclass) that describes an abnormal condition that arose during execution. When the condition happens, the runtime creates the exception object and "throws" it.
  • Compile-time error — a static fault the compiler detects by analysis of source text. The program never starts.
  • Runtime error / exception — a dynamic fault discovered by executing code. The program started, then failed at a specific statement.

Simple rule to separate them: Ask "Does the compiler have enough information without running the program and without reading runtime input?" If yes, it is compile-time. If the fault depends on runtime data (user input, file existence, array length at execution, arithmetic divisor value), it is an exception.

Intuition — airport metaphor: Compile-time error is being denied boarding at the gate because your passport expired (checked before takeoff). Runtime exception is an engine warning light that appears mid-flight because a sensor read a bad value — the plane was already airborne. The gate cannot predict the warning light; the pilot (exception handler) must handle it in flight.

Mapping: compiler = gate agent, compilation = boarding check, execution = flight, runtime exception = in-flight abnormal condition, stack trace = black-box log. Break: unlike a flight, the JVM can catch and continue the flight — we will add oxygen masks (try-catch-finally).

21.8.2 Why Abnormal Termination Must Be Avoided

If a runtime error occurs, the program's default behaviour is to terminate abnormally inside the method where the fault happened, unwind the call stack, print a raw stack trace (class, method, file, line number), and stop — with no explanation tailored for the user. That trace is useful for debugging but bewildering for an end user.

Exception handling is about making the user aware of the problem in a controlled way and keeping the system in a defined state. Instead of vanishing with Exception in thread "main" java.lang.ArithmeticException: / by zero at Exc0.main..., the program should print a clear message that some exception has occurred (division by 0), handle it gracefully (perhaps retry, perhaps return a safe default), and continue or exit cleanly without an abrupt crash. The entire try-catch-finally-throw-throws apparatus serves this single goal: convert an uncontrolled crash into a controlled, user-visible handling path.

Goals of exception handling — why it is not optional luxury:

  • Avoid abnormal termination — catch near the point of failure and decide: recover, retry, report, or close cleanly.
  • Make the user aware — replace a raw stack trace with a user-facing message ("input output exception generated", "invalid width") on System.out.
  • Keep the contract explicit — checked exceptions force the API consumer to acknowledge the risk via throws or try-catch; unchecked ones keep accidental arithmetic/index faults debuggable.

The professor's verbal emphasis: handling costs extra code (try wrapper, catch variable), but unbounded crashing costs trust.

21.8.3 The Try Block — Guarding Risky Statements

The statements for which you know an exception may occur you write inside a try block. The verbal description preserved from the lecture: put the division c = a / b; inside try { ... } because division may generate an exception if b is zero. A method can have nested or multiple try blocks; nesting — a try inside a try — is permitted and is illustrated in section 21.9.6.

Syntax and skeleton — purpose, inputs, outputs:

  • Purpose: Mark the risky statements that the JVM should monitor for throwables.
  • Inputs: Ordinary Java statements that might throw — e.g. c = a/b;, String s = br.readLine();, int x = a[42];, int v = Integer.parseInt(s);.
  • Outputs: Either normal exit (no exception → fall through to after catch) or abrupt exit via a thrown Throwable that jumps to a matching catch.
int a, b, c;
a = 10; b = 0; // b will be read from user in the general case; compiler cannot know it is 0
try {
    c = a / b; // this may generate ArithmeticException at runtime
    System.out.println("result " + c); // skipped if exception thrown
} catch (ArithmeticException e) {
    // handler (next subsection)
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
    String s = br.readLine(); // may throw IOException (checked)
}

Here a / b where tends to infinity mathematically and is illegal for integer division; at runtime the JVM throws ArithmeticException: / by zero. Wrapping it in try marks the risk and gives a place for a handler to catch it, instead of letting the default handler terminate the program.

Important execution flow: Once an exception is thrown inside try, the remaining statements inside that try are not executed — control jumps immediately to the matching catch. So System.out.println("This will not be printed.") after int d = 42 / b; inside try is skipped when b == 0.

The lecture's mathematical note: for integers, division by zero is undefined and the JVM defines it to throw ArithmeticException. For floating-point double division, a / 0.0 would yield Infinity per IEEE 754 and not throw — a distinction that sometimes appears as a boundary-condition pitfall.

Visual — picture a highway with a dashed construction zone labelled try { risky statements }. A guard rail catch runs alongside. Cars (control flow) travel normally through the zone; when a red exception flag pops up on a statement (a / b with b=0), the car swerves instantly over the guard rail into the adjacent catch lane, skipping the remaining zone. X-axis is program time (statement order), y-axis is execution path (normal vs handling). Takeaway: try encloses risk; an exception exits the try at the exact failing statement.

Scope — when try applies and nesting:

  • Applies: Any sequence with at least one checked or unchecked risk — arithmetic divisor, array index, type conversion (parseInt), or I/O readLine. Multiple risky statements may share one try.
  • Does not: Wrap code where you never intend to handle locally — then use throws to forward (see 21.8.6).
  • Nesting: try { outer ... try { inner ... } catch (Inner) {} } catch (Outer) {} is legal; unhandled inner propagates to outer. Each try needs at least one catch or one finally.

21.8.4 The Catch Block — Receiving and Handling the Thrown Exception

Every try should be followed by at least one catch block. A catch receives an argument describing the exception type — just like a method parameter — and its block contains the handling code. It is conditional — it works only when an exception is present; if no exception is generated inside try, the catch is skipped entirely and execution continues after the try-catch.

int a = 10, b = 0, c;
try {
    c = a / b; // may throw ArithmeticException
    System.out.println("no exception path");
} catch (ArithmeticException e) {
    System.out.println("division by 0"); // e is the thrown object
}
System.out.println("after catch"); // always reached unless handler rethrows

catch receives an argument of Exception or its derived class, matching the exception the try threw. The parameter ArithmeticException e names the throwable object so the handler can inspect it (e, e.getMessage(), e.printStackTrace()), or just ignore details and print a fixed message.

You can have multiple catches for one try, which behave like case labels in a switch, each targeting a different exception class — ordered most-specific first (see section 21.9.5):

try {
    c = a / b; // this may generate ArithmeticException
    String s = br.readLine(); // or IOException
} catch (ArithmeticException e) {
    System.out.println("division by 0");
} catch (IOException e) {
    System.out.println("input output exception generated");
} catch (Exception e) {
    System.out.println("some other exception");
}
System.out.println("after catch");

If a / b throws ArithmeticException (say with integer division), the first catch (ArithmeticException e) matches (exact type), prints division by 0, and the program avoids abnormal termination, proceeding to after catch. The second catch (IOException e) and third catch (Exception e) are bypassed — first match wins, like switch fallthrough without fall.

The second catch (Exception e) is broader — it would handle other Exception-based types if they arose — for instance, if inputs were not initialized and the user typed a non-numeric string where Integer.parseInt was called, a NumberFormatException could appear; or if an array of size 10 received 12 values via a[12], an ArrayIndexOutOfBoundsException could appear. Each fault has its own appropriate catch class (see the multiple-catch example where all three are demonstrated).

Crucial control-flow fact: a catch does not "return to" the try. Once jumped to catch, remaining try statements are not replayed; after handling, execution continues after the whole try-catch-finally construct.

Worked trace — the integer division by zero the lecture built:

int a = 10, b = 0, c;
try {
    c = a / b;                              // throws ArithmeticException: / by zero
    System.out.println("This will not be printed.");
} catch (ArithmeticException e) {
    System.out.println("division by 0");    // prints
} catch (Exception e) {
    System.out.println("some other exception");
}
System.out.println("after catch");          // prints — program survived

// Without the catch, the default handler would have printed:
// Exception in thread "main" java.lang.ArithmeticException: / by zero
//    at Exc0.main(Exc0.java:4)

Algebraic note: For integer arithmetic, with is undefined and defined by the JVM to throw ArithmeticException. For double, yields or NaN and does not throw — a worthwhile boundary check in mixed-numeric code.

Sense-check: catch (Exception e) must appear after catch (ArithmeticException e), because ArithmeticException is a subclass of Exception; reversing the order makes the subclass catch unreachable and the compiler rejects it (exception has already been caught).

21.8.5 The Finally Block — Unconditional Execution

The third construct is finally. There can be only one finally associated with a try-catch sequence, and it does not receive an exception argument. It is unconditional: whether there is an exception or not, whether a catch handled it or an exception slipped through, the statements inside finally always execute — on the way out of the try-catch, before the method returns or propagates.

try {
    // risky statements: c = a / b; String s = br.readLine(); s = a[2];
} catch (ArithmeticException e) {
    System.out.println("division by 0");
} catch (IOException e) {
    System.out.println("IOException generated");
} finally {
    // always runs — close resources, print completion note, etc.
    System.out.println("finally always executes");
}

Typical use: cleanup that must happen regardless — closing a file handle, releasing a lock, flushing a buffer, printing a procC's finally completion trace as in the reference example.

try {
    br = new BufferedReader(new InputStreamReader(System.in));
    String s = br.readLine();
} catch (IOException e) {
    System.out.println("IOException generated");
} finally {
    if (br != null) br.close(); // always close, success or failure
}

A question answered in the source: Is finally optional? Inclusion of finally in your program is optional — it is your choice whether to write it. But if you do include it, it will always execute. It is not conditional like catch, which runs only when its exception type appears. You may have try-catch-finally, try-catch without finally, or try-finally without catch (mainly for cleanup when you intend to let the exception propagate).

Important subtlety the reference flags: If a finally block appears, it executes even when the try exits via return or via an uncaught exception — just before the method returns. And each try needs at least one catch or one finally — a bare try { ... } alone does not compile.

Scope — when finally is right and who must write it:

  • Use finally for: Resource release (close()), lock release, completion logging — anything that must occur on both paths.
  • Do not: Use finally to handle the exception itself — it has no catch parameter and cannot distinguish which exception fired. For that you need catch.
  • What breaks if you rely on catch alone: try { br = new ...; s = br.readLine(); return s; } catch (IOException e){ ... } without finally may leak the BufferedReader when early return succeeds — finally with br.close() would still run.

Visual — picture three doors in sequence: trycatch (conditional door)finally (unconditional door). Two balls are drawn: one labelled "no exception" that rolls straight through try, skips the catch door, and exits through finally; one labelled "exception" that bounces off try into catch, then both balls converge and must exit through the same finally door, whose sign reads "always". Takeaway: catch is conditional; finally is unconditional.

21.8.6 Throw vs Throws — Generating vs Forwarding

Two more statements complete the four-keyword picture (try, catch, finally plus this pair). Their names differ by one letter but roles are opposite.

  • throw is used to generate an exception at the point where you detect a fault. You write throw new InvalidBoxDimensionException(length); to create a new exception object and immediately throw it — control leaves that point and seeks a matching catch up the call stack. This clause can only be used with objects of throwable type (throw new String("hi") would not compile).
if (l &lt;= 0) throw new InvalidBoxDimensionException(l);
  • throws is used to forward (declare) an exception on a method/constructor header. You annotate the header with throws IOException or throws InvalidBoxDimensionException to say: if any exception of this type is generated while this method executes and is not caught locally, forward it to the caller — declare who should handle it. You are not handling it locally; you are declaring the handling obligation upward. This is the handle-or-specify contract for checked exceptions.
void read() throws IOException {
    String s = br.readLine(); // readLine declares throws IOException — caller must handle or re-throws
}
Box(int l, int w, int h) throws InvalidBoxDimensionException {
    if (l &lt;= 0) throw new InvalidBoxDimensionException(l);
}

Example contrast from the BufferedReader discussion that the professor returned to twice:

// Strategy 1 — forward with throws (let caller handle)
void input() throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String s = br.readLine(); // if IOException fired, it is forwarded to IOException handler
}

// Strategy 2 — handle locally with try-catch (handle here)
void input() {
    try {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine();
    } catch (IOException e) {
        System.out.println("input output exception generated");
    }
}

These are two equivalent ways to satisfy the checked-exception contract for IOException: handle it now with try-catch, or specify that someone else will handle it with throws on the method header and let the exception propagate — as seen in class ThrowsDemo { static void throwOne() throws IllegalAccessException { throw new IllegalAccessException("demo"); } } where main then catches it.

throw vs throws — one-line discriminator:

  • throw new X(args)inside a method/constructor body, creates and fires an X right there.
  • throws Xon a method/constructor declaration, lists types that may escape, forwarding responsibility to the caller.

One is a verb (do it now), one is a declaration (may happen, handle above).

Math-flavoured instantiation — invalid box dimension as throw:

For a box with dimensions , validity is . The check and throw pair:

if (w &lt;= 0) throw new InvalidBoxDimensionException(w);
// w = 0 triggers w ≤ 0, new object InvalidBoxDimensionException(0) created, stack jumps to nearest
// catch (InvalidBoxDimensionException e)

For division, where the code detects could pre-throw:

if (b == 0) throw new ArithmeticException("/ by zero");
c = a / b;

Either the JVM auto-throws on a / 0 or you manually throw after the if; both reach a catch (ArithmeticException e).

Common pitfall flipped: Writing throws new InvalidBoxDimensionException(l); on a header (merging the two) does not compile — throws lists a type, not an instantiation. Correct split is Box(...) throws InvalidBoxDimensionException { ... throw new InvalidBoxDimensionException(l); }.

Pitfalls — throw/throws/try/catch confusion the exam hunts:

  • Writing throw on a method header or throws inside a body without new. void foo() throw IOException { throw IOException; } fails — correct is void foo() throws IOException { throw new IOException("oops"); }.
  • Forgetting to handle or specify a checked exception. Bare String s = br.readLine(); with neither surrounding try-catch(IOException) nor method header throws IOException gives unreported exception java.io.IOException must be caught or declared to be thrown — line flagged as culprit (section 21.9.4).
  • Catching in wrong order for throws hierarchy. catch (Exception e) before catch (IOException e) makes the latter unreachable — subclasses first. finally cannot receive a typed parameter at all.
  • Expecting finally not to run after return. try { return; } finally { cleanup(); }cleanup() does run before the method returns, which matters for close() before exit.

Recap — Compile-time error is caught by javac (missing ;); runtime exception is a throwable object at c = a/b with and is caught by handlers, not by the compiler. The guard pattern is try { risky } catch (X e){handle} [finally {always}]; throw new X() generates at the if point and throws X on a header forwards the obligation upward. Bridge — IOException forced throws in the examples while ArithmeticException did not — why some exceptions must be declared and others need not. That is the Throwable hierarchy and the checked vs unchecked contract, next.

Exam note: Expect to wrap c = a/b; with try { } catch (ArithmeticException e){ System.out.println("division by 0"); } finally { }, to distinguish throw new InvalidBoxDimensionException(l) inside an if (l<=0) from Box(...) throws InvalidBoxDimensionException on the header, and to state that finally inclusion is optional but if present it is unconditional.

Real-world — This try-catch-finally-throw-throws pattern underpins production error handling: BufferedReader.readLine() and Files.readAllLines force checked IOException handling; arithmetic and indexing faults are naturally caught as unchecked; the finally block still appears in resource handling (file close, connection close, lock unlock) where cleanup must be guaranteed whether the try succeeded or threw, even though modern try-with-resources often supersedes manual finally for Closeable resources.

21.9 Exception Hierarchy, Checked vs Unchecked and Multiple and Nested Handling

21.9.1 The Throwable Hierarchy — Throwable, Exception and Error

Hook: Not every throwable is meant to be caught — the hierarchy tells you which branch you are allowed to handle and which branch you should leave alone.

Java organizes throwables as an inheritance hierarchy with a single root. At the root is Throwable, an inbuilt class that extends Object and provides the stack-trace machinery (printStackTrace(), getMessage(), getCause()). Throwable has two direct subclasses that partition all throwables into two distinct branches — the standard diagram the lecture referenced and reproduced from T6 Chapter 10:

  • Exception — the root of all exceptions that a programmer can and should handle under normal circumstances. As a user you can catch runtime faults, I/O faults, and other handleable conditions; they all belong to Exception or one of its subclasses. Creating your own custom exception means subclassing Exception (or RuntimeException — see below).
  • Error — exceptions that are not to be caught under normal circumstances, even though technically you could write catch (Error e). You as a user cannot sensibly handle high-level errors that belong to Error — they model virtual-machine or environment failures such as StackOverflowError, OutOfMemoryError, VirtualMachineError, or LinkageError. They are present in the hierarchy but are not the focus of ordinary try-catch.

Under Exception, several subclasses branch further, each refining the fault family:

Throwable
├── Error (unchecked, not normally caught — VM failures)
│   └── StackOverflowError, OutOfMemoryError, ...
└── Exception (handleable)
    ├── IOException (I/O faults — checked)
    │   └── FileNotFoundException, ...
    ├── InterruptedException, ClassNotFoundException, ... (checked)
    └── RuntimeException (unchecked — runtime faults)
        ├── NullPointerException
        ├── ArithmeticException (e.g. divide by zero)
        ├── ArrayIndexOutOfBoundsException (≈ IndexOutOfBoundsException branch)
        ├── ClassCastException
        ├── NumberFormatException
        ├── IllegalArgumentException, ...
        └── ...

Notes the professor emphasized:

  • The picture Throwable at top, splitting into Exception and Error, with Exception splitting into IOException and RuntimeException, and RuntimeException splitting into the arithmetic/null-pointer/cast families, is the diagram to reproduce on the exam. Labelling each level with one example suffices.
  • Error and Exception are both subclasses of Throwable; Error is-not a subclass of Exception. Error and Exception are siblings, not parent-child.
  • Exception is checked, Error is unchecked, RuntimeException (a subclass of Exception) is unchecked — the checked vs unchecked split cuts within Exception, with Error on a separate unchecked branch.

Java built-in exception families at a glance (T6 Tables 10-1 / 10-2):

  • Unchecked via RuntimeException branch (no throws required): ArithmeticException (divide-by-zero), ArrayIndexOutOfBoundsException (and IndexOutOfBoundsException), NullPointerException, ClassCastException, IllegalArgumentException, NumberFormatException, UnsupportedOperationException, etc.
  • Checked via other Exception branch (must handle or declare throws): IOException (and FileNotFoundException, EOFException), ClassNotFoundException, CloneNotSupportedException, IllegalAccessException, InstantiationException, InterruptedException, etc.
  • Error branch (not normally caught): StackOverflowError, OutOfMemoryError, thread death.

The companion text lists these in Tables 10-1 (unchecked RuntimeException subclasses) and 10-2 (checked Exception subclasses).

21.9.2 Checked Exceptions — Compile-Time Checking and the Throws Contract

Checked exceptions are those checked at compile time — the compiler enforces handling. Formally, any subclass of Exception except RuntimeException and its descendants, and except Error, is checked. Equivalently: the set Exception \ (RuntimeException subtree ∪ Error subtree) is checked. IOException is the canonical canonical example the lecture returned to.

Rule: If a method throws any checked exception — either by explicitly throw new IOException(...) or by calling a method that declares throws IOException (such as BufferedReader.readLine()) — then the method has to handle the exception itself (wrap the risky statement in try-catch for IOException), or it must specify the exception using a throws clause on its header so the caller handles it. The compiler enforces this contract; the burden is static.

void input() throws IOException {               // specify
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String s = br.readLine(); // readLine declares throws IOException — caller must handle or specify
}

If you write bare String s = br.readLine(); without try-catch(IOException) and without throws IOException on the enclosing method, the compiler stops with error: unreported exception java.io.IOException must be caught or declared to be thrown and points to that line as culprit. This demonstrates that checked exceptions are indeed checked by the compiler: the problematic statement must be kept within try-catch or the method must declare throws.

The design rationale the professor borrowed from the companion text: checked exceptions model recoverable faults the caller can reasonably be expected to plan for — missing file, broken network, console failure — so the API contract forces the call site to acknowledge them. readLine returning a line from the console is exactly that: the console may fail at input time.

Checked vs throws contract choices per method:

  • Handle locally: try { s = br.readLine(); } catch (IOException e){ System.out.println("input output exception generated"); } — the method is self-contained.
  • Forward: void input() throws IOException { s = br.readLine(); } — the method declares throws and each caller must in turn handle or forward until some try-catch eventually appears in the chain; if no one catches, the default handler prints the stack trace.

The lecture used static void throwOne() throws IllegalAccessException { throw new IllegalAccessException("demo"); } and static void compute(int a) throws MyException { if (a>10) throw new MyException(a); } as checked-throw prototypes where throws on the header is compulsory — delete it and compilation fails.

21.9.3 Unchecked Exceptions — Runtime Subclasses

Unchecked exceptions are those not checked at compile time; they are automatically defined for programs and the compiler never forces you to write throws for them. Formally, any subclass of RuntimeException (and any Error) is unchecked.

Any subclass of RuntimeException is by default unchecked — the compiler does not force you to write a throws declaration for them, though you may still catch them if you wish. Similarly, Error subclasses are unchecked and are normally permitted to propagate to the default handler.

Examples the lecture listed — keep this list for multiple-catch discrimination:

  • ArithmeticException — integer division by zero (c = a / b with , or a/(a-a)).
  • ArrayIndexOutOfBoundsException — accessing a third element of a two-element array (int a[] = {5,10}; int x = a[2]; where valid indices are 0 and 1).
  • ClassCastException — casting an object to an incompatible type.
  • NumberFormatException (a subclass of IllegalArgumentException) — type-casting a non-numeric string, e.g. Integer.parseInt("hello") or the lecture's "casting a string to an int" naming.
  • NullPointerException — dereferencing null (String s = null; s.length();).

These all arise at runtime and the compiler does not force you to write throws ArithmeticException etc. You may write catch (ArithmeticException e) voluntarily to survive the fault (Division by zero. and a = 0; continue in the HandleError loop), but omitting it is not a compile error.

Intuition — why two kinds? Think of Checked vs Unchecked as appointments vs accidents. A checked exception (IOException) is a scheduled road closure you were warned about in the route plan (throws IOException on the sign). You must plan a detour in advance (try-catch or re-throws), or the journey refuses to start. An unchecked exception (ArithmeticException) is a pothole that may appear on any road at any speed — the map cannot list every pothole in advance, so you patch tires reactively where you choose (catch (ArithmeticException) if you care), but no advance detour declaration is demanded.

Real-world illustration the professor drew from the distinction: The reason network/file/console code is forced to handle IOException (checked — declared on readLine, FileReader, Socket APIs) while a pure arithmetic slip a / b or indexing a[2] is caught only where you choose to guard it is exactly the checked/unchecked divide. Production code often wraps the unchecked NumberFormatException from parseInt with its own catch to report "invalid input, expected a number", while throws IOException on the method is demanded by the compiler for any readLine path that is not caught.

One-line test the exam will ask:

  • "Is X checked?" → Is X a subclass of Exception excluding the RuntimeException subtree? If yes, checked (IOException yes, RuntimeException itself no, any RuntimeException subclass no).
  • Equivalently: checked = Exception minus RuntimeException and its descendants; unchecked = RuntimeException descendants + Error.
checked:    Exception \ RuntimeException subtree  (must try-catch or throws)
unchecked:  RuntimeException subtree ∪ Error       (may catch, never must throws)

Throwable itself sits above both — normally you do not catch Throwable because it would also swallow Error.

Scope — what the compiler actually demands:

  • Applies to: Any path that can throw a checked type — calling readLine(), new FileReader(...), throw new IllegalAccessException(...). The enclosing method must contain try-catch(Checked) or declare throws Checked.
  • Does not mandate for: throw new NullPointerException(...) or a / b where or a[2] or parseInt — all RuntimeException family; no throws required, no compile error for omitting a catch.
  • Common over-annotation: Writing throws RuntimeException or throws ArithmeticException on a method is legal but pointless and rarely done — it documents intent but does not satisfy a compiler obligation.

Picture a border checkpoint labelled "compiler": checked travellers (IOException) are stopped unless holding either a try-catch passport or a throws visa; unchecked travellers (ArithmeticException family and Error) flow through unchecked. Takeaway: catch is optional for unchecked, throws is compulsory for checked that escapes.

21.9.4 Worked Example — BufferedReader and the Compulsory Handling of IOException

Scenario — taking console input via BufferedReader and System.in with readLine, the textbook example that makes the checked contract concrete:

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine(); // problem line if written bare — readLine declares throws IOException

Why readLine throws IOException: it reads from a stream that may fail — console not responding, pipe broken, input unavailable at input time, or an IOException wrapped from the underlying Reader. The method signature is public String readLine() throws IOException, so the type IOException (a checked Exception subclass outside RuntimeException) forces the call site obligation.

Two handling methods were presented — handle-or-specify, choose one:

Method 1 — Forward with throws on the method header (pass the obligation to the caller):

void input() throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String s = br.readLine(); // if exception during execution, it is forwarded to IOException handler higher up
}

Line by line: void input() throws IOException declares the type that may escape; the caller of input() must in turn either try-catch(IOException) around input() or itself declare throws IOException, propagating until some catch appears. If no handler ever catches, the default handler prints the IOException stack trace.

Method 2 — Handle with try-catch locally (absorb the obligation here):

void input() {
    try {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine();
    } catch (IOException e) {
        System.out.println("input output exception generated");
        // e.getMessage() / e.printStackTrace() available if you want details
    }
}

Here the try guards the readLine statement; on IOException the catch prints input output exception generated and the program continues without the caller ever knowing an IOException happened. This is the "handle it yourself" branch.

Bare method — compilation error if neither is done — the lecture reproduced the message verbatim:

void input() {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String s = br.readLine(); // bare, no try, no throws on method
}
error: unreported exception java.io.IOException must be caught or declared to be thrown
    String s = br.readLine();
                 ^

The caret points to the readLine() call as the culprit. This demonstrates that checked exceptions are indeed checked by the compiler: the problematic statement must be kept within try-catch or the method must declare throws. Swapping the example to int c = a / b; with no try would not produce a similar error — ArithmeticException is unchecked, so bare division compiles silently, even though it may throw at runtime.

Full compile/run contrast:

class OK1 { void m() throws IOException { new BufferedReader(...).readLine(); } } // ok — forward
class OK2 { void m() { try { new BufferedReader(...).readLine(); } catch(IOException e){} } } // ok — handle
class Bad { void m() { new BufferedReader(...).readLine(); } } // error: unreported exception must be caught or declared
class AlsoOK { void m() { int c = 10/0; } } // ok even without catch — ArithmeticException is unchecked (but will throw at runtime)

Sense-check: readLine forces a choice; 10/0 does not — the same throws logic explains both.

21.9.5 Multiple Catch Blocks for a Single Try

A try block may contain multiple statements, each capable of throwing a different exception category. Therefore one try may be capable of throwing multiple distinct types, and you should catch all expected types with one catch per type — the general pattern the lecture modelled on switch cases selected by the runtime type of the throwable.

Worked example exceptionDemoTest — this is the lecture's integrated fault-mix, deliberately combining three fault families in one scope:

Setup: int a[] = {5, 10}; a two-element integer array with valid indices 0 and 1; the try will mix arithmetic, array indexing, and Stringint type conversion.

try {
    int b = ...; // e.g. int divisor = Integer.parseInt(s); or int k = a[0]/0;
    int x = ...; // conceptually: Integer.parseInt-style cast plus division using array indices
    System.out.println(x);
} catch ... // see below

This try mixes:

  • Arithmetic (x = a[0] / b where b may be 0ArithmeticException).
  • Array indexing (a[2] where only a[0] and a[1] exist → ArrayIndexOutOfBoundsException).
  • Type conversion (Integer.parseInt(s) where s may be "hello"NumberFormatException).

Catches — one per type, like cases in a switch, each targeting the class whose condition failed:

catch (ArithmeticException e) {
    System.out.println("arithmetic exception — division by zero etc.");
}
catch (NumberFormatException e) {
    System.out.println("number-format exception — type-casting mismatch, e.g. non-numeric string to int");
}
catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("array-index-out-of-bounds — attempted access beyond size 2");
}

Rules for multiple catches — memorise for the exam:

  • One try, many catch statements — exactly one catch runs, determined by instanceof on the actual throwable. After one catch executes, the others are bypassed and execution continues after the whole try-catch chain.
  • Order matters: subclasses must come before their supertypes. catch (Exception e) cannot precede catch (ArithmeticException e) because Exception would swallow ArithmeticException and the latter becomes unreachable — the compiler rejects it with exception has already been caught / unreachable catch block. Correct order is most specific first (ArithmeticException, NumberFormatException, ArrayIndexOutOfBoundsException) then broader Exception last if you include it.
  • Each catch has its own parameter variable e; the three e names do not conflict because only one block runs.
  • Matching is by is-a: catch (Exception e) catches any Exception descendant, but catch (ArithmeticException e) catches only ArithmeticException (and its rare subclasses). So with false ordering catch (Exception) before catch (RuntimeException), the latter is dead.

General skeleton shown in T6 Chapter 10:

try { ... }
catch (ArithmeticException e) { ... }
catch (ArrayIndexOutOfBoundsException e) { ... }
catch (Exception e) { ... } // broader last

Each catch targets one exception class. If the try throws ArithmeticException (divisor 0), the first catch matches; if it throws NumberFormatException (parsing "abc"), the second matches; if a[2] is accessed with only a[0] and a[1] present, the third matches. This is the general pattern: one try, many catch statements like cases in a switch on runtime type.

Concrete three-fault trace — exactly the kind the exam will ask you to assign to a catch:

int a[] = {5, 10};
try {
    int divisor = 0;
    int x = a[0] / divisor;   // → ArithmeticException: / by zero   → catch 1
    // Suppose divisor was non-zero but next line ran:
    int v = Integer.parseInt("hello"); // → NumberFormatException → catch 2
    System.out.println(a[2]);         // → ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2 → catch 3
} catch (ArithmeticException e) {
    System.out.println("arithmetic — " + e);
} catch (NumberFormatException e) {
    System.out.println("number-format — " + e);
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("index out-of-bounds — " + e);
}
System.out.println("after try-catch blocks."); // always continues after handling

// With args.length trick (MultipleCatches):
// java MultipleCatches           → a=0 → 42/0 → ArithmeticException → divide by 0 path
// java MultipleCatches TestArg   → a=1 → c[42] with c length 1 → ArrayIndexOutOfBoundsException → array index oob path

Sense-check: Exactly one catch fires per thrown throwable — the try exits at the first throw, so only one fault per entry is observed; loop the try to see each family on successive runs.

Exam note precisely as phrased: Expect questions asking you to associate a specific line with its appropriate catch class among ArithmeticException, NumberFormatException, and ArrayIndexOutOfBoundsException. The three-way split above is the answer template — arithmetic → ArithmeticException, string-to-int conversion → NumberFormatException, illegal index → ArrayIndexOutOfBoundsException.

Visual — picture a funnel try with three coloured exit tunnels labelled ArithmeticException, NumberFormatException, ArrayIndexOutOfBoundsException. A throwable ball falls into the funnel and the trapdoor whose label matches the ball's runtime type opens — like a switch on instanceof. X-axis is exception type, y-axis is handler order (specific before broad). Takeaway: one try can throw many types; each catch is a typed trapdoor; first match wins.

21.9.6 Nested Try and Catch Blocks

You can have a try inside a try, with catches at different lexical levels. Nesting can be explicit (braces inside braces) or via a method call where the called method itself contains a try — in both cases the inner try's lifetime is nested inside the outer's lifetime.

Layout 1 — inner try with its own catch, outer try with its own catch:

try {
    // outer statements
    int a = args.length;
    int b = 42 / a; // ArithmeticException if a==0 — handled by outer catch
    try {
        // inner statements
        if (a==1) a = a/(a-a); // ArithmeticException if a==1 — would need inner catch or propagates
        if (a==2) {
            int c[] = {1};
            c[42] = 99; // ArrayIndexOutOfBoundsException if a==2 — caught by inner
        }
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("Array index out-of-bounds: " + e);
    }
} catch (ArithmeticException e) {
    System.out.println("Divide by 0: " + e);
}

Layout 2 — outer try, inner try, statements, and then associated catches together at the same level — both nestings are permitted; your program will not complain if you nest them in different valid arrangements, because the compiler cares only that each try has a matching catch or finally in scope.

The principle is lexical scoping plus stack unwinding: an inner catch handles faults thrown from its inner try first; if no inner catch matches, the throwable propagates and the next enclosing catch is consulted; this continues until one succeeds or the chain reaches the default handler. Inner handlers therefore handle narrowly; outer handlers are the backstop.

Concrete sample runs from T6's NestTry that the lecture reused:

java NestTry            // a = 0 → outer 42 / 0 → "Divide by 0: java.lang.ArithmeticException: / by zero"
java NestTry One        // a = 1 → a==1 → a/(a-a) = 1/0 → inner throws Arithmetic, inner only catches IndexOutOfBounds → propagates to outer → "Divide by 0: ..."
java NestTry One Two    // a = 2 → a==2 → c[42]=99 in inner → "Array index out-of-bounds: java.lang.ArrayIndexOutOfBoundsException: 42"

The call-stack nesting variant MethNestTry shows the same propagation through a method: try { int a = args.length; int b = 42/a; nestTry(a); } catch(Array...) where nestTry(int a) itself has an inner try-catch(ArrayIndexOutOfBounds) — the method call is implicitly a second nesting level.

Scope — when nesting helps:

  • Use nested when: You want different handling granularity — inner catches "index" locally and continues; outer catches "division" globally. I/O readLine inside inner catch(IOException) can recover locally while arithmetic faults bubble to a broader outer catch(ArithmeticException).
  • Do not: Nest merely to be clever — if all exceptions need the same handling, a single try with multiple catch on the same level is clearer.

Visual — two concentric rectangles: outer try labelled "divide" for args.length, inner try labelled "index/convert". Bottom of inner points to catch ArrayIndexOutOfBounds; bottom of outer points to catch ArithmeticException. An arrow that misses the inner catch exits through the inner rectangle's edge and is caught by the outer catch wall. Takeaway: inner catch first, then outer catch, then default.

Pitfalls — multiple and nested handling traps:

  • Catching super before sub. catch (Exception e) before catch (ArithmeticException e) makes the latter unreachable — reorder to sub first.
  • Thinking all catches in a chain run. Only one catch executes per thrown throwable, like a switch; the rest are bypassed.
  • Expecting a try with no catch or finally to compile. Each try needs at least one catch or a finally; a lone try { ... } is a compile error.
  • Assuming ArrayIndexOutOfBoundsException is checked. It extends RuntimeException (via IndexOutOfBoundsException), so it is unchecked and needs no throws — but you may still catch it for recovery.

21.9.7 Student Questions and Answers

Q: Is finally optional or mandatory and when does it execute? A: Including finally is optional — you decide whether your program needs it. But if you include it, it is unconditional and will always run, whether an exception existed or not. This is true even when the try exits via return or when a different exception propagates — the finally runs on the way out before the method returns. Typical use is finally { if (br != null) br.close(); } where you must release a resource on both success and failure paths. A try may appear as try-catch, try-finally, or try-catch-finally, but never as bare try { } alone — each try needs at least one catch or finally. The lecture illustration (procA/procB/procC) showed three methods: procA throws and finally still prints, procB returns and finally still prints, procC executes normally and finally still prints.

Q: Can a try block throw multiple exception types, and should I catch all? A: Yes, a single try can throw multiple types because it may contain multiple risky statements — each statement may throw a different family. For the exam: one try that mixes arithmetic (a/b with ), index (a[2] on a length-2 array), and conversion (Integer.parseInt("abc")) can throw ArithmeticException, ArrayIndexOutOfBoundsException, and NumberFormatException respectively. You should provide one catch per expected type so each fault has a matching handler — they sit like case labels selected by runtime instanceof. Only the first matching catch runs per throw. Order them most-specific first (ArithmeticException, NumberFormatException, ArrayIndexOutOfBoundsException) then broader Exception if included, otherwise subclass catches become unreachable.

Recap — Throwable splits into Exception (handleable) + Error (usually fatal, unchecked). Exception further splits into checked (all Exception minus RuntimeException subtree — IOException is the archetype, must be try-catch or throws) and unchecked (RuntimeException + its ArithmeticException/ArrayIndexOutOfBoundsException/NumberFormatException family — no throws required, caught where you choose). Multiple catch act as typed case labels on one try (one winner, specific before broad); inner try-catch nests inside try-catch with propagation outward.

Bridge — The hierarchy and handle-or-specify rules tell you where the compiler will force throws and where it allows optimism. The final pattern to master is how you create such throwable types yourself — authoring checked or unchecked custom exceptions with extends, deciding throw vs throws, and wiring the lifecycle end-to-end.

Exam note: Expect to draw the Throwable → Exception/Error tree with RuntimeException under Exception and IOException alongside it; to state checked = subclass of Exception except RuntimeException subtree and unchecked = subclass of RuntimeException; to reproduce the error: unreported exception java.io.IOException must be caught or declared to be thrown line flagged on br.readLine();; and to assign ArithmeticException, NumberFormatException, ArrayIndexOutOfBoundsException respectively to arithmetic, conversion, and indexing within a single try, plus sketch valid nested try–catch layouts.

Real-world — Fault-tolerant production code mirrors the two-branch hierarchy: checked IOException from BufferedReader.readLine() is handled by try-catch or forwarded via throws IOException, while unchecked ArithmeticException, ArrayIndexOutOfBoundsException, NumberFormatException, NullPointerException are caught reactively where arithmetic or indexing is risky. Nested and multiple-catch patterns and finally blocks appear in resource handling where outer try-catch scopes separate I/O faults from business-logic faults.

21.10 User-Defined Exceptions with Throw and Throws

21.10.1 Creating Your Own Exception by Extending Exception or RuntimeException

Hook: The Java library has IOException and ArithmeticException for its faults — but what throwable name do you use when the fault is yours, such as a box with length zero?

A programmer can write either a checked or an unchecked custom exception by extending the appropriate root class with extends. You use exactly the same extends keyword as for any class hierarchy; the type system then treats your new class as an exception and the five-keyword machinery (try-catch-throw-throws-finally) applies to it unchanged.

The inheritance rule that fixes checked vs unchecked for your own type:

  • To create a checked exception (checked at compile time — callers must try-catch or declare throws YourException), extend a subclass of Exception other than RuntimeException. Commonly: class YourException extends Exception { ... } or extends IOException { ... }. Any such child sits in the checked branch of the hierarchy.
  • To create an unchecked exception (runtime, not compile-time checked — no mandatory throws at call sites unless you choose to catch), extend RuntimeException or any of its subclasses (any unchecked hierarchy node). Commonly: class YourException extends RuntimeException { ... } or extends IllegalArgumentException { ... }.
Checked (must handle or throws):   class MyChecked extends Exception { }
                                  class MyIOChecked extends IOException { }

Unchecked (may catch, never must): class MyUnchecked extends RuntimeException { }
                                  class InvalidBoxDimensionException extends RuntimeException { } // lecture

You then annotate every throwing site header with throws YourException if you want compile-time forwarding for the checked branch (for the unchecked branch it is optional but still allowed as documentation), and you generate instances at the precise validation branch with throw new YourException(args).

The companion text (T6, Chapter 10) notes that Exception inherits from Throwable and defines constructors Exception() and Exception(String msg). Your subclass may simply reuse them, add a detail field, or override toString() / provide getMessage() to tailor output — as in MyException(int detail) storing detail and returning "MyException["+detail+"]".

Two practical design guidelines the professor emphasized:

  • Choose the root by intent: Is the fault a programming error that callers can hardly anticipate or locally recover from (invalid dimensions, illegal argument)? Make it unchecked (extends RuntimeException) so you fail fast without burdening every call site — this is the InvalidBoxDimensionException choice. Is the fault a recoverable condition callers should plan for (missing configuration file, unavailable service)? Make it checked (extends Exception) so the compiler forces acknowledgement.
  • Error is not the right root for domain exceptions — reserve Error for VM failures. Domain rules belong under Exception or RuntimeException.

The lecture's tiny example MyException extends Exception { private int detail; MyException(int a){ detail=a; } public String toString(){return "MyException["+detail+"]";}} with static void compute(int a) throws MyException { if(a>10) throw new MyException(a); } is the checked counterpart to the unchecked InvalidBoxDimensionException that follows.

21.10.2 Worked Example — InvalidBoxDimensionException and the Box Class

Purpose: Define an unchecked exception InvalidBoxDimensionException that is thrown whenever an attempt is made to create a Box instance whose length, width, or height is less than or equal to . The verbal description the professor preserved: for a box, if any of the three dimensions is , the instance is invalid and an exception should be generated carrying the faulty dimension, rather than silently storing a degenerate box.

Step 1 — The exception class itself:

class InvalidBoxDimensionException extends RuntimeException {
    InvalidBoxDimensionException(int dim) {
        System.out.println("box instance with invalid dimension: " + dim);
        // optionally: super("invalid dimension: " + dim);
    }
}

Because it extends RuntimeException, it is unchecked — callers are not forced by the compiler to handle it, though the lecture's driver chooses to. The constructor prints a diagnostic line with the invalid dimension passed as argument, so instantiation itself logs the fault. In production you would often call super(String.valueOf(dim)) or super("invalid dimension: " + dim) to let Throwable.getMessage() carry the detail and let printStackTrace render it; the lecture's print-based form is the minimal visible-effect version. This is the definition of the user-defined exception class — its mere existence in the type system makes catch (InvalidBoxDimensionException e) legal.

Step 2 — The Box class that uses it:

class Box {
    int length, width, height;
    Box(int l, int w, int h) throws InvalidBoxDimensionException {
        if (l &lt;= 0) throw new InvalidBoxDimensionException(l);
        if (w &lt;= 0) throw new InvalidBoxDimensionException(w);
        if (h &lt;= 0) throw new InvalidBoxDimensionException(h);
        // all dimensions valid
        this.length = l; this.width = w; this.height = h;
        // surface area of a rectangular cuboid — total area of all six faces:
        // area = 2*(l*w + w*h + h*l)
    }
    int area() {
        return 2 * (length * width + width * height + height * length);
        // = 2lw + 2wh + 2hl
    }
}

Key points line by line:

  • Class header class Box { int length, width, height; ... } stores the three dimensions as instance state.
  • Constructor header Box(int l, int w, int h) throws InvalidBoxDimensionException — this throws clause lists the exception type that may escape the constructor. Because InvalidBoxDimensionException is unchecked, this throws is not required by the compiler; it is included as explicit documentation and as the lecture's idiom that "forward to that exception class to handle." For the checked variant MyException extends Exception, the same throws MyException would be compulsory.
  • Inside, each dimension is checked with if (dim <= 0) throw new InvalidBoxDimensionException(dim); — the throw keyword explicitly creates a new throwable instance at the failing branch and immediately exits that branch's normal flow. The ordering matters: l checked first, then w, then h; the first failing dimension throws, the later checks are skipped, so the reported dim identifies which dimension failed.

Mathematical form — reconciled surface area:

The total surface area of an axis-aligned rectangular box (cuboid) with edge lengths , , and is the sum of the areas of the six faces, which form three congruent pairs. The derivation, step by step:

So the box's surface area is

where , , are length, width, height respectively, each expected to be . For example, for , The verbal description preserved alongside is exactly this: area equals two times length times width plus width times height plus height times length. If valid values are entered, this computation follows; if any dimension is , the constructor never reaches it — the throw diverts to the handler.

Domain checks on the formula:

  • Dimensional: , , each have dimension , so their sum has area dimension, and multiplying by stays area — consistent.
  • Boundary: If , area , which matches a vanishing box. If one dimension fails (\le 0), the exception prevents the nonsensical case where a "negative area" would be computed.
  • Spot-check: (unit cube) → area — correct, six unit squares.

21.10.3 Worked Example — Driver Code with Try and Catch for Two Invalid Boxes

Driver class main must guard the construction because the dimensions may be invalid — the Box constructor may throw. The lecture worked this as two separate try-catch blocks, each isolating one risky construction, so that the second try still runs even if the first threw.

First invalid instance — width zero (the middle dimension fails):

try {
    Box b1 = new Box(5, 0, 10); // l=5 ok, w=0 triggers w ≤ 0, h=10 not yet tested
} catch (InvalidBoxDimensionException e) {
    System.out.println("invalid width"); // or other diagnostic / handling; e already logged at throw site
}

Tracing: Box(5,0,10) enters constructor, if (l <= 0) with is false, so next line checks if (w <= 0) with — because is true, throw new InvalidBoxDimensionException(0) creates the exception object (which prints box instance with invalid dimension: 0 from the exception's constructor) and abruptly exits the Box constructor. The call new Box(5,0,10) was inside try, so control jumps to the matching catch (InvalidBoxDimensionException e) which receives that generated throwable into e and prints the handling text invalid width. The program does not abnormally terminate; b1 was never fully initialised and should not be used after the exception.

Execution order for this block: constructor entry → l check pass → w check fail → new InvalidBoxDimensionException(0) → exception constructor print → throw → catch print invalid width → continue after catch.

Second invalid instance — height zero (the last dimension fails):

try {
    Box b2 = new Box(5, 10, 0); // l=5 ok, w=10 ok, h=0 triggers h ≤ 0
} catch (InvalidBoxDimensionException e) {
    System.out.println("invalid height / dimension");
}

Same flow with h instead of w: l=5 and w=10 both pass their >0 checks, h=0 satisfies , so throw new InvalidBoxDimensionException(0) fires, the exception constructor prints box instance with invalid dimension: 0, and the catch prints invalid height / dimension.

Each risky statement is kept within its own try and each has a catch capable of catching InvalidBoxDimensionException objects. A matching catch must create a variable (e.g., e) to receive the throwable and then execute handling code such as System.out.println. Wrapping each new Box(...) separately (rather than try { new Box(5,0,10); new Box(5,10,0); } catch...) matters: a single try with two constructions would skip the second construction if the first threw, because control jumps at the first throw and remaining try statements are skipped.

What a correct driver prints — ordered output:

// First block:
Box b1 = new Box(5, 0, 10);
→ box instance with invalid dimension: 0   (from InvalidBoxDimensionException constructor)
→ invalid width                          (from catch)

 // Second block (always runs — separate try):
Box b2 = new Box(5, 10, 0);
→ box instance with invalid dimension: 0
→ invalid height / dimension

// If both had been valid:
Box b3 = new Box(5, 10, 7);
// no exception; area() → 310 as computed above
System.out.println("valid box area = " + b3.area()); // → 310

Sense-check: Box(5,0,10) failing at w still prints 0 as the offending dimension — the throw carried w's value, not a fixed string. A third driver new Box(-3, 2, 9) would print box instance with invalid dimension: -3 and be caught as "invalid length" if the message were adapted.

A third valid case is the positive control the lecture left implicit: when all three dimensions are positive, no throw fires, the fields initialize, and area() evaluates the formula above — you should test with to verify 310.

Scope — when two tries vs one try matters:

  • Two separate tries (lecture's form): try { new Box(5,0,10); } catch(...) {} try { new Box(5,10,0); } catch(...) {} — both constructions are attempted. Each risky new has its own recovery.
  • One try wrapping both: try { new Box(5,0,10); new Box(5,10,0); } catch(...) {} — only the first failure is handled; the second new is never reached because the first throw jumps out of the try. Useful when the two constructions are atomic, not when each should be evaluated independently.
  • What breaks if you swallow: Writing catch (InvalidBoxDimensionException e){} with empty body and no print would still avoid crash but silently swallow the log — prefer at least e.getMessage() or a specific message so the failure is visible.

This pair demonstrates the full lifecycle: define the custom unchecked exception by extends RuntimeException; generate it with throw new ... at the precise if (dim <= 0) validation point; optionally document it with throws on the constructor header; and in the driver, guard each risky site with try and provide a catch (InvalidBoxDimensionException e) to catch and handle it without abnormal termination.

21.10.4 Generalizing Throw and Throws for Custom Checks

The InvalidBoxDimensionException pattern generalizes: Any user-defined invariant — not present in the language's predefined ArithmeticException/IOException catalogue — can be turned into an exception hierarchy this way. You inherit from Exception (or a subclass) to fix whether the type lives in the checked or unchecked branch, you mark generators with throws YourException on the method/constructor header (compulsory for checked, optional for unchecked but still useful documentation), and you raise specific faults with throw new YourException(args) at the branch where the invariant fails. The compiler or runtime then ensures the fault is either declared or handled somewhere on the call stack.

General skeleton the professor advised you to copy for any domain check:

// 1. Define: choose checked vs unchecked root
class NegativeAgeException extends RuntimeException { // unchecked — domain programming error
    NegativeAgeException(int age){ super("negative age: "+age); }
}
// 2. Generate: validate and throw where the invariant fails
void setAge(int age) throws NegativeAgeException { // throws optional for unchecked, required for checked
    if (age &lt; 0) throw new NegativeAgeException(age);
    this.age = age;
}
// 3. Guard: caller handles or forwards
try { person.setAge(-2); } catch (NegativeAgeException e){ System.out.println(e.getMessage()); }

Replace InvalidBoxDimensionException with InsufficientFundsException, InvalidScoreException, or any domain name — the steps are identical.

The professor also connected this to the MyException checked example from T6 Chapter 10: with class MyException extends Exception { MyException(int a){ detail=a; } public String toString(){return "MyException["+detail+"]";}} and static void compute(int a) throws MyException { if (a>10) throw new MyException(a); }, the driver must handle:

try { compute(1); compute(20); } catch (MyException e){ System.out.println("Caught " + e); }
// → Called compute(1) → Normal exit
// → Called compute(20) → Caught MyException[20]

The only difference from InvalidBoxDimensionException is the inheritance branch: extends Exception forces throws MyException at every call site, while extends RuntimeException keeps throws optional.

Decision summary — full authoring checklist:

  • Invariant: dim > 0 for each of l, w, h; violation is .
  • New type: class InvalidBoxDimensionException extends RuntimeException { InvalidBoxDimensionException(int dim){ ... } } for unchecked fail-fast; switch to extends Exception if you want compile-time handle-or-specify for callers.
  • Generate guard: if (dim <= 0) throw new InvalidBoxDimensionException(dim); inline at each dimension check; first failure throws, later checks skipped.
  • Forward declaration: Box(int l,int w,int h) throws InvalidBoxDimensionException — header forward; throw new ... — point generation.
  • Call-site guard: Separate try { new Box(...); } catch (InvalidBoxDimensionException e){ handling } per risky construction for independent evaluation.
  • Formula path: On valid l,w,h > 0, area = 2(lw+wh+hl) computed; invalid path never reaches it.

Pitfalls — custom-exception mistakes that the lecture and T6 exam ask:

  • Choosing the wrong parent. Extending Exception when you wanted runtime fail-fast (now every caller needs try-catch) or extending RuntimeException when you wanted mandatory acknowledgement (now callers can silently ignore).
  • Throwing a non-throwable. throw "bad" or throw new String("bad") fails — only throw new ThrowableSubclass(args) is legal (only throwable objects can be thrown).
  • Writing throws new X on a header. Correct split is throws X (type) on the header, throw new X(args) (instance) at the if. The one-letter s is the clue.
  • Forgetting extends and thinking implements creates an exception. Exception types are classes — you extends, never implements, to enter the hierarchy.
  • Using area = 2*(l*w + w*h + h*l) but computing with integer overflow silently. With large int dimensions the product may overflow before the multiply-by-2; for exam numbers keep small (e.g., fits in int).

Visual — picture three vertical lanes labelled l, w, h feeding into a constructor gate Box(l,w,h). Each lane has a diamond ≤ 0? that, on failure, spawns a throwable arrow throw new InvalidBoxDimensionException(dim) diverting into a catch (InvalidBoxDimensionException e) basin that prints the offending dim and continues. The success path through all three diamonds leads to a box node labelled area = 2(lw+wh+hl) with a sample numeric bubble 5,10,7 → 310. X-axis is dimension order, y-axis is normal vs exceptional flow. One-sentence takeaway: validated dimensions converge either to area or to a domain-named throw.

21.10.5 Student Questions and Answers

Q: Should my custom dimension exception be checked or unchecked, and which class do I extend? A: For a dimension validation that is a programming-time, runtime invariant concern — a box with non-positive edge cannot be used and the fault is detected at execution right where the dimensions are supplied — extend RuntimeException to make it unchecked, exactly as the example did: class InvalidBoxDimensionException extends RuntimeException { InvalidBoxDimensionException(int dim){ System.out.println("box instance with invalid dimension: " + dim); } }. The unchecked branch means callers may catch (InvalidBoxDimensionException e) where they want graceful reporting (separate try-catch per new Box), but are not forced by the compiler to wrap every construction. If you wanted compile-time enforcement — callers must write try or throws and cannot forget handling — extend a checked Exception subclass other than RuntimeException (e.g., class MyException extends Exception) and declare throws MyException on every throwing method/constructor, as in static void compute(int a) throws MyException { if (a>10) throw new MyException(a); } where main must try { compute(20); } catch (MyException e){...} or itself declare throws. The throws clause on the header lists the forwarding target type; throw new ... inside the if (dim <= 0) actually creates and fires the exception instance at the failing branch — the s distinguishes declaration from generation. A third choice, extends IOException, would make it a checked I/O-flavoured exception, which is misleading here — dimension validity is not an I/O fault.

Recap — extends RuntimeException → unchecked custom exception (fail-fast, throws optional); extends Exception → checked (handle-or-specify, throws compulsory). Define once (class InvalidBoxDimensionException extends RuntimeException), generate at each failing check (if (l<=0) throw new ...), declare throws on constructor header for documentation, guard each risky construction with its own try-catch(InvalidBoxDimensionException e) to avoid abnormal termination, and compute area = 2(lw+wh+hl) (e.g., ) only on the valid path.

Bridge — This completes the nesting → anonymous/local → throw-catch-finally → hierarchy → custom lifecycle arc. The whole lecture is now testable as ten concepts where interfaces and nested forms share construction/access rules, and exceptions share a single five-keyword model plus a hierarchy that decides throws obligations.

Exam note: Be able to (a) declare class InvalidBoxDimensionException extends RuntimeException { InvalidBoxDimensionException(int dim){...}}, (b) write Box(int l,int w,int h) throws InvalidBoxDimensionException { if(l<=0) throw new ...; ... } and the surface area formula area = 2(lw+wh+hl), and (c) guard two invalid constructions with separate try { new Box(5,0,10); } catch (InvalidBoxDimensionException e) and try { new Box(5,10,0); } catch... and predict the box instance with invalid dimension: 0 plus catch message output order.

Real-world — Custom exceptions like InvalidBoxDimensionException appear in production code to fail fast on invalid domain inputs with self-documenting exception names rather than returning silent error codes or -1. Frameworks check if (pageSize <= 0) throw new InvalidPageSizeException(pageSize) at API boundaries; service layers catch InsufficientFundsException separately from IOException; and validated-value objects (e.g., Box, Age, Score) enforce invariants at construction and let callers discriminate faults by catch type rather than by parsing a generic IllegalArgumentException message.

Exam Guidance Summary

All consolidated exam-specific guidance for this lecture — keep this as a quick checklist the night before the exam. Each bullet maps directly to a trace you have practiced in the sections above.

  • Exam duration is two hours and will be conducted as an open-book exam. This was confirmed in response to a direct question at the end of this lecture.
  • Syllabus scope: content covered in the contact session together with content available in the pre-contact and post-contact slides is included. The statement emphasized that the exam will include nothing apart from what is included — the included material is exactly the contact, pre-contact and post-contact content — so do not study beyond that boundary.
  • Expect questions on interface fundamentals: the meaning of interface as a blueprint of a class, the compiler insertions public static final for every field and public abstract for every method as ease-of-use additions, and the strict class extends class vs class implements interface(s) vs interface extends interface(s) taxonomy.

Exam note: Multiple inheritance — be prepared to write or analyze both class Trial implements Printable, Showable (single show() body satisfies two declaration-only interfaces — no diamond) and to explain in one sentence why class C extends A, B is disallowed while class C implements P, Q is allowed (complete classes carry competing instance state and bodies → ambiguity; interfaces are partial declarations → no competing bodies until defaults are involved).

Exam note: Default and static interface methods (Java 8) — know the distinguishing syntax default void show() { ... } (instance fallback, omission allowed, class show() wins) vs static void show() { ... } (type-level helper), how p.show() falls back to the default when the implementing class omits it (Trial p = new Trial(); p.show()within show of Printable), and how to invoke a static interface method as Printable.show() by interface name rather than t.show() on the object.

Exam note: Disambiguation of two defaults with the same signature — interface Printable { default void show(){...}} plus interface Showable { default void show(){...}} with class Trial implements Printable, Showable forces you to public void show(){ Printable.super.show(); Showable.super.show(); }; silence now gives inherits unrelated defaults and plain super.show() is illegal — always qualified InterfaceName.super.

Exam note: Nested interfaces and nested classes — practice the four interface-nesting variants and their qualified forms: (a) interface Printable { interface Showable {void show();}} with implements Printable implements only outer (print) vs implements Printable.Showable implements inner (show) — removal of show from Trial implements Printable causes no error, removal when implements Printable.Showable causes does not override abstract method; (b) class Printable { interface Showable...} with implements Printable.Showable where t.print() fails (only inner contract, not outer class method); (c) interface Showable { class Printable{ void print(){...}}} with extends Showable.Printable where t.print() succeeds (inherited via extends); (d) combined class Trial extends Showable.Printable implements Showable { public void showOne(){...}} where showOne is compulsory and print is optional inherited.

Exam note: Member inner classes — know the construction Outer o = new Outer(); Outer.Inner in = o.new Inner(); in.show();, the two class files Outer.class and Outer\\$Inner.class (\\$ encoding), and that Inner as a member can access private outer members (private int data) via the hidden Outer.this. new Outer.Inner() alone is only for static nested.

Exam note: Anonymous and local inner classes — be able to identify { ... } after new Outer() or new OuterInterface() as the anonymous class body (ends at } before ; in Outer o = new Outer(){...};), know it is compiled to Enclosing\\$1.class, it cannot expose an extra void extra() via o.extra() because Outer has no extra, and know that a local inner class Inner declared inside outerMethod() is visible only inside that method's { }new Inner() outside gives cannot find symbol.

Exam note: Static nested classes — know class Outer { static class Inner { void show(){ System.out.println(sData); } } } can be declared only as an inner class (never top-level static class Outer), can access outer static members including private static int sData but cannot access instance iData, and is instantiated as Outer.Inner in = new Outer.Inner(); without an outer instance.

Exam note: Three illustrative practice scenarios flagged for revision — (1) an instance method attempting to override a parent static method without static causes instance method cannot override a static method from parent error; fix by adding static with compatible return type to the child or by not claiming override; (2) Printable with static void show() vs Showable with default void show() and a blank class Trial implements Showable {} vs implements Printable — know which t.show() delegates to default and which Printable.show() is the static-by-name call; (3) an outer interface declaring a constant 20 and an inner class accessing that constant — the access path is via the outer type's constant, and the static/implicit-static nature means no outer instance is needed.

Exam note: Exception handling — expect try-catch-finally, throw vs throws, and custom exceptions. Know the wrapper try { c = a / b; } catch (ArithmeticException e){ System.out.println("division by 0"); } finally { /* optional, always */ } for integer division by zero with , the precise definitions checked = subclass of Exception except RuntimeException subtree vs unchecked = subclass of RuntimeException, and handling IOException from BufferedReader.readLine() either via try { String s = br.readLine(); } catch (IOException e){ ... } or void input() throws IOException { String s = br.readLine(); }, with the bare-line compiler error error: unreported exception java.io.IOException must be caught or declared to be thrown flagged on the readLine() call.

Exam note: Multiple and nested try-catch — be able to assign the three families to distinct catches for a single try that mixes arithmetic (a/bArithmeticException), String→int conversion (Integer.parseIntNumberFormatException), and array indexing (a[2] on length-2 → ArrayIndexOutOfBoundsException), like catch (ArithmeticException e){} catch (NumberFormatException e){} catch (ArrayIndexOutOfBoundsException e){} in that order (subclasses before super), and to draw two valid nesting diagrams — (Layout 1) try { outer; try { inner; } catch (InnerEx){} } catch (OuterEx){} and (Layout 2) try { try{...} ... } catch together — with the rule that only one catch fires per throw and inner is consulted before outer.

Exam note: User-defined exceptions — be able to declare class InvalidBoxDimensionException extends RuntimeException { InvalidBoxDimensionException(int dim){ System.out.println("box instance with invalid dimension: " + dim); } }, to generate with if (dim <= 0) throw new InvalidBoxDimensionException(dim) inside each of the three checks if (l<=0), if (w<=0), if (h<=0) in the Box constructor header Box(int l,int w,int h) throws InvalidBoxDimensionException, to guard each risky construction with its own try { Box b = new Box(5,0,10); } catch (InvalidBoxDimensionException e){ System.out.println("invalid ..."); } (separate tries so both get tested), and to compute area = 2(lw + wh + hl) only on the valid path (e.g., ).

Real-world: The exam is open-book, so organizing notes by these exact patterns — interface taxonomy, default/static resolution including InterfaceName.super, nesting qualifiers Outer.Inner vs Outer\\$Inner, exception hierarchy Throwable→Exception/Error and checked/unchecked contracts, multiple/nested catch wiring, and InvalidBoxDimensionException throw/throws lifecycle — allows rapid lookup rather than re-reading prose under time pressure.

Key Industry Applications

Consolidated real-world connections for quick reference — each maps the lecture pattern to a production Java idiom you will recognise in frameworks and libraries.

  • Interface-based API design in Java frameworks — publishing a stable interface such as List, IntStack, or a domain Bank contract with only abstract declarations lets many teams (FixedStack vs DynStack, Client vs AnotherClient, multiple database drivers) implement it independently and be accessed polymorphically through the interface type at runtime.
  • Multiple inheritance through interfaces enables a single service class to conform to several capability contracts at once (e.g., class ReportService implements Printable, Exportable, Auditable or class Trial implements Printable, Showable) without the ambiguity of inheriting two complete classes with conflicting instance state and method bodies.
  • Default methods power evolution of Java's standard libraries — additions like Collection.forEach, Collection.removeIf, List.sort, List.replaceAll, and Stream defaults were shipped as default on long-published collection interfaces so that existing ArrayList/LinkedList implementations kept compiling while newer ones could override for performance.
  • Static interface methods are invoked by interface name (e.g., Printable.show() or Comparator.comparing(...)) and are used for helper factories or validators that conceptually belong to the interface namespace rather than any single implementation instance — the call t.show() on an implementing object is not idiomatic.
  • Conflicts of two default methods with the same signature (Printable.super.show() / Showable.super.show()) model real dependency diamond problems in library composition, resolved by explicit InterfaceName.super.method() delegation in the implementing class; a class that adopts two library interfaces with the same default name must override and choose.
  • Nested interfaces of the form Outer.Inner model scoped contracts — for example, Map.Entry scoped inside Map, a top-level NetworkService exposing inner Callback, or a framework's Outer.Strategy — and the implicit static nature avoids requiring an outer instance just to name the inner contract type.
  • Member inner classes used for tight coupling such as an ArrayList.Itr iterator class that needs private access to its enclosing collection's internal elementData array and size count, a GUI panel's listener that manipulates private fields of the outer panel, or a SceneComponent that toggles private selection flags via the inner iterator.
  • Compiler artifacts Outer.class plus Outer\\$Inner.class (and Test\\$1.class for anonymous) appear in build outputs, coverage reports, and stack traces (Outer\\$Inner.show(Outer.java:6)); understanding the \\$ naming helps diagnose class-loading, shading, and serialization issues and explains why tools must be configured to instrument \\$ classes.
  • Anonymous inner classes for one-off overrides — supplying an inline implementation of an abstract class or interface at the call site (button.addActionListener(new ActionListener(){...}), Collections.sort(list, new Comparator<Country>(){...}), one-shot Callback stubs) — with the single-object and "cannot expose extra() via outer type" constraints that make them lightweight but narrow.
  • Local inner classes confine a helper class to a single method (void outerMethod(){ class Inner{...} Inner i = new Inner(); i.show(); }), reducing namespace pollution when the helper is meaningful only inside that method's scope, such as a method-local validator, parser state, or short-lived comparator not reused elsewhere.
  • Static nested classes for grouping logically related utilities that need access only to static configuration of the outer class, including private static constants and factories (Map.Entry, Outer.Builder, configuration holders) without holding an outer instance and without leaking the Outer.this reference that a member inner would retain.
  • Exception hierarchy (Throwable → Exception/Error, with IOException and RuntimeException subtrees under Exception) underpins production error handling — file and console code must try-catch or throws IOException for BufferedReader.readLine(), while arithmetic and indexing faults (ArithmeticException on a/0, ArrayIndexOutOfBoundsException on a[2] of length 2, NumberFormatException on Integer.parseInt) are caught as unchecked only where recovery is desired.
  • Frameworks distinguish checked exceptions (compile-time contracts you must declare via throws and handle — e.g., SQLException, InterruptedException, IOException) from unchecked runtime exceptions (IllegalArgumentException, NullPointerException); user-defined unchecked exceptions like InvalidBoxDimensionException extends RuntimeException are used to fail fast on invalid domain inputs (e.g., non-positive box dimensions) with a self-documenting, domain-meaningful name rather than a silent error code or generic IllegalArgumentException.
  • Nested and multiple-catch patterns and finally blocks appear in resource handling where finally { if (br != null) br.close(); } guarantees file, socket, or connection close and lock release whether the try succeeded or threw, and where outer try-catch scopes separate I/O faults (IOException) from business-logic faults (NumberFormatException) for appropriate recovery or escalation; modern try-with-resources builds on the same guarantees.

OODAP Lecture 21 notes · Interfaces, Nested Interfaces, Inner Classes and Exception Handling

Object Oriented Design, Analysis and Programming Architecture· postgraduate· 2026-08-19

Sections Breakdown

121.1 Interfaces — The Blueprint of a Class

Interface as exact blueprint with public-abstract methods and public-static-final constants, compiler-inserted modifiers, and why it cannot be instantiated directly.

221.2 Class-Interface Relationships and Multiple Inheritance

Strict extends vs implements vs interface-extends taxonomy; Bank hierarchy showing extends+implements together; why class multiple inheritance is banned but interface multiple inheritance is allowed; Printable/Showable single-body satisfies both declarations.

321.3 Default Methods in Interfaces

Evolution problem of classic interfaces and Java 8 default methods as fallback with syntax default and resolution priority class over interface.

421.4 Static Methods in Interfaces and Default Methods Under Multiple Inheritance

Static interface methods as type-level helpers called by interface name; diamond of two default show() methods requiring InterfaceName.super resolution with qualified super rules.

521.5 Nested Interfaces

Lexical nesting Outer.Inner, qualified names, implicit static, public-inside-interface vs any-visibility-inside-class, and four implementation permutations including combined extends+implements.

621.6 Nested Classes — Member Inner and Static Inner Classes

Member inner as data member with Outer.this, o.new Inner(), Outer$Inner artifact and private access; static nested as type member with only static access and new Outer.Inner() without outer instance.

721.7 Anonymous and Local Inner Classes

Anonymous class as unnamed one-object subclass via new Outer(){...}; synthetic $1 naming, outer-typed reference hides extra(), and local class confined to method braces with instantiation only inside.

821.8 Exception Handling — Fundamentals and the Try-Catch-Finally Model

Compile-time vs runtime errors, abnormal termination avoidance, try as guard, catch as conditional handler, finally as unconditional cleanup, and throw (generate) vs throws (forward) with division and BufferedReader sketches.

921.9 Exception Hierarchy, Checked vs Unchecked and Multiple and Nested Handling

Throwable into Exception and Error, checked vs unchecked split, IOException checked contract with BufferedReader readLine, multiple catch as switch on RuntimeException family, and nested try-catch propagation.

1021.10 User-Defined Exceptions with Throw and Throws

Authoring checked vs unchecked custom exceptions via extends; InvalidBoxDimensionException as RuntimeException, Box constructor throw/throws on l,w,h <=0, driver separate try-catch per construction, and area=2(lw+wh+hl) derivation with numeric 5,10,7 → 310.

11Exam Guidance Summary

Consolidated exam patterns for interfaces, defaults, static, nesting, inner classes, exceptions and custom InvalidBoxDimensionException.

12Key Industry Applications

Production mappings of interfaces, defaults, statics, nesting, inner classes and exception patterns to Java frameworks and libraries.

Postgraduate students in Object Oriented Design, Analysis and Programming

Exam Revision Notes

Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.

Interfaces — The Blueprint of a Class

Must-know: Interface fields are implicitly public static final (must be initialized); methods are implicitly public abstract (end with ;); explicit public on implementing methods is compulsory; cannot write new Interface().

⚠️ Top pitfall: Omitting public on implementing method gives weaker-access error; treating interface field as mutable per-object variable fails because it is static final shared constant.

Self-check: If you write int min = 5; inside interface Printable, what does the compiler see and how must you access it?

Connects to: 21.2, 21.5

Class-Interface Relationships and Multiple Inheritance

Must-know: Three keywords: class extends class (one), class implements interface(s) (many), interface extends interface(s) (many); class C extends A,B never compiles; one show() body satisfies two declaration-only interfaces; extends precedes implements.

⚠️ Top pitfall: Writing interface Showable implements Printable or duplicating implements keyword; thinking implements inherits instance fields; omitting public on implementing methods.

Self-check: Two interfaces both declare void show(); how many show() bodies must Trial supply and why?

Connects to: 21.1, 21.4, 21.5

Default Methods in Interfaces

Must-know: default marks a fallback body in interface; class may omit it and inherits default; if class supplies show() that wins; without default the class must be abstract or supply body.

⚠️ Top pitfall: Writing void show(){...} in interface without default; omitting public on overriding show(); expecting super.show() to reach default instead of InterfaceName.super.show().

Self-check: Printable has void print(); and default void show(){...}. Does class Trial implements Printable needing only print compile? What does new Trial().show() print?

Connects to: 21.2, 21.4

Static Methods in Interfaces and Default Methods Under Multiple Inheritance

Must-know: Static interface method called as Printable.show(), not t.show(); two competing defaults force override in Trial with InterfaceName.super.show() delegation; plain super.show() illegal.

⚠️ Top pitfall: Calling static via object reference; omitting qualified interface name on super; expecting compiler to pick one default automatically.

Self-check: Interfaces P and Q each default void show(). Class T implements P,Q with no show(). Does it compile? How do you fix it to run both defaults?

Connects to: 21.2, 21.3, 21.5

Nested Interfaces

Must-know: Nested interface always Outer.Inner outside; implicitly static; inside interface must be public; inside class any visibility. implements outer does NOT pull inner; implements inner is Outer.Inner; class inside interface uses extends Outer.Inner.

⚠️ Top pitfall: Writing bare Showable instead of Printable.Showable; thinking implements brings outer instance method print(); confusing implements vs extends for inner class.

Self-check: interface P { interface S {void show();}} — does class Trial implements P need to define show()? What about implements P.S?

Connects to: 21.1, 21.2, 21.6

Nested Classes — Member Inner and Static Inner Classes

Must-know: Member inner: Outer o = new Outer(); Outer.Inner in = o.new Inner(); accesses private instance+static; two files Outer.class and Outer$Inner.class. Static nested: Outer.Inner in = new Outer.Inner(); accesses only static; cannot access instance; top-level cannot be static.

⚠️ Top pitfall: Calling new Outer.Inner() for non-static inner (needs o.new Inner()); marking top-level class static; accessing instance field from static nested without explicit Outer reference; leaking outer via long-lived inner.

Self-check: When can an inner class access Outer's private int data and how do you construct it vs a static nested that accesses static int sData?

Connects to: 21.5, 21.7

Anonymous and Local Inner Classes

Must-know: Anonymous: new Outer(){ void show(){...}}; ends with ; and $1 synthetic; o.extra() illegal; local class class Inner inside method, only new Inner() inside that method, no Outer.Inner.

⚠️ Top pitfall: Calling extra() via outer type; forgetting ; after anonymous; trying new Interface() without body; instantiating local Inner outside its method.

Self-check: How do you create an anonymous implementation of interface Outer with show()? How do you instantiate a local Inner declared inside outerMethod()?

Connects to: 21.6, 21.8

Exception Handling — Fundamentals and the Try-Catch-Finally Model

Must-know: Compile-time errors caught by compiler (missing ;); runtime exceptions need try-catch; c=a/b with b=0 throws ArithmeticException; try marks risk, catch(ExceptionType e) conditional, finally unconditional and optional; throw new generates at if, throws on header forwards.

⚠️ Top pitfall: Confusing throw on body vs throws on header; catching in wrong subclass order; expecting finally to be conditional or to be skipped on return.

Self-check: Write try-catch for c=a/b where b may be 0; when does finally run and can it have a catch parameter?

Connects to: 21.9, 21.10

Exception Hierarchy, Checked vs Unchecked and Multiple and Nested Handling

Must-know: Hierarchy Throwable->Exception/Error; checked=Exception minus RuntimeException (IOException must handle or throws); unchecked=RuntimeException family. One try many catch like switch, specific-before-broad; nested inner catch first then outer; BufferedReader readLine forces IOException handling; finally optional unconditional.

⚠️ Top pitfall: Labeling RuntimeException family as checked; catching Exception before subclass making it unreachable; thinking bare try compiles; expecting multiple catches to all fire.

Self-check: Is ArithmeticException checked? What causes error: unreported exception must be caught or declared on br.readLine() and how to fix? Assign the three exceptions to arithmetic, parse, and index faults in one try.

Connects to: 21.8, 21.10

User-Defined Exceptions with Throw and Throws

Must-know: extends RuntimeException = unchecked fail-fast, extends Exception = checked forced; throw new creates at if(dim<=0), throws on header forwards type; Box area =2(lw+wh+hl) only on valid path; guard each new Box with its own try-catch.

⚠️ Top pitfall: Extending wrong parent (checked vs unchecked swapped); throw vs throws one-letter swap; throwing non-throwable or implements instead of extends; expecting one try for two Boxes to test both.

Self-check: Define InvalidBoxDimensionException as unchecked, write Box constructor that throws on any dim <=0, guard new Box(5,0,10) with try-catch, compute area for 5,10,7.

Connects to: 21.8, 21.9

Was this lecture useful?

Loading comments…
🤖

BitsNotes AI Assistant

Subject Notes Assistant

Configure AI Chat

Choose how to access the chatbot
Have your own API key?

Switch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.

🔑 Enter API key above to fetch live models from provider, or enter model name manually.
OpenAI-Compatible API Support

Choose any provider preset (Gemini, DeepSeek, Kimi, GLM, MiniMax, Qwen, OpenAI, Groq, Ollama, etc.) or enter a custom endpoint URL.

Security & Privacy First

Your API key is sent directly from your browser to your specified provider. BitsNotes servers never store or see your key.