Abstraction, Abstract Classes and Interfaces in Java
Abstraction exposes what a type does and hides how. Abstract classes share partial implementation, interfaces declare a capability.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
Abstraction as an idea
Abstraction means describing what a type offers without committing to how it works. A caller depending on the abstraction is insulated from every implementation detail behind it. Java provides two tools for it: abstract classes and interfaces.
Abstract classes
abstract class Employee {
private final String name;
protected Employee(String name) {
this.name = name;
}
public String name() { // shared implementation
return name;
}
public abstract double monthlyPay(); // each subtype must supply this
public String payslip() { // built on the abstract method
return name + " earns " + monthlyPay();
}
}class SalariedEmployee extends Employee {
private final double annualSalary;
SalariedEmployee(String name, double annualSalary) {
super(name);
this.annualSalary = annualSalary;
}
@Override
public double monthlyPay() {
return annualSalary / 12;
}
}- An abstract class cannot be instantiated, but it can have constructors, called by subclasses.
- It may hold state, and it may mix abstract and concrete methods.
- A subclass must implement every abstract method or be declared abstract itself.
Interfaces
interface Searchable {
List<String> search(String term); // implicitly public abstract
default boolean matches(String term) { // a default implementation
return !search(term).isEmpty();
}
static Searchable empty() { // a static factory
return term -> List.of();
}
}class NoteIndex implements Searchable {
@Override
public List<String> search(String term) {
return List.of("Introduction to Java");
}
}What an interface may contain
| Member | Since | Note |
|---|---|---|
| Abstract methods | 1.0 | Implicitly public abstract |
| Constants | 1.0 | Implicitly public static final |
default methods | 8 | Allow interfaces to evolve without breaking implementers |
static methods | 8 | Helpers that belong with the type |
private methods | 9 | Shared code between default methods |
Default methods exist mainly for backward compatibility. WhenforEachwas added toIterablein Java 8, every existing implementation would have broken without them. They are not a licence to put substantial logic into interfaces.
Choosing between them
| Question | Abstract class | Interface |
|---|---|---|
| Can hold instance state | Yes | No, only constants |
| Constructors | Yes | No |
| How many per class | One | Many |
| Access modifiers on members | Any | Public, or private helpers |
| Models | An is-a relationship with shared code | A capability or a contract |
| Typical name | A noun: Employee | A capability: Comparable, Runnable |
The practical rule: if subtypes share state and real implementation, use an abstract class. If you are declaring what something can do, use an interface. When in doubt, start with an interface, because a class may implement many.
Using both together
interface Payment {
double amount();
String reference();
}
abstract class OnlinePayment implements Payment {
private final String reference;
protected OnlinePayment(String reference) {
this.reference = reference;
}
@Override
public String reference() {
return reference;
}
protected abstract String gateway();
}
class CardPayment extends OnlinePayment {
private final double amount;
CardPayment(String reference, double amount) {
super(reference);
this.amount = amount;
}
@Override public double amount() { return amount; }
@Override protected String gateway() { return "card-gateway"; }
}The interface defines the contract, the abstract class supplies what every online payment shares, and the concrete class fills in the rest. This layering is common in real systems.
Functional interfaces
@FunctionalInterface
interface Validator {
boolean isValid(String value);
}
Validator notBlank = value -> value != null && !value.isBlank();An interface with exactly one abstract method can be implemented by a lambda. The annotation is optional but makes the intent explicit and prevents a second abstract method being added by accident.
Common mistakes
- Creating an interface for a class that will only ever have one implementation.
- Declaring fields in an interface and being surprised they are constants shared by all.
- Putting real business logic into default methods.
- Using an abstract class where no state or shared code exists.
- Forgetting that an implementing method must be
public, since interface methods are public by definition.
Best practices
- Program to interfaces:
List<String> names = new ArrayList<>(); - Keep interfaces small and focused on one capability.
- Give an abstract class a
protectedconstructor, since only subclasses should call it. - Use default methods to add convenience or to evolve an API, not to hold the core logic.
- Consider a sealed interface when the set of implementations is meant to be closed.
Practice
- Design a
Notificationabstraction with email and SMS implementations, and justify interface or abstract class. - Why can an interface not declare a constructor?
- Add a default method to an interface with two existing implementations and explain what did not break.
- What happens when a class implements two interfaces that declare the same default method?
- Convert an abstract class with no state into an interface and list what changed.
Conclusion
Abstraction is choosing what to expose. Use an interface to declare a capability, an abstract class to share partial implementation, and prefer depending on the abstraction rather than on any concrete class.