Robust Oop
Abstract Classes & Interfaces
An abstract class is a partial type. It can hold fields, constructors and concrete methods next to abstract methods with no body, and you can't instantiate it, so subclasses must fill in the blanks. An interface is a contract that a class implements. Default methods give it concrete behavior, private methods share code between them, and static methods belong to the interface alone. A class extends one class but implements many interfaces, so interfaces give multiple inheritance of type. A clash between two defaults is a compile error you resolve with X.super.m(). Every rule is shown with verified output.
Suggest an editAbstract Classes & Interfaces — Contracts and Partial Types
With inheritance, a subclass specializes a concrete superclass. Java has two more tools that let you specify behavior a type must provide, without saying how:
- An abstract class is a partial implementation. It can declare abstract methods (signatures with no body), and it can't be instantiated itself. That forces every concrete subclass to supply the missing pieces.
- An interface is a contract: a set of methods that any class can
implement, with no instance fields.
Here's the crucial asymmetry: a class extends exactly one class, but it implements as many interfaces as it likes. So interfaces give Java multiple inheritance of type (a class can be many things), without multiple inheritance of state. This is the machinery behind "program to the interface."
💡 The core idea.
- An abstract class is a partial type with body-less methods — it can't be instantiated.
- An interface is a contract a class
implements, with no instance fields. - A class
extendsone class butimplementsmany interfaces. - So interfaces give multiple inheritance of type, never of state.
Every output below was produced by compiling and running the code on Java 25.
You'll be able to: write an abstract class with a field, a constructor and a concrete method, and predict the error you get when it's instantiated or when a subclass skips an abstract method; write an interface whose default methods share a private helper, and call its static method through the interface's name; predict from its access level whether an implementing method compiles; resolve a clash between two inherited default methods with X.super.m(); choose between an abstract class and an interface for a design.
📘 How to read the Intuition boxes. Each one is built in three moves:
- The mechanism — what the compiler and the JVM do.
- A concrete bite — a specific, runnable failure (often a real compiler error), shown so the trap is visible.
- The earned rule — the decision heuristic, now justified rather than asserted, plus its cost.
Table of contents
- Abstract classes
- Interfaces
defaultmethodsstaticandprivateinterface methods- One class, many interfaces
- Mental-model summary
- Gotcha checklist
- Check yourself
- Sources
1. Abstract classes
An abstract class can declare abstract methods — signatures with no body [2]. Each one says, "every subclass must provide this." Because it has unfinished methods, an abstract class can't be instantiated; only its concrete subclasses can.
Output:
12.57
9.00Analysis. Shape declares area() with no body: it knows that every shape has an area, but not how to compute it. Circle and Square each supply their own version, and the polymorphic loop calls the right one (12.57, 9.00). Shape provides the common type and contract, while the subclasses provide the specifics.
Don't forget that an abstract class is still a class. Alongside its abstract methods, it can hold fields, constructors and ordinary methods. Its constructor runs whenever a subclass object is created [1]. Below, the concrete describe() calls the abstract area(), which dispatches to the subclass:
Output:
circle with area 12.57
square with area 9.00Shape owns the shared state (name) and the shared sentence (describe()). Each subclass writes only the part that differs: area().
Intuition. Mechanism. An abstract method has no implementation. So an object of the abstract class would have a "hole" in it: a method with nothing to run. That's why the compiler forbids creating one [1]. It also requires every concrete subclass to override all of the abstract methods it inherits.
Concrete bite. Try to instantiate the abstract class directly, and it won't compile:
Compiler error:
Main.java:5: error: Shape is abstract; cannot be instantiated
Shape s = new Shape();
^new Shape() is rejected, because Shape.area() has no body, so there's no complete object to make. You can hold a Shape reference (pointing to a Circle or a Square), but you can't create a bare Shape.
Non-example: a subclass that forgets the abstract method. A class that isn't abstract may not have an abstract method, whether declared or inherited [1]. Here, Triangle inherits area() without a body:
Compiler error:
Main.java:3: error: Triangle is not abstract and does not override abstract method area() in Shape
class Triangle extends Shape {
^
1 errorThe message hints at both fixes: override area(), or declare Triangle abstract too. You'll see the same message if you write abstract double area(); inside a class you forgot to mark abstract — oddly, that class is told it doesn't override its own method.
💡 Earned rule. Use an abstract class when subtypes share both a common type and some implementation or state, but a key operation (like area()) must differ per subtype.
- The cost: a class can extend only one abstract class (single inheritance).
- The payoff: shared code plus an enforced contract. The compiler guarantees that no subclass forgets to implement the abstract methods.
2. Interfaces
An interface is a contract: a set of method signatures, with no instance fields to inherit. A class implements an interface by providing all of its methods. You can then write code against the interface, and it works with any implementer.
Output:
drawing a circleAnalysis. Drawable declares draw() with no body, and Circle implements Drawable supplies it. A Drawable reference can point at any implementer and call the contracted method — the same "program to the interface" idea you used with List.
- An interface method with no body is implicitly
abstract[3]. - An interface method with no access modifier is implicitly
public[3]. So the implementing method must bepublictoo.
Intuition.
Mechanism. An interface defines a type and a method contract. It has no instance fields and no constructor: there's nothing to instantiate, only something to implement. Any field you declare in an interface is implicitly public static final — in other words, a constant [4]. A class that declares implements promises to provide every abstract method, and the compiler checks that promise.
Concrete bite. Leave a contracted method unimplemented, and the class won't compile:
Compiler error:
Main.java:3: error: Circle is not abstract and does not override abstract method draw() in Drawable
class Circle implements Drawable { }
^Circle claims to be Drawable but never provides draw(), so it's incomplete. Either implement the method, or declare Circle itself abstract. Either way, the contract is enforced at compile time.
Non-example: an implementation without public. draw() in the interface is public. If you write the implementation with no modifier, you narrow the access — and an override may not do that [8]:
Compiler error:
Main.java:4: error: draw() in Circle cannot implement draw() in Drawable
@Override void draw() { System.out.println("drawing a circle"); }
^
attempting to assign weaker access privileges; was public
1 errorThe interface never wrote public, yet draw() is public anyway. The fix is to add public to the implementation.
💡 Earned rule. Define an interface to specify what a type can do, without dictating how or sharing state. Then program against it, so any implementer fits.
- The cost: a plain interface carries no implementation (until
defaultmethods, coming up next). - The payoff: a contract decoupled from any class. It's the foundation for swappable implementations, and for lambdas.
3. default methods
Since JDK 8, an interface method can have a body, as long as it's marked default [10]. Implementers inherit it for free, and may override it if they like. This lets an interface ship actual behavior, not just signatures. It's also how you add a method to an interface without breaking every existing implementer.
Output:
Hello, AdaAnalysis. Person implemented only name(). It inherited greet() from the interface's default body, which called name() polymorphically ("Hello, Ada"). A default method is concrete behavior built on top of the interface's abstract methods: the interface provides the how, in terms of the implementer's what.
Intuition.
Mechanism. A default method's implementation lives in the interface, and implementers inherit it unless they override it [3]. It can call the interface's abstract methods (like name()), which dispatch to the implementing class. So the default's behavior adapts to each implementer.
Concrete bite. Defaults are what let interfaces evolve. Before JDK 8, adding a method to a widely implemented interface broke every implementer's compile, because each one suddenly lacked the new method. A default method adds the capability along with a fallback, so old implementers keep compiling. The cost shows up only when a class inherits two defaults with the same signature. §5 shows that clash, and how to fix it.
💡 Earned rule. Use a default method to provide optional or derivable behavior on an interface — especially to extend an existing interface compatibly — while keeping the interface's core as abstract signatures.
- The cost: an interface with many defaults drifts toward being an abstract class, just without state.
- The payoff: interfaces that carry useful behavior, and can grow without breaking implementers.
4. static and private interface methods
An interface can hold two more kinds of method with a body [3]:
- A
staticmethod (JDK 8) belongs to the interface itself [13]. You call it asInterface.method(), without any object. It's a good home for helpers that go with the type. - A
privatemethod (JDK 9) is a helper for the interface's own method bodies [11]. Twodefaultmethods can share its code without making it part of the contract.
Output:
100.0 C
212.0 F
98.6Analysis.
Kettlewrote onlycelsius(), and inheritedreport()andreportF().- Both defaults do their formatting through a single
privatehelper,label(...). reportF()calls thestatictoFahrenheitby its simple name, since it's inside the interface.maincalls it asTemperature.toFahrenheit(37): the interface's name, and no object.
Intuition.
Mechanism. A class doesn't inherit private or static methods from its interfaces [5]. The private helper is invisible outside the interface. The static method stays attached to the interface, and you can reach it only through the interface's name.
Concrete bite. Call the static method through an implementing class, and javac can't find it:
Compiler error:
Main.java:12: error: cannot find symbol
System.out.println(Kettle.toFahrenheit(37));
^
symbol: method toFahrenheit(int)
location: class Kettle
1 errorKettle implements Temperature, but toFahrenheit isn't a member of Kettle. Calling it through a Kettle variable, as k.toFahrenheit(37), fails the same way (location: variable k of type Kettle). Note that this differs from a class's static method, which a subclass does inherit [5].
Non-example: calling the private helper from outside.
Compiler error:
Main.java:15: error: label(double,String) has private access in Temperature
System.out.println(k.label(100, "C"));
^
1 errorlabel is an implementation detail of Temperature. Callers get report() and reportF(), and never see the helper.
💡 Earned rule. Put a helper that belongs with the type in a static interface method, and call it by the interface's name. Put code that several default methods share in a private method.
- The cost: you can never reach a
staticinterface method through an implementer, so callers must name the interface. - The payoff: the contract stays small. Implementers and callers see only the abstract and
defaultmethods.
5. One class, many interfaces
Here's the defining difference: a class extends one class, but implements many interfaces [9]. That means a type can be several things at once. Interfaces give you multiple inheritance of type, which a single superclass can't.
Output:
swimming and flyingAnalysis. Duck implements both Swimmer and Flyer, inheriting move() and fly(). It's a Swimmer and a Flyer at the same time, so a method that takes either type will accept a Duck. The diagram shows the two implements arrows (<|..). No single superclass could give Duck both identities, but interfaces can.
Intuition.
Mechanism. Interfaces carry no instance fields, so combining several of them raises no questions about state — such as which constructor sets which field. That's one reason Java allows many interfaces but only one superclass [12]. default methods do bring in one form of multiple inheritance of implementation [12]. When two of them collide, the compiler makes you choose.
Concrete bite. Try to extend two classes, and you get a syntax error — a class has one superclass, and that's a hard rule:
Compiler error:
Main.java:3: error: '{' expected
class C extends A, B {}
^extends A, B isn't even valid syntax, because a class has at most one superclass. For C to be "both," A and B would need to be interfaces, and C would implement them.
Non-example: two defaults with the same signature. Give Swimmer and a new Walker interface each a default move(). A class that implements both now inherits two bodies for one method, and that's a compile-time error [6]:
Compiler error:
Main.java:4: error: types Swimmer and Walker are incompatible;
class Duck implements Swimmer, Walker { }
^
class Duck inherits unrelated defaults for move() from types Swimmer and Walker
1 errorThe fix is to override move() in Duck. Inside the override, Swimmer.super.move() calls one specific interface's default by name [7]:
Output:
swimming and walkingDuck chose both, in its own order. It could just as well pick one, or write something entirely new. Because javac reports the clash when it compiles Duck, it can't slip by unnoticed.
💡 Earned rule. Reach for an interface (or several) when a type needs to play multiple roles, or when you want maximum decoupling. Reach for an abstract class when subtypes share state and implementation.
- The cost of interfaces: they can't hold instance state.
- The payoff: a class can implement as many as it needs, freely modeling "is-a-kind-of-capability." That's why most Java APIs are built on interfaces, with abstract classes as an implementation convenience.
6. Mental-model summary
| Principle | Consequence |
|---|---|
| An abstract class has abstract methods and can't be instantiated | Subclasses must implement them; new AbstractType() won't compile |
| An abstract class can hold fields, constructors and concrete methods | Shared state and code live there; its constructor runs for every subclass object |
An interface is a contract a class implements; its methods are implicitly public |
An unimplemented method makes the class not compile (or be abstract); an implementation without public is rejected |
Interface fields are implicitly public static final |
An interface holds constants, never instance state |
A default method gives an interface a method body |
Implementers inherit it; interfaces can evolve without breaking them |
A static interface method belongs to the interface; a private one is its helper |
Call Interface.m(); neither is inherited by implementers |
A class extends one class but implements many interfaces |
Multiple inheritance of type, not state |
| Two inherited defaults with one signature | A compile error; override, and pick with X.super.m() |
7. Gotcha checklist
| Symptom | Likely cause | Fix |
|---|---|---|
Shape is abstract; cannot be instantiated |
new on an abstract class (or on an interface) |
instantiate a concrete subclass or implementer |
Triangle is not abstract and does not override abstract method area() in Shape |
a concrete subclass or implementer is missing an abstract method | implement it, or declare the class abstract |
Shape is not abstract and does not override abstract method area() in Shape |
an abstract method inside a class not marked abstract |
add abstract to the class, or give the method a body |
draw() in Circle cannot implement draw() in Drawable … weaker access privileges; was public |
the implementation left off public |
declare it public |
cannot find symbol … location: class Kettle for an interface's static method |
static interface methods are not inherited |
call it as Temperature.toFahrenheit(…) |
label(double,String) has private access in Temperature |
a private interface method called from outside |
call a default method that uses it |
cannot assign a value to static final variable MAX |
interface fields are constants | keep state in a class, not an interface |
'{' expected at extends A, B |
a class has one superclass | make A/B interfaces and implement them |
types Swimmer and Walker are incompatible … inherits unrelated defaults for move() |
two inherited default methods with one signature |
override move(); call Swimmer.super.move() to reuse one |
| You can't decide interface vs abstract class | — | shared state or implementation: abstract class; a contract or several roles: interface(s) |
✅ Check yourself
One check per objective. Answer before you open anything.
The 🧪 box below: Triangle, loudGreet(), and Robot.
Output:
3.14
4.00
6.00
HELLO, ADA
swimming and flyingTriangle(3, 4)has area0.5 * 3 * 4 = 6.00.loudGreet()is a seconddefaultbuilt on top of the first.Robotcompiles with an empty body: both interfaces supply their methods as defaults, and the names differ. Two interfaces are fine because they bring no fields along. Two classes aren't, because a class has only one superclass.
📚 Sources
- The Java Language Specification, Java SE 25, §8.1.1.1 "
abstractClasses" (no instance bynew; a subclass object runs its constructor; no abstract method in a non-abstract class) — https://docs.oracle.com/javase/specs/jls/se25/html/jls-8.html#jls-8.1.1.1 - The Java Language Specification, Java SE 25, §8.4.3.1 "
abstractMethods" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-8.html#jls-8.4.3.1 - The Java Language Specification, Java SE 25, §9.4 "Method Declarations" (implicitly
public; implicitlyabstractwithoutprivate,defaultorstatic; default, static and private methods) — https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.4 - The Java Language Specification, Java SE 25, §9.3 "Field (Constant) Declarations" ("implicitly public, static, and final") — https://docs.oracle.com/javase/specs/jls/se25/html/jls-9.html#jls-9.3
- The Java Language Specification, Java SE 25, §8.4.8 "Inheritance, Overriding, and Hiding" ("A class does not inherit private or static methods from its superinterface types") — https://docs.oracle.com/javase/specs/jls/se25/html/jls-8.html#jls-8.4.8
- The Java Language Specification, Java SE 25, §8.4.8.4 "Inheriting Methods with Override-Equivalent Signatures" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-8.html#jls-8.4.8.4
- The Java Language Specification, Java SE 25, §15.12.1 "Compile-Time Step 1: Determine Type to Search" (
TypeName.super.m()) — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.12.1 - The Java Language Specification, Java SE 25, §8.4.8.3 "Requirements in Overriding and Hiding" ("at least as much access") — https://docs.oracle.com/javase/specs/jls/se25/html/jls-8.html#jls-8.4.8.3
- The Java Language Specification, Java SE 25, §8.1.4 "Superclasses and Subclasses" and §8.1.5 "Superinterfaces" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-8.html#jls-8.1.4
- JEP 126: Lambda Expressions & Virtual Extension Methods (JDK 8) — https://openjdk.org/jeps/126
- JEP 213: Milling Project Coin (JDK 9; "support for private interface methods") — https://openjdk.org/jeps/213
- The Java Tutorials, "Multiple Inheritance of State, Implementation, and Type" (Oracle) — https://docs.oracle.com/javase/tutorial/java/IandI/multipleinheritance.html
- The Java Language Specification, Java SE 8 Edition, §9.4 "Method Declarations" ("An interface can declare static methods"; the SE 7 edition made a
staticinterface method a compile-time error) — https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.4
🧪 Predict, then check.
- Add a
Triangle extends Shapewhosearea()returns0.5 * base * height. Predict the three areas printed for aCircle(1), aSquare(2)and aTriangle(3, 4). - Give
Greetera seconddefaultmethod,loudGreet(), that returnsgreet().toUpperCase(), and predictnew Person("Ada").loudGreet(). - Predict whether
class Robot implements Swimmer, Flyer {}compiles, and whatmove()andfly()return on aRobot. Explain why two interfaces are fine but two classes are not.
Your Turn
Before you move on, check your understanding with the coach — explain the idea, apply it, weigh the trade-offs, then defend your reasoning.