Definition
- Single supertype rule – the
new Type() { ... }syntax names exactly one class or one interface, with no place for a combinedextends/implementsclause. - Class-based anonymous – when
Typeis a class, the anonymous class extends that class and may use its constructor arguments. - Interface-based anonymous – when
Typeis an interface, the anonymous class implements that one interface with an empty argument list. - Transitive interfaces – an anonymous object can satisfy an interface only if the single superclass already implements it.
- Workaround – define a combined interface that
extendsmultiple interfaces, then implement the combined interface anonymously.
Think of a temporary helper at National Bank of India who is given a single job slip. The slip can say either "act as a Teller" or "follow the Auditor rulebook," but it physically has room for only one role. If Rajesh needs someone who is both a Teller and follows the Auditor rulebook, he must formally hire a named employee whose contract lists both roles. The nameless one-slip helper can wear only one hat.
Understanding the Concepts
What is the single-supertype rule for anonymous classes?
The grammar of an anonymous class is new SuperType(args) { body }, where SuperType is a single class name or a single interface name. There is no syntactic slot for an extends clause combined with an implements clause. The compiler allows exactly one supertype.
package com.bank.service;
import java.math.BigDecimal;
public class SingleSupertypeDemo {
interface Auditable {
String auditTrail();
}
static abstract class AccountProcessor {
abstract BigDecimal process(BigDecimal amount);
}
public static void main(String[] args) {
// OK: one interface
Auditable audit = new Auditable() {
@Override
public String auditTrail() {
return "Audit entry for Priya's account";
}
};
// OK: one class
AccountProcessor processor = new AccountProcessor() {
@Override
BigDecimal process(BigDecimal amount) {
return amount.multiply(new BigDecimal("1.05"));
}
};
System.out.println(audit.auditTrail());
System.out.println(processor.process(new BigDecimal("100000")));
}
}
Audit entry for Priya's account
105000.00
Why can an anonymous class not do both at once?
The anonymous syntax new Type() { } names exactly one type. There is no place to write extends AccountProcessor implements Auditable in that expression. A named class has separate extends and implements clauses, but the anonymous form was deliberately kept simple with only one supertype slot. This prevents ambiguity about which constructor to call and keeps the inline expression readable.
// This does NOT compile:
// Auditable a = new AccountProcessor() implements Auditable { };
// There is no syntax for it.
How does transitive interface implementation work?
An anonymous object can end up being both a subclass instance and an interface implementer, but only when the single class being extended already implements that interface. The interface comes from the base class's type hierarchy, not from the anonymous declaration.
package com.bank.service;
import java.math.BigDecimal;
public class TransitiveDemo {
interface Schedulable {
void schedule();
}
static abstract class SettlementTask implements Schedulable {
abstract BigDecimal settle();
}
public static void main(String[] args) {
// Anonymous class extends SettlementTask
// It is also Schedulable because SettlementTask implements Schedulable
SettlementTask task = new SettlementTask() {
@Override
public void schedule() {
System.out.println("Scheduled settlement for National Bank of India");
}
@Override
BigDecimal settle() {
return new BigDecimal("5000000");
}
};
Schedulable s = task; // legal: transitive interface
s.schedule();
System.out.println("Settled: " + task.settle() + " INR");
}
}
Scheduled settlement for National Bank of India
Settled: 5000000 INR
What is the combined-interface workaround?
Define a single interface that extends multiple interfaces, then implement that combined interface anonymously. Interfaces can extend multiple interfaces, so this composes cleanly while respecting the one-supertype rule.
package com.bank.service;
import java.math.BigDecimal;
public class CombinedInterfaceWorkaround {
interface Auditable {
String auditTrail();
}
interface Comparable2 {
int compareByAmount(BigDecimal other);
}
// Combined interface extends both
interface AuditableComparable extends Auditable, Comparable2 {
}
public static void main(String[] args) {
AuditableComparable entry = new AuditableComparable() {
private final BigDecimal amount = new BigDecimal("75000");
@Override
public String auditTrail() {
return "Trade entry: " + amount + " INR";
}
@Override
public int compareByAmount(BigDecimal other) {
return amount.compareTo(other);
}
};
System.out.println(entry.auditTrail());
System.out.println("Compare to 50000: " + entry.compareByAmount(new BigDecimal("50000")));
}
}
Trade entry: 75000 INR
Compare to 50000: 1
When should you use a named class instead?
When you must extend a concrete class and implement a new interface that the class does not already carry, the anonymous form cannot express it. Use a named class, which also improves testability and readability in a bank's codebase.
package com.bank.service;
import java.math.BigDecimal;
public class NamedClassExample {
interface Exportable {
String export();
}
static class LedgerEntry {
String id;
BigDecimal amount;
LedgerEntry(String id, BigDecimal amount) {
this.id = id;
this.amount = amount;
}
}
// Named class: extends class AND implements interface
static class ExportableLedgerEntry extends LedgerEntry implements Exportable {
ExportableLedgerEntry(String id, BigDecimal amount) {
super(id, amount);
}
@Override
public String export() {
return id + "," + amount;
}
}
public static void main(String[] args) {
ExportableLedgerEntry entry =
new ExportableLedgerEntry("TXN-5001", new BigDecimal("320000"));
System.out.println(entry.export());
}
}
TXN-5001,320000
How is this different from a named inner class?
A named inner class can appear in an extends and implements clause just like a top-level class. An anonymous class cannot, because it has no declaration syntax for those clauses. A named inner class is also reusable by name and testable in isolation.
| Feature | Anonymous Class | Named Inner Class |
|---|---|---|
| Supertypes | Exactly one | One class + multiple interfaces |
| Reusable by name | No | Yes |
| Testable in isolation | Difficult | Easy |
| Constructor | No | Yes |
What are common mistakes with the single-supertype rule?
Developers sometimes try to add an interface in the anonymous declaration, which does not compile.
// WRONG: trying to extend and implement in one anonymous class
// AccountProcessor p = new AccountProcessor() implements Auditable {
// ... // COMPILE ERROR: syntax not supported
// };
// CORRECT: use the combined-interface workaround or a named class
What should a senior engineer know?
In a banking production codebase, the single-supertype restriction is actually a nudge toward better design. A settlement handler that must be both a domain SettlementStrategy and a Spring ApplicationListener cannot be an anonymous class satisfying both independently. It becomes a named class, which is easier to test, document, and review. Senior engineers embrace this restriction because it pushes complex, multi-contract types into named, well-structured classes.
package com.bank.service;
import java.math.BigDecimal;
// Production pattern: named class with multiple contracts
public class SettlementHandler
extends SettlementStrategy
implements ApplicationListener<SettlementEvent>, Auditable {
@Override
public BigDecimal execute(BigDecimal amount) {
return amount; // settlement logic
}
@Override
public void onApplicationEvent(SettlementEvent event) {
// handle event
}
@Override
public String auditTrail() {
return "SettlementHandler audit";
}
}
Visual Overview
┌─────────────────────────────────────────────┐
│ Anonymous Class: Single Supertype Rule │
├─────────────────────────────────────────────┤
│ │
│ new SuperType(args) { body } │
│ │ │
│ ├── SuperType is a class? │
│ │ └── Extends that class │
│ │ (inherits its interfaces) │
│ │ │
│ └── SuperType is an interface? │
│ └── Implements that interface │
│ (args must be empty) │
│ │
├─────────────────────────────────────────────┤
│ │
│ Need both class + interface? │
│ │ │
│ ├── Transitive: base class already │
│ │ implements the interface → OK │
│ │ │
│ ├── Combine interfaces into one │
│ │ combined interface → OK │
│ │ │
│ └── Otherwise: use named class │
│ │
└─────────────────────────────────────────────┘
Key Takeaway
An anonymous class cannot extend a class and implement an interface at the same time, because the anonymous syntax names exactly one supertype. When you write new SomeType() { body }, SomeType is either a single class or a single interface, and there is no place for a second extends or implements clause. A named class, by contrast, can extend one class and implement many interfaces.
There is one subtle case that interviewers like to probe. An anonymous object can end up being both a subclass instance and an interface implementer, but only when the single class being extended already implements that interface. In a banking project a settlement task that implements Schedulable can be anonymously subclassed, and the resulting object is both a settlement task and a Schedulable, but the interface came transitively from the base class, not from a second supertype declaration.
When you genuinely need a throwaway object that satisfies two independent contracts, the clean solution is to define one combined interface that extends both, because interfaces are allowed to extend multiple interfaces. Then implement that single combined interface anonymously, supplying both method sets. That respects the one-supertype rule while still giving you both behaviours.
When you must extend a concrete class and implement a brand-new interface at the same time, there is no anonymous way to express it, so you write a named class. In practice this is also better for a bank's codebase because named classes are easier to test, easier to read, and easier to reuse.
Remember one sentence: an anonymous class takes exactly one supertype, so it can never extend a class and implement an interface at the same time; use a combined interface or a named class when you need both.
---