Lab 08: Interfaces & Abstract Classes
Objective
Define and implement Java interfaces, use default and static interface methods, understand functional interfaces, apply common design patterns (Strategy, Observer), and know when to choose interface vs abstract class.
Background
Interfaces define contracts — what a class can do without specifying how. They enable loose coupling, multiple implementation, and polymorphism across unrelated class hierarchies. Java 8+ interfaces with default methods bridge the gap between interfaces and abstract classes. Functional interfaces power lambdas and the Streams API.
Time
40 minutes
Prerequisites
Lab 07 (Inheritance & Polymorphism)
Tools
Java 21 (Eclipse Temurin)
Docker image:
innozverse-java:latest
Lab Instructions
Step 1: Defining and Implementing Interfaces
💡 A class can implement multiple interfaces but extend only one class. Interfaces define capabilities (
Printable,Saveable,Comparable) that cross class hierarchies — aDocument,Invoice, andReportcan all bePrintablewithout sharing an ancestor.
📸 Verified Output:
Step 2: Functional Interfaces & Lambdas
💡
@FunctionalInterfacemarks an interface as having exactly one abstract method. The compiler enforces this. Lambda expressions and method references can be assigned to any functional interface. Thejava.util.functionpackage providesFunction,Predicate,Consumer,Supplier, andBiFunctionfor common patterns.
📸 Verified Output:
Step 3: Strategy Pattern
💡 The Strategy pattern encapsulates algorithms behind an interface, letting you swap them at runtime. With functional interfaces, strategies are just lambdas — no need for separate classes. This is how
Comparator,Runnable,Callable, andComparator.comparing().thenComparing()chains work in the JDK.
📸 Verified Output:
Step 4: Observer Pattern with Interfaces
💡
computeIfAbsentcreates the list only when the key is first seen — no null check needed.Consumer<T>is a built-in functional interface forvoid-returning operations. The Observer/Event Bus pattern powers React's event system, Android's LiveData, and Spring's ApplicationEvent.
📸 Verified Output:
Step 5: Comparable & Comparator
💡
Comparablefor natural ordering,Comparatorfor custom ordering. ImplementComparablewhen there's one obvious "natural" order (semantic versioning, dates, priorities). UseComparatorwhen you need multiple orderings or can't modify the class. Always implement both consistently withequals.
📸 Verified Output:
Step 6: Interface vs Abstract Class
💡 Rule of thumb: Use an interface when you're defining what something does (a contract). Use an abstract class when you're defining how it partially does it (shared implementation + state). In modern Java, prefer interfaces with
defaultmethods — they're more flexible because classes can implement multiple interfaces.
📸 Verified Output:
Step 7: AutoCloseable — try-with-resources
💡
try-with-resourcesguarantees cleanup —close()is called even if an exception is thrown. Multiple resources are closed in reverse order of declaration (Transaction before Connection). This eliminates thetry/finallyboilerplate that was required before Java 7.
📸 Verified Output:
Step 8: Complete Example — Plugin Architecture
💡 Plugin architectures use interfaces to define the contract and registries to manage lifecycle. This is how OSGi, JDBC drivers, SPI (Service Provider Interface), and Spring's
BeanPostProcessorwork. New plugins can be added without modifying the registry — the Open/Closed Principle in action.
📸 Verified Output:
Verification
Summary
Interfaces in Java define contracts, enable multiple inheritance of type, power lambdas, and support patterns like Strategy, Observer, and Plugin Architecture. Default methods let interfaces evolve without breaking existing implementations. Choose interfaces for capabilities and abstract classes for partial implementations with shared state.
Further Reading
Last updated
