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.,
Shapehierarchy). - The
finalkeyword makes variables/methods/classes immutable or non-overridable (e.g.,Math.PI, thread-safe constants). Objectclass methods (toString(),equals(),hashCode()) control object behavior and hashing (critical for collections likeHashSet).- 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 : containsExample: 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:
Addressis 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.,
Mathhelper 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
ActionListenerclass. - 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 : implementsExample: 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:
finalvariables 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 : extendsExample: 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:
HashSetuseshashCode()andequals()to store unique objects. - Debugging:
toString()provides readable object info (e.g.,System.out.println(user)).
In the Real World
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).
- Uses nested classes to store
Daraz’s Order Processing
- Interfaces define contracts like
OrderProcessor(e.g.,void process()). - Abstract classes provide base logic for
Order(e.g.,calculateTax()).
- Interfaces define contracts like
Ncell’s Billing System
finalvariables store immutable data likecustomerIdto prevent fraud.Objectmethods ensure correct equality checks for duplicate transactions.
Pathao’s Ride Matching
- Anonymous inner classes implement
Runnablefor background tasks (e.g., "Find Nearby Drivers").
- Anonymous inner classes implement
Exam Tip
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()).
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.
finalKeyword:- 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).
- Variables: Constants (e.g.,
ObjectClass:- Always override
toString()for readable output. equals()andhashCode()must be overridden together for collections likeHashSet.- Common pitfall: Forgetting
@Overrideor incorrecthashCode()logic.
- Always override
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:
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.DATInterface 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…