Lesson 17 of 20
Abstract Classes and Interfaces
Define shared contracts and partial implementations using abstract classes and interfaces.
Learning objectives
- Create an abstract class and abstract method.
- Create and implement an interface.
- Understand when shared state belongs in an abstract class.
- Use interfaces to model capabilities.
- Program against a common contract.
1. Abstract classes
An abstract class cannot be instantiated directly. It can hold fields, constructors, concrete methods, and abstract methods that subclasses must implement.
Example: abstract Shape
public abstract class Shape {
private final String name;
public Shape(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract double area();
}
Concrete subclass
public class Circle extends Shape {
private final double radius;
public Circle(double radius) {
super("Circle");
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
2. Interfaces
An interface defines a contract that implementing classes agree to provide. Interfaces are especially useful for capabilities that may be shared by otherwise unrelated classes.
Example: Printable contract
public interface Printable {
void print();
}
public class Invoice implements Printable {
@Override
public void print() {
System.out.println("Printing invoice...");
}
}
3. Abstract class or interface?
| Situation | Prefer | Reason |
|---|---|---|
| Need shared instance state or a constructor | Abstract class | An abstract class can store fields and initialize them. |
| Need a capability shared across unrelated classes | Interface | Many different classes can implement the same contract. |
| Need multiple contracts | Interface | A class can implement multiple interfaces. |
| Need a common base implementation | Abstract class | Concrete methods can be inherited and reused. |
Mini project: payment processing
public interface PaymentMethod {
boolean pay(double amount);
}
public class CardPayment implements PaymentMethod {
@Override
public boolean pay(double amount) {
System.out.printf("Charging card: RM %.2f%n", amount);
return true;
}
}
public class EWalletPayment implements PaymentMethod {
@Override
public boolean pay(double amount) {
System.out.printf("Charging e-wallet: RM %.2f%n", amount);
return true;
}
}
public class Checkout {
public static void processPayment(PaymentMethod method, double amount) {
if (method.pay(amount)) {
System.out.println("Payment successful.");
}
}
public static void main(String[] args) {
processPayment(new CardPayment(), 120.00);
processPayment(new EWalletPayment(), 55.50);
}
}
Checkout depends on the PaymentMethod interface rather than one specific payment class. This makes the design easier to extend.
Common mistakes
| Problem | Why it happens | How to fix it |
|---|---|---|
| Trying to instantiate an abstract class | Abstract types are incomplete by design. | Instantiate a concrete subclass. |
Using extends for an interface | Classes implement interfaces. | Write implements InterfaceName. |
| Interface contains unrelated responsibilities | The contract is too broad. | Split it into smaller capability-focused interfaces. |
Summary
Abstract classes provide a shared base with optional state and implementation; interfaces define contracts that many classes can fulfill. Lesson 18 turns to runtime failures and how Java handles them with exceptions.