멤버(필드, 메서드, 생성자) 또는 클래스에 대한 접근 범위를 컴파일 타임에 제한하는 키워드.
객체지향의 캡슐화(Encapsulation) 핵심은 "외부에서 직접 건드리면 안 되는 것을 막는 것"이다. 접근 제한자가 그 수단이다.
| 제한자 | 같은 클래스 | 같은 패키지 | 자식 클래스 | 전체 |
|---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | ✗ |
(default) | ✓ | ✓ | ✗ | ✗ |
private | ✓ | ✗ | ✗ | ✗ |
(default)는 키워드를 아무것도 붙이지 않은 상태로, package-private 이라고도 부른다.
publicpublic class Digimon {
public String name;
}
어디서든 접근 가능하다. 제한이 없다.
privatepublic class Digimon {
private int hp;
public int getHp() { return hp; }
public void setHp(int hp) { this.hp = hp; }
}
같은 클래스 내부에서만 접근 가능하다. 외부에서 digimon.hp 직접 접근은 불가능하다.
필드를 private으로 막고 getter/setter로 노출하는 것이 캡슐화의 기본 패턴이다.
default (package-private)// com.digimon 패키지
class DigimonHelper { // public 없음 → default
void help() { } // public 없음 → default
}
같은 패키지 내에서만 접근 가능하다. 다른 패키지에서는 클래스 자체가 보이지 않는다.
// com.other 패키지
import com.digimon.DigimonHelper; // 컴파일 에러 — 접근 불가
protecteddefault에서 자식 클래스(상속) 접근만 추가된 것이다.
// com.digimon 패키지
public class Digimon {
protected int level;
}
// com.other 패키지
public class Agumon extends Digimon {
public void printLevel() {
System.out.println(level); // 가능 — 자식 클래스이므로
}
}
// com.other 패키지
public class Other {
public void test(Digimon d) {
System.out.println(d.level); // 컴파일 에러 — 자식 클래스가 아님
}
}
protected는 "자식 클래스면 패키지가 달라도 된다" 는 의미다.
Agumon이 Digimon을 상속받으면, level은 Agumon 객체 안에 포함된다.
[ Agumon 인스턴스 ]
└── level = 10 (Digimon에서 상속받아 내 것이 된 필드)
[ Digimon 인스턴스 ]
└── level = 10 (Digimon 자신의 필드)
둘은 완전히 별개의 객체다.
// com.other 패키지, Agumon 안에서
public class Agumon extends Digimon {
public void test() {
Digimon d = new Digimon();
System.out.println(d.level); // 컴파일 에러
}
}
Agumon이 level을 상속받았다는 건 Agumon 객체 안의 level에 접근할 수 있다는 것이지, Digimon 인스턴스의 level에 접근할 수 있다는 게 아니다.
com.other에서 Digimon 인스턴스의 protected 멤버를 직접 찍는 건, 상속과 무관하게 다른 패키지에서 외부 접근하는 것과 동일하게 취급된다.
protected는 "상속받은 내 것"에 접근하는 것이지, "부모 인스턴스의 것"에 접근하는 게 아니다.
public class Agumon extends Digimon {
public void test() {
System.out.println(level); // 가능 — 자기 자신의 level
System.out.println(this.level); // 가능 — 명시적으로 자기 자신의 level
Agumon a = new Agumon();
System.out.println(a.level); // 가능 — Agumon 타입이므로
Digimon d = new Digimon();
System.out.println(d.level); // 불가 — 부모 인스턴스를 외부에서 직접 접근
}
}
클래스 접근 권한과 멤버 접근 권한은 별개다. Digimon이 public 클래스라 인스턴스 생성은 가능하지만, protected 멤버에 대한 접근 권한은 별도로 적용된다.
top-level 클래스(파일 최상위에 선언된 클래스, 어떤 클래스 안에도 속해있지 않은 클래스) 에는 public과 default만 쓸 수 있다.
public class Agumon { } // 가능
class Agumon { } // 가능 (default)
private class Agumon { } // 컴파일 에러
protected class Agumon { } // 컴파일 에러
private/protected는 상위 구조 안에서의 관계를 정의하는 것인데, top-level 클래스는 상위 구조가 없어서 의미 자체가 성립하지 않는다.
단, 중첩 클래스(nested class) 는 4가지 전부 쓸 수 있다. 클래스 안에 있어서 상위 구조가 존재하기 때문이다.
public class Digimon {
private int hp = 100;
public class Inner {
public void show() {
System.out.println(hp); // 가능 — 같은 클래스 안이므로
}
}
}
오버라이딩은 메서드에만 적용되는 개념이다. 필드에서 같은 이름을 선언하면 오버라이딩이 아니라 필드 숨김(Field Hiding) 이 발생한다.
public class Digimon {
protected int level = 10;
}
public class Agumon extends Digimon {
int level = 99; // 오버라이딩 X — 새로운 필드 선언
}
이 상태에서 Agumon 인스턴스 안에는 level이 두 개 존재한다.
[ Agumon 인스턴스 ]
├── level = 10 (Digimon에서 상속받은 것)
└── level = 99 (Agumon이 새로 선언한 것)
Agumon a = new Agumon();
System.out.println(a.level); // 99 — Agumon의 level
System.out.println(((Digimon)a).level); // 10 — Digimon의 level
| 메서드 | 필드 | |
|---|---|---|
| 같은 이름 재선언 | 오버라이딩 | 필드 숨김(Field Hiding) |
| 런타임 다형성 적용 | O | X |
| 부모 것 접근 | super.method() | super.field |
가능한 한 가장 좁은 범위로 선언하고, 필요할 때만 넓혀라.
public class Digimon {
private String name;
private int level;
public Digimon(String name, int level) {
this.name = name;
this.level = level;
}
public String getName() { return name; } // 읽기만 허용
// setter 없음 → 불변 필드로 관리
}
setter를 만들지 않으면 외부에서 값을 바꿀 수 없다. 의도적인 설계다.
| 제한자 | 범위 | 주요 용도 |
|---|---|---|
public | 전체 | API, 외부에 공개할 것 |
protected | 패키지 + 자식 클래스 | 상속 계층에서 내부 공유 |
default | 같은 패키지 | 패키지 내부 구현 |
private | 같은 클래스 | 캡슐화, 내부 구현 은닉 |