Java Abstract Classes
Table of Contents + −
In the last lesson you learned about Java interfaces. You also met abstract classes back in the abstraction lesson. Now let’s look at abstract classes closely, and settle the question every learner asks: abstract class or interface?
🤔 The problem abstract classes solve
Picture payroll software with full-time staff, part-time staff, contractors, and interns. They all have a name. They all print a pay slip the same way. But each works out its salary differently. Both obvious fixes hurt:
- Copy the name field and pay-slip code into every class. Same code in four places. Fix a bug in one, forget the other three.
- Make a plain parent with a
calculateSalarythat returns zero. Now nothing stopsnew Employee(). A fake employee with salary zero. A quiet bug.
You want a parent that does two jobs at once:
- Hands over the shared code. Take it, do not rewrite it.
- Forces you to fill in the missing part. It refuses to guess it.
That is an abstract class, a half-finished parent. Think of a recipe template. Most steps are filled in, but one line says “add your filling here”. You cannot bake from the blank template alone. That blank line is the abstract method. The filled steps are the concrete methods.
🧩 What makes an abstract class special
An abstract class sits between a normal class and an interface. You write it with the abstract keyword. Its traits:
- You cannot create an object from it directly.
newon it fails to compile. It is not complete. - Abstract methods have no body. They end with a semicolon. Any child must write the body, or that child stays abstract too.
- Concrete methods have a full body. Children inherit and use them as-is.
- Fields hold shared state, like a name or an id.
- A constructor runs when a child is built, to set up that shared state in one place.
A plain class cannot force anything on its children. A traditional interface cannot hold real state. The abstract class does both at once. Here is the smallest shape.
abstract class Shape { abstract double area(); // ❌ no body, every child must define this
void describe() { // ✅ full body, shared by every shape System.out.println("This shape covers " + area() + " units."); }}Notice describe calls area, even though area has no body here:
- The parent trusts that every real shape will provide a body later.
- Java will not let you create a bare
Shapethat skippedarea. - So by the time
describeruns, a realareais guaranteed to exist.
Abstract is a promise, not a value
An abstract method is a promise that the body will arrive later, in a child class. So an abstract method never has curly braces of its own. It ends with a semicolon. The moment you give it a body, it stops being abstract.
One more rule. If a class has even a single abstract method, the class itself must be marked abstract. You cannot hide an abstract method inside an ordinary class and hope it compiles.
🏗️ Mixing abstract and concrete methods
Let’s build the real Employee example. Every employee shares a name and prints info the same way, but salary differs per type. So name and the print method are concrete. Salary is abstract. Here is the abstract parent.
abstract class Employee { String name; // ✅ shared field, lives in one place
Employee(String name) { // ✅ abstract classes CAN have a constructor this.name = name; }
abstract double calculateSalary(); // each employee type decides this for itself
void displayInfo() { // ✅ shared concrete method System.out.println(name + " earns " + calculateSalary()); }}Read each line:
String nameis a field. The parent holds the shared state, so children do not each declare their own name.Employee(String name)is a constructor. It runs throughsuper(...)when a child is built. A traditional interface cannot have this.calculateSalary()is abstract. No body, just a semicolon. Each child is forced to define it.displayInfo()is concrete. It has a real body, so every child gets it for free.
Again displayInfo calls calculateSalary, which has no body here:
- The parent does not need to know how salary is worked out.
- It trusts that some child will hand over the real answer at runtime.
- The parent owns the shared steps. The child owns the one step that varies.
Fields and constructors are the real reason to choose this
A traditional interface cannot hold instance fields and cannot have a constructor. An abstract class can have both. So when your child classes share real data (a name) and shared setup (assign that name), the abstract class is the natural home for it. The constructor runs via super(...) from the child.
💡 Concrete children fill in the rest
Now two employee types. Each writes its own calculateSalary, and both inherit displayInfo untouched.
class FullTimeEmployee extends Employee { double monthlySalary;
FullTimeEmployee(String name, double monthlySalary) { super(name); // ✅ call the abstract class constructor first this.monthlySalary = monthlySalary; }
@Override double calculateSalary() { // ✅ fills the hole the parent left return monthlySalary; }}
class PartTimeEmployee extends Employee { double hourlyRate; int hours;
PartTimeEmployee(String name, double hourlyRate, int hours) { super(name); this.hourlyRate = hourlyRate; this.hours = hours; }
@Override double calculateSalary() { return hourlyRate * hours; }}What each child does:
- Both call
super(name)first. That hands the name to the parent constructor, so the name field is set in one shared place. - Each adds its own extra fields. Those belong only to that child, not the whole family.
- Each writes its own
calculateSalary. Full-time returns a fixed amount. Part-time returns rate times hours. Same name, different answer. - Neither child writes
displayInfo. They inherited it, so a fix to the pay-slip format updates everyone at once.
The @Override above each calculateSalary tells the compiler you are implementing the parent’s abstract method. Spell the name wrong and the compiler complains, instead of quietly creating a new method and leaving the abstract one unfilled.
Now run it.
public class Main { public static void main(String[] args) { Employee e1 = new FullTimeEmployee("Alex", 50000); Employee e2 = new PartTimeEmployee("Riya", 200, 160); e1.displayInfo(); e2.displayInfo(); }}Output
Alex earns 50000.0Riya earns 32000.0Notice the types on the left:
- Both variables are declared as
Employee, the abstract type. - You still cannot write
new Employee(...), but you can hold anEmployeereference pointing at a real child. - The type on the left is the promise. The thing on the right is the real object.
So you can keep full-time and part-time staff together in one Employee[] array and call displayInfo on all of them in a single loop:
- Each one quietly runs its own
calculateSalary. The loop does not care which type it holds. - One reference type, many real behaviours. The same idea you saw with polymorphism.
- Add a
Contractorclass tomorrow and the loop needs zero changes.
⚖️ Abstract class vs interface
Both let you declare methods that children must implement. So why two tools? They answer different questions:
- An interface answers “what can this thing do?”.
- An abstract class answers “what is this thing, and what do all of its kind share?”.
Here they are side by side.
| Feature | Abstract class | Interface |
|---|---|---|
| How a class uses it | extends (only one) | implements (many allowed) |
| Instance fields with state | Yes | No (only constants) |
| Constructor | Yes | No |
| Method bodies | Abstract and concrete, freely mixed | Mostly abstract (plus default methods) |
| Holds shared code | Yes, that is its main job | A little, through default methods |
| The question it answers | ”What is this and what do its kind share?" | "What can this thing do?” |
| Best for | A shared family base with common code and state | A capability or contract across unrelated classes |
The biggest practical difference is the count. A class can extend only one abstract class, but implement many interfaces. How to choose:
- Children are one family, sharing real code and fields? Reach for an abstract class.
FullTimeEmployeeandPartTimeEmployeeare both employees with a name. - You just need “this thing can do X” across unrelated classes? Reach for an interface. A
Bird, aPlane, and aDroneare not one family, but all three canfly(). - Quick gut check: “a part-timer is an employee” sounds right, so
Employeeis a good abstract base. “A plane is a flyer” sounds odd. A plane can fly, soFlyableis an interface.
The two are friends too. A single class can extend one abstract class and implement several interfaces, like FullTimeEmployee extends Employee implements Serializable, Comparable<Employee>. Base from the abstract class, abilities from the interfaces.
⚠️ Common Mistakes
A few abstract-class slip-ups trip up almost everyone:
Trying to instantiate the abstract class. You cannot create the half-finished parent directly. It has holes.
Employee e = new Employee("Alex"); // ❌ does not compile, Employee is abstractEmployee e = new FullTimeEmployee("Alex", 50000); // ✅ create a finished child insteadNot implementing an abstract method. If a child skips the parent’s abstract method, the child still has a hole, so it must itself be marked abstract, or it will not compile.
// ❌ wrong: Intern forgot calculateSalary, so this class will not compileclass Intern extends Employee { Intern(String name) { super(name); }}
// ✅ right: provide the body, even if it is simpleclass Intern extends Employee { Intern(String name) { super(name); }
@Override double calculateSalary() { return 0; }}Giving an abstract method a body. An abstract method is a promise with no body. Add curly braces and it stops being abstract, which is a contradiction.
abstract double calculateSalary() { return 0; } // ❌ abstract method cannot have a bodyabstract double calculateSalary(); // ✅ semicolon, no bodyForgetting super(...) in the child constructor. If the abstract class has a constructor with arguments, the child must call it with super(...) as the very first line. Skip it and the shared field never gets set up.
// ❌ wrong: name was never passed to the parentFullTimeEmployee(String name, double pay) { this.monthlySalary = pay;}
// ✅ right: super(...) first, hands name to the parentFullTimeEmployee(String name, double pay) { super(name); this.monthlySalary = pay;}Making everything abstract out of habit. Mark every method abstract and you have an abstract class with no shared code, which is basically an interface. The whole reason to choose an abstract class is the shared code and state. If there is none, you picked the wrong tool.
✅ Best Practices
Habits that keep abstract classes clean:
- Use an abstract class for a shared base with common code or fields. Leave only the parts that truly differ as abstract.
- Make a method abstract only when each child really must define it. If every child would write the same body, keep it concrete.
- Always call
super(...)to reuse the parent’s setup, instead of repeating the field assignment in every child. - Keep
@Overrideon every implemented method. A typo cannot then silently become a new method while the abstract one stays empty. - Name the base after the family, not the action.
Employee,Shape,Accountread well. Save verb names likeComparablefor interfaces. - Reach for an interface when you only need a contract or several abilities. Save the abstract class for real shared code and state.
🧩 What You’ve Learned
Great, that completes the OOP concepts module. Let’s recap abstract classes.
- ✅ An abstract class cannot be instantiated. It exists to be extended, and it mixes abstract and concrete methods.
- ✅ Abstract methods have no body and force children to implement them. Concrete methods carry shared code for free.
- ✅ It can have fields and a constructor, unlike a traditional interface. The child reuses that setup with
super(...). - ✅ Use an abstract class for a shared family base with common code. Use an interface for a pure ability across unrelated classes.
- ✅ A class can extend one abstract class but implement many interfaces, and it can do both at once.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What can an abstract class contain that a traditional interface cannot?
Why: Abstract classes can hold instance fields and constructors; traditional interfaces cannot.
- 2
How many abstract classes can one class extend?
Why: A class can extend only one class, abstract or not, but it can implement many interfaces.
- 3
When is an abstract class the better choice over an interface?
Why: Abstract classes shine when there is a shared base with common code and fields.
- 4
What must a child constructor do if the abstract class has a parameterized constructor?
Why: The child must call the parent constructor with super(...) as the first line to set up inherited fields.
🚀 What’s Next?
You now understand how abstract classes act as a half-finished family base. Next, let’s meet the one class that every Java class quietly extends, the root of the whole class tree. It gives every object methods like toString, equals, and hashCode.