접근 제어자
public class SpeakerMain {
public static void main(String[] args) {
Speaker speaker = new Speaker(90);
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
//필드에 직접 접근
System.out.println("volume 필드 직접 접근 수정");
speaker.volume = 200;
speaker.showVolume();
}
}
Speaker 객체를 사용하는 사용자는 Speaker 의 volume 필드와 메서드에 모두 접근할 수 있다.
앞서 volumeUp() 과 같은 메서드를 만들어서 음량이 100을 넘지 못하도록 기능을 개발했지만 소용이 없다. 왜냐하면 Speaker 를 사용하는 입장에서는volume 필드에 직접 접근해서 원하는 값을 설정할 수 있기 때문이다.
이런 문제를 근본적으로 해결하기 위해서는 volume 필드의 외부 접근을 막을 수 있는 방법이 필요하다.
이 문제를 근본적으로 해결하는 방법은 volume 필드를 Speaker 클래스 외부에서는 접근하지 못하게 막는 것이다.
public class Speaker {
private int volume; //private 사용
...
}
private 접근 제어자는 모든 외부 호출을 막는다. 따라서 private 이 붙은 경우 해당 클래스 내부에서만 호출할 수 있다.
그림을 보면 volume 필드를 private 을 사용해서 Speaker 내부에 숨겼다.
외부에서 volume 필드에 직접 접근할 수 없게 막은 것이다. volume 필드는 이제 Speaker 내부에서만 접근할 수 있다.
접근 제어자 종류
자바는 4가지 종류의 접근 제어자를 제공한다.
- private: 모든 외부 호출을 막는다.
- default: (package-private): 같은 패키지안에서 호출은 허용한다.
- protected: 같은 패키지안에서 호출은 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다. 주의할 점은 자식 클래스에서 부모 클래스의 protected에 접근이 가능한다는거다.
- public: 모든 외부 호출을 허용한다.
접근 제어자 사용 위치
접근 제어자는 필드와 메서드, 생성자에 사용된다. 추가로 클래스 레벨에도 일부 접근 제어자를 사용할 수 있다.
접근 제어자 예시
public class Speaker { //클래스 레벨
private int volume; //필드
public Speaker(int volume) {} //생성자
public void volumeUp() {} //메서드
public void volumeDown() {}
public void showVolume() {}
}
접근 제어자의 핵심은 속성과 기능을 외부로부터 숨기는 것이다.
- private 은 나의 클래스 안으로 속성과 기능을 숨길 때 사용, 외부 클래스에서 해당 기능을 호출할 수 없다.
- default는 나의 패키지 안으로 속성과 기능을 숨길 때 사용, 외부 패키지에서 해당 기능을 호출할 수 없다.
- protected 는 상속 관계로 속성과 기능을 숨길 때 사용, 상속 관계가 아닌 곳에서 해당 기능을 호출할 수 없다.
- public 은 기능을 숨기지 않고 어디서든 호출할 수 있게 공개한다.
접근 제어자 사용 - 클래스 레벨
클래스 레벨의 접근 제어자 규칙
- 클래스 레벨의 접근 제어자는 public, default 만 사용할 수 있다.
- private, protected는 사용할 수 없다.
- public 클래스는 반드시 파일명과 이름이 같아야 한다.
- 하나의 자바 파일에 public 클래스는 하나만 등장할 수 있다.
- 하나의 자바 파일에 default 접근 제어자를 사용하는 클래스는 무한정 만들 수 있다.
public class PublicClass {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
DefaultClass1 class1 = new DefaultClass1();
DefaultClass2 class2 = new DefaultClass2();
}
}
class DefaultClass1 {
}
class DefaultClass2 {
}
캡슐화
캡슐화(Encapsulation)는 객체 지향 프로그래밍의 중요한 개념 중 하나다. 캡슐화는 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어서 외부에서의 접근을 제한하는 것을 말한다. 캡슐화를 통해 데이터의 직접적인 변경을 방지하거나 제한할 수 있다. 캡슐화는 쉽게 이야기해서 속성과 기능을 하나로 묶고, 외부에 꼭 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것이다.
이전에 객체 지향 프로그래밍을 설명하면서 캡슐화에 대해 알아보았다. 이때는 데이터와 데이터를 처리하는 메서드를 하나로 모으는 것에 초점을 맞추었다. 여기서 한발짝 더 나아가 캡슐화를 안전하게 완성할 수 있게 해주는 장치가 바로 접근 제어자다.
그럼 어떤 것을 숨기고 어떤 것을 노출해야 할까?
1. 데이터를 숨겨라
객체에는 속성(데이터)과 기능(메서드)이 있다. 캡슐화에서 가장 필수로 숨겨야 하는 것은 속성(데이터)이다.
Speaker의 volume 을 떠올려보자. 객체 내부의 데이터를 외부에서 함부로 접근하게 두면, 클래스 안에서 데이터를 다루는 모든 로직을 무시하고 데이터를 변경할 수 있다. 결국 모든 안전망을 다 빠져나가게 된다. 따라서 캡슐화가 깨진다.
우리가 일상에서 생각할 수 있는 음악 플레이어를 떠올려보자. 우리는 음악 플레이어가 제공하는 기능을 통해서 음악 플레이어를 사용하는 것이다. 복잡하게 음악 플레이어의 내부를 까서 그 내부 데이터까지 우리가 직접 사용 하는 것은 아니다.
객체의 데이터는 객체가 제공하는 기능인 메서드를 통해서 접근해야 한다. 즉 메소드를 통해 자신이 가진 데이터를 사용한다는 점이다.
2. 기능을 숨겨라
객체의 기능 중에서 외부에서 사용하지 않고 내부에서만 사용하는 기능들이 있다. 이런 기능도 모두 감추는 것이 좋다.
우리가 자동차를 운전하기 위해 자동차가 제공하는 복잡한 엔진 조절 기능, 배기 기능까지 우리가 알 필요는 없다. 우리는 단지 엑셀과 핸들 정도의 기능만 알면 된다. 만약 사용자에게 이런 기능까지 모두 알려준다면, 사용자가 자동차에 대해 너무 많은 것을 알아야 한다. 사용자 입장에서 꼭 필요한 기능만 외부에 노출하자. 나머지 기능은 모두 내부로 숨기자
정리하면 데이터는 모두 숨기고, 기능은 꼭 필요한 기능만 노출하는 것이 좋은 캡슐화이다.
잘 캡슐화된 예제를 하나 만들어보자.
public class BankAccount {
private int balance;
public BankAccount() {
balance = 0;
}
// public 메서드: deposit
public void deposit(int amount) {
if (isAmountValid(amount)) {
balance += amount;
} else {
System.out.println("유효하지 않은 금액입니다.");
}
}
// public 메서드: withdraw
public void withdraw(int amount) {
if (isAmountValid(amount) && balance - amount >= 0) {
balance -= amount;
} else {
System.out.println("유효하지 않은 금액이거나 잔액이 부족합니다.");
}
}
// public 메서드: getBalance
public int getBalance() {
return balance;
}
// private 메서드: isAmountValid
private boolean isAmountValid(int amount) {
// 금액이 0보다 커야함
return amount > 0;
}
}
접근 제어자와 캡슐화를 통해 데이터를 안전하게 보호하는 것은 물론이고, BankAccount를 사용하는 개발자 입장에서 해당 기능을 사용하는 복잡도도 낮출 수 있다.
public void increment() {
// 검증로직
if (count >= max) {
System.out.println("최대값을 초과할 수 없습니다.");
return;
}
// 실행로직
count++;
}
- else 문을 쓰면 좀 지저분해 보인다. 그래서 return 을 통해 검증 로직과 실행로직을 나누면 깔끔하다.
참고
김영한의 실전 자바 기본 편 강의를 보고 정리한 내용입니다.
'JAVA > 초급' 카테고리의 다른 글
8. final (0) | 2024.07.09 |
---|---|
7. 자바 메모리 구조와 static (0) | 2024.07.08 |
5. 패키지 (0) | 2024.06.14 |
4. 생성자 (0) | 2024.06.14 |
3. 객체 지향 프로그래밍 (1) | 2024.06.14 |