Lab 06: OOP — Classes & Encapsulation

Objective

Design and implement Java classes with fields, constructors, methods, and proper encapsulation. Use access modifiers, getters/setters, this, static members, and records.

Background

Object-Oriented Programming is Java's core paradigm. Classes are blueprints; objects are instances. Encapsulation — hiding implementation details behind a public interface — makes code maintainable. Java 16+ records reduce boilerplate for pure data classes. Understanding class design is the foundation for everything from Android apps to enterprise Spring Boot services.

Time

40 minutes

Prerequisites

  • Lab 02 (Variables & Primitives)

  • Lab 05 (Control Flow)

Tools

  • Java 21 (Eclipse Temurin)

  • Docker image: innozverse-java:latest


Lab Instructions

Step 1: Your First Class

💡 private fields + public methods is encapsulation. External code can't directly modify balance — it must go through deposit()/withdraw(), which enforce business rules. This is how you prevent invalid state: balance can never go negative.

📸 Verified Output:


Step 2: Constructors & Constructor Chaining

💡 this(...) constructor chaining avoids duplicating initialization logic. The delegating constructor must call this(...) as its first statement. Mark fields final when they shouldn't change after construction — the compiler enforces this.

📸 Verified Output:


Step 3: Static Members — Class-Level State

💡 static belongs to the class, not instances. totalCreated increments every time new Counter() is called, regardless of which instance you're looking at. Static factory methods (of()) are preferred over constructors when: the name conveys meaning, you want to return cached instances, or the return type might differ.

📸 Verified Output:


Step 4: Records — Immutable Data Classes

💡 Records are perfect for DTOs, value objects, and API responses. They're immutable by default (all fields are final). The compact constructor lets you add validation without rewriting the full constructor. Use records instead of Lombok's @Data in modern Java.

📸 Verified Output:


Step 5: equals, hashCode & toString

💡 The equals/hashCode contract: if a.equals(b) then a.hashCode() == b.hashCode(). Breaking this contract silently breaks HashMap, HashSet, and HashTable. Use Objects.equals() (null-safe) and Objects.hash() to implement correctly.

📸 Verified Output:


Step 6: Inner Classes & Nested Types

💡 The Builder pattern solves the "telescoping constructor" problem — instead of 5 constructors with different parameter combinations, you chain method calls and call build(). It also makes construction self-documenting: .host("...").port(443).ssl(true) reads like configuration, not a mystery list of arguments.

📸 Verified Output:


Step 7: Object Lifecycle & Garbage Collection

💡 Java's Garbage Collector reclaims memory automatically when objects have no more references. You can't force GC (System.gc() is a suggestion). For resource cleanup (files, DB connections, sockets), use try-with-resources with AutoCloseable — the JVM guarantees close() is called.

📸 Verified Output:


Step 8: Complete Class — Shopping Cart

💡 ifPresentOrElse on Optional handles both "found" and "not found" in one readable expression. Combining records, streams, and switch expressions shows how modern Java is concise and expressive without external libraries.

📸 Verified Output:


Verification

Summary

You've built complete Java classes with encapsulation, constructor chaining, static members, records, equals/hashCode, builders, and the shopping cart capstone. These patterns are the daily vocabulary of Java development.

Further Reading

Last updated