🌱BeginnerObject-Oriented Programming

Encapsulation

Protect object data with private fields and expose it safely through public getters and setters.

Overview

Encapsulation is one of the four pillars of OOP. It means hiding internal state (fields) behind controlled access points (methods). Instead of allowing code anywhere to read or write an object's fields directly, you declare fields private and provide public getter and setter methods. This gives you two benefits: (1) you can validate data before storing it (e.g., reject a negative age), and (2) you can change the internal representation without breaking code that uses the class. Encapsulation is enforced with access modifiers: private (this class only), protected (this class and subclasses), and public (everywhere).

Key Points

  • •private fields can only be accessed from inside the class
  • •public getters (getName()) provide read access
  • •public setters (setName(String name)) provide write access with validation
  • •Keeping fields private prevents external code from putting objects in invalid states
  • •Encapsulation also lets you refactor internal implementation without changing the public API
  • •By convention: getter = getFieldName(), setter = setFieldName(type value)
  • •boolean getters often use "is": isActive(), isValid()

Examples

private fields with getters and setters

class Person {
    private String name;
    private int age;

    // Constructor
    Person(String name, int age) {
        this.name = name;
        setAge(age); // use setter so validation runs
    }

    // Getter
    public String getName() { return name; }

    // Setter with validation
    public void setName(String name) {
        if (name == null || name.isEmpty()) {
            System.out.println("Name cannot be empty.");
            return;
        }
        this.name = name;
    }

    public int getAge() { return age; }

    public void setAge(int age) {
        if (age < 0 || age > 150) {
            System.out.println("Invalid age: " + age);
            return;
        }
        this.age = age;
    }

    @Override
    public String toString() {
        return name + " (age " + age + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person("Alice", 28);
        System.out.println(p);

        p.setAge(-5);     // Invalid age: -5
        p.setAge(32);     // OK
        System.out.println(p); // Alice (age 32)

        // p.age = 200;   // compile error - age is private
    }
}

p.age = 200 would be a compile error. External code must go through setAge(), which validates the value.

Read-only field (no setter)

class Circle {
    private final double radius;  // can't change after construction

    Circle(double radius) {
        if (radius <= 0) throw new IllegalArgumentException("Radius must be positive");
        this.radius = radius;
    }

    public double getRadius() { return radius; }
    public double area() { return Math.PI * radius * radius; }
    public double circumference() { return 2 * Math.PI * radius; }
}

public class Main {
    public static void main(String[] args) {
        Circle c = new Circle(5.0);
        System.out.printf("Radius: %.1f%n", c.getRadius());
        System.out.printf("Area: %.2f%n", c.area());
        // c.radius = 10; // compile error
        // new Circle(-1); // throws IllegalArgumentException
    }
}

final + private + no setter = effectively immutable field. The class enforces that radius is always positive.

Computed property - getter that calculates

class Rectangle {
    private int width;
    private int height;

    Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() { return width; }
    public int getHeight() { return height; }

    // Computed - no corresponding field
    public int getArea() { return width * height; }
    public int getPerimeter() { return 2 * (width + height); }
    public boolean isSquare() { return width == height; }
}

public class Main {
    public static void main(String[] args) {
        Rectangle r = new Rectangle(4, 4);
        System.out.println("Area: " + r.getArea());
        System.out.println("Square? " + r.isSquare());
    }
}

Getters don't have to return stored fields - they can compute values on the fly. Callers don't need to know the difference.

Common Mistakes

  • Making setters that just assign without any validation - misses the point of encapsulation
  • Returning a mutable object from a getter (e.g., an array) - the caller can modify it, bypassing encapsulation; return a copy instead
  • Forgetting to use the setter in the constructor - if the constructor sets the field directly, validation in the setter is bypassed

Note

As a rule of thumb, make all fields private by default. Only add getters/setters for fields that genuinely need to be accessible, and only add setters if the field needs to change after construction.

Exercises

Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

What is the main purpose of making fields private?