[Java] 자바 접근제어자 정리 (private, default, protected, public)

지현·2026년 4월 21일

오늘은 자바에서 가장 많이 헷갈렸던 접근제어자를 정리해 보려고 한다.
접근 제어자는 클래스, 메서드, 필드에 대한 접근 범위를 제한하는 키워드다.
캡슐화(Encapsulation)를 구현하는 핵심 도구이며, 외부로부터 내부 구현을 보호한다.


💊 접근 제어자 종류 및 범위

접근 제어자같은 클래스같은 패키지자식 클래스 (다른 패키지)전체
private
default
protected
public

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


🔒 private

특징

  • 같은 클래스 내부에서만 접근 가능
  • 외부에서 직접 접근 불가 → getter / setter로 간접 접근
  • 캡슐화의 핵심, 가장 강력한 접근 제한

예시 코드

public class AccessModifier {
public class AccessModifier {
    public static void main(String[] args) {
        Person p = new Person();
        // p.name = "홍길동";  ❌ 컴파일 에러!
        System.out.println(p.setName("")); // ✅ setter로 접근
        System.out.println(p.getName()); // ✅ getter로 접근

    }
}

class Person {
    private String name;  // 외부에서 직접 접근 불가

    // getter로 간접 접근 허용
    public String getName() {
        return this.name;
    }

    // setter에서 해당 데이터 수정
    public String setName(String name) {
        if (name != null && name != "" && name.length() > 0) {
            this.name = name;
            return this.name;
        }
        return "빈 문자열은 입력할 수 없습니다. 이름을 다시 입력해 주세요.";
    }
}

주로 사용하는 경우

  • 클래스의 필드(멤버 변수) 거의 대부분에 사용
  • 외부에 노출할 필요 없는 내부 로직 메서드에 사용
  • 데이터 유효성 검사가 필요한 경우 setter와 함께 사용

📦 default (package-private)

특징

  • 접근 제어자를 아무것도 명시하지 않은 상태
  • 같은 패키지 내에서만 접근 가능
  • 다른 패키지에서는 import 해도 접근 불가

예시 코드

실제로는 아래처럼 패키지가 나뉘어 있을 때 동작한다.

📁 com.example.service
    ├── UserService.java      ← 같은 패키지
    └── UserRepository.java  ← 같은 패키지

📁 com.example.controller
    └── UserController.java  ← 다른 패키지 → UserRepository 접근 불가

같은 파일(같은 패키지)에서 default로 선언된 클래스와 메서드에 접근하는 예시다.

public class AccessModifier {
    public static void main(String[] args) {
        UserRepository repo = new UserRepository();  // ✅ 같은 패키지라 접근 가능
        System.out.println(repo.findById(1));        // ✅ "User-1" 출력

        // 다른 패키지에서 아래처럼 접근하면 컴파일 에러!
        // import com.example.service.UserRepository;
        // UserRepository repo = new UserRepository();  ❌
    }
}

class UserRepository {        // default 클래스 (접근 제어자 없음)
    String findById(int id) { // default 메서드 (접근 제어자 없음)
        return "User-" + id;
    }
}

주로 사용하는 경우

  • 패키지 내부에서만 사용할 유틸리티 클래스
  • 외부에 노출하고 싶지 않은 패키지 내부 구현체
  • 같은 패키지 내 클래스들이 협력할 때

🛡️ protected

특징

  • 같은 패키지 + 다른 패키지의 자식 클래스(상속)에서 접근 가능
  • 상속 관계에서 부모의 기능을 자식이 사용/오버라이딩할 수 있게 열어줌
  • public보다는 제한적, 상속을 의도한 설계에서 사용

예시 코드

public class AccessModifier {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.bark();
        // 출력:
        // 강아지이(가) 숨을 쉰다.
        // 강아지이(가) 짖는다.

        Animal animal = new Animal();
        // animal.name = "고양이";  ❌ 상속받은 자식이 아니므로 외부에서 직접 접근 불가
        // animal.breathe();       ❌ 상속받은 자식이 아니므로 외부에서 직접 접근 불가
    }
}

class Animal {
    protected String name;  // 자식 클래스에서 접근 가능

    protected void breathe() {  // 자식 클래스에서 오버라이딩 가능
        System.out.println(name + "이(가) 숨을 쉰다.");
    }
}

class Dog extends Animal {  // Animal을 상속받은 자식 클래스
    public void bark() {
        this.name = "강아지";  // ✅ protected 필드 접근 가능 (상속)
        breathe();             // ✅ protected 메서드 호출 가능 (상속)
        System.out.println(name + "이(가) 짖는다.");
    }
}

주로 사용하는 경우

  • 상속을 설계할 때 자식 클래스에서만 접근 가능하도록 열어줄 때
  • 템플릿 메서드 패턴에서 자식이 오버라이딩할 메서드
  • 부모 클래스의 핵심 필드를 자식이 직접 사용해야 할 때

🌍 public

특징

  • 어디서든 접근 가능 (패키지, 상속 관계 무관)
  • API의 진입점, 외부에 공개할 기능에 사용
  • 하나의 .java 파일에 public 클래스는 1개만 가능하며 파일명과 일치해야 함

예시 코드

public class AccessModifier {
    public static void main(String[] args) {
        Person p = new Person();
        p.name = "홍길동";  // 내부 변수 수정 가능
        System.out.println(p.name); // 내부 변수 바로 조회 가능

    }
}

class Person {
    public String name;  // 외부에서 직접 접근 불가
    
    // getter로 간접 접근 허용
    public String getName() {
        return this.name;
    }

    // setter에서 해당 데이터 수정
    public String setName(String name) {
        if (name != null && name != "" && name.length() > 0) {
            this.name = name;
            return this.name;
        }
        return "빈 문자열은 입력할 수 없습니다. 이름을 다시 입력해 주세요.";
    }
}

주로 사용하는 경우

  • 외부에 공개하는 API, 라이브러리의 진입점
  • 다른 패키지에서도 사용해야 하는 공통 유틸리티 메서드
  • 클래스 자체를 외부에서 사용해야 할 때

🔑 핵심 요약

"최소한의 접근 권한을 부여하라" (Principle of Least Privilege)

  1. private: 기본값으로 생각하자. 클래스 필드는 특별한 이유가 없으면 private
  2. default: 패키지 내부 구현 숨기기. 외부에 굳이 노출 안 해도 될 때
  3. protected: 상속 설계 시. 자식 클래스에만 열어줄 때
  4. public: 진짜 외부에 공개해야 할 API에만
    접근 제어자를 제대로 활용하면 코드 변경의 파급 효과를 최소화하고, 유지보수성이 크게 향상된다.

0개의 댓글