Learn about encapsulation, access modifiers, defensive copying, and the final keyword in Java.
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.
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 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.
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); } }
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.
public class Person { private String name; private Listhobbies; // 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 in Java has different effects depending on where it's used:
public class MathConstants { // Constant (cannot be changed) public static final double PI = 3.14159265359; // Immutable object reference (reference cannot change) private final Listprimes; 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; } }