Matheion

MA-JAVA0 · Module 7

Encapsulation and simple invariants

Topics: private · getters · Validation

Overview

Session 7 — Encapsulation and simple invariants

You will hide fields behind private, expose controlled getters, and validate inputs in setters or behaviour methods so object state cannot be corrupted by distant code.

Time: about 85 minutes.

Concepts

1. Why hide fields?

When fields are public, any caller can assign nonsense (counter.total = -999). Encapsulation means: keep data private, offer methods that enforce rules, and expose only what clients need.

Benefits:

  • Invariants — internal relationships stay true (balance >= 0).
  • Evolution — you can change storage (e.g., cache) without breaking callers if the method contracts stay stable.

2. Accessor pattern

public class Tank {
    private double litres;

    public double getLitres() {
        return litres;
    }

    public void add(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("amount must be non-negative");
        }
        litres += amount;
    }
}

Callers cannot bypass add to inject negative amounts.


2b. Getters and setters — one pair per field

For each private field, expose controlled read/write through methods:

| Field type | Getter (accessor) | Setter (mutator) | |------------|-------------------|------------------| | String | getName() | setName(String name) | | int | getQuantity() | setQuantity(int quantity) | | double | getPrice() | setPrice(double price) | | boolean | isInStock() | setInStock(boolean inStock) |

Naming rules:

  • Most types: get + FieldName with capital letter (getName).
  • boolean: prefer is + FieldName (isInStock()), not getIsInStock().
  • Setters: set + FieldName (setInStock, not setIsInStock).

Use this.field = parameter when the parameter name matches the field name:

public void setQuantity(int quantity) {
    this.quantity = quantity;
}

public boolean isInStock() {
    return inStock;
}

public void setInStock(boolean inStock) {
    this.inStock = inStock;
}

Full four-field example (Session 7 Examples tab, Product class).


3. Mutability discipline

Returning internal mutable collections directly breaks encapsulation—callers could clear your list. Prefer unmodifiable views or copies (later session with lists).


4. toString for debugging

Override toString to print meaningful snapshots while learning:

@Override
public String toString() {
    return "Tank(" + litres + " L)";
}

IDE templates generate stubs—learn the annotation @Override meaning “I intend to replace a superclass method.”


5. Exceptions as guardrails

IllegalArgumentException signals programmer misuse; checked exceptions (later) model recoverable failures. For now, fail fast on impossible parameters.

Examples

Example — bounded tank

public class BoundedTank {
    private final double capacity;
    private double litres;

    public BoundedTank(double capacityLitres) {
        if (capacityLitres <= 0) {
            throw new IllegalArgumentException("capacity must be positive");
        }
        this.capacity = capacityLitres;
        this.litres = 0.0;
    }

    public double fill(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("amount must be non-negative");
        }
        double room = capacity - litres;
        double accepted = Math.min(amount, room);
        litres += accepted;
        return accepted; // how much actually fit
    }

    public double getLitres() {
        return litres;
    }
}

Callers learn how much overflow was rejected via the return value—transparent behaviour without exposing mutable internals.


Example — constructor, getters, setters, and toString

A common mid-module pattern: private fields, a constructor that sets them, get/set methods, and toString for readable printing.

public class Product {
    private String name;
    private int quantity;
    private double price;
    private boolean inStock;

    public Product(String name, int quantity, double price) {
        this.name = name;
        this.quantity = quantity;
        this.price = price;
        this.inStock = false;   // starts false per typical spec
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public boolean isInStock() {
        return inStock;
    }

    public void setInStock(boolean inStock) {
        this.inStock = inStock;
    }

    @Override
    public String toString() {
        return "name: " + name + "\n"
             + "quantity: " + quantity + "\n"
             + "price: " + price + "\n"
             + "inStock: " + inStock;
    }
}

Teaching points:

  • boolean getters often use isInStock() instead of getIsInStock().
  • @Override toString — System.out.println(obj) calls toString() automatically.
  • private fields force callers to use setters when state must change after construction (see Session 10 arrays of objects).

Bridge

Encapsulation ↔ trustworthy modules

  1. API boundaries mirror class boundaries—public methods are contracts; private fields are implementation detail.

  2. Unit tests target public behaviour; they should still pass if you rename a private field.

  3. Concurrency (advanced courses) relies on guarding shared state—encapsulation is the moral prerequisite.

  4. Records (modern Java) generate accessors automatically—understand manual pattern first so generated code is legible.

When invalid states become impossible to construct, debugging time drops sharply.

Practice check — encapsulation and invariants

Multiple-choice check from the Matheion app lesson. Pick an answer for each question, then submit.

  1. 1.Why mark fields `private`?

  2. 2.A getter typically…

  3. 3.What is an invariant in this context?

  4. 4.A setter rejects a negative age by throwing `IllegalArgumentException`. Why throw instead of silently clamping?

  5. 5.Which access is allowed from another class if a field is `private`?