← Back to Home

Module 1: Encapsulation

Module Overview

Learn about encapsulation, access modifiers, defensive copying, and the final keyword in Java.

Learning Objectives

Key Concepts

Encapsulation

Encapsulation is the process of hiding implementation details and combining data and methods into a single unit (class). This protects internal state from external interference and allows implementation to change without affecting dependent code.

Benefits of Encapsulation:

  • Protects classes from misuse
  • Prevents unexpected behaviors
  • Allows internal representation to change without affecting dependents

Example:

public class Species {
    // Private fields (encapsulated data)
    private String name;
    private int population;
    private double yearlyGrowthRatePercentage;

    // Constructor validates data upon creation
    public Species(String name, int population, double yearlyGrowthRatePercentage) {
        this.name = name;
        if(population < 0) {
            throw new IllegalArgumentException("Population must be positive.");
        }
        this.population = population;
        this.yearlyGrowthRatePercentage = yearlyGrowthRatePercentage;
    }
    
    // Getter methods provide controlled access
    public String getName() {
        return name;
    }
    
    public int getPopulation() {
        return population;
    }
    
    public double getYearlyGrowthRatePercentage() {
        return yearlyGrowthRatePercentage;
    }
    
    // Controlled way to modify population
    public void setPopulation(int population) {
        if(population < 0) {
            throw new IllegalArgumentException("Population must be positive.");
        }
        this.population = population;
    }
}

Defensive Copying

Defensive copying creates copies of mutable objects when they enter or leave your class to prevent unintended modifications. This is essential when your class contains references to mutable objects (like arrays or collections) to maintain encapsulation.

When to use defensive copying:

  • When accepting mutable objects as constructor parameters
  • When returning mutable objects from getter methods

Example:

public class Student {
    private final String name;
    private final int[] scores; // Mutable array
    
    // Defensive copy in constructor
    public Student(String name, int[] scores) {
        this.name = name;
        // Create a defensive copy (don't store the reference directly)
        this.scores = (scores != null) ? Arrays.copyOf(scores, scores.length) : new int[0];
    }
    
    // Defensive copy in getter
    public int[] getScores() {
        // Return a copy, not the original reference
        return Arrays.copyOf(scores, scores.length);
    }
}

Copy Constructors

A copy constructor creates a new object with the same values as an existing object. It performs a deep copy, creating entirely new instances of mutable members rather than copying references.

Example:

public class Person {
    private String name;
    private List hobbies;
    
    // Regular constructor
    public Person(String name, List hobbies) {
        this.name = name;
        this.hobbies = new ArrayList<>(hobbies); // Defensive copy
    }
    
    // Copy constructor
    public Person(Person original) {
        this.name = original.name;
        this.hobbies = new ArrayList<>(original.hobbies); // Deep copy
    }
}

The final Keyword

The final keyword in Java has different effects depending on where it's used:

  • final variables: Cannot be reassigned after initialization
  • final methods: Cannot be overridden by subclasses
  • final classes: Cannot be extended (no subclasses allowed)

Example:

public class MathConstants {
    // Constant (cannot be changed)
    public static final double PI = 3.14159265359;
    
    // Immutable object reference (reference cannot change)
    private final List primes;
    
    public MathConstants() {
        // Initialize final field
        primes = new ArrayList<>();
        primes.add(2);
        primes.add(3);
        primes.add(5);
    }
    
    // Final method that cannot be overridden
    public final double calculateCircleArea(double radius) {
        return PI * radius * radius;
    }
}

Resources