자바는 public, private 같은 접근 제어자(access modifier)를 제공한다.
접근 제어자를 사용하면 클래스 외부에서 특정 필드나 메서드에 접근하는 것을 허용하거나 제한할 수 있다.
왜 접근 제어자가 필요할까? 예시를 통해 알아보자.
자동차 소프트웨어를 개발한다고 생각해보자. 자동차의 속도는 절대로 200을 넘으면 안 된다는 요구사항이 있다.
자동차의 속도를 조절하는 Car 객체를 만들어보자.
public class Car {
int speed;
Car(int speed) {
this.speed = speed;
}
void accelerate() {
if (speed >= 200) {
System.out.println("속도를 더 이상 올릴 수 없습니다. 최대 속도입니다.");
} else {
speed += 20;
System.out.println("속도를 20 증가시킵니다.");
}
}
void brake() {
speed -= 20;
System.out.println("브레이크를 밟았습니다. 속도가 줄어듭니다.");
}
void showSpeed() {
System.out.println("현재 속도: " + speed);
}
}
위 클래스에서 자동차의 속도는 최대 200까지만 올라가도록 설정했다.
만약 속도가 200을 넘으면 메세지를 출력하고, 더 이상 속도를 증가시키지 않도록 제한을 두었다.
public class CarMain {
public static void main(String[] args) {
Car car = new Car(180);
car.showSpeed();
car.accelerate();
car.showSpeed();
car.accelerate();
car.showSpeed();
}
}
현재 속도: 180
속도를 20 증가시킵니다.
현재 속도: 200
속도를 더 이상 올릴 수 없습니다. 최대 속도입니다.
현재 속도: 200
초기 속도를 180으로 설정한 후, accelerate() 메서드를 호출했다.
속도는 200을 넘지 않으며, 기대한 대로 작동한다.
이제 다른 개발자가 이 코드를 유지보수하게 되었다고 가정해보자.
그 개발자는 속도를 300까지 올리면 좋겠다고 생각해, Car 클래스의 speed 필드에 직접 접근해서 속도를 300으로 설정한다.
이렇게 하면 자동차 부품에 무리가 가게 되고, 위험한 상황이 벌어질 수도 있다.
public class CarMain {
public static void main(String[] args) {
Car car = new Car(180);
car.accelerate();
car.showSpeed();
// 필드에 직접 접근
System.out.println("속도를 강제로 300으로 설정");
car.speed = 300;
car.showSpeed();
}
}
속도를 20 증가시킵니다.
현재 속도: 200
속도를 강제로 300으로 설정
현재 속도: 300
이 문제를 방지하기 위해서는 필드에 대한 외부 접근을 막는 방법이 필요하다.
이 문제를 해결하는 방법은 speed 필드에 private 접근 제어자를 추가하는 것이다.
이를 통해 Car 클래스 외부에서는 speed 필드에 직접 접근할 수 없게 막을 수 있다.
public class Car {
private int speed; // private 사용
// 나머지 코드는 동일
}
이제 CarMain에서 speed에 직접 접근하려고 하면 오류가 발생한다.
car.speed = 300; // 오류 발생: speed has private access in Car
자바에서는 4가지 접근 제어자를 제공한다
private: 외부에서의 모든 접근을 막음default (package-private): 같은 패키지 내에서 접근 허용protected: 같은 패키지와 상속 관계에서 접근 허용public: 모든 외부에서 접근 허용제어 강도는 private > default > protected > public 순서이다.
public class Car {
public int publicSpeed;
int defaultSpeed;
private int privateSpeed;
public void publicMethod() {
System.out.println("public method");
}
void defaultMethod() {
System.out.println("default method");
}
private void privateMethod() {
System.out.println("private method");
}
public void showAccess() {
publicSpeed = 100;
defaultSpeed = 200;
privateSpeed = 300;
}
}
이제 CarMain에서 필드와 메서드에 어떻게 접근할 수 있는지 확인해보자.
public class CarMain {
public static void main(String[] args) {
Car car = new Car();
// public 접근 가능
car.publicSpeed = 1;
car.publicMethod();
// default 접근 가능 (같은 패키지 내에서)
car.defaultSpeed = 2;
car.defaultMethod();
// private 접근 불가
// car.privateSpeed = 3;
// car.privateMethod();
car.showAccess();
}
}
아래 코드의 Car 클래스는 캡슐화 원칙을 잘 적용한 예시이다.
먼저, speed와 engineHealthy 필드는 private로 선언되어 외부에서 직접 접근할 수 없다.
이를 통해 외부에서 자동차의 속도나 엔진 상태를 임의로 변경하는 것을 방지하고, 시스템의 안정성을 유지할 수 있다.
속도는 accelerate() 메서드를 통해서만 제어되며, 외부에서는 이 메서드를 사용해 간접적으로만 속도에 영향을 줄 수 있다. 가속하기 전에는 내부적으로 checkEngine()이라는 private 메서드를 호출해 엔진 상태를 확인한다. 엔진 상태가 좋지 않다면 가속이 제한된다.
이처럼 캡슐화를 통해 자동차의 내부 상태와 동작을 숨기고, 사용자는 제공된 메서드만 이용해 안전하게 자동차를 조작할 수 있게 설계되었다.
public class Car {
private int speed;
private boolean engineHealthy = true; // 엔진 상태를 나타내는 필드
// 엑셀 페달을 밟아 가속하는 메서드
public void accelerate(int amount) {
if (checkEngine()) { // 가속 전 엔진 상태 확인
if (amount > 0 && speed + amount <= 200) {
speed += amount;
System.out.println("속도가 " + amount + "만큼 증가하여, 현재 속도는 " + speed + "입니다.");
} else {
System.out.println("속도 초과로 인해 더 이상 가속할 수 없습니다.");
}
} else {
System.out.println("엔진 상태가 좋지 않아 가속할 수 없습니다.");
}
}
// 현재 속도를 반환하는 메서드
public int getSpeed() {
return speed;
}
// 엔진 상태를 확인하는 private 메서드 (외부에서 접근 불가)
private boolean checkEngine() {
return engineHealthy; // 엔진 상태가 좋으면 true 반환
}
}