전체 목록
JavaEasy#55

Java의 접근 제어자(Access Modifier) 4가지를 설명해주세요.

#Java#접근제어자#캡슐화#OOP
힌트

public, protected, default, private의 접근 범위 차이를 생각해보세요.

정답 및 해설

Java의 접근 제어자(Access Modifier) 4가지를 설명해주세요.

접근 제어자(Access Modifier)는 클래스, 메서드, 변수 등의 멤버에 대한 접근 범위를 제한하는 키워드입니다. 캡슐화(Encapsulation)의 핵심 도구로, 외부에 노출할 것과 숨길 것을 구분합니다.

4가지 접근 제어자

1. private

같은 클래스 내에서만 접근 가능합니다. 캡슐화의 기본 원칙을 구현하는 가장 강한 접근 제한입니다.

public class BankAccount {
    private double balance; // 외부에서 직접 접근 불가

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount; // 같은 클래스 내부에서는 접근 가능
        }
    }

    public double getBalance() {
        return balance; // public getter로만 노출
    }
}

BankAccount account = new BankAccount();
// account.balance = 1000; // 컴파일 오류!
account.deposit(1000);      // OK

2. default (package-private)

같은 패키지 내에서만 접근 가능합니다. 키워드 없이 선언하며, 명시적인 접근 제어자를 붙이지 않으면 자동으로 적용됩니다.

// com.example.service 패키지
class UserValidator {         // default 접근 제어자
    boolean isValid(String email) { // default 메서드
        return email.contains("@");
    }
}

// 같은 패키지 내에서 접근 가능
class UserService {
    private UserValidator validator = new UserValidator(); // OK

    public void register(String email) {
        if (validator.isValid(email)) { // OK — 같은 패키지
            System.out.println("등록 성공");
        }
    }
}

// com.example.controller 패키지 (다른 패키지)
class UserController {
    // UserValidator validator = new UserValidator(); // 컴파일 오류!
}

3. protected

같은 패키지 + 다른 패키지의 자식 클래스에서 접근 가능합니다. 상속 관계에서 부모 클래스의 구현을 자식 클래스에 선택적으로 노출할 때 사용합니다.

// com.example.base 패키지
public class Shape {
    protected String color;      // 자식 클래스에서 접근 가능
    protected double area;

    protected void draw() {      // 자식 클래스에서 오버라이드 가능
        System.out.println(color + " 도형을 그립니다.");
    }
}

// com.example.shapes 패키지 (다른 패키지, Shape 상속)
public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
        this.color = "빨간색"; // protected 필드 접근 가능 (상속 관계)
    }

    @Override
    protected void draw() {
        System.out.println(color + " 원을 그립니다."); // OK
    }
}

// com.example.other 패키지 (다른 패키지, 상속 관계 아님)
public class OtherClass {
    public void test() {
        Shape shape = new Shape();
        // shape.color = "파란색"; // 컴파일 오류! (상속 관계가 아닌 직접 접근)
    }
}

4. public

어디서든 접근 가능합니다. 패키지나 상속 여부에 관계없이 모든 클래스에서 접근할 수 있습니다.

public class Calculator {
    public int add(int a, int b) {     // 어디서든 접근 가능
        return a + b;
    }

    public int subtract(int a, int b) {
        return a - b;
    }
}

// 어떤 패키지, 어떤 클래스에서든 사용 가능
Calculator calc = new Calculator();
calc.add(10, 5);      // OK
calc.subtract(10, 5); // OK

접근 범위 비교

접근 제어자같은 클래스같은 패키지자식 클래스외부
privateOXXX
defaultOOXX
protectedOOOX
publicOOOO

접근 범위: private < default < protected < public

클래스에 적용되는 접근 제어자

최상위(top-level) 클래스에는 publicdefault만 사용할 수 있습니다. private이나 protected는 중첩 클래스(inner class)에만 사용 가능합니다.

public class OuterClass {          // public 또는 default만 가능
    private class PrivateInner {}  // 중첩 클래스는 private 가능
    protected class ProtectedInner {} // 중첩 클래스는 protected 가능
    public class PublicInner {}
    class DefaultInner {}
}

최소 권한 원칙

실무에서는 가능한 한 좁은 접근 제어자를 사용하는 것을 권장합니다. 이 원칙은 캡슐화를 강화하고 코드의 변경 범위를 제한합니다.

public class UserService {
    // 외부에 공개할 필요 없는 것은 private
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    // 같은 패키지에서만 사용하는 유틸성 메서드는 default
    boolean isEmailFormat(String email) {
        return email.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$");
    }

    // 상속 계층에서만 공유할 메서드는 protected
    protected User createUser(String email, String password) {
        String encoded = passwordEncoder.encode(password);
        return new User(email, encoded);
    }

    // 외부에 실제로 공개할 API만 public
    public void register(String email, String password) {
        if (!isEmailFormat(email)) {
            throw new IllegalArgumentException("유효하지 않은 이메일 형식입니다.");
        }
        userRepository.save(createUser(email, password));
    }
}

실무 권장 패턴

public class Product {
    // 1. 필드는 항상 private
    private Long id;
    private String name;
    private BigDecimal price;

    // 2. 외부에 공개할 접근자는 public
    public Long getId() { return id; }
    public String getName() { return name; }

    // 3. 내부 검증 로직은 private
    private boolean isValidPrice(BigDecimal price) {
        return price != null && price.compareTo(BigDecimal.ZERO) > 0;
    }

    // 4. 상태 변경은 의미 있는 public 메서드로만 허용
    public void updatePrice(BigDecimal newPrice) {
        if (!isValidPrice(newPrice)) {
            throw new IllegalArgumentException("가격은 0보다 커야 합니다.");
        }
        this.price = newPrice;
    }
}