When should you choose an abstract class over an interface or a concrete class, and what are the long-term costs of exposing an abstract class in a public API?
answer
- abstract class = shared state + partial impl + is-a
- interface = contract + multiple inheritance of type
- interface + skeletal abstract class combo
- public abstract class: single-inheritance tax, protected is forever
- adding abstract method = breaking change
- design for inheritance or prohibit it
basics
~20 sUse an abstract class when subclasses share real state and implementation under a single 'is-a' hierarchy. Prefer an interface when you only need a contract or multiple inheritance of type. Exposing an abstract class in a public API locks you into its structure, so favor composition where you can.
solid answer
~50 sReach for an abstract class when you have genuine shared mutable state plus partially-implemented behavior that several closely-related subclasses inherit under one 'is-a' relationship — the skeletal-implementation case (AbstractList). Prefer an interface when you only need to publish a contract, want multiple inheritance of type, or expect unrelated implementers; since Java 8 interfaces can carry default methods, so an interface plus a companion abstract skeletal class (AbstractX implements X) often beats a lone abstract class. Prefer a concrete class or composition when there's no real variation to defer — inheritance adds coupling for nothing. The long-term costs of an abstract class in a public API are steep: single inheritance means clients can't also extend their own base; you can add concrete methods later but not abstract ones without breaking subclasses; protected members become part of your forever-contract; and base constructors that call overridable methods create fragile-base-class hazards. Effective Java's guidance: design for inheritance and document it, or prohibit it.
go deeper
Can state the rough rule: abstract class for shared code under one hierarchy, interface for a pure contract.
Distinguishes shared state (abstract class only) vs multiple inheritance of type (interface) and knows default methods narrowed the gap.
Recommends the interface + skeletal-abstract-class combo, explains composition-over-inheritance, and the construction-order/fragile-base hazards.
Reasons about API evolution (concrete-add OK, abstract-add breaks; protected-is-forever), Liskov obligations, 'design for inheritance or prohibit it', and when sealed types/Strategy supersede an abstract base.
## The three candidates When modeling shared behavior you choose among: a **concrete class** (fully implemented, instantiable), an **abstract class** (partial implementation + abstract slots, not instantiable), and an **interface** (a contract; since Java 8 it may carry `default`/`static` method bodies but no instance state). Picking well is a design decision with long-lived consequences. ## When an abstract class wins Choose an abstract class when **all** of these hold: - There is a genuine **'is-a'** relationship (a `Circle` IS-A `Shape`) — not merely 'can-do.' - Subclasses share **mutable instance state** that should be declared and initialized once. Interfaces *cannot* hold instance fields, so only an abstract class can centralize this. - You have **partial implementation** to share — concrete methods written in terms of a few abstract primitives (the **skeletal implementation** / Template Method case, e.g. `AbstractList` built on `get(int)`+`size()`). - A **single, controlled hierarchy** is acceptable, because `extends` consumes the subclass's one inheritance slot. ## When an interface wins Prefer an interface when: - You only need to **publish a contract** ('anything that can be Comparable / Closeable'), with no shared state. - Implementers may be **unrelated** types that already extend something else — interfaces give **multiple inheritance of type** (a class can implement many). - You want maximum flexibility to **retrofit** existing classes (they can implement a new interface without changing their superclass). - Default methods can supply the small bit of shared behavior you need without instance state. The canonical combo is **interface + skeletal abstract class**: publish `List` (interface), provide `AbstractList` (abstract skeleton) for implementers who want the freebies, but don't *force* the inheritance on anyone. This gets the best of both. ## When neither — use composition If there's no real variation to defer, don't introduce a hierarchy at all. A concrete class, or **composition + delegation** (hold a collaborator, forward calls) or a **strategy object** (a functional interface injected at runtime), often beats inheritance: it avoids the coupling, sidesteps the single-inheritance limit, and lets behavior change at runtime. 'Favor composition over inheritance' is the default; inheritance is the exception you justify. ## The long-term costs of a *public* abstract class Exposing an abstract class as part of an API is a heavy, near-irreversible commitment: 1. **Single-inheritance tax.** Every subclass spends its one `extends` slot on you. If a client already has a base class, they *cannot* use your abstract class by extension at all. 2. **Adding abstract methods is a breaking change.** You can append *concrete* methods in a later version, but adding an *abstract* method breaks every existing subclass (they no longer compile). Interfaces with default methods are more evolvable here. 3. **`protected` is forever.** Any protected field or method you expose for subclasses becomes part of your permanent contract; you can't tighten or remove it without breaking subclasses. Protected members leak implementation detail into the API. 4. **Fragile base class.** Changing the base's internal call sequence can silently break subclasses that overrode hooks expecting the old order. And a base constructor that calls an overridable method invokes subclass code before the subclass is initialized — a latent NPE generator. 5. **Liskov obligations.** Subclasses must honor the base's invariants; a poorly specified base makes correct subclassing impossible. ## Effective Java's rule The guidance (Bloch): **'Design and document for inheritance, or else prohibit it.'** If you ship an abstract class meant to be extended, document *exactly* which methods call which overridable methods, in what order (the self-use patterns), and provide a skeletal implementation. If you don't intend extension, make the class `final` or hide its constructor. Half-measures — an extensible-looking class with undocumented self-use — are how fragile-base-class bugs are born. ## Modern alternatives For closed type families, **sealed classes/interfaces** plus **pattern-matching switch** let you express 'these and only these subtypes' with exhaustive handling, often replacing an abstract base + visitor. For varying a single behavior, **functional interfaces + lambdas** (Strategy) beat subclassing. Reserve abstract classes for the true skeletal-implementation case where shared state and a single hierarchy genuinely pay for themselves.
- Why is the interface + skeletal-implementation (e.g. List + AbstractList) combo often better than a lone abstract class?It decouples the published contract (interface, multiple-inheritable, retrofittable) from the optional implementation help (abstract skeleton). Implementers who already extend something can still implement the interface directly; those who want the freebies extend the skeleton. You don't force the single-inheritance cost on everyone.
- Why is adding an abstract method to a published abstract class a breaking change, while adding a concrete method usually isn't?An added abstract method has no implementation, so every existing subclass instantly fails to compile until it provides one. A concrete method ships with a body, so existing subclasses keep working (barring a name clash). This is why default methods made interfaces more evolvable.
- When would sealed classes replace an abstract-class hierarchy?When the set of subtypes is closed and known. A sealed hierarchy plus pattern-matching switch gives exhaustive, default-free handling and lets logic live outside the type hierarchy, often replacing an abstract base plus the Visitor pattern while keeping the compiler's exhaustiveness check.
saying these in an interview costs you the question
- Defaulting to inheritance when composition/strategy would do — adds coupling for no gain.
- Assuming you can add abstract methods to a published abstract class without breaking subclasses.
- Exposing protected mutable fields and treating them as private implementation detail.
- Shipping an extensible-looking abstract class with undocumented self-use (fragile base class).
- Believing interfaces can hold mutable instance state like abstract classes (they cannot).