intermediate21 min

Classes, Objects, Constructors, and Encapsulation

How a class becomes a blueprint for objects, why constructors exist, and why hiding a field behind private plus getters/setters is worth the extra typing.

What you'll learn

  • Define a class with fields, constructors, and methods
  • Explain what encapsulation prevents, with a concrete example
  • Use access modifiers (private, public) deliberately, not by default

Prerequisites

Explanation

A class is a blueprint; an object is a specific instance built from it via new. Enrollment e = new Enrollment("alice", "cs101"); runs the class's constructor, a special method with no return type that shares the class's name, whose job is to establish the object in a valid starting state — every field should leave the constructor holding a sensible value, never left to whatever Java's default happens to be (0, null, false) by accident.

Encapsulation means a class's fields are private by default, exposed to the outside world only through deliberately chosen public methods — usually a getter (getStatus()) and, if and only if external mutation should genuinely be allowed, a setter (setStatus(...)). The point isn't bureaucracy for its own sake: a private field with a setter can validate every change in one place (setStatus(String s) { if (!VALID_STATUSES.contains(s)) throw new IllegalArgumentException(...); this.status = s; }), guaranteeing the object can never end up in an invalid state no matter how many places in the codebase call the setter. A public field offers no such guarantee — any code, anywhere, can set it to anything, and there is no single place left to add a rule later without hunting down every call site.

A field with no setter at all, only a getter, is effectively immutable after construction — a strong, deliberate design choice, not an oversight, for data that should never change once created (an order's ID, a user's registration date). Java's this keyword refers to the current object; it's most often needed to disambiguate a constructor or setter parameter from a field of the same name (this.status = status; — without this, that line would just assign the parameter to itself and leave the field untouched, a real and common bug). Since Java 16, a record (record Point(int x, int y) {}) generates a constructor, getters, equals/hashCode/toString automatically for the common case of an immutable data holder — worth knowing about, though this course still teaches the manual class form first since it's what a record expands into under the hood.

Example

Encapsulation modeled with a JS class: a private-style field (# prefix) only reachable through validating methods.

class Enrollment {
  #status; // private field -- unreachable from outside this class, like Java's private

  constructor(learnerId, courseId) {
    this.learnerId = learnerId;
    this.courseId = courseId;
    this.#status = "active"; // constructor establishes a valid starting state
  }

  getStatus() {
    return this.#status;
  }

  setStatus(next) {
    const valid = ["active", "completed", "withdrawn"];
    if (!valid.includes(next)) {
      throw new Error("invalid status: " + next);
    }
    this.#status = next;
  }
}

const e = new Enrollment("alice", "cs101");
console.log(e.getStatus());  // "active"
e.setStatus("completed");
console.log(e.getStatus());  // "completed"
e.setStatus("bogus");        // throws -- validation runs no matter who calls setStatus

Try it yourself

Try calling e.setStatus("bogus") and observe the thrown error -- then fix it to a valid status.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Model a Java class BankAccount with a private-style #balance field, a constructor taking an opening balance, a getBalance() getter, and a deposit(amount) method that throws for a non-positive amount and otherwise increases the balance.

Checks: getBalance reflects the opening balance · deposit increases the balance · negative opening balance throws · non-positive deposit throws

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Model a Java class Temperature with a private #celsius field, a constructor, getCelsius(), and getFahrenheit() (computed on demand, not stored) -- Fahrenheit = celsius * 9/5 + 32. There is deliberately no setter: a Temperature is immutable once created.

Checks: getCelsius returns the constructed value · 0C converts to 32F · 100C converts to 212F · no setCelsius method exists (immutability)

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Making every field public 'to keep things simple' -- this removes any single place to validate or later change how a value is stored, and every caller becomes coupled to the field's exact representation.
  • Writing `status = status;` inside a setter instead of `this.status = status;` when the parameter and field share a name -- this assigns the parameter to itself and silently leaves the field untouched.
  • Adding a setter for every field 'just in case' -- a field that should never change after construction (like an ID) should have no setter at all; adding one anyway invites bugs where something mutates data it shouldn't.

Knowledge check

Knowledge check

1. Why does a setter method (rather than a public field) let you guarantee an object never enters an invalid state?
2. Inside a constructor `Enrollment(String status) { status = status; }`, what's wrong?
3. A class has a private field with a getter but no setter. What does that design communicate?

Takeaway

Private fields plus deliberately-chosen public getters/setters (or no setter at all, for immutable data) give you one guaranteed place to validate every change — a public field gives you no such place, ever.

Summary

A class is a blueprint; new creates an object from it, running the constructor to establish a valid starting state. Encapsulation hides fields behind private and exposes only deliberately chosen public methods, so validation and future changes have exactly one place to live.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.