Day 10: Access Modifiers and Encapsulation
Encapsulation is the principle of protecting an object’s internal data from the outside and allowing access only through designated methods. It’s like operating a car through the steering wheel and pedals instead of directly touching the engine. Java implements this through access modifiers.
The Four Access Modifiers
Let’s understand the scope of each access modifier.
// File: AccessModifierDemo.java
public class AccessModifierDemo {
public String publicField = "Accessible from anywhere";
protected String protectedField = "Same package + subclasses";
String defaultField = "Same package only"; // package-private
private String privateField = "This class only";
public void publicMethod() {
System.out.println("public method");
System.out.println(privateField); // Accessible from within the class
}
protected void protectedMethod() {
System.out.println("protected method");
}
void defaultMethod() {
System.out.println("default method");
}
private void privateMethod() {
System.out.println("private method");
}
// Access scope summary:
// public > protected > default > private
// Anywhere Same pkg+child Same pkg Same class
public static void main(String[] args) {
AccessModifierDemo demo = new AccessModifierDemo();
demo.publicMethod(); // OK
demo.protectedMethod(); // OK (same class)
demo.defaultMethod(); // OK (same class)
demo.privateMethod(); // OK (same class)
}
}
Encapsulation with Getters/Setters
Hide fields as private and provide controlled access through public methods.
public class User {
private String name;
private String email;
private int age;
private String password;
public User(String name, String email, int age) {
this.name = name;
setEmail(email);
setAge(age);
}
// Getter: retrieve value
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public int getAge() {
return age;
}
// Setter: set value (with validation)
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Name cannot be empty.");
}
this.name = name.trim();
}
public void setEmail(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Please enter a valid email address.");
}
this.email = email;
}
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150.");
}
this.age = age;
}
public void setPassword(String password) {
if (password.length() < 8) {
throw new IllegalArgumentException("Password must be at least 8 characters.");
}
this.password = password;
}
// No getter for password (intentionally preventing external exposure)
@Override
public String toString() {
return "User{name='" + name + "', email='" + email + "', age=" + age + "}";
}
public static void main(String[] args) {
User user = new User("Alice", "alice@example.com", 25);
System.out.println(user);
user.setAge(30);
System.out.println("Updated age: " + user.getAge());
// user.age = -5; // Compile error! (private)
// user.setAge(-5); // Runtime error! (validation)
}
}
Immutable Class
A safe class whose state cannot be changed once created.
public final class Money {
private final long amount;
private final String currency;
public Money(long amount, String currency) {
if (amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative.");
}
this.amount = amount;
this.currency = currency;
}
// Only getters (no setters)
public long getAmount() {
return amount;
}
public String getCurrency() {
return currency;
}
// Return new objects instead of modifying state
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currencies do not match.");
}
return new Money(this.amount + other.amount, this.currency);
}
public Money subtract(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currencies do not match.");
}
return new Money(this.amount - other.amount, this.currency);
}
@Override
public String toString() {
return amount + " " + currency;
}
public static void main(String[] args) {
Money price = new Money(50000, "KRW");
Money tax = new Money(5000, "KRW");
Money total = price.add(tax);
System.out.println("Price: " + price); // 50000 KRW
System.out.println("Tax: " + tax); // 5000 KRW
System.out.println("Total: " + total); // 55000 KRW
// The price object remains unchanged (immutable)
}
}
Record Classes (Java 16+)
A concise syntax for creating immutable data classes.
// record: automatically generates constructor, getters, equals, hashCode, toString
public record Product(String name, int price, String category) {
// Compact constructor: validation
public Product {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Product name is required.");
}
if (price < 0) {
throw new IllegalArgumentException("Price cannot be negative.");
}
}
// Additional methods can be defined
public int discountedPrice(int percent) {
return price - (price * percent / 100);
}
public String displayInfo() {
return String.format("[%s] %s - %,d won", category, name, price);
}
public static void main(String[] args) {
Product laptop = new Product("MacBook Pro", 2500000, "Electronics");
Product phone = new Product("Galaxy S24", 1200000, "Electronics");
// Getters use the field name directly (not getXxx)
System.out.println("Name: " + laptop.name());
System.out.println("Price: " + laptop.price());
System.out.println(laptop.displayInfo());
System.out.println("10% discount: " + laptop.discountedPrice(10) + " won");
// Auto-generated toString
System.out.println(phone);
// Auto-generated equals
Product another = new Product("MacBook Pro", 2500000, "Electronics");
System.out.println(laptop.equals(another)); // true
}
}
Today’s Exercises
-
Encapsulated Bank Account: Redesign a
BankAccountclass following encapsulation principles. The balance should not be directly accessible; deposits and withdrawals should only be possible through methods, and throw an exception if the balance is insufficient during withdrawal. -
Student Grade Management: Create a
StudentGradeclass. Manage the name and per-subject scores (array) as private, and provide methods to calculate the average, and retrieve the highest and lowest scores. Only allow scores in the 0-100 range. -
Using Records: Create an
Addressrecord that stores postal code, city/state, district, and detailed address, and returns the full address as a string via afullAddress()method. Add postal code format validation in the compact constructor.