Abstract Classes and Methods
A class that is deliberately incomplete
Sometimes a base class knows that something must happen but not how. Every subsystem needs a periodic update, but what “update” means depends entirely on which subsystem it is.
You could give the parent an empty method and hope subclasses remember to override it:
class Subsystem {
void periodic() { } // does nothing — easy to forget to override
}
That compiles, and a subclass that forgets silently does nothing at all. An abstract method makes it a compile error instead:
abstract class Subsystem {
protected final String name;
Subsystem(String name) {
this.name = name;
}
abstract void periodic(); // no body — subclasses must supply one
void describe() { // ordinary method — inherited as usual
System.out.println("subsystem: " + name);
}
}
An abstract method declares a signature and ends with a semicolon instead of a body. Any concrete subclass must implement it:
class Intake extends Subsystem {
Intake() { super("intake"); }
@Override
void periodic() {
System.out.println("checking intake sensor");
}
}
Omit periodic in Intake and the code will not compile. The requirement is enforced rather than hoped for.
Abstract classes cannot be instantiated
Subsystem s = new Subsystem("thing"); // compile error
Subsystem s = new Intake(); // fine
This is the other half of the point. A Subsystem with no periodic is not a usable object, so Java refuses to create one. You can still use the type as a variable, parameter, or return type — you just cannot construct it directly.
The two effects of `abstract`
On a method: no body, and every concrete subclass must provide one.
On a class: cannot be instantiated. Required if the class has any abstract method, but also usable on its own when you simply want to prevent direct construction.
A class with an abstract method must be declared abstract. The reverse is not true — an abstract class may have no abstract methods at all.
Mixing abstract and concrete
This is what abstract classes offer that interfaces historically did not: real fields, constructors, and finished methods alongside the abstract ones.
abstract class Subsystem {
protected final String name;
private boolean enabled = false;
Subsystem(String name) {
this.name = name;
}
// concrete — shared by every subclass
void enable() { enabled = true; }
void disable() { enabled = false; }
boolean isEnabled() { return enabled; }
// abstract — each subclass decides
abstract void periodic();
// concrete, but built on the abstract part
final void update() {
if (enabled) {
periodic();
}
}
}
update is interesting: it is finished code in the parent that calls a method the parent has not written. The parent controls when the work happens; the subclass controls what the work is.
Marking update as final means subclasses cannot change that ordering — they can only fill in periodic. That combination, a fixed skeleton with subclass-supplied steps, is a common and useful shape.
Partial implementations
An abstract class may extend another abstract class and implement only some of the inherited abstract methods, leaving the rest for further subclasses:
abstract class Subsystem {
abstract void periodic();
abstract void stop();
}
abstract class MotorSubsystem extends Subsystem {
protected final Motor motor;
MotorSubsystem(Motor motor) { this.motor = motor; }
@Override
void stop() { // supplied here for all motor subsystems
motor.set(0);
}
// periodic() left abstract — still each subclass's job
}
class Intake extends MotorSubsystem {
Intake(Motor motor) { super(motor); }
@Override
void periodic() { motor.set(0.6); }
}
MotorSubsystem is still abstract because periodic remains unimplemented — but it has removed duplication for everything below it.
Abstract class or interface
Both let you write code against a type rather than a specific class. The distinction has narrowed since interfaces gained default methods, but real differences remain.
| Abstract class | Interface | |
|---|---|---|
| How many can a class have? | One | Many |
| Instance fields | Yes | No — constants only |
| Constructors | Yes | No |
| Method bodies | Yes | Yes, via default |
| Private / protected members | Yes | Mostly public |
| Expresses | is a kind of | is capable of |
Choosing between them
Use an abstract class when subclasses share state — fields, a constructor, or partially-finished behaviour that operates on those fields.
Use an interface when you are describing a capability that unrelated classes might have, or when a class needs to fill several roles at once.
When both would work, prefer the interface. Single inheritance is a scarce resource: a class can implement many interfaces but extend only one class, so spending that slot commits the design in a way an interface does not.
A template method
The update/periodic split above has a name — the template method pattern. The parent defines the sequence; subclasses fill in steps.
abstract class AutoRoutine {
// fixed sequence, subclasses cannot reorder it
final void run() {
initialise();
while (!isFinished()) {
step();
}
cleanUp();
}
void initialise() { } // optional hook, default does nothing
abstract void step(); // required
abstract boolean isFinished(); // required
void cleanUp() { } // optional hook
}
Distinguishing required steps (abstract) from optional hooks (concrete, empty) is deliberate. A subclass must supply step and isFinished; it may ignore initialise and cleanUp.
Common mistakes
- A body on an abstract method. It ends with a semicolon, not
{ }. - An abstract method in a non-abstract class. The class must be abstract too.
- Trying to instantiate an abstract class.
- Forgetting a subclass is still abstract if it leaves any abstract method unimplemented.
private abstract— contradictory, since a private method cannot be overridden. It does not compile.- Calling an abstract method from the constructor. The subclass’s version runs before its fields are initialised.
- Using an abstract class where an interface would do, spending the one inheritance slot for nothing.
Practice
- Write an abstract
Shapeclass with an abstractarea()method and a concretedescribe()that prints the area. AddCircleandRectangle. - Try to instantiate
Shapedirectly and read the compile error. - Write a subclass that forgets to implement
area()and read that error too. - Add an abstract
MotorSubsystembetween aSubsystembase and a concrete subclass, implementing only some methods at the middle level. - Write a template method: a
finalmethod defining a fixed sequence, with one abstract step and one optional hook.
Hints
describe()can callarea()even though the parent never implements it.- and 3. Both errors are worth seeing once so you recognise them later.
- The middle class stays abstract because something is still unimplemented.
- Empty concrete methods make good optional hooks.