Java의 접근 제어자 (Access Modifier) 정리

StrayCat·2026년 2월 14일

1. 접근 제어자란

클래스, 필드, 메서드, 생성자에 대한 접근 범위를 제한하는 키워드다. 객체지향의 핵심 원칙인 캡슐화(Encapsulation)을 구현하는 도구이며, 외부에 공개할 것과 내부에 숨길 것을 명확히 구분하기 위해 사용된다.


2. 4가지 접근 제어자

접근 제어자같은 클래스같은 패키지자식 클래스(상속)외부 전체
privateOXXX
(default)OOXX
protectedOOOX
publicOOOO

좁은 순서 : private -> default -> protected -> public


3. 각 제어자 상세 설명

3-1.private - 나만 쓸 것

  • 같은 클래스 내부에서만 접근 가능하다.
public class Member {

    private String name;   // 외부에서 직접 접근 불가
    private int age;

    // 외부에서는 getter/setter를 통해서만 접근
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // 내부 로직용 메서드 — 외부에 노출할 필요 없음
    private boolean isAdult() {
        return this.age >= 18;
    }
}
Member member = new Member();
member.name = "John";       // ❌ 컴파일 에러 -> private 필드이므로
member.getName();           // ✅ OK -> public 메서드 이므로 (getter)
member.isAdult();           // ❌ 컴파일 에러 -> private 메서드 이므로

사용 시점 : 필드값은 항상 private 으로 선언하고, 외부에 공개할 필요 없는 내부 헬퍼 메서드에도 사용한다.

3-2. default(패키지 private) - 같은 패키지끼리만 공유

접근 제어자를 작성하지 않으면 default가 적용된다. 같은 패키지 내에서만 접근 가능.

// com.example.domain 패키지
class OrderValidator {  // default 클래스 — 같은 패키지에서만 사용 가능

    boolean isValid(Order order) {  // default 메서드
        return order.getAmount() > 0;
    }
}
// com.example.domain 패키지 → ✅ 접근 가능
OrderValidator validator = new OrderValidator();

// com.example.controller 패키지 → ❌ 접근 불가
OrderValidator validator = new OrderValidator();

사용 시점 : 패키지 내부에서만 쓰이는 유틸리티 클래스, 헬퍼 클래스 등에 사용한다. 실무에서는 의도적으로 쓰는 경우는 많지 않다.

3-3. protected - 상속까지만 허용

같은 패키지 + 다른 패키지라도 상속 관계이면 접근 가능하다.

// com.example.domain 패키지
public class BaseEntity {

    protected LocalDateTime createdAt;
    protected LocalDateTime updatedAt;

    protected void updateTimestamp() {
        this.updatedAt = LocalDateTime.now();
    }
}
// com.example.order 패키지 (다른 패키지)
public class Order extends BaseEntity {

    public void changeStatus(OrderStatus status) {
        this.status = status;
        updateTimestamp();  // ✅ 상속받았으므로 접근 가능
    }
}
// com.example.service 패키지 (상속 관계 아님)
BaseEntity entity = new BaseEntity();
entity.updateTimestamp();  // ❌ 다른 패키지 + 상속 아님 → 접근 불가

사용 시점 : JPA 엔티티의 기본 생성자, 상속용 부모 클래스의 메서드/필드값, 템플릿 메서드 패턴 등에 사용

3-4. public : 전체 공개

어디서든 접근 가능

public class MemberService {

    public Member findById(Long id) {  // 외부에서 자유롭게 호출 가능
        return memberRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("회원 없음"));
    }
}

사용 시점 : 외부에 공개하는 API(Controller, Service의 공개 메서드), 엔티티 클래스 선언 등.


4. 예제 - 접근 제어자를 종합 적용한 엔티티

public class Member {  // public: 어디서든 사용하는 엔티티

    private Long id;           // private: 외부 직접 접근 차단
    private String name;
    private String email;
    private int loginFailCount;

    protected Member() {}      // protected: JPA 리플렉션 + 프록시용

    public Member(String name, String email) {  // public: 객체 생성 API
        this.name = name;
        this.email = email;
        this.loginFailCount = 0;
    }

    // public: 외부에 공개하는 비즈니스 메서드
    public void loginFailed() {
        this.loginFailCount++;
        if (isBlocked()) {
            throw new IllegalStateException("계정이 잠겼습니다");
        }
    }

    // private: 내부 판단 로직 — 외부에서 알 필요 없음
    private boolean isBlocked() {
        return this.loginFailCount >= 5;
    }

    // public: getter — 외부에서 값 조회용
    public String getName() { return name; }
    public String getEmail() { return email; }
}

5. 클래스에 사용할 수 있는 접근 제어자

  • 최상위(top-level) 클래스에는 publicdefault만 사용 가능하다.
public class Member { }    // ✅ 파일명과 같아야 함 (Member.java)
class OrderValidator { }   // ✅ default — 같은 패키지에서만 사용
private class Foo { }      // ❌ 최상위 클래스에 private 불가
protected class Bar { }    // ❌ 최상위 클래스에 protected 불가
  • 내부 클래스 (inner class)에는 4가지 모두 사용 가능
public class Outer {
    private class Inner { }      // ✅
    protected class Inner2 { }   // ✅
}

6. 접근 제어자 설계 원칙

가능한 가장 좁은 범위를 사용 하는 것이 핵심 원칙

필드       → private (거의 예외 없음)
생성자     → 용도에 따라 선택 (JPA용은 protected, 팩토리 패턴이면 private)
메서드     → 외부 공개 API만 public, 나머지는 private
클래스     → 외부 노출 필요 시 public, 내부용이면 default

넓은 접근 범위를 할당하게 되면 차후 줄이기 어려움. 처음에는 좁게 시작하고, 필요시 별도로 넓히는 것이 안전함. 이 원칙은 SpringBoot에서 Layer Architecture를 설계할 때도 동일하게 적용된다.


참고 자료

  • Oracle Java Tutorial - Controlling Access to Members of a Class
  • Joshua Bloch, 『Effective Java』 Item 15: 클래스와 멤버의 접근 권한을 최소화하라

profile
알면 좋은 것보단 잊어버리기 싫은 것들을 기록합니다.

0개의 댓글