건물의 문마다 누가 들어올 수 있는지가 다르다. 정문은 누구나, 사무실은 같은 회사 사람만, 금고는 주인만 연다. 클래스와 그 안의 값·동작에도 이런 문패를 붙여서, 중요한 것을 바깥 코드가 함부로 건드리지 못하게 한다. 값을 금고에 넣어 두고 창구를 통해서만 넣고 꺼내게 하면, 창구에서 이상한 값을 걸러 낼 수 있다. 문패를 아무것도 안 붙이면 “누구나”가 아니라 “같은 묶음 안에서만”이 되므로, 다른 묶음에서 쓰려던 코드가 막힌다.
예시로 보기
건설로봇의 체력은 0에서 45 사이여야 한다. 체력 필드를 그냥 두면 어느 코드나 scv.health = -10;이라고 쓸 수 있다. 필드를 private로 감추고, 값을 넣는 메서드에서 검사하게 한다.
class Scv {
private int health = 45; // 클래스 밖에서는 못 건드린다
private String name;
public int getHealth() { // 읽는 창구
return health;
}
public void setHealth(int health) { // 넣는 창구: 값을 검사한다
if (health < 0) {
this.health = 0;
} else if (health > 45) {
this.health = 45;
} else {
this.health = health;
}
}
public String getName() { // 밖에서 쓰기 좋게 바꿔서 준다
if (name == null) {
return "(이름 없음)";
}
return name;
}
public void setName(String name) {
this.name = name;
}
}
Scv s = new Scv();
s.setHealth(30); System.out.println(s.getHealth()); // 30
s.setHealth(-10); System.out.println(s.getHealth()); // 0
s.setHealth(100); System.out.println(s.getHealth()); // 45
System.out.println(s.getName()); // (이름 없음)
s.health = -10;이라고 쓰면 health has private access in Scv 오류로 번역이 멈춘다. 체력을 바꾸는 길은 setHealth뿐이고, 그 안에서 범위를 벗어난 값을 걸러 낸다1.
package1·package2 폴더를 만들어 한 줄씩 컴파일해 확인 — 19_java-access-modifier_impl.java정의
접근 제한자는 중요한 필드와 메서드가 밖에 드러나지 않게 막아, 객체가 늘 올바른 상태로 있게(무결성) 하는 데 쓴다. 캡슐화를 실제로 해 주는 문법이다.
키워드는 public, protected, private 셋이다. 여기에 아무것도 쓰지 않은 경우(default)까지 더해 단계는 넷이다. 아래로 갈수록 더 좁게 막는다.
| 접근 제한자 | 붙일 수 있는 곳 | 쓸 수 있는 범위 |
|---|---|---|
public |
클래스, 필드, 생성자, 메서드 | 제한 없음 |
protected |
필드, 생성자, 메서드 | 같은 패키지이거나, 자식 객체 |
| (default) 아무것도 안 씀 | 클래스, 필드, 생성자, 메서드 | 같은 패키지 |
private |
필드, 생성자, 메서드 | 객체(클래스) 안 |
한눈에 보는 표
package1의 public class B에 네 가지 필드를 두고, 네 곳에서 써 보았다1.
B의 필드 |
B 안 |
같은 패키지의 다른 클래스 | 다른 패키지의 자식 클래스 | 다른 패키지의 남 |
|---|---|---|---|---|
public int pub |
된다 | 된다 | 된다 | 된다 |
protected int pro |
된다 | 된다 | 된다 | 오류 |
int def |
된다 | 된다 | 오류 | 오류 |
private int pri |
된다 | 오류 | 오류 | 오류 |
오류 문구는 pro has protected access in B, def is not public in B; cannot be accessed from outside package, pri has private access in B다. 메서드도 필드와 똑같다. “자식 클래스”는 상속에서 다룬다.
클래스
클래스에는 public을 붙이거나 안 붙이거나 둘 중 하나다.
public을 빼면 다른 패키지에서 그 클래스를 쓸 수 없다.public을 붙이면 같은 패키지뿐 아니라 다른 패키지에서도 쓸 수 있다.
package1 package2
┌────────────────────┐ ┌─────────────┐
│ class A { } │ │ class C { } │ C → A : 쓸 수 없다 (A에 public이 없다)
│ class B { } │ │ │ B → A : 쓸 수 있다 (같은 패키지)
└────────────────────┘ └─────────────┘
┌────────────────────┐ ┌─────────────┐
│ public class A { } │ │ class C { } │ C → A : 쓸 수 있다
│ class B { } │ │ │ B → A : 쓸 수 있다
└────────────────────┘ └─────────────┘
package2의 C에서 public이 없는 A를 쓰면 A is not public in package1; cannot be accessed from outside package 오류가 난다1.
생성자
생성자의 접근 제한자는 “누가 new로 이 객체를 만들 수 있는가”를 정한다.
| 접근 제한자 | 생성자 | 뜻 |
|---|---|---|
public |
클래스(…) |
모든 패키지에서 생성자를 부를 수 있다 = 모든 패키지에서 객체를 만들 수 있다 |
protected |
클래스(…) |
같은 패키지에서 객체를 만들 수 있다. 다른 패키지에서는 자식 클래스의 생성자가 super(…)로 부를 수만 있다2 |
| (default) | 클래스(…) |
같은 패키지에서만 객체를 만들 수 있다 |
private |
클래스(…) |
클래스 안에서만 객체를 만들 수 있다 |
생성자를 private로 두면 밖에서는 new를 못 한다. 클래스가 안에서 객체를 하나만 만들어 두고 메서드로 내주면, 프로그램 전체에 그 객체가 하나뿐이게 할 수 있다1.
class Map {
private static Map only = new Map();
private Map() {}
static Map getInstance() { return only; }
}
// Map.getInstance() == Map.getInstance() → true
필드와 메서드
필드와 메서드에 붙인 접근 제한자는 누가 그 필드를 읽고 바꾸는지, 누가 그 메서드를 부르는지를 정한다.
| 접근 제한자 | 뜻 |
|---|---|
public |
모든 패키지에서 필드를 읽고 바꿀 수 있다. 메서드를 부를 수 있다 |
protected |
같은 패키지와, 다른 패키지의 자식 클래스 안에서 필드를 읽고 바꿀 수 있다. 메서드를 부를 수 있다2 |
| (default) | 같은 패키지에서만 필드를 읽고 바꿀 수 있다. 메서드를 부를 수 있다 |
private |
클래스 안에서만 필드를 읽고 바꿀 수 있다. 메서드를 부를 수 있다 |
getter와 setter
필드를 private로 감추고, 읽는 메서드와 넣는 메서드를 public으로 내놓는다.
- setter는 값을 검사해서 맞는 값만 필드에 넣는 메서드다.
- getter는 필드 값이 객체 밖에서 쓰기에 알맞지 않으면 알맞게 바꿔서 돌려줄 수 있는 메서드다.
private 타입 fieldName; // 필드는 private
public 타입 getFieldName() { // getter: public, 돌려주는 타입은 필드 타입
return fieldName; // 이름은 get + 필드 이름(첫 글자 대문자)
}
public void setFieldName(타입 fieldName) { // setter: public, void
this.fieldName = fieldName; // 이름은 set + 필드 이름(첫 글자 대문자), 매개변수는 필드 타입
}
예시의 setHealth가 검사하는 setter, getName이 바꿔서 돌려주는 getter다.
활용
- 기본값으로 삼을 것: 필드는
private, 밖에서 쓸 메서드만public. 넓히기는 쉬워도 한 번 내놓은 것을 다시 감추면 그것을 쓰던 코드가 다 깨진다1. - 흔한 실수: 다른 패키지에서 쓸 클래스나 생성자에
public을 빠뜨리는 것. 아무것도 안 쓰면public이 아니라 default(같은 패키지만)다. - 흔한 실수: 검사 없이 값을 그대로 넣는 setter를 필드마다 달고 캡슐화했다고 여기는 것. 필드를
public으로 둔 것과 다르지 않다. private의 경계는 객체가 아니라 클래스다. 같은 클래스의 다른 객체가 가진private필드에는 닿을 수 있다1.- 메서드를 다시 쓸 때는 부모보다 더 좁게 막을 수 없다(메서드 오버라이딩).
연결
확인 문제
C1 접근 단계 넷을 넓은 것부터 쓰고, 각각 어디까지 쓸 수 있는지 적어라. 클래스에 붙일 수 있는 것은 무엇인가?
답: public(제한 없음) → protected(같은 패키지, 그리고 다른 패키지의 자식) → default(같은 패키지) → private(클래스 안). 클래스에는 public과 default만 붙는다.
이유: default와 protected의 순서를 바꿔 쓰기 쉽다. protected는 default가 주는 것(같은 패키지)에 자식을 더한 것이라 더 넓다.
C2 package1에 public class B { public int pub; protected int pro; int def; private int pri; }가 있다. package2의 (자식이 아닌) 클래스 C에서 B b = new B(); 뒤에 쓸 수 있는 줄을 모두 고르라. (가) b.pub = 1; (나) b.pro = 2; (다) b.def = 3; (라) b.pri = 4;
답: (가)만 된다.
이유: C는 다른 패키지이고 자식도 아니다. protected는 자식이 아니라서, default는 패키지가 달라서, private는 클래스 밖이라서 막힌다. (나)를 되는 것으로 고르는 오답이 흔하다. protected는 “보호된 공개”가 아니라 “같은 패키지와 자식에게만”이다.
C3 필드를 public으로 내놓지 않고 private 필드에 getter와 setter를 두는 이유는? 예시의 체력으로 설명하라.
답: public 필드에는 아무 값이나 들어간다. private로 감추고 setHealth로만 넣게 하면, 그 안에서 0보다 작으면 0, 45보다 크면 45로 걸러 체력이 늘 0~45에 있게 할 수 있다. getter도 필드 값을 그대로 주지 않고 밖에서 쓰기 좋게 바꿔 줄 수 있다(name이 없으면 “(이름 없음)”).
이유: setter가 검사 없이 this.health = health; 한 줄뿐이면 이 이득이 없다.
C4 private double speed; 필드의 getter와 setter를 이름 규칙대로 쓰라. setter는 음수를 0으로 바꿔 넣게 하라.
답: public double getSpeed() { return speed; } / public void setSpeed(double speed) { if (speed < 0) { this.speed = 0; } else { this.speed = speed; } }
이유: getter는 get + 필드 이름(첫 글자 대문자), 돌려주는 타입은 필드 타입. setter는 set + 필드 이름, void, 매개변수는 필드 타입. setter 안에서 this.를 빼면 매개변수에 매개변수를 넣게 된다1.
-
package1(A,B, 같은 패키지의 클래스)과package2(남C, 자식D)를 만들어 JDK 21에서 직접 컴파일해 확인했다. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 -
생성자 표와 필드·메서드 표의
protected줄은 Java Language Specification §6.6.2에 따라 보탰다.package1의B에protected생성자·필드·메서드를 두고 JDK 21에서 컴파일해 확인했다. 같은 패키지의 클래스와package2의 자식D(super(), 물려받은 필드와 메서드)는 통과했고,package2에서new B()를 쓰면 자식 클래스 안에서도B() has protected access in B로 막혔다. ↩ ↩2