오늘은 자바에서 가장 많이 헷갈렸던 접근제어자를 정리해 보려고 한다.
접근 제어자는 클래스, 메서드, 필드에 대한 접근 범위를 제한하는 키워드다.
캡슐화(Encapsulation)를 구현하는 핵심 도구이며, 외부로부터 내부 구현을 보호한다.
| 접근 제어자 | 같은 클래스 | 같은 패키지 | 자식 클래스 (다른 패키지) | 전체 |
|---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ |
default | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ |
접근 범위:
private<default<protected<public
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 "빈 문자열은 입력할 수 없습니다. 이름을 다시 입력해 주세요.";
}
}
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;
}
}
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 + "이(가) 짖는다.");
}
}
.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 "빈 문자열은 입력할 수 없습니다. 이름을 다시 입력해 주세요.";
}
}
"최소한의 접근 권한을 부여하라" (Principle of Least Privilege)
private: 기본값으로 생각하자. 클래스 필드는 특별한 이유가 없으면 privatedefault: 패키지 내부 구현 숨기기. 외부에 굳이 노출 안 해도 될 때protected: 상속 설계 시. 자식 클래스에만 열어줄 때public: 진짜 외부에 공개해야 할 API에만