An abstract class is a class you are not allowed to instantiate, because it deliberately leaves part of itself unfinished. That is exactly the right tool when several subclasses share state, a constructor and a fixed sequence of steps, but differ in one or two places.
What does abstract actually change?
Two things. An abstract class cannot be created with new, and it may declare abstract methods: methods with a name and signature but no body, which every concrete subclass must supply.
Use the abstract keyword on the class itself, and on each method you leave unfinished. The class below declares two of them, and the main method only prints a note so that the file compiles and runs as it stands:
public class Demo {
abstract static class Exporter {
abstract void open();
abstract void writeRow(String row);
}
public static void main(String[] args) {
System.out.println("Exporter leaves 2 methods for its subclasses");
}
}Running it prints that one line. Any concrete subclass of Exporter now has to supply both open and writeRow, or be declared abstract itself.
An abstract class can also hold ordinary methods, fields and constructors, which is the difference between it and an interface. The rule is simple: if a subclass does not implement every abstract method, that subclass must itself be declared abstract. The compiler enforces this, so you never end up with a half-built object that is missing a piece of behaviour.
An abstract method cannot be private, static or final. Each of those combinations is rejected with an illegal combination of modifiers error, and the reasons are worth spelling out. A private method would be invisible to the very subclass that is supposed to implement it. A static method would belong to the class rather than to an object, so there would be nothing for an instance to override. A final method would forbid the overriding that the abstract declaration exists to demand in the first place.
How do abstract classes share behaviour?
The classic use is a fixed sequence of steps with one or two steps left open. The base class owns the sequence; the subclasses fill in the gaps. Here an exporter writes a file of some kind, and the details of the format differ per subclass.
public class Demo {
abstract static class Exporter {
protected final String fileName;
protected Exporter(String fileName) {
this.fileName = fileName;
}
void export(String[] rows) {
open();
for (String row : rows) {
writeRow(row);
}
close();
}
protected abstract void open();
protected abstract void writeRow(String row);
protected void close() {
System.out.println("[" + fileName + "] closed");
}
}
static class CsvExporter extends Exporter {
CsvExporter(String fileName) {
super(fileName);
}
@Override
protected void open() {
System.out.println("[" + fileName + "] writing csv");
}
@Override
protected void writeRow(String row) {
System.out.println(" " + row + ",");
}
}
public static void main(String[] args) {
Exporter exporter = new CsvExporter("report.csv");
exporter.export(new String[] {"id,name", "1,Ada"});
}
}The export method is not abstract and never changes. It calls open, then writeRow once per row, then close, and the subclass only decides what those two steps actually do. The output shows the header line, both rows with a trailing comma, and the closing line. Because the sequence lives in one place, adding a step to it later cannot be forgotten by one of the subclasses.
Two visibility conventions make this pattern easy to read. The template method itself is usually public, so any caller can start the work. The steps that subclasses fill in are usually protected, so a subclass can implement them without exposing them to the whole program. The naming convention follows the same logic: the template carries a verb like export or print, while the hooks describe the pieces, such as open, writeRow and close.
Why can you not instantiate an abstract class?
Because an abstract class may be missing behaviour, so there is no guarantee the object could do what it is supposed to do. Trying it is a compile error rather than a runtime surprise:
public class Demo {
abstract static class Exporter {
abstract void export();
}
public static void main(String[] args) {
Exporter exporter = new Exporter();
exporter.export();
}
}Demo.java:7: error: Exporter is abstract; cannot be instantiated
Exporter exporter = new Exporter();
^
1 errorThe message points at the new expression, which is where the mistake is. Declaring a variable of the abstract type is perfectly fine; only creating one is not.
The related error appears when a concrete subclass forgets a piece of the contract:
public class Demo {
abstract static class Exporter {
abstract void export(String[] rows);
}
static class PartialExporter extends Exporter {
}
}Demo.java:6: error: PartialExporter is not abstract and does not override abstract method export(String[]) in Exporter
static class PartialExporter extends Exporter {
^
1 errorEither implement the method or mark the subclass abstract as well. The compiler will not let a class claim to be concrete while quietly missing a method.
What can an abstract class hold that an interface cannot?
Fields, constructors, private helper methods and any other state. That is the practical difference, and it is why an abstract class is often the better choice for a family of objects that share data.
public class Demo {
abstract static class Exporter {
protected final String fileName;
protected Exporter(String fileName) {
this.fileName = fileName;
}
abstract void export(String[] rows);
void finish() {
System.out.println("[" + fileName + "] finished");
}
}
static class JsonExporter extends Exporter {
private int rowCount;
JsonExporter(String fileName) {
super(fileName);
}
@Override
void export(String[] rows) {
rowCount = rows.length;
System.out.println("[" + fileName + "] writing " + rowCount + " json rows");
}
@Override
void finish() {
super.finish();
System.out.println("rows written: " + rowCount);
}
}
public static void main(String[] args) {
Exporter exporter = new JsonExporter("data.json");
exporter.export(new String[] {"a", "b"});
exporter.finish();
}
}The subclass keeps its own private counter and calls super.finish() to reuse the shared closing line. The output shows the write line, the finished line and the row count, all three coming from one call to export followed by one call to finish. An interface cannot hold a field like rowCount or run a constructor, so when you need shared state plus a constructor, an abstract class is the natural choice.
The constructor is the other reason to reach for an abstract class. A base class can set up shared state before any subclass code runs, and it can insist on the arguments it needs to do that. A subclass whose constructor has to hand a file name to its parent is forced by the compiler to supply one, which keeps the base class's assumptions true for every subclass that will ever exist.
Abstract class or interface?
| Question | Abstract class | Interface |
|---|---|---|
| Can it hold instance fields? | Yes, any number | No, only constants |
| Can it have a constructor? | Yes | No |
| Can it have private members? | Yes | Yes, as helpers |
| How many can a class extend or implement? | One class | As many interfaces as you like |
| When to choose it | Shared state and shared steps | A capability or a contract |
Ask one question first: do the subtypes share data and a sequence of steps? If yes, an abstract class. If you are describing a capability that unrelated classes might have, an interface is the better fit, and often the more honest one.
Abstract class checklist
- Mark the class
abstractand list the parts each subclass must supply. - Keep the shared sequence of steps in concrete methods of the base class.
- Give the base class a constructor and call it with
super(...)from subclasses. - Let subclasses override concrete methods only when they really need to.
- Never instantiate an abstract type; hold a concrete subclass instead.
- If a subclass cannot fill in every abstract method, mark it abstract too.
Try it yourself: design a small report generator. Write an abstract class Report holding the report title, with a constructor, a concrete method print() that calls an abstract header(), an abstract body() and a concrete footer(), and a field rowCount that the concrete body() implementations set. Then write two subclasses, one that prints a simple text report and one that prints a table with column separators. Print both with the same call, then add a third subclass that overrides footer() and calls super.footer() to see how much of the shared behaviour you get for free.