When subclasses need to share mutable state and initialization logic, why does an abstract class fit better than an interface?
answer
- interface fields = public static final constants, not per-object
- no constructor in an interface -> nowhere to init state
- abstract class has fields + super() initialization
- default methods are stateless; lean on abstract accessors
- contract = interface, shared state = abstract skeletal class
basics
~20 sAn abstract class can have instance fields and a constructor, so it can store shared data per object and set it up when the object is created. An interface can't hold per-object data or have a constructor, so it can't manage shared mutable state.
solid answer
~50 sShared mutable state means data each object carries and can change — for example a counter or a name field common to a family of subclasses. An abstract class supports this directly: it declares instance fields (typically `protected`) and a constructor that initializes and validates them via `super(...)`, so every subclass reuses that storage and setup. An interface cannot: its only fields are implicit `public static final` constants (one shared value for the whole type, not per object), and it has no constructor, so there is no place to allocate or initialize per-instance data. Default methods let an interface ship *behavior*, but that behavior can only operate on values it receives or on abstract accessors — it has nowhere to keep state. So when the requirement is shared, constructed, per-instance data plus the invariants around it, an abstract class is the correct tool; an interface would force you to re-declare the state and its initialization in every implementer.
code
java · 21 lines// Abstract class: real per-instance state + constructor that maintains an invariant
abstract class Connection {
protected final String url; // per-instance, shared by all subclasses
protected int retryCount = 0; // mutable shared state
protected Connection(String url) {
if (url == null || url.isBlank())
throw new IllegalArgumentException("url required"); // invariant enforced at construction
this.url = url;
}
void recordRetry() { retryCount++; } // shared behavior over shared state
abstract void open(); // hook each subclass fills in
}
// An interface cannot do the above: this field is a CONSTANT, not instance state,
// and there is no constructor to initialize anything per object.
interface ConnectionLike {
String DEFAULT_SCHEME = "https"; // implicitly public static final
void open();
// 'int retryCount;' here would NOT compile as instance state.
}go deeper
Knows abstract classes can have fields and constructors and interfaces basically can't hold object data, so shared data goes in an abstract class.
Explains that interface fields are public static final constants, that interfaces lack constructors, and that default methods are stateless and must rely on abstract accessors — concluding shared mutable per-instance state belongs in an abstract class.
Discusses enforcing invariants in the abstract constructor, the single-inheritance cost, and the interface-plus-skeletal-abstract-class pattern as the way to get both a stable contract and shared state.
Considers memory/lifecycle pitfalls of faking interface state (instance-keyed maps), evolution of skeletal classes, and when composition/delegation beats an abstract base for sharing state across an inheritance-constrained type set.
## Terms first **State** is the data an object holds. **Instance state** (or *instance fields*) is data stored *per object* — each `Account` has its own `balance`. Contrast a **static** field, which is shared by the whole class (one value for all instances). **Mutable** means it can change after construction. A **constructor** is special code that runs once when an object is created; it's where fields get their initial values and where you enforce **invariants** (rules that must always hold, e.g. 'balance is never negative'). ## What an abstract class gives you An `abstract class` is still a class, so it has the full machinery: - **Instance fields**, often `protected` so subclasses can read/use them: `protected int retries;` - **Constructors**: `protected AbstractTask(String name) { this.name = name; }`. A subclass's constructor *must* call a superclass constructor (explicitly via `super(...)` or implicitly), so the shared fields are guaranteed to be initialized before the subclass body runs. - **Concrete methods** that read and mutate those fields, giving every subclass the same behavior over the same storage. So an abstract class is the natural home for *shared, constructed, per-instance data and the logic that maintains it*. The JDK's `AbstractList` keeps a `modCount` field that its concrete subclasses share for fail-fast iteration — that's shared mutable state living in an abstract base. ## Why an interface can't do this An interface is a contract, not a class, and it is *stateless by design*: 1. **No per-instance fields.** Any field you write in an interface is implicitly `public static final` — a single constant shared by everything, computed once, never per object and never mutable. You literally cannot declare `int balance;` as instance state in an interface. 2. **No constructor.** You never write `new SomeInterface()`. Without a constructor there is no moment to allocate or initialize per-object data, and no place to enforce construction-time invariants. 3. **Default methods are stateless.** A `default` method has a body, but it has no instance fields of its own to touch. It can only work with method arguments or call *abstract accessor* methods (like `getBalance()`) that each implementer must define — pushing the actual state back onto the implementer. People sometimes simulate per-instance state in interfaces using an external `Map<Instance, Value>`, but that's a leaky workaround with lifecycle and memory-leak hazards, not real instance state. ## The consequence If you force shared mutable state into an interface, every implementing class has to *re-declare the field, re-write the initialization, and re-enforce the invariants* — duplicating exactly the code an abstract class would have centralized. That duplication is error-prone and defeats the reuse you wanted. ## The balanced view This does **not** mean 'always use abstract classes.' The cost of an abstract class is the single-inheritance slot — a subtype that extends it can't extend anything else. The mature pattern is to keep the *public contract* as an interface and provide an *optional abstract skeletal class* that supplies the shared state and implementation for those who want it (the `List` interface + `AbstractList` skeleton). Implementers who can afford the inheritance slot extend the skeleton and get the shared state for free; those who can't still satisfy the interface. So: **interface for the contract, abstract class when shared per-instance state and its initialization must live in one place.**
- Can a default method in an interface read or modify an implementer's field?Not directly — the interface has no knowledge of the implementer's fields. The default method can only call abstract accessor methods the interface declares (e.g. getCount()/setCount()) that each implementer wires to its own field. The state still lives in the implementing class, not the interface.
- If you want a stable public contract AND shared state, what's the idiomatic structure?Publish an interface as the contract and provide an abstract 'skeletal' class implementing it that holds the shared state and common methods (the Collection/AbstractCollection, List/AbstractList pattern). Implementers extend the skeleton for reuse, or implement the bare interface if they can't spend the single-inheritance slot.
saying these in an interview costs you the question
- Believing you can add per-instance mutable fields to an interface.
- Thinking a default method has its own instance state to read/write.
- Ignoring that an abstract class costs the single extends slot when recommending it.
- Using a static Map keyed by instance to fake interface state without noting the lifecycle/memory-leak hazard.
- Confusing a static (class-wide) field with per-instance state.