BIT401 Advanced Java Programming

Advanced Java ProgrammingUnit 113 min read

Advanced OOP: Inner Classes, Interfaces, Abstract Classes & Final Keyword

Unit 1 of Advanced Java Programming covers inner classes (member, static, local, anonymous), interfaces vs abstract classes, final keyword usage, and object class methods. Learn how to implement real-world designs (e.g., employee databases, geometric shapes) and solve exam-style problems with code traces and visuals.

TAKEAWAYS:

  • Inner classes (member, anonymous, lambda) enable encapsulation and code reuse inside outer classes (e.g., GUI event handlers, file I/O).
  • Interfaces enforce contracts (method signatures) for multiple inheritance, while abstract classes provide partial implementation (e.g., Shape hierarchy).
  • The final keyword makes variables/methods/classes immutable or non-overridable (e.g., Math.PI, thread-safe constants).
  • Object class methods (toString(), equals(), hashCode()) control object behavior and hashing (critical for collections like HashSet).
  • Design patterns (e.g., Adapter, Factory) use these concepts to structure scalable software (e.g., Daraz’s order processing, Ncell’s billing).

1. Inner Classes in Java

Inner classes are classes defined inside another class. They improve encapsulation and scope control. There are 4 types:

A. Member Inner Class

  • Defined inside a class but outside methods.
  • Can access private members of the outer class.
  • Requires an outer class instance to be instantiated.
class Outer {
    private int x = 10;
    class Inner {
        void display() {
            System.out.println("x = " + x); // Accesses outer class member
        }
    }
}

Visual: Member Inner Class Structure

classDiagram
    class Outer {
        -int x
        +Inner getInner()
    }
    class Inner {
        +void display()
    }
    Outer --> Inner : contains

Example: Employee Database (Real-World Tie)

class Employee {
    private String name;
    Employee(String name) { this.name = name; }

    class Address {  // Inner class for nested data
        private String city;
        Address(String city) { this.city = city; }
        void print() { System.out.println(name + " lives in " + city); }
    }
}

Trace:

Employee emp = new Employee("Ramesh");
Employee.Address addr = emp.new Address("Kathmandu"); // Requires outer instance
addr.print(); // Output: "Ramesh lives in Kathmandu"

Why?

  • Encapsulation: Address is hidden from other classes.
  • Use Case: E-sewa’s user profile system stores nested data (e.g., User.Address).

B. Static Nested Class

  • Not tied to an outer class instance.
  • Cannot access non-static members of the outer class.
  • Used for utility classes (e.g., Math helper classes).
class Outer {
    static class StaticInner {
        void show() { System.out.println("Static Inner Class"); }
    }
}

Visual: Static vs Member Inner Class

classDiagram
    class Outer {
        -int x
        +StaticInner getStaticInner()
    }
    class StaticInner {
        +void show()
    }
    class MemberInner {
        +void display()
    }
    Outer --> StaticInner : contains (static)
    Outer --> MemberInner : contains (non-static)

Example: Ncell Billing System

class Plan {
    static class Prepaid {  // Static nested for plan types
        void applyDiscount() { System.out.println("10% off"); }
    }
}

Trace:

Plan.Prepaid prepaid = new Plan.Prepaid(); // No outer instance needed
prepaid.applyDiscount(); // Output: "10% off"

C. Local Inner Class

  • Defined inside a method or block.
  • Can only be used within that block.
  • Useful for short-lived objects (e.g., GUI event handlers).
public class LocalInnerExample {
    void display() {
        class Local {
            void print() { System.out.println("Local Inner Class"); }
        }
        Local local = new Local();
        local.print();
    }
}

Visual: Local Inner Class Scope

flowchart TD
    A["Method display()"] --> B["Local Inner Class"]
    B --> C["print() method"]
    C --> D["Output: 'Local Inner Class'"]

Example: Daraz Order Queue (Real-World)

class OrderProcessor {
    void processOrder() {
        class Order {
            void confirm() { System.out.println("Order confirmed!"); }
        }
        Order order = new Order();
        order.confirm(); // Used only inside processOrder()
    }
}

Trace:

OrderProcessor processor = new OrderProcessor();
processor.processOrder(); // Output: "Order confirmed!"

D. Anonymous Inner Class

  • No name, defined and instantiated in one line.
  • Extends a class or implements an interface on the fly.
  • Used for one-time implementations (e.g., event listeners, Runnable threads).
interface Greeting {
    void sayHello();
}
public class AnonymousExample {
    public static void main(String[] args) {
        Greeting greet = new Greeting() {  // Anonymous class
            public void sayHello() {
                System.out.println("Hello from Anonymous Class!");
            }
        };
        greet.sayHello();
    }
}

Visual: Anonymous Class Instantiation

classDiagram
    class Greeting {
        <<interface>>
        +void sayHello()
    }
    anonymousClass --> Greeting : implements
    anonymousClass : +void sayHello() { ... }

Example: Pathao Driver App (Real-World)

Button startButton = new Button("Start Ride");
startButton.addActionListener(new ActionListener() {  // Anonymous listener
    public void actionPerformed(ActionEvent e) {
        System.out.println("Ride started!");
    }
});

Trace:

// Clicking the button triggers the anonymous listener.

Why?

  • Conciseness: Avoids writing a separate ActionListener class.
  • Use Case: WhatsApp’s message send button uses anonymous listeners.

2. Interfaces vs Abstract Classes

Feature Interface Abstract Class
Instantiation Cannot be instantiated Cannot be instantiated
Methods Only abstract (Java 7+) or default/static (Java 8+) Can have abstract + concrete methods
Fields Only public static final (constants) Can have any fields (even non-final)
Inheritance A class can implement multiple interfaces A class can extend only one abstract class
Use Case Define contracts (e.g., Comparable, Serializable) Provide partial implementation (e.g., AbstractList)

Visual: Interface vs Abstract Class

classDiagram
    class Shape {
        <<abstract>>
        +double area()
    }
    class Circle {
        +double radius
        +double area()
    }
    class Rectangle {
        +double length
        +double breadth
        +double area()
    }
    Shape <|-- Circle : extends
    Shape <|-- Rectangle : extends
    class Drawable {
        <<interface>>
        +void draw()
    }
    Circle ..|> Drawable : implements
    Rectangle ..|> Drawable : implements

Example: NEPSE Stock Prices (Real-World)

interface Stock {
    double getPrice();
    void updatePrice(double newPrice);
}
abstract class StockData {
    protected String symbol;
    abstract void display();
}
class NEPSEStock extends StockData implements Stock {
    private double price;
    public double getPrice() { return price; }
    public void updatePrice(double newPrice) { price = newPrice; }
    public void display() { System.out.println(symbol + ": " + price); }
}

Trace:

Stock nepse = new NEPSEStock();
nepse.updatePrice(1200.50);
nepse.display(); // Output: "NEPSE: 1200.5"

3. The final Keyword

Makes variables, methods, or classes immutable or non-overridable.

Usage Example Purpose
Final Variable final int MAX_SPEED = 100; Constant value (e.g., Math.PI)
Final Method final void show() Prevents overriding (e.g., String methods)
Final Class final class ImmutablePoint Prevents inheritance (e.g., String, Integer)

Example: Khalti Payment Security (Real-World)

final class Transaction {
    private final String id;  // Immutable ID
    public Transaction(String id) { this.id = id; }
    public final void process() {  // Cannot be overridden
        System.out.println("Processing " + id);
    }
}

Trace:

Transaction txn = new Transaction("TXN123");
txn.process(); // Output: "Processing TXN123"

Why?

  • Security: Prevents tampering with critical data (e.g., payment IDs).
  • Thread Safety: final variables are safely shared across threads.

4. The Object Class

Every Java class implicitly extends Object. Key methods:

Method Purpose Example
toString() Returns string representation @Override public String toString() { return name; }
equals(Object obj) Compares objects for equality @Override public boolean equals(Object o) { ... }
hashCode() Returns hash code for hashing @Override public int hashCode() { return id.hashCode(); }

Visual: Object Class Hierarchy

classDiagram
    class Object {
        +String toString()
        +boolean equals(Object)
        +int hashCode()
    }
    class Employee {
        -String name
        -int id
    }
    Employee --> Object : extends

Example: NTC Bus Route System (Real-World)

class BusRoute {
    private String routeId;
    public BusRoute(String routeId) { this.routeId = routeId; }
    @Override
    public String toString() { return "Route: " + routeId; }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        BusRoute other = (BusRoute) obj;
        return routeId.equals(other.routeId);
    }
}

Trace:

BusRoute route1 = new BusRoute("KT101");
BusRoute route2 = new BusRoute("KT101");
System.out.println(route1); // Output: "Route: KT101"
System.out.println(route1.equals(route2)); // Output: "true"

Why?

  • Collections: HashSet uses hashCode() and equals() to store unique objects.
  • Debugging: toString() provides readable object info (e.g., System.out.println(user)).

In the Real World

  1. E-sewa’s User Profile

    • Uses nested classes to store User.Address (member inner class) for encapsulation.
    • Anonymous classes handle one-time event listeners (e.g., "Submit Payment" button).
  2. Daraz’s Order Processing

    • Interfaces define contracts like OrderProcessor (e.g., void process()).
    • Abstract classes provide base logic for Order (e.g., calculateTax()).
  3. Ncell’s Billing System

    • final variables store immutable data like customerId to prevent fraud.
    • Object methods ensure correct equality checks for duplicate transactions.
  4. Pathao’s Ride Matching

    • Anonymous inner classes implement Runnable for background tasks (e.g., "Find Nearby Drivers").

Exam Tip

  1. Inner Classes:

    • Remember the 4 types and their scopes.
    • Anonymous classes are often tested with interfaces (e.g., Runnable, ActionListener).
    • Trace instantiation: Always show how to create an inner class object (e.g., Outer.Inner inner = outer.new Inner()).
  2. Interfaces vs Abstract Classes:

    • Interfaces = "What to do" (contracts).
    • Abstract classes = "Partial implementation" (e.g., AbstractList).
    • Exam trick: If a question asks for "multiple inheritance," the answer is interfaces.
  3. final Keyword:

    • Variables: Constants (e.g., public static final double PI = 3.14;).
    • Methods: Prevent overriding (e.g., final void lock() in thread safety).
    • Classes: Prevent extension (e.g., String, Integer).
  4. Object Class:

    • Always override toString() for readable output.
    • equals() and hashCode() must be overridden together for collections like HashSet.
    • Common pitfall: Forgetting @Override or incorrect hashCode() logic.
  5. Code Traces:

    • Show step-by-step execution (e.g., inner class instantiation, interface method calls).
    • Use tables for variable states (e.g., before/after equals() comparison).

Past Exam Questions Solved:

  1. Inner Class Example:

    class Employee {
        String name;
        Employee(String name) { this.name = name; }
        class Doctor extends Employee {
            Doctor(String name) { super(name); }
            void writeToFile() {
                try (FileWriter fw = new FileWriter("DOC.DAT")) {
                    fw.write(name + " is a doctor\n");
                } catch (IOException e) { e.printStackTrace(); }
            }
        }
    }
    

    Trace:

    Employee emp = new Employee("Dr. Sharma");
    Employee.Doctor doc = emp.new Doctor("Dr. Sharma");
    doc.writeToFile(); // Writes to DOC.DAT
    
  2. Interface Example:

    interface Shape { double area(); }
    class Rectangle implements Shape {
        double length, breadth;
        public double area() { return length * breadth; }
    }
    class Circle implements Shape {
        double radius;
        public double area() { return Math.PI * radius * radius; }
    }
    

    Trace:

    Shape rect = new Rectangle();
    rect.length = 5; rect.breadth = 3;
    System.out.println(rect.area()); // Output: 15.0
    

Based on the TU BIT syllabus for Advanced Java Programming (BIT401), unit 1.

Discussion

Loading…