1. 접근 제어자 정의와 필요 이유
접근 제어자란 접근을 허용 및 거부할 수 있게 하는 키워드를 의미한다. 접근 제어자를 통해 외부에서 필드, 메서드, 클래스에 접근 하는 것을 제어 할 수 있다.
접근 제어자를 사용하는 이유는 캡슐화와 관련이 있으며 외부로부터 속성과 기능을 숨기고 필요한 부분만 노출하기 위해서이다. 자동차를 운전하는데 내가 자동차 엔진이 어떻게 돌아가는지 알 필요가 없는 것과 같다.
4가지 종류의 접근 제어자(제어가 강한 순에서 약한 순으로 정렬함)
- private: 같은 클래스 내에서만 접근 O
- default: 같은 패키지 내에서만 접근 O
- protected: 같은 패키지 내에서만 접근 O 추가로 다른 패키지이자만 상속 관계인 경우 호출 O
- public: 제한 없음
필드와 메서드는 4가지 접근 제어자 사용 O
클래스는 default, public 2가지만 사용 O
아래는 필드와 메서드에 접근 제어자를 설정한 예시이다.
// 클래스 선언
public class AccessData {
public int publicField;
int defaultField;
private int privateField;
public void publicMethod() {
System.out.println("publicMethod 호출" + publicField);
}
void defaultMethod() {
System.out.println("publicMethod 호출" + defaultField);
}
private void privateMethod() {
System.out.println("privateMethod 호출" + privateField);
}
// AccessData의 메소드에 해당함. 그래서 AccessData의 필드에 접근하는 것이 허용됨
// AccessData라는 파일 안에 있는 것이라 접근 제어자 상관없이 모든 필드에서 접근 가능
public void innerAccess() {
System.out.println("내부 호출");
publicField = 100;
defaultField = 200;
privateField = 300;
publicMethod();
defaultMethod();
privateMethod();
}
}
// 선언한 클래스 사용
public class AccessInnerMain {
public static void main(String[] args) {
AccessData data = new AccessData();
// public 호출 가능
data.publicField = 1;
data.publicMethod();
// 같은 패키지라 default 호출 가능
data.defaultField = 2;
data.defaultMethod();
// private은 외부에서 호출 불가 즉 자기 파일에서만 호출 가능
// data.privateField = 3;
// data.privateMethod();
data.innerAccess();
}
아래는 클래스에 접근 제어자를 설정한 예시이다.
public class PublicClass {
public static void main(String[] args) {
PublicClass pubClass = new PublicClass();
DefaultClass1 defaultClass1 = new DefaultClass1();
DefaultClass2 defaultClass2 = new DefaultClass2();
}
}
class DefaultClass1 {
}
class DefaultClass2 {
}
2. 캡슐화
- 데이터와 그것을 처리하는 기능인 메서드를 하나로 묶어 접근을 제한하는 것을 의미한다. 이것을 통해 데이터의 직접적인 변경을 방지한다. 또한 외부에 필요한 기능(메서드)만 노출한다.
- 추가로 외부에 필요한 기능(메서드)만 노출한다. 따라서 데이터는 반드시 숨겨야 한다. 그 이유는 외부에서 데이터를 변경할 가능성을 차단하기 위함이다.
- 외부에 필요한 기능만 노출하는 이유는 사용자 입장에서 쓸데없는 것을 숨기기 위함이다. ex) 자동차에 엑셀을 밣으면 차 속도가 올라간다는 것만 알면되지 왜 속도가 올라가며 어떤 원리인지는 알 필요가 없다.
// 클래스 선언
public class BankAccount {
private int balance;
public BankAccount() {
this.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 boolean isAmountValid(int amount) {
// 금액이 0보다 커야함
return amount > 0;
}
}
// 클래스 사용
public class BankAccountMain {
public static void main(String[] args) {
BankAccount account = new BankAccount();
account.deposit(10000);
account.withdraw(3000);
System.out.println("balance: " + account.getBalance());
}
}
실무에 써먹기(캡슐화를 통해 얻는 이득)
- 중복을 줄일 수 있다. 예를 들어 메서드가 객체가 아닌 각 파일마다 있다면 코드가 너무 길어질 것이다.
- 외부에서 데이터를 변경하는 것을 방지할 수 있다. 예를 들어 Account에 -값은 못 넣도록 메서드에 조치를 했는데 사용자가 직접 객체의 데이터에 접근해서 -1000을 넣는다면 막을 방법이 없다.
- getter, setter 사용 이유 - 데이터를 직접 변경하거나 직접 가져오는 것이 아닌 메서드를 통해 변경 및 가져오기, 클래스에 선언되어 있기에 코드 중복 줄임
public class BankAccountMain {
public static void main(String[] args) {
BankAccount account = new BankAccount();
// 외부에서 데이터 수정
account.balance = - 1000;
System.out.println("balance: " + account.getBalance());
}
}
🔗레퍼런스
참고 강의/글
'Java' 카테고리의 다른 글
| 30. final 키워드 (0) | 2025.08.27 |
|---|---|
| 29. static (2) | 2025.08.19 |
| 27. 생성자 (1) | 2025.07.25 |
| 26. 절차지향 VS 객체지향 (1) | 2025.07.24 |
| 25. 기본형과 참조형, 변수와 초기화 그리고 null (0) | 2025.06.24 |