클래스, 필드, 메서드, 생성자에 대한 접근 범위를 제한하는 키워드다. 객체지향의 핵심 원칙인 캡슐화(Encapsulation)을 구현하는 도구이며, 외부에 공개할 것과 내부에 숨길 것을 명확히 구분하기 위해 사용된다.
| 접근 제어자 | 같은 클래스 | 같은 패키지 | 자식 클래스(상속) | 외부 전체 |
|---|---|---|---|---|
private | O | X | X | X |
(default) | O | O | X | X |
protected | O | O | O | X |
public | O | O | O | O |
좁은 순서 : private -> default -> protected -> public
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 으로 선언하고, 외부에 공개할 필요 없는 내부 헬퍼 메서드에도 사용한다.
접근 제어자를 작성하지 않으면 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();
사용 시점 : 패키지 내부에서만 쓰이는 유틸리티 클래스, 헬퍼 클래스 등에 사용한다. 실무에서는 의도적으로 쓰는 경우는 많지 않다.
같은 패키지 + 다른 패키지라도 상속 관계이면 접근 가능하다.
// 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 엔티티의 기본 생성자, 상속용 부모 클래스의 메서드/필드값, 템플릿 메서드 패턴 등에 사용
어디서든 접근 가능
public class MemberService {
public Member findById(Long id) { // 외부에서 자유롭게 호출 가능
return memberRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("회원 없음"));
}
}
사용 시점 : 외부에 공개하는 API(Controller, Service의 공개 메서드), 엔티티 클래스 선언 등.
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; }
}
public과 default만 사용 가능하다.public class Member { } // ✅ 파일명과 같아야 함 (Member.java)
class OrderValidator { } // ✅ default — 같은 패키지에서만 사용
private class Foo { } // ❌ 최상위 클래스에 private 불가
protected class Bar { } // ❌ 최상위 클래스에 protected 불가
public class Outer {
private class Inner { } // ✅
protected class Inner2 { } // ✅
}
가능한 가장 좁은 범위를 사용 하는 것이 핵심 원칙
필드 → private (거의 예외 없음)
생성자 → 용도에 따라 선택 (JPA용은 protected, 팩토리 패턴이면 private)
메서드 → 외부 공개 API만 public, 나머지는 private
클래스 → 외부 노출 필요 시 public, 내부용이면 default
넓은 접근 범위를 할당하게 되면 차후 줄이기 어려움. 처음에는 좁게 시작하고, 필요시 별도로 넓히는 것이 안전함. 이 원칙은 SpringBoot에서 Layer Architecture를 설계할 때도 동일하게 적용된다.