Skip to main content

7.4 Abstraction & Interface

In object-oriented design, abstraction means modeling the complex real world as a program by hiding unnecessary details and extracting only the essential common attributes and behaviors to create a skeleton.

Java provides two tools for abstraction:

  1. Abstract Class
  2. Interface

1. Abstract Class

An abstract class is an incomplete blueprint. It is a class that contains methods whose concrete implementation has not yet been decided (abstract methods).

Declaring an Abstract Class

// Declare an abstract class with the abstract keyword
public abstract class Shape {
protected String color;

// Can have a regular constructor
public Shape(String color) {
this.color = color;
}

// Abstract method: declaration only, no body ({})
// → child classes MUST implement this
public abstract double area();
public abstract double perimeter();

// Regular method: fully implemented common behavior
public void displayInfo() {
System.out.printf("Shape: %s, Color: %s, Area: %.2f, Perimeter: %.2f%n",
getClass().getSimpleName(), color, area(), perimeter());
}

public String getColor() { return color; }
}

Implementing the Abstract Class (Child Classes)

// Circle
public class Circle extends Shape {
private double radius;

public Circle(String color, double radius) {
super(color); // call parent constructor
this.radius = radius;
}

@Override
public double area() {
return Math.PI * radius * radius;
}

@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}

// Rectangle
public class Rectangle extends Shape {
private double width;
private double height;

public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}

@Override
public double area() {
return width * height;
}

@Override
public double perimeter() {
return 2 * (width + height);
}
}

// Triangle
public class Triangle extends Shape {
private double a, b, c; // three side lengths

public Triangle(String color, double a, double b, double c) {
super(color);
this.a = a; this.b = b; this.c = c;
}

@Override
public double area() {
// Heron's formula
double s = (a + b + c) / 2;
return Math.sqrt(s * (s-a) * (s-b) * (s-c));
}

@Override
public double perimeter() {
return a + b + c;
}
}

Abstract Class Characteristics

public class AbstractTest {
public static void main(String[] args) {
// Shape shape = new Shape("red"); // Compile error! Cannot instantiate abstract class

// Create instances from child classes
Shape circle = new Circle("red", 5.0);
Shape rectangle = new Rectangle("blue", 4.0, 6.0);
Shape triangle = new Triangle("green", 3.0, 4.0, 5.0);

// Polymorphism: manage with parent type array
Shape[] shapes = { circle, rectangle, triangle };

for (Shape s : shapes) {
s.displayInfo(); // calls each child's area() and perimeter()
}
}
}

Output:

Shape: Circle, Color: red, Area: 78.54, Perimeter: 31.42
Shape: Rectangle, Color: blue, Area: 24.00, Perimeter: 20.00
Shape: Triangle, Color: green, Area: 6.00, Perimeter: 12.00
Purpose of Abstract Classes

An abstract class provides a common template for child classes. It allows reuse of common fields and implemented methods, while forcing child classes to provide concrete implementations for specific methods. Use it when expressing an "IS-A relationship".

2. Interface

An interface provides even stronger abstraction than an abstract class. It defines the behavioral specification (contract) that classes must implement.

Basic Interface Declaration

public interface Printable {
// All constants are automatically public static final
int MAX_SIZE = 1000; // == public static final int MAX_SIZE = 1000

// All methods are automatically public abstract
void print(); // == public abstract void print()
String getContent(); // == public abstract String getContent()
}

// Implementing an interface: implements keyword
public class Document implements Printable {
private String content;

public Document(String content) {
this.content = content;
}

@Override
public void print() {
System.out.println("[Document Print]: " + content);
}

@Override
public String getContent() {
return content;
}
}

Multiple Implementation

A class can only extend one parent, but it can implement multiple interfaces simultaneously.

public interface Flyable {
void fly();
default String getTransportType() { return "Air"; }
}

public interface Swimmable {
void swim();
default String getTransportType() { return "Water"; }
}

public interface Drivable {
void drive();
}

// Implementing multiple interfaces at once
public class AmphibiousVehicle implements Flyable, Swimmable, Drivable {
@Override
public void fly() { System.out.println("Flying through the sky!"); }

@Override
public void swim() { System.out.println("Gliding over water!"); }

@Override
public void drive() { System.out.println("Driving on the road!"); }

// Must override if two interfaces have the same default method
@Override
public String getTransportType() {
return "Land·Sea·Air";
}
}

3. Java 8/9+ Interface Features

Since Java 8, interfaces can include implementation code.

default Methods (Java 8+)

public interface Collection<E> {
// abstract method: implementing class must provide implementation
void add(E element);
void remove(E element);
int size();

// default method: provides a default implementation (overriding optional)
default boolean isEmpty() {
return size() == 0;
}

default void printAll() {
System.out.println("Collection with " + size() + " elements");
}
}

static Methods (Java 8+)

public interface Validator<T> {
boolean validate(T value);

// static method: called directly via InterfaceName.methodName()
static <T> boolean validateNotNull(T value) {
return value != null;
}
}

// Usage
boolean result = Validator.validateNotNull("hello"); // true

private Methods (Java 9+)

public interface Logger {
default void info(String message) { log("INFO", message); }
default void error(String message) { log("ERROR", message); }
default void warn(String message) { log("WARN", message); }

// private method: only usable inside default/static methods (not exposed externally)
private void log(String level, String message) {
System.out.printf("[%s] %s%n", level, message);
}
}

4. Functional Interfaces and Lambdas

A Functional Interface is an interface with exactly one abstract method. The @FunctionalInterface annotation makes this explicit, and it can be implemented concisely with a lambda expression.

// Declaring a functional interface
@FunctionalInterface
public interface Calculator {
int calculate(int a, int b);
// Only one abstract method allowed! (default/static may have multiple)
}

public class LambdaExample {
public static void main(String[] args) {
// Old way: anonymous class
Calculator add = new Calculator() {
@Override
public int calculate(int a, int b) {
return a + b;
}
};

// Lambda way: much more concise
Calculator addLambda = (a, b) -> a + b;
Calculator subtractLambda = (a, b) -> a - b;
Calculator multiplyLambda = (a, b) -> a * b;

System.out.println(addLambda.calculate(10, 5)); // 15
System.out.println(subtractLambda.calculate(10, 5)); // 5
System.out.println(multiplyLambda.calculate(10, 5)); // 50

// Using Java's standard functional interfaces
java.util.function.Predicate<String> isLong = s -> s.length() > 5;
java.util.function.Function<String, Integer> length = String::length;

System.out.println(isLong.test("Hello")); // false
System.out.println(isLong.test("Hello World!")); // true
System.out.println(length.apply("Java")); // 4
}
}

5. Abstract Class vs Interface Comparison

AspectAbstract Class (abstract class)Interface (interface)
Inheritance/Implementationextends (single inheritance)implements (multiple implementation)
VariablesRegular member variables allowedOnly public static final constants
MethodsAbstract + regular methods both allowedAbstract by default; default/static allowed
ConstructorHas constructorNo constructor
Access modifiersAll access modifiers allowedpublic or private (Java 9+)
PurposeIS-A relationship, sharing common codeCAN-DO relationship, defining behavioral spec
When to useCommon parent of related classesAdding common behavior to unrelated classes

6. Practical Example: Payment System

A real-world payment system implemented using interfaces.

// Payment processing interface
public interface PaymentProcessor {
boolean processPayment(double amount);
void refund(double amount);
String getPaymentMethod();

default void printReceipt(double amount) {
System.out.printf("[Receipt] Method: %s, Amount: $%.2f%n",
getPaymentMethod(), amount);
}
}

// Credit card payment
public class CreditCardProcessor implements PaymentProcessor {
private String cardNumber;
private double limit;
private double balance;

public CreditCardProcessor(String cardNumber, double limit) {
this.cardNumber = cardNumber;
this.limit = limit;
this.balance = 0;
}

@Override
public boolean processPayment(double amount) {
if (balance + amount > limit) {
System.out.println("[Credit Card] Limit exceeded! Payment failed.");
return false;
}
balance += amount;
System.out.printf("[Credit Card] Card %s charged $%.2f%n",
cardNumber.substring(cardNumber.length() - 4), amount);
printReceipt(amount);
return true;
}

@Override
public void refund(double amount) {
balance -= amount;
System.out.printf("[Credit Card] $%.2f refunded. Current balance: $%.2f%n",
amount, balance);
}

@Override
public String getPaymentMethod() { return "Credit Card"; }
}

// PayPal payment
public class PayPalProcessor implements PaymentProcessor {
private String userId;
private double walletBalance;

public PayPalProcessor(String userId, double walletBalance) {
this.userId = userId;
this.walletBalance = walletBalance;
}

@Override
public boolean processPayment(double amount) {
if (walletBalance < amount) {
System.out.println("[PayPal] Insufficient balance! Payment failed.");
return false;
}
walletBalance -= amount;
System.out.printf("[PayPal] %s paid $%.2f. Balance: $%.2f%n",
userId, amount, walletBalance);
printReceipt(amount);
return true;
}

@Override
public void refund(double amount) {
walletBalance += amount;
System.out.printf("[PayPal] $%.2f refunded. Balance: $%.2f%n",
amount, walletBalance);
}

@Override
public String getPaymentMethod() { return "PayPal"; }
}

// Crypto payment
public class CryptoProcessor implements PaymentProcessor {
private double cryptoBalance;

public CryptoProcessor(double cryptoBalance) {
this.cryptoBalance = cryptoBalance;
}

@Override
public boolean processPayment(double amount) {
if (cryptoBalance < amount) {
System.out.println("[Crypto] Insufficient crypto! Payment failed.");
return false;
}
cryptoBalance -= amount;
System.out.printf("[Crypto] $%.2f paid in crypto. Remaining: $%.2f%n",
amount, cryptoBalance);
printReceipt(amount);
return true;
}

@Override
public void refund(double amount) {
cryptoBalance += amount;
System.out.printf("[Crypto] $%.2f refunded. Remaining: $%.2f%n",
amount, cryptoBalance);
}

@Override
public String getPaymentMethod() { return "Cryptocurrency"; }
}

// Payment service (same code works with any payment method — polymorphism!)
public class PaymentService {
private PaymentProcessor processor;

public PaymentService(PaymentProcessor processor) {
this.processor = processor;
}

// Payment method can be swapped (OCP: open for extension)
public void setProcessor(PaymentProcessor processor) {
this.processor = processor;
}

public void checkout(double amount) {
System.out.println("\n=== Processing Payment ===");
boolean success = processor.processPayment(amount);
if (success) {
System.out.println("Payment completed successfully.");
} else {
System.out.println("Payment failed. Please try another method.");
}
}
}

// Test
public class PaymentTest {
public static void main(String[] args) {
PaymentProcessor creditCard = new CreditCardProcessor("1234-5678-9012-3456", 5000);
PaymentProcessor paypal = new PayPalProcessor("user123", 500);
PaymentProcessor crypto = new CryptoProcessor(300);

PaymentService service = new PaymentService(creditCard);
service.checkout(150); // credit card payment

service.setProcessor(paypal);
service.checkout(300); // PayPal payment

service.setProcessor(crypto);
service.checkout(500); // crypto payment fails (insufficient balance)
}
}

Output:

=== Processing Payment ===
[Credit Card] Card 3456 charged $150.00
[Receipt] Method: Credit Card, Amount: $150.00
Payment completed successfully.

=== Processing Payment ===
[PayPal] user123 paid $300.00. Balance: $200.00
[Receipt] Method: PayPal, Amount: $300.00
Payment completed successfully.

=== Processing Payment ===
[Crypto] Insufficient crypto! Payment failed.
Payment failed. Please try another method.

Summary

ConceptKey Point
AbstractionExtract common attributes/behaviors, hide unnecessary details
Abstract Classabstract declaration; cannot be instantiated; forces child implementation
InterfaceComplete abstraction; multiple implementation; defines behavioral spec
default methodJava 8+; adds default implementation to interface
Functional InterfaceOne abstract method; @FunctionalInterface; used with lambdas
Abstract Class vs InterfaceIS-A relationship vs CAN-DO relationship