Introduction: "Why not just make the fields public?"
\n\nImagine a situation: you are writing a Person class in Java. It has a name, age, and email. The simplest way is to declare the fields as public and access them directly:
public class Person {\n public String name;\n public int age;\n public String email;\n}\n\n"It works! — you might say. — Why do I need these getters and setters? Extra code, lines are bloated..."
\n\nThis question is one of the most popular on Stack Overflow. Thousands of developers, especially beginners, ask it. The answer you will get from an experienced Java programmer will be short: encapsulation. But behind this word lies a whole design philosophy that saves projects from chaos.
\n\nIn this article, we will not just give a definition. We will break down the real problems that getters and setters (also called accessors and mutators) solve, and demonstrate this with live code. Let's go!
\n\n1. Encapsulation: Not Just a Buzzword
\n\nEncapsulation is an OOP principle that states: hide the internal state of an object and provide a controlled interface for interacting with it. Getters and setters are exactly that controlled interface.
\n\nCompare the two approaches:
\n\nBad Approach (public fields)
\nPerson person = new Person();\nperson.age = -5; // Negative age? Easy!\nperson.email = "not-an-email"; // Garbage data passes without validation\n\nGood Approach (getters and setters)
\npublic class Person {\n private String name;\n private int age;\n private String email;\n\n public int getAge() {\n return age;\n }\n\n public void setAge(int age) {\n if (age < 0 || age > 150) {\n throw new IllegalArgumentException("Age must be between 0 and 150");\n }\n this.age = age;\n }\n\n public String getEmail() {\n return email;\n }\n\n public void setEmail(String email) {\n if (email == null || !email.contains("@")) {\n throw new IllegalArgumentException("Invalid email");\n }\n this.email = email;\n }\n\n // other getters/setters...\n}\n\nNow, if someone tries to set the age to -5, the program will throw an exception. The data is protected. That is the power of encapsulation.
\n\n2. Data Validation on the Fly
\n\nSetters are the perfect place to validate input data. Without them, you would have to write validation in every place where you assign a value. That leads to code duplication and the risk of forgetting to check somewhere.
\n\nExample with a bank account:
\n\npublic class BankAccount {\n private double balance;\n\n public double getBalance() {\n return balance;\n }\n\n public void deposit(double amount) {\n if (amount <= 0) {\n throw new IllegalArgumentException("Deposit amount must be positive");\n }\n this.balance += amount;\n }\n\n public void withdraw(double amount) {\n if (amount <= 0) {\n throw new IllegalArgumentException("Withdrawal amount must be positive");\n }\n if (amount > balance) {\n throw new IllegalStateException("Insufficient funds");\n }\n this.balance -= amount;\n }\n}\n\nNotice: there is no setter for balance directly. We manage the balance through the deposit and withdraw methods, which already contain all the validation logic. The getter simply returns the current state.
3. Flexibility: You Can Change the Implementation Without Breaking Code
\n\nImagine you store age in an int field, and then decide to store the date of birth and calculate the age dynamically. If you had