IT234 Object Oriented Programming With Java

Object Oriented Programming With JavaUnit 515 min read

Interfaces, Abstract Classes, Sealed Classes & Wrapper Classes

Unit 5 of Object Oriented Programming With Java covers abstract classes (partial implementations, abstract methods), interfaces (multiple inheritance, default/static methods), sealed classes (restricted hierarchies), and wrapper classes (autoboxing/unboxing) with real-world examples from Nepalese apps like eSewa and Ka

TAKEAWAYS:

  • Abstract classes enforce partial implementation (some methods defined, others abstract) and inheritance contracts (subclasses must implement abstract methods).
  • Interfaces enable multiple inheritance (a class can implement many interfaces) and define contracts (what methods a class must provide).
  • Sealed classes restrict inheritance to specific subclasses, improving API design and preventing misuse.
  • Wrapper classes (e.g., Integer, Double) convert primitive types to objects, enabling use in collections and generic methods.
  • Autoboxing/unboxing automatically converts between primitives and their wrapper classes (e.g., int ↔ Integer).
  • Serialization uses Serializable interface and ObjectOutputStream/ObjectInputStream to save/load objects.

1. Abstract Classes: Partial Implementation and Inheritance

Definition and Purpose

An abstract class is a class that cannot be instantiated and may contain:

  • Abstract methods (no body, declared with abstract keyword).
  • Concrete methods (fully implemented).
  • Fields (instance variables).
  • Constructors (to initialize fields).

Why use abstract classes?

  • Define a common base for related classes (e.g., Shape → Circle, Rectangle).
  • Enforce method implementation in subclasses (e.g., calculateArea() must be defined).
  • Share common code (e.g., a constructor or utility method).

Syntax

abstract class AbstractClass {
    // Abstract method (no body)
    abstract void abstractMethod();

    // Concrete method (has body)
    void concreteMethod() {
        System.out.println("Implemented method");
    }
}

Example: Traffic Light System

Consider a traffic management system where different traffic lights (e.g., PedestrianLight, VehicleLight) share common behavior but have unique logic.

abstract class TrafficLight {
    private String location;

    public TrafficLight(String location) {
        this.location = location;
    }

    // Abstract method: must be implemented by subclasses
    public abstract void changeState();

    // Concrete method: common to all traffic lights
    public void displayLocation() {
        System.out.println("Traffic light at: " + location);
    }
}

class PedestrianLight extends TrafficLight {
    public PedestrianLight(String location) {
        super(location);
    }

    @Override
    public void changeState() {
        System.out.println("Pedestrian light: Walk → Don't Walk");
    }
}

Visual: Abstract Class Hierarchy

classDiagram
    class TrafficLight {
        <<abstract>>
        +String location
        +TrafficLight(String location)
        +abstract changeState()
        +displayLocation()
    }
    class PedestrianLight {
        +PedestrianLight(String location)
        +changeState()
    }
    class VehicleLight {
        +VehicleLight(String location)
        +changeState()
    }
    TrafficLight <|-- PedestrianLight
    TrafficLight <|-- VehicleLight

Key Points

  • Cannot instantiate: TrafficLight light = new TrafficLight("Kathmandu"); → Error.
  • Subclasses must implement abstract methods: PedestrianLight provides changeState().
  • Use case: When you want to share code but enforce method implementation.

2. Interfaces: Contracts and Multiple Inheritance

Definition and Purpose

An interface is a pure abstract class (all methods are public abstract by default) that defines a contract for classes to implement. Key features:

  • Multiple inheritance: A class can implement many interfaces.
  • Default methods: Interfaces can have default methods (with implementation).
  • Static methods: Interfaces can have static utility methods.
  • Functional interfaces: Interfaces with one abstract method (used in lambda expressions).

Syntax

interface InterfaceName {
    // Abstract method (public abstract by default)
    void abstractMethod();

    // Default method (has implementation)
    default void defaultMethod() {
        System.out.println("Default method");
    }

    // Static method
    static void staticMethod() {
        System.out.println("Static method");
    }
}

Example: Payment Gateway (eSewa-like System)

Suppose we design a payment system where different payment methods (CreditCard, MobileWallet) must implement a common processPayment() method.

interface PaymentMethod {
    void processPayment(double amount);

    // Default method: common to all payment methods
    default void displayReceipt() {
        System.out.println("Payment processed successfully");
    }
}

class CreditCard implements PaymentMethod {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment: $" + amount);
    }
}

class MobileWallet implements PaymentMethod {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing mobile wallet payment: Rs. " + amount);
    }
}

Visual: Interface Implementation

classDiagram
    class PaymentMethod {
        <<interface>>
        +processPayment(double amount)
        +default displayReceipt()
    }
    class CreditCard {
        +processPayment(double amount)
    }
    class MobileWallet {
        +processPayment(double amount)
    }
    PaymentMethod <|-- CreditCard
    PaymentMethod <|-- MobileWallet

Default and Static Methods

  • Default methods allow adding new methods to interfaces without breaking existing implementations.
  • Static methods are utility methods (e.g., Collections.sort()).
interface Logger {
    void log(String message);

    // Default method
    default void logWithTimestamp(String message) {
        System.out.println("[" + new Date() + "] " + message);
    }

    // Static method
    static void clearLog() {
        System.out.println("Log cleared");
    }
}

Functional Interfaces and Lambdas

A functional interface has exactly one abstract method. Used with lambdas:

@FunctionalInterface
interface Greeting {
    void sayHello(String name);
}

public class Main {
    public static void main(String[] args) {
        Greeting greet = (name) -> System.out.println("Hello, " + name);
        greet.sayHello("Rohan");
    }
}

Key Points

  • Multiple inheritance: A class can implement PaymentMethod and NotificationService.
  • Default methods: Resolve the "diamond problem" (conflicts in multiple inheritance).
  • Functional interfaces: Enable lambda expressions and stream APIs.

3. Sealed Classes: Restricted Inheritance

Definition and Purpose

Sealed classes (Java 17+) restrict which classes can extend or implement them. Useful for:

  • API design: Prevent misuse (e.g., only specific payment methods allowed).
  • Performance: JVM optimizes sealed hierarchies.
  • Safety: Ensure only intended subclasses exist.

Syntax

sealed class Shape permits Circle, Rectangle, Triangle {
    // ...
}

final class Circle extends Shape { /* ... */ }
non-sealed class Rectangle extends Shape { /* ... */ }

Example: NEPSE Stock Order Types

Suppose NEPSE allows only specific order types (MarketOrder, LimitOrder, StopOrder):

sealed interface Order permits MarketOrder, LimitOrder, StopOrder {
    void execute();
}

```figure
{"type":"network","nodes":["Order","MarketOrder","LimitOrder","StopOrder"],"edges":[["Order","MarketOrder"],["Order","LimitOrder"],["Order","StopOrder"]],"directed":true,"caption":"Sealed class hierarchy with permitted subclasses"}

final class MarketOrder implements Order { @Override public void execute() { System.out.println("Executing market order"); } }

non-sealed class LimitOrder implements Order { @Override public void execute() { System.out.println("Executing limit order"); } }


#### **Visual: Sealed Hierarchy**
```mermaid
classDiagram
    class Order {
        <<sealed>>
        +execute()
        +final void finalizeOrder()
    }
    class MarketOrder {
        +execute()
    }
    class LimitOrder {
        +execute()
    }
    class StopOrder {
        +execute()
    }
    Order <|-- MarketOrder
    Order <|-- LimitOrder
    Order <|-- StopOrder
    class SealedPermitted {
        <<permitted>>
    }
    SealedPermitted ..> Order

Key Points

  • permits clause: Specifies allowed subclasses/interfaces.
  • final or non-sealed: Subclasses can be final (no further extension) or non-sealed (can be extended further).
  • Use case: API design (e.g., Java’s switch expression patterns).

4. Wrapper Classes: Primitives to Objects

Definition and Purpose

Wrapper classes convert primitive types (int, double) to objects (Integer, Double). Why?

  • Collections: ArrayList<int> → Invalid (collections require objects).
  • Generics: List<Integer> vs. List<int>.
  • Null values: Objects can be null (e.g., Integer num = null).

Common Wrapper Classes

Primitive Wrapper Class Autoboxing Example Unboxing Example
int Integer Integer num = 10; int x = num;
double Double Double d = 3.14; double y = d;
char Character Character c = 'A'; char ch = c;
boolean Boolean Boolean flag = true; boolean b = flag;

Example: Prime/Composite String Checker

Write strings to Prime.txt or Composite.txt based on string length:

import java.io.*;
import java.util.Scanner;

public class PrimeCompositeChecker {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter a string: ");
        String input = scanner.nextLine();

        // Autoboxing: int → Integer
        Integer length = input.length();
        boolean isPrime = isPrime(length);

        try {
            FileWriter writer = new FileWriter(isPrime ? "Prime.txt" : "Composite.txt");
            writer.write(input);
            writer.close();
            System.out.println("String written to " + (isPrime ? "Prime.txt" : "Composite.txt"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static boolean isPrime(Integer num) {
        if (num <= 1) return false;
        for (int i = 2; i <= Math.sqrt(num); i++) {
            if (num % i == 0) return false;
        }
        return true;
    }
}

Visual: Autoboxing/Unboxing

flowchart TD
    A["Primitive int"] -->|"Autoboxing"| B["Integer Object"]
    B -->|"Unboxing"| C["Primitive int"]
    D["Primitive double"] -->|"Autoboxing"| E["Double Object"]
    E -->|"Unboxing"| F["Primitive double"]

Key Points

  • Autoboxing: Automatic conversion (e.g., int → Integer).
  • Unboxing: Automatic conversion (e.g., Integer → int).
  • Use case: Collections, generics, and APIs requiring objects.

5. Serialization: Saving Objects to Files

Definition and Purpose

Serialization converts an object’s state into a byte stream for storage/transmission. Used in:

  • File I/O: Save objects to disk (e.g., user profiles).
  • Networking: Send objects over sockets (e.g., chat messages).
  • Distributed systems: Pass objects between services.

Steps to Serialize/Deserialize

  1. Implement Serializable:
    class User implements Serializable {
        private String name;
        private int age;
        // Constructors, getters, setters
    }
    
  2. Serialize to File:
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
        User user = new User("Rohan", 25);
        oos.writeObject(user); // Serialize
    }
    
  3. Deserialize from File:
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
        User user = (User) ois.readObject(); // Deserialize
        System.out.println(user.getName());
    }
    

Visual: Serialization Process

sequenceDiagram
    participant User as User Object
    participant OOS as ObjectOutputStream
    participant File as File ("user.dat")
    User->>OOS: writeObject()
    OOS->>File: Save byte stream
    File-->>OIS: Load byte stream
    OIS-->>User: readObject()

Key Points

  • Serializable interface: Marks a class as serializable.
  • transient keyword: Fields not serialized (e.g., passwords).
  • Use case: eSewa transaction history, Khalti user data.

In the Real World

  1. eSewa (Nepal):

    • Interfaces: PaymentProcessor interface defines processPayment() for CreditCardPayment, MobilePayment, etc.
    • Abstract Classes: User abstract class enforces login() and logout() in Customer and Admin subclasses.
  2. Pathao (Ride-Hailing):

    • Sealed Classes: Order sealed interface restricts order types to RideOrder, DeliveryOrder, etc.
    • Serialization: Driver locations are serialized to update ride statuses in real-time.
  3. NTC (Telecom Billing):

    • Wrapper Classes: Integer used for call durations in ArrayList<Integer> for billing calculations.
    • Autoboxing: double call charges are autoboxed to Double for storage in databases.
  4. NEPSE (Stock Exchange):

    • Abstract Classes: Order abstract class defines execute() for MarketOrder, LimitOrder.
    • Interfaces: Serializable used to save trade history to files.

Exam Tip

  1. Abstract vs. Interface:

    • Abstract classes can have fields and constructors; interfaces cannot (pre-Java 8).
    • Use abstract classes for shared code (e.g., TrafficLight constructor).
    • Use interfaces for multiple inheritance (e.g., PaymentMethod + NotificationService).
  2. Sealed Classes:

    • Always mention permits clause and final/non-sealed modifiers.
    • Example: "A sealed class Shape permits Circle and Rectangle."
  3. Wrapper Classes:

    • Remember autoboxing/unboxing examples (e.g., int ↔ Integer).
    • Collections require objects: List<int> → Error; List<Integer> → Valid.
  4. Serialization:

    • Key interfaces: Serializable, Externalizable.
    • Methods: ObjectOutputStream.writeObject(), ObjectInputStream.readObject().
    • Exception: ClassNotFoundException when deserializing.
  5. Common Pitfalls:

    • Forgetting implements Serializable → NotSerializableException.
    • Not handling IOException in file operations.
    • Confusing abstract methods (no body) with default methods (has body in interfaces).

Practice Questions

  1. Code:

    abstract class Animal {
        abstract void makeSound();
    }
    class Dog extends Animal {
        void makeSound() { System.out.println("Bark"); }
    }
    
    • What is the output of new Dog().makeSound()? Answer: Bark.
  2. Design:

    • How would you model a Vehicle hierarchy with Car, Bike, and Truck using sealed classes?
  3. Wrapper Classes:

    • Write a program to check if a number (input as String) is a palindrome using Integer.parseInt() and autoboxing.

Summary Table

Concept Key Feature Example
Abstract Class Partial implementation, abstract methods TrafficLight → PedestrianLight
Interface Multiple inheritance, default methods PaymentMethod → CreditCard
Sealed Class Restricted inheritance with permits Order → MarketOrder
Wrapper Class Primitives ↔ Objects, autoboxing int ↔ Integer
Serialization Save/load objects, Serializable User object to file

Based on the TU BITM syllabus for Object Oriented Programming With Java (IT234), unit 5.

Discussion

Loading…