☕ Java
Multiple Inheritance using Interface
Java does not allow a class to extend more than one class, but a class can implement any number of interfaces. This is Java's solution to the multiple inheritance problem: it provides the benefits — a class belonging to multiple types, code reuse through default methods — while avoiding the diamond problem that makes multiple class inheritance unpredictable. This entry covers why multiple class inheritance is problematic, how interface-based multiple type adoption works, diamond resolution rules with default methods, interface inheritance, practical patterns for composing behaviour through interfaces, and the design philosophy behind Java's choice.
Why Java Forbids Multiple Class Inheritance
The diamond problem is the reason Java restricts class inheritance to a single parent. Imagine class D extends both B and C, and both B and C extend A. A defines a method calculate(). B overrides it one way; C overrides it a different way. When D inherits both, which version of calculate() does D get? There is no unambiguous answer. C++ resolves this through explicit qualification, but the resolution is complex and error-prone. Java's designers chose to eliminate the problem entirely by forbidding it at the class level.
The problem is deeper than just which method wins. When B and C both inherit from A, there are two copies of A's fields in the diamond inheritance path — one through B and one through C. When D inherits both, does it have one copy of A's fields or two? If one, how are B's and C's independent modifications reconciled? If two, the same logical entity (one D object) has two instances of the same superclass concept inside it. This structural incoherence is why multiple class inheritance is fundamentally difficult.
Interfaces avoid the diamond problem because they carry no instance state. An interface method has no fields to be duplicated, no state to be incoherently doubled. When two interfaces both provide a default method with the same signature, the conflict is a compile error that the developer must resolve explicitly — the language does not guess. The resolution is always explicit and always in the implementing class, which has full knowledge of what both default methods do and what the correct behaviour should be.
Java
// ── The diamond problem with classes (hypothetical — Java forbids this) ──
//
// A { void compute() { result = 1; } }
// / // B C
// { compute() { result = 2; } } { compute() { result = 3; } }
// /
// D
// D.compute() — which version? B's or C's?
// Java prevents this with a compile error: "Cannot extend multiple classes"
// ── What Java actually allows ─────────────────────────────────────────
// D can implement interfaces from multiple "inheritance paths"
// because interfaces have no instance state to duplicate
interface Printable {
void print();
default String getFormat() { return "text/plain"; }
}
interface Saveable {
void save();
default String getFormat() { return "application/json"; }
}
// ── Both interfaces define getFormat() with a default ─────────────────
// Java forces explicit resolution
class Document implements Printable, Saveable {
@Override public void print() { System.out.println("Printing..."); }
@Override public void save() { System.out.println("Saving..."); }
// MUST override — ambiguity between Printable.getFormat() and
// Saveable.getFormat() is a compile error without this
@Override
public String getFormat() {
// Explicit choice — developer decides which makes sense
return Saveable.super.getFormat(); // or Printable.super.getFormat()
// or an entirely different value: return "application/pdf";
}
}
// ── No ambiguity when only one interface provides the default ──────────
interface Describable {
default String describe() { return "A describable object"; }
}
interface Identifiable {
String getId(); // abstract, no default
}
// No conflict — only Describable provides describe()
class Product implements Describable, Identifiable {
private final String id;
public Product(String id) { this.id = id; }
@Override public String getId() { return id; }
// describe() inherited from Describable — no override needed
}Multiple Type Adoption
The most valuable aspect of implementing multiple interfaces is that the implementing class simultaneously belongs to multiple types. A Dog that implements Animal, Trainable, and Comparable<Dog> can be stored in a List<Animal>, a List<Trainable>, or sorted in a TreeSet — all at the same time, with the same object. This multi-type membership is what gives interfaces their unique power in Java's single-inheritance class model.
Multiple type adoption models real-world capabilities accurately. A smartphone is a Phone, a Camera, a MusicPlayer, a GPS, and a Computer simultaneously — these are all genuine capabilities it has, not a single linear ancestry. Modelling this in Java without multiple interface implementation would require choosing one primary type and losing all the others. With multiple interfaces, the natural richness of capabilities is preserved.
Each interface a class implements creates a separate view onto the object. Code working with the Camera interface sees only camera operations. Code working with the GPS interface sees only location operations. The same object participates in each context with the appropriate face — this is role-based programming, and interfaces are the mechanism that enables it.
Java
// ── Multiple interfaces modelling real-world capabilities ────────────
public interface Flyable {
void takeOff();
void land();
double getMaxAltitude();
}
public interface Swimmable {
void dive();
void surface();
double getMaxDepth();
}
public interface Trackable {
String getId();
double getLatitude();
double getLongitude();
Instant getLastSeen();
}
public interface Measurable extends Comparable<Measurable> {
double getMeasurement();
@Override
default int compareTo(Measurable other) {
return Double.compare(this.getMeasurement(),
other.getMeasurement());
}
}
// ── Duck: a real bird that both flies AND swims ───────────────────────
public class Duck implements Flyable, Swimmable, Trackable, Measurable {
private final String id;
private double latitude, longitude;
private Instant lastSeen;
public Duck(String id) {
this.id = id;
this.lastSeen = Instant.now();
}
// Flyable
@Override public void takeOff() { System.out.println(id + " takes off"); }
@Override public void land() { System.out.println(id + " lands"); }
@Override public double getMaxAltitude() { return 150.0; }
// Swimmable
@Override public void dive() { System.out.println(id + " dives"); }
@Override public void surface() { System.out.println(id + " surfaces"); }
@Override public double getMaxDepth() { return 2.5; }
// Trackable
@Override public String getId() { return id; }
@Override public double getLatitude() { return latitude; }
@Override public double getLongitude() { return longitude; }
@Override public Instant getLastSeen() { return lastSeen; }
// Measurable — wingspan as the measurement
@Override public double getMeasurement() { return 0.92; }
public void updatePosition(double lat, double lon) {
this.latitude = lat;
this.longitude = lon;
this.lastSeen = Instant.now();
}
}
// ── Same object used through different interface types ─────────────────
Duck donald = new Duck("DUCK-001");
List<Flyable> flightControl = new ArrayList<>();
List<Swimmable> lifeGuard = new ArrayList<>();
List<Trackable> tracker = new ArrayList<>();
flightControl.add(donald); // Duck is Flyable
lifeGuard.add(donald); // Duck is Swimmable
tracker.add(donald); // Duck is Trackable
// Same object in three different collections serving three different roles
TreeSet<Measurable> sorted = new TreeSet<>();
sorted.add(donald); // Duck is Measurable — sortableInterface Inheritance
Interfaces can extend other interfaces, creating interface hierarchies. An interface that extends another inherits all its abstract and default methods. A class implementing the child interface must implement all abstract methods from both the child and parent interfaces. This allows interfaces to build on each other, creating richer contracts from simpler ones.
Unlike class inheritance, interface inheritance supports extending multiple interfaces simultaneously. An interface can extend two, three, or any number of other interfaces. This creates a rich capability composition mechanism: a ReadWriteRepository can extend both ReadRepository and WriteRepository, inheriting all operations from both and adding none of its own — it is purely a combination contract.
Interface inheritance combined with default methods enables sophisticated mixin-style patterns. An interface can define a capability entirely in terms of a small set of abstract methods, with a rich set of default methods built on top of those primitives. Any class implementing the abstract methods gets the full default method suite for free. This is how Comparable, Iterable, and Collection work in the JDK.
Java
// ── Interface inheritance — building richer contracts ────────────────
public interface ReadRepository<T, ID> {
Optional<T> findById(ID id);
List<T> findAll();
boolean existsById(ID id);
long count();
}
public interface WriteRepository<T, ID> {
T save(T entity);
void deleteById(ID id);
void deleteAll();
List<T> saveAll(List<T> entities);
}
// ── Combined interface — extends BOTH ────────────────────────────────
public interface Repository<T, ID>
extends ReadRepository<T, ID>, WriteRepository<T, ID> {
// Inherits ALL methods from both — no additional methods needed
// But CAN add combined operations:
default T findByIdOrThrow(ID id) {
return findById(id).orElseThrow(() ->
new EntityNotFoundException("Entity not found: " + id));
}
}
// ── Interface hierarchy with capability layering ──────────────────────
public interface Identifiable {
String getId();
}
public interface Timestamped extends Identifiable {
Instant getCreatedAt();
Instant getUpdatedAt();
}
public interface Versioned extends Timestamped {
int getVersion();
default boolean isNewerThan(Versioned other) {
return this.getVersion() > other.getVersion();
}
default boolean isModifiedAfter(Instant when) {
return getUpdatedAt().isAfter(when);
}
}
// ── Implementing the deepest interface gives everything ───────────────
public class Order implements Versioned {
private final String id;
private final Instant createdAt;
private Instant updatedAt;
private int version;
public Order(String id) {
this.id = id;
this.createdAt = Instant.now();
this.updatedAt = Instant.now();
this.version = 1;
}
@Override public String getId() { return id; }
@Override public Instant getCreatedAt() { return createdAt; }
@Override public Instant getUpdatedAt() { return updatedAt; }
@Override public int getVersion() { return version; }
public void update() {
this.updatedAt = Instant.now();
this.version++;
}
}
// ── All interface types are available ─────────────────────────────────
Order order = new Order("ORD-001");
Identifiable identifiable = order; // String getId()
Timestamped timestamped = order; // + getCreatedAt(), getUpdatedAt()
Versioned versioned = order; // + getVersion(), isNewerThan(), etc.
// Free default methods from the Versioned interface
Order v2 = new Order("ORD-001"); v2.update();
System.out.println(v2.isNewerThan(order)); // true — version 2 > 1Diamond Resolution Rules
Java has three explicit rules for resolving conflicts when a class inherits the same method from multiple sources. These rules apply when a class implements interfaces that provide conflicting default methods, or when class inheritance and interface default methods provide competing implementations.
Rule 1: Classes win over interfaces. If a class (or abstract class in the hierarchy) provides a concrete implementation of a method, that implementation wins over any interface default method with the same signature, regardless of which interface is involved. This rule ensures backward compatibility — existing class hierarchies are not disrupted by default methods added to interfaces they implement.
Rule 2: More specific interfaces win over less specific ones. If one interface extends another, and both provide a default method with the same signature, the more specific (child) interface's default method wins. This is analogous to method overriding in class hierarchies — the more derived version takes precedence.
Rule 3: If neither Rule 1 nor Rule 2 resolves the ambiguity — two independent interfaces both provide a default method — the class must explicitly override the method. The developer uses InterfaceName.super.methodName() to call a specific interface's default method if desired, or writes an entirely new implementation.
Java
// ── Rule 1: Class beats interface ─────────────────────────────────────
interface Greeter {
default String greet() { return "Hello from Greeter interface"; }
}
class BaseGreeter {
public String greet() { return "Hello from BaseGreeter class"; }
}
class MyGreeter extends BaseGreeter implements Greeter {
// No override needed — BaseGreeter.greet() wins over Greeter default
}
System.out.println(new MyGreeter().greet());
// "Hello from BaseGreeter class" — Rule 1: class wins
// ── Rule 2: More specific interface wins ──────────────────────────────
interface Vehicle {
default String describe() { return "Vehicle"; }
}
interface Car extends Vehicle {
@Override
default String describe() { return "Car"; } // more specific
}
class Tesla implements Car {
// No override needed — Car.describe() wins over Vehicle.describe()
// because Car is more specific (extends Vehicle)
}
System.out.println(new Tesla().describe()); // "Car" — Rule 2
// ── Rule 3: Explicit override required when neither rule resolves ──────
interface A {
default String hello() { return "Hello from A"; }
}
interface B {
default String hello() { return "Hello from B"; }
}
// A and B are independent — neither is more specific
// Rule 1 does not apply (no class provides hello())
// Rule 2 does not apply (neither A nor B extends the other)
// Rule 3 applies: must override
class C implements A, B {
@Override
public String hello() {
// Option 1: choose one interface's default
return A.super.hello();
// Option 2: combine both
// return A.super.hello() + " and " + B.super.hello();
// Option 3: write new implementation
// return "Hello from C";
}
}
System.out.println(new C().hello()); // "Hello from A"
// ── All three rules in one example ───────────────────────────────────
interface Base { default void method() { System.out.println("Base"); } }
interface Extended extends Base
{ default void method() { System.out.println("Extended"); } }
class ConcreteClass { public void method() { System.out.println("ConcreteClass"); } }
// Rule 1 wins:
class Test1 extends ConcreteClass implements Extended {
// ConcreteClass.method() wins — prints "ConcreteClass"
}
// Rule 2 wins (no class):
class Test2 implements Base, Extended {
// Extended.method() wins — prints "Extended"
}Mixin-Style Composition with Interfaces
A mixin is a unit of reusable behaviour that can be added to a class without affecting its class hierarchy. Java does not have true mixins, but default methods in interfaces come close. An interface can provide a complete, reusable capability — logging, auditing, timestamping, observable notifications — as a set of default methods built on top of a small set of abstract methods the class must provide. The implementing class provides the primitives; the interface provides the derived behaviour.
This pattern works best when the default methods only depend on the abstract methods defined in the same interface, not on external state. The interface is then self-contained — any class providing the abstract method primitives gets the full default behaviour suite. The implementing class chooses its capabilities at the implements clause level, like configuring a component by selecting which mixins to include.
The difference from full mixins (as in Scala traits or Ruby modules) is that Java interface default methods cannot access the implementing class's fields directly — they can only call other methods on this. This restriction keeps default methods from becoming tangled with implementation details, which is actually a healthy constraint.
Java
// ── Mixin-style interfaces with default methods ───────────────────────
// ── Observable mixin — adds event notification to any class ──────────
public interface Observable {
// Abstract primitive — implementing class manages listeners
List<EventListener> getListeners();
// Default behaviour — built entirely on the primitive
default void addListener(EventListener listener) {
getListeners().add(listener);
}
default void removeListener(EventListener listener) {
getListeners().remove(listener);
}
default void notifyListeners(String event, Object data) {
getListeners().forEach(l -> l.onEvent(event, data));
}
}
// ── Auditable mixin — adds audit trail to any entity ─────────────────
public interface Auditable {
// Abstract primitives — implementing class provides these values
String getCreatedBy();
String getLastModifiedBy();
Instant getCreatedAt();
Instant getLastModifiedAt();
// Default behaviour built on primitives
default String getAuditSummary() {
return String.format(
"Created by %s at %s; last modified by %s at %s",
getCreatedBy(), getCreatedAt(),
getLastModifiedBy(), getLastModifiedAt());
}
default boolean wasCreatedBy(String user) {
return getCreatedBy().equals(user);
}
default boolean wasModifiedAfter(Instant when) {
return getLastModifiedAt().isAfter(when);
}
}
// ── Validatable mixin ─────────────────────────────────────────────────
public interface Validatable {
// Abstract — implementing class defines its rules
List<String> getValidationErrors();
// Default behaviour
default boolean isValid() {
return getValidationErrors().isEmpty();
}
default void assertValid() {
List<String> errors = getValidationErrors();
if (!errors.isEmpty()) {
throw new ValidationException(
"Validation failed: " + String.join(", ", errors));
}
}
}
// ── A domain class composes multiple mixins ───────────────────────────
public class Order implements Observable, Auditable, Validatable {
private final String id;
private final List<OrderItem> items;
private final List<EventListener> listeners = new ArrayList<>();
// Audit fields
private final String createdBy;
private final Instant createdAt;
private String lastModifiedBy;
private Instant lastModifiedAt;
public Order(String id, String createdBy) {
this.id = id;
this.items = new ArrayList<>();
this.createdBy = createdBy;
this.createdAt = Instant.now();
this.lastModifiedBy = createdBy;
this.lastModifiedAt = Instant.now();
}
// Observable primitive
@Override public List<EventListener> getListeners() { return listeners; }
// Auditable primitives
@Override public String getCreatedBy() { return createdBy; }
@Override public String getLastModifiedBy() { return lastModifiedBy; }
@Override public Instant getCreatedAt() { return createdAt; }
@Override public Instant getLastModifiedAt() { return lastModifiedAt; }
// Validatable primitive
@Override
public List<String> getValidationErrors() {
List<String> errors = new ArrayList<>();
if (items.isEmpty()) errors.add("Order must have at least one item");
if (id == null) errors.add("Order ID is required");
return errors;
}
public void addItem(OrderItem item) {
items.add(item);
lastModifiedAt = Instant.now();
notifyListeners("ITEM_ADDED", item); // from Observable mixin
}
}
// ── All mixin behaviour available on Order ────────────────────────────
Order order = new Order("ORD-001", "alice");
order.addListener(event -> System.out.println("Event: " + event));
order.addItem(new OrderItem("PROD-1", 2));
System.out.println(order.getAuditSummary()); // from Auditable mixin
order.assertValid(); // from Validatable mixinRelated Topics in Object-Oriented Programming
OOP Concepts
Object-Oriented Programming (OOP) is a programming paradigm that organises software around objects — self-contained units that combine data (fields) and behaviour (methods). Java is a class-based, object-oriented language where almost everything is an object. OOP provides four foundational principles: encapsulation, inheritance, polymorphism, and abstraction. Together they produce software that is modular, reusable, maintainable, and easier to reason about as systems grow in complexity.
Classes
A class is the fundamental building block of Java. It is a blueprint that defines the structure and behaviour of objects — what data each object holds (fields) and what operations it can perform (methods). Every Java program is composed of classes. Understanding how to design a class well — choosing the right access modifiers, separating state from behaviour, and writing cohesive single-responsibility classes — is the foundation of object-oriented programming in Java. This entry covers class anatomy, fields, methods, access modifiers, static vs instance members, the this keyword, and class design principles.
Objects
An object is a runtime instance of a class. Where a class is a blueprint that exists in source code, an object is a living entity that exists in memory during program execution — it has its own identity, its own state stored in its fields, and the ability to respond to method calls. Every object has three fundamental properties: identity (a unique memory address), state (the current values of its fields), and behaviour (the methods it responds to). This entry covers object identity vs equality, the Object class hierarchy, object state and mutation, method calls, toString, equals and hashCode, and the object lifecycle.
Object Creation
Object creation in Java is the process of allocating memory, initialising fields, and running constructor logic to bring an object into existence. The new keyword is the primary mechanism, but Java also provides factory methods, builder patterns, copy constructors, and object cloning. Constructors are special methods that set up the initial state — their design determines how easy or difficult the class is to use correctly. This entry covers constructors in depth, constructor overloading and chaining, copy constructors, factory methods, the builder pattern, and the difference between shallow and deep copy.