Topic 6: Encapsulation in Java
📖 5 min read · 🎯 intermediate · 🧭 Prerequisites: android-building-blocks-i-activity, android-building-blocks-ii-services
Why this matters
Here's the thing — when you first start writing Java classes, it feels natural to just make everything public. Anyone can read it, anyone can change it. Easy. But then something breaks, and you have no idea who changed what, or when, or how. I've seen this trip up so many beginners. Encapsulation is the fix: you lock down your object's data so nothing can reach in and mess with it directly. You decide exactly what can be seen, and exactly how it gets changed. Once you feel this click, your code starts feeling much more in control.
What You'll Learn
- What encapsulation means and why it exists in Java
- How to declare private fields and expose them via public getter and setter methods
- How to apply validation logic inside setters to protect data integrity
- How to wire an encapsulated class into an Android
ActivityandTextView
The Analogy
Think of a bank vault. The gold inside is your class's data — valuable, sensitive, and definitely not something you want strangers touching directly. The vault door is the private keyword: it physically blocks direct access. The bank teller window is your getter and setter methods — a controlled interface where every transaction is logged, validated, and handled properly. You never hand a customer a shovel and say "help yourself"; instead, you process each request through the teller, who checks IDs, enforces limits, and keeps the ledger consistent. Encapsulation is that teller window, built right into your Java class.
Chapter 1: What Encapsulation Is
Encapsulation is the practice of bundling data (fields) and the methods that operate on that data into a single unit (a class), while simultaneously restricting direct external access to those fields. The two mechanisms that make this work in Java are:
- The
privateaccess modifier — prevents outside code from reading or writing a field directly. - Public getter and setter methods — provide a controlled, intentional interface for reading and modifying that field.
Without encapsulation, any code anywhere in the codebase can do:
user.age = -999; // nothing stops this
With encapsulation, that line won't compile, and your setter can enforce that age must be positive before the assignment ever happens.
Chapter 2: Building an Encapsulated User Class
Here is a complete User class that encapsulates name and age with private fields, a constructor, and public getters and setters.
public class User {
// Private fields — inaccessible from outside this class
private String name;
private int age;
// Constructor
public User(String name, int age) {
this.name = name;
this.age = age;
}
// Public getter for name
public String getName() {
return name;
}
// Public setter for name
public void setName(String name) {
this.name = name;
}
// Public getter for age
public int getAge() {
return age;
}
// Public setter for age — includes validation
public void setAge(int age) {
if (age > 0) {
this.age = age;
} else {
throw new IllegalArgumentException("Age must be positive");
}
}
}
Key points to notice:
nameandageareprivate— they cannot be accessed directly from outside theUserclass.getName()andgetAge()return the values without letting callers mutate them.setAge()validates the incoming value before accepting it; passing0or a negative number throws anIllegalArgumentExceptionimmediately, so invalid state can never be stored.setName()accepts any non-null String — no special guard needed here, but you could add one.
classDiagram
class User {
-String name
-int age
+User(name, age)
+getName() String
+setName(name)
+getAge() int
+setAge(age)
}
note for User "Private fields\nPublic interface"
Chapter 3: Using the Encapsulated Class in an Android Activity
Now let's wire the User class into a real Android Activity. The activity creates a User object, updates its fields through the setters, then displays the result in a TextView.
Activity code (MainActivity.java)
public class MainActivity extends AppCompatActivity {
private TextView userTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
userTextView = findViewById(R.id.user_text_view);
// Create a new User object via the constructor
User user = new User("Alice", 25);
// Update user details through the public setters
user.setName("Bob");
user.setAge(30);
// Read back through getters and display in the TextView
String userDetails = "Name: " + user.getName() + ", Age: " + user.getAge();
userTextView.setText(userDetails);
}
}
Layout XML (activity_main.xml)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/user_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="User Details"
android:textSize="18sp" />
</RelativeLayout>
When the app runs, the TextView in the center of the screen displays: Name: Bob, Age: 30 — the values set through the controlled setter interface, not by poking at fields directly.
Chapter 4: Benefits of Encapsulation
Three concrete advantages that encapsulation brings to every Android project:
-
Improved Security — Restricting direct access to a class's internals prevents unauthorized reads and accidental (or malicious) overwrites. Nobody can set
age = -1from another Activity. -
Enhanced Maintainability — Because all field access goes through a single choke point (the getter/setter), you can change the internal representation of a field — say, switching from
int ageto aLocalDate birthDate— without touching any code outside the class. The public interface stays the same. -
Increased Flexibility — Setters are the right place to add validation, logging, formatting, or event notifications. The
setAgeguard above is a simple example; in production you might also fire aLiveDataupdate or write to a database there.
🧪 Try It Yourself
Task: Add a third encapsulated field, email, to the User class. The setter must reject any string that doesn't contain an @ character by throwing an IllegalArgumentException. Then update MainActivity to set an email on your User and display it alongside the name and age in the TextView.
Success criterion: Running the app shows Name: Bob, Age: 30, Email: bob@example.com on screen. Passing "notanemail" to setEmail() should crash with the message "Invalid email address" — confirm this in Logcat by temporarily calling it with a bad value.
Starter snippet:
// Inside the User class
private String email;
public String getEmail() {
return email;
}
public void setEmail(String email) {
if (email != null && email.contains("@")) {
this.email = email;
} else {
throw new IllegalArgumentException("Invalid email address");
}
}
🔍 Checkpoint Quiz
Q1. What is the primary purpose of marking a class field private in Java?
A) To make the field available only within the same package
B) To prevent any code outside the class from accessing the field directly
C) To stop the field from being garbage-collected
D) To make the field read-only everywhere
Q2. Given the User class above, what happens when the following line executes?
user.setAge(-5);
A) age is set to -5 without error
B) age is set to 0 as a fallback
C) An IllegalArgumentException is thrown
D) A NullPointerException is thrown
Q3. A teammate writes this code in another class:
User u = new User("Priya", 28);
System.out.println(u.age); // direct field access
What is the result, and how do you fix it?
Q4. You need to log every time a user's name changes. Where in the encapsulated User class is the correct place to add that logging call, and why?
A1. B) — private restricts access to within the declaring class only. Package-private is the default (no modifier); private is stricter.
A2. C) — setAge checks if (age > 0) before assigning; -5 fails that check and the else branch throws IllegalArgumentException("Age must be positive").
A3. The line fails to compile with "age has private access in User." Fix it by accessing the field through the getter: System.out.println(u.getAge());
A4. Inside setName(), because that method is the single controlled entry point for all name changes. Any code that changes the name must go through setName, so a log statement there is guaranteed to fire every time — unlike placing it at every call site, which is error-prone and easy to miss.
🪞 Recap
- Encapsulation bundles fields and methods into one class and hides fields behind
privateto prevent uncontrolled access. - Public getter methods expose field values for reading; public setter methods expose them for writing — through a controlled interface.
- Setters are where you enforce validation rules, such as rejecting negative ages or malformed email addresses.
- Using an encapsulated class in an Android
Activitymeans creating objects via constructors and reading/writing through getters and setters, never touching fields directly. - The payoff is code that is more secure, easier to maintain, and easier to extend without ripple-effect bugs.
📚 Further Reading
- Oracle Java Tutorial — Controlling Access to Members of a Class — the source of truth on Java access modifiers
- Effective Java, 3rd Edition — Item 15: Minimize the accessibility of classes and members — Bloch's canonical argument for why encapsulation discipline pays off at scale
- Android Developer Guide — Activities — how Activities interact with the Java classes you encapsulate
- ⬅️ Previous: Android Building Blocks II: Services
- ➡️ Next: Android Building Blocks III: Broadcast Receivers