Encapsulation

IB Syllabus: B3.1 – Apply encapsulation by using access modifiers and accessor/mutator methods.

Table of Contents

  1. Key Concepts
    1. What is Encapsulation?
    2. Why Encapsulate?
    3. Accessor Methods (Getters)
    4. Mutator Methods (Setters)
    5. The Encapsulation Pattern
    6. What Happens Without Encapsulation
  2. Worked Examples
    1. Example 1: Designing an Encapsulated Class
    2. Example 2: Before and After
  3. Quick Check
  4. Spot the Error
  5. Fill in the Blanks
  6. Predict the Output
  7. Practice Exercises
    1. Core
    2. Extension
    3. Challenge
  8. Connections

Key Concepts

What is Encapsulation?

Encapsulation means putting variables and methods together into one unit (the class) and restricting direct access to the variables by making them private. External code can only interact with the object’s data through its public methods – the class controls how its data is read and modified.

Think of a vending machine: you cannot reach inside and grab items directly. You interact through the defined interface – insert money, press a button, receive the product. The machine’s internal state (stock levels, cash stored) is hidden and protected.

Why Encapsulate?

Without encapsulation, any code anywhere can change any variable to any value:

// WITHOUT encapsulation -- attributes are public
public class BankAccount {
    public String owner;
    public double balance;
}

BankAccount acc = new BankAccount();
acc.balance = -999999.99;  // nothing stops this
acc.owner = "";            // empty string is allowed

With encapsulation, the class controls what values are acceptable:

// WITH encapsulation -- attributes are private, access through methods
public class BankAccount {
    private String owner;
    private double balance;

    public BankAccount(String owner, double balance) {
        this.owner = owner;
        this.balance = (balance >= 0) ? balance : 0;
    }

    public double getBalance() { return balance; }

    public void deposit(double amount) {
        if (amount > 0) {
            balance = balance + amount;
        }
    }

    public boolean withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance = balance - amount;
            return true;
        }
        return false;
    }
}

Now balance can never go negative – the class enforces this rule in every method that modifies it. External code cannot bypass this protection.

Encapsulation provides four key advantages:

Advantage How
Data hiding Internal representation is hidden; other classes interact only through the public interface
Validation Setters and mutators check that values are valid before accepting them
Flexibility Internal implementation can change without affecting code that uses the class
Security Prevents accidental or malicious modification of an object’s state

Encapsulation = “putting variables and methods into one unit; variables private; accessed only through public members.” This is the exact phrasing IB mark schemes use. Memorise it.

Accessor Methods (Getters)

An accessor method returns the value of a private instance variable without modifying it. It provides read-only access to the object’s data.

Naming convention: getAttributeName() for most types, isAttributeName() for booleans.

public class Student {
    private String name;
    private int grade;
    private boolean graduated;

    // Accessor methods
    public String getName() { return name; }
    public int getGrade() { return grade; }
    public boolean isGraduated() { return graduated; }
}

Getters are simple – they just return the value. They do not change anything.

Mutator Methods (Setters)

A mutator method modifies the value of a private instance variable. It provides controlled write access – typically with validation to ensure only valid values are accepted.

Naming convention: setAttributeName(newValue) or a descriptive verb like deposit(), enrol().

public class Student {
    private String name;
    private int grade;
    private boolean graduated;

    // Constructor
    public Student(String name, int grade) {
        this.name = name;
        this.grade = grade;
        this.graduated = false;
    }

    // Mutator with validation
    public void setGrade(int grade) {
        if (grade >= 0 && grade <= 100) {
            this.grade = grade;
        }
    }

    // Mutator with logic
    public void graduate() {
        if (grade >= 50) {
            this.graduated = true;
        }
    }

    // Accessors
    public String getName() { return name; }
    public int getGrade() { return grade; }
    public boolean isGraduated() { return graduated; }
}

Not every attribute needs a setter. Some attributes should be read-only after construction – for example, a student’s name or ID should not change after the object is created. Only provide setters for attributes that legitimately need to be modified.

The Encapsulation Pattern

The standard pattern for encapsulated classes:

  1. Attributes are private – never public
  2. Getters are public – provide read access
  3. Setters are public with validation – provide controlled write access
  4. Constructor initialises all attributes, with validation if needed
public class Temperature {
    private double celsius;  // 1. private attribute

    public Temperature(double celsius) {
        if (celsius >= -273.15) {
            this.celsius = celsius;
        } else {
            this.celsius = -273.15;
        }
    }

    public double getCelsius() {       // 2. public getter
        return celsius;
    }

    public void setCelsius(double celsius) {  // 3. public setter with validation
        if (celsius >= -273.15) {
            this.celsius = celsius;
        }
    }

    public double toFahrenheit() {     // public method using the data
        return celsius * 9.0 / 5.0 + 32;
    }
}

What Happens Without Encapsulation

Consider a CircularBuffer class where the internal index must always be between 0 and capacity-1. Without encapsulation:

// Dangerous: public fields
buffer.index = 9999;  // way beyond capacity -- corrupts memory
buffer.index = -1;    // invalid -- causes ArrayIndexOutOfBoundsException

With encapsulation, the class protects itself:

public void advanceIndex() {
    index = (index + 1) % capacity;  // always stays in range
}

No external code can set index to an invalid value because index is private.


Worked Examples

Example 1: Designing an Encapsulated Class

Scenario: A Product class for an online store. Each product has a name, price, and stock quantity. Prices must be positive. Stock cannot go negative.

public class Product {
    private String name;
    private double price;
    private int stock;

    public Product(String name, double price, int stock) {
        this.name = name;
        this.price = (price > 0) ? price : 0.01;
        this.stock = (stock >= 0) ? stock : 0;
    }

    // Getters
    public String getName() { return name; }
    public double getPrice() { return price; }
    public int getStock() { return stock; }
    public boolean isInStock() { return stock > 0; }

    // Setters with validation
    public void setPrice(double price) {
        if (price > 0) {
            this.price = price;
        }
    }

    // Business methods
    public boolean sell(int quantity) {
        if (quantity > 0 && quantity <= stock) {
            stock = stock - quantity;
            return true;
        }
        return false;
    }

    public void restock(int quantity) {
        if (quantity > 0) {
            stock = stock + quantity;
        }
    }
}

What encapsulation prevents:

  • Setting price to 0 or negative
  • Setting stock to negative
  • Selling more items than available
  • Restocking with a negative amount
  • Directly modifying name after creation (no setter provided – read-only)

Example 2: Before and After

Without encapsulation (broken):

Product p = new Product();
p.price = -50.0;    // invalid price
p.stock = -10;      // impossible stock
// The object is now in an impossible state

With encapsulation (safe):

Product p = new Product("Widget", -50.0, -10);
// Constructor clamps: price becomes 0.01, stock becomes 0
System.out.println(p.getPrice());  // 0.01
System.out.println(p.getStock());  // 0

p.setPrice(-20.0);  // rejected -- price stays 0.01
p.sell(5);           // rejected -- not enough stock
// The object is always in a valid state

Quick Check

Q1. What is encapsulation?

Q2. Which of the following is NOT an advantage of encapsulation?

Q3. What does an accessor method do?

Q4. A Product has price 19.99. What happens when setPrice(-5) is called?

Q5. Should every private attribute have a public setter?


Spot the Error

This class compiles but violates encapsulation. Click the buggy line, then pick the fix.

1public class BankAccount { 2 private String owner; 3 public double balance; 4 public void deposit(double amount) { 5 if (amount > 0) { balance += amount; } 6 } 7}

Pick the fix:


Fill in the Blanks

Complete the encapsulated class:

public class Student {
     String name;
     int grade;

    public Student(String name, int grade) {
         = name;
         = grade;
    }

    // Accessor method
    public String () {
        return name;
    }

    // Mutator method with validation
    public void setGrade(int grade) {
        if (grade >= 0 && grade <= 100) {
             = grade;
        }
    }
}

Predict the Output

Using the Product class from Example 1:

Product p = new Product("Widget", 9.99, -5);
p.sell(3);
System.out.println(p.getStock());

What is printed?

What is printed?

Product p = new Product("Gadget", 29.99, 10);
p.sell(3);
p.sell(5);
p.sell(5);
System.out.println(p.getStock());

What is printed?


Practice Exercises

Core

  1. Explain why – A student writes public int age; in their Person class. Explain two problems this causes and how encapsulation solves each one.

  2. Write an encapsulated class – Create a BankAccount class with private owner (String) and balance (double). Include a constructor, getters, deposit(double), and withdraw(double) with validation (no negative amounts, cannot withdraw more than the balance). Test with several scenarios including invalid operations.

  3. Read-only attributes – Create a Student class where name and studentID have getters but NO setters (read-only after construction), while grade has both a getter and a validated setter (0-100).

Extension

  1. Encapsulation audit – The following class has three encapsulation violations. Find and fix all of them:
    public class Ticket {
        public String event;
        public double price;
        private boolean used;
        public void useTicket() { used = true; }
        public void setPrice(double p) { price = p; }
    }
    
  2. Design challenge – A Password class stores a hashed password. Design the class so that: the password can be set (with minimum length validation) but NEVER read back (no getter for the actual password). Provide a checkPassword(String attempt) method that returns true/false.

Challenge

  1. Full encapsulated system – Design a Library class that manages an array of Book objects. The library should encapsulate its book collection (private array) and provide methods to: add a book, remove by ISBN, check out (sets a flag), return (clears the flag), and count available books. No external code should be able to access the array directly or modify books without going through Library methods.

Connections

  • Prerequisites: The this Keywordthis enables the getter/setter pattern
  • Prerequisites: Constructors – constructors initialise private attributes
  • Next: Access Modifiers – deeper dive into private, public, protected
  • Related: UML Class Diagrams – - means private, + means public
  • Forward: Inheritance – encapsulated attributes are inherited but accessed through getters in child classes

Back to top

© EduCS.me — A resource hub for IB Computer Science

This site uses Just the Docs, a documentation theme for Jekyll.