2024. 2. 5. 20:05ㆍJava
◎ 접근 제어자
-> 자바는 public, private, protected, default라는 접근 제어자를 제공한다.
-> 접근 제어자를 통해 해당 클래스 외부에서 특정 필드나 메서드를 접근하는 것을 허용하거나 제한할 수 있다.
-> 예제
- 스피커 클래스를 통해 스피커의 음량을 조절하는 기능을 구현한다.
- 스피커의 음량은 100을 넘을 수 없다.
package access;
public class Speaker {
int volume;
// 생성자로 volume 초기화
public Speaker(int volume) {
this.volume = volume;
}
// 볼륨 증가 메서드
void volumeUp(){
if (volume >= 100) {
System.out.println("최대 음량!");
}else{
volume += 10;
System.out.println("볼륨 10 증가");
}
}
void volumeDown(){
volume -= 10;
System.out.println("볼륨 10 감소");
}
void showVolume() {
System.out.println("현재 음량 : " + volume);
}
}
- volumeUp메서드를 통해 스피커의 음량을 높이는데 volume의 값을 확인하여 100이상이면 더 이상 음량을 높이지 않도록 했다.
package access;
public class SpeakerMain {
public static void main(String[] args) {
Speaker speaker = new Speaker(90);
speaker.showVolume();
speaker.volumeUp();
speaker.volumeUp();
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
speaker.showVolume();
}
}
-> 결과
- 음량을 증가 시키다가 100 이상이 되면 "최대 음량!"을 출력하고 더 이상 음량을 높이지 않는다.
-> volumeUp()메서드로 volume을 체크하여 음량을 조절하였다. 그런데 만약 SpeakerMain 클래스의 main메서드에서 volumeUp()메서드가 아닌 volume 필드에 직접 접근하여 volume을 조절한다면 어떤 결과가 나올까?
public class SpeakerMain {
public static void main(String[] args) {
Speaker speaker = new Speaker(90);
speaker.showVolume();
speaker.volumeUp();
speaker.volumeUp();
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
// 필드에 직접 접근
System.out.println("volume 필드에 직접 접근");
speaker.volume = 200; // private 필드에 직접 접근 불가
speaker.showVolume();
}
}
- speaker.volume = 200; 은 Speaker 인스턴스의 volume 필드에 직접 접근하여 값을 200으로 설정하였다.
-> 결과
-> volume 필드에 직접 접근을 하면서 200으로 값을 설정하였고 결국 음량이 200이 되어버렸다.
-> 스피커의 볼륨은 100을 넘을 수 없다는 초기 요구사항을 위반했다.
-> volumeUp()메서드로 volume 값을 확인하여 볼륨을 조절을 하여 volumeUp()메서드를 통해 볼륨을 높이면 정상적으로 잘 동작하지만, 필드로 직접 접근을 해버리면 volume 값이 어떤 값이 되든 그대로 저장이 되기 때문에 문제가 발생한다.
-> 이 문제를 해결하려면 volume 필드를 변경할 수 없게 하면 될 것이다.
-> Speaker 클래스의 volume 필드 앞에 private를 붙인다.
-> private는 모든 외부 호출을 막는다. private이 붙은 경우 해당 클래스 내부에서만 사용할 수 있다.
-> Speaker 클래스의 volume 필드에 private을 붙인 후 Speaker 클래스를 보면 volume필드에 직접 접근한 코드에 에러가 발생한다.
-> Speaker 클래스의 volume은 private 접근제어자로 Speaker 클래스 내부에서만 사용가능하다. 따라서 외부 클래스인 speakerMain클래스에서는 volume에 접근이 불가능하다. volume을 조절하려면 Speaker 클래스의 볼륨 조절 메서드(volumeUp(), volumeDow())을 이용해 조절해야 한다.
-> 이렇게 접근 제어자를 통해 필드나 메서드의 접근을 허용하거나 제한하여 데이터가 외부에서 임의로 수정되지 않도록한다.
◎ 접근 제어자의 종류
-> 접근 제어자는 private, default, protected, public으로 4가지가 있다.
-> private : 모든 외부의 호출을 막는다.
-> default : 같은 패키지 내에서 호출은 허용한다.(필드나 메서드에 접근제어자를 따로 붙이지 않으면 기본적으로 default다.)
-> protected : 같은 패키지 내에서 호출은 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.
-> public : 모든 외부 호출을 허용한다.
-> 접근 제어자는 필드, 메서드, 생성자에 사용된다.
※ 접근 제어자의 핵심은 속성과 기능을 외부로부터 숨기는 것이다.
◎ 접근 제어자 활용 - 필드, 메서드
-> 예제
package access.a;
public class AccessData {
public int publicField; // public
int defaultField; // default
private int privateField; // private
public void publicMethod(){ // public
System.out.println("publicMethod 호출 : " + publicField);
}
void defaultMethod() { // default
System.out.println("defaultMethod 호출 : " + defaultField);
}
private void privateMethod() { // private
System.out.println("privateMethod 호출 : " + privateField);
}
public void innerAccess(){ // public
System.out.println("내부 호출 ");
publicField = 100;
defaultField = 200;
privateField = 300;
publicMethod();
defaultMethod();
privateMethod();
}
}
-> 해당 클래스의 패키지 위치는 access.a다.
package access.a;
public class AccessInnerMain {
public static void main(String[] args) {
AccessData accessData = new AccessData();
// public 호출 가능
accessData.publicField = 1;
accessData.publicMethod();
// 같은 패키지 내에서 default 호출 가능
accessData.defaultField = 2;
accessData.defaultMethod();
// private 호출 불가
// accessData.privateField = 3;
// accessData.privateMethod();
accessData.innerAccess();
}
}
-> AccessData 클래스에 접근하는 클래스다. 해당 클래스의 패키지 위치는 access.a다.
-> 결과
-> public은 모든 외부에서 호출이 가능하기 떄문에 publicField와 publicMethod에 접근이 가능하다.
// public 호출 가능
accessData.publicField = 1;
accessData.publicMethod();
-> AccessData 클래스와 AccessInnerMain 클래스 모두 access.a에 위치하기 때문에 defaultField와 defaultMethod에 접근이 가능하다.
// 같은 패키지 내에서 default 호출 가능
accessData.defaultField = 2;
accessData.defaultMethod();
-> AccessData 클래스의 privateField와 privateMethod의 접근제어자는 private로 외부 클래스인 AccessInnerMain에서 접근이 불가능하다.
-> 그런데 결과를 보면 privateMethod가 호출된 것을 볼 수 있다.
-> AccessData클래스의 메서드인 innerAccess()메서드를 통해 호출했다.
-> innerAccess()는 AccessData 클래스 내부에 선언된 메서드다. 따라서 innerAccess()메서드에서 같은 클래스에 있는 privateField와 privateMethod에 접근을 할 수 있다. 또한 innerAccess()는 public 접근 제어자로 외부에서 호출이 가능하다. innerAccess()에서 privateField를 초기화하고, privateMethod를 호출하는 코드를 구현하고 이를 외부에서 호출한 것이다.
-> 다음은 access.b 패키지에 위치한 AccessOuterMain클래스다.
package access.b;
import access.a.AccessData;
public class AccessOuterMain {
public static void main(String[] args) {
AccessData accessData = new AccessData();
// public 호출 가능
accessData.publicField = 1;
accessData.publicMethod();
// 다른 패키지에서 default 호출 불가
// accessData.defaultField = 2;
// accessData.defaultMethod();
// private 호출 불가
// accessData.privateField = 3;
// accessData.privateMethod();
accessData.innerAccess();
}
}
-> access.b에 위치한 AccessOuterMain에서는 access.a에 위치한 AccessData클래스의 default 접근 제어자를 가진 defaultField와 defaultMethod에 접근할 수 없고, private 접근 제어자인 privateField와 privateMethod에 접근할 수 없다.
-> publicField, publicMethod, innerAccess()는 모두 public 접근 제어자를 가졌기 때문에 접근이 가능하다.
-> 결과
◎ 접근 제어자 활용 - 클래스
-> 클래스 레벨에서 접근 제어자는 public과 default만 사용할 수 있다. private와 protected는 사용할 수 없다.
-> public 클래스는 반드시 파일명과 이름이 같아야 한다.
-> 하나의 자바 파일에 public 클래스는 하나만 등장한다.
-> 하나의 자바 파일에 default 클래스는 무한정 만들 수 있다.
ackage access.a;
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 {
}
-> access.a 패키지에 있는 PublicClass 클래스다.
-> PublicClass는 public 접근 제어자를 가진다. 해당 클래스는 외부에서 접근 가능하다.
-> DefaultClass1,2는 모두 default 접근 제어자를 가진다. 동일 패키지 내에서 접근 가능하다.
※ 참고로 public 클래스는 하나의 자바 파일에 하나만 등장한다고 했는데 PublicClass에 public 클래스를 하나 더 정의해봤다.
-> 에러가 발생한다.
-> 다음은 PublicClass를 사용하는 PublicClassInnerMain클래스다.
package access.a;
public class PublicClassInnerMain {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
DefaultClass1 class1 = new DefaultClass1();
DefaultClass2 class2 = new DefaultClass2();
}
}
-> access.a 패키지에 위치한다.
-> PublicClass에 접근 가능하고, PublicClass와 같은 패키지에 있기 때문에 DefaultClass1,2에 모두 접근 가능하다.
-> 다음은 access.b에 위치한 PublicClassOuterMain 클래스다.
package access.b;
//import access.a.DefaultClass1;
import access.a.PublicClass;
public class PublicClassOuterMain {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
// 다른 패키지에서 접근 불가
// DefaultClass1 class1 = new DefaultClass1();
}
}
-> public 접근 제어자를 가진 PublicClass에는 접근이 가능하지만 default 접근 제어자를 가진 DefaultClass1,2는 다른 패키지에 존재하기 때문에 접근할 수 없다.
◎ 캡슐화(Encapsulation)
-> 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어 외부에서 접근 하는 것을 제한하는 것이다.
-> 캡슐화를 통해 데이터의 직접적인 변경을 막을 수 있다.
-> 이전에 봤던 Speaker 클래스애서 Speaker 클래스의 volume 필드를 private으로 설정하여 volume 필드에 직접 접근하는 것을 막고, volumeUp/Down()메서드를 통해서만 볼륨을 조절할 수 있도록 한 것이 캡슐화다.
-> Speaker를 사용하는 곳에서는 볼륨을 조절하는 기능만 알면 되지 볼륨 데이터 자체를 알 필요가 없다.
-> 객체의 데이터는 객체에서 제공하는 메서드를 통해서 접근해야 한다.
-> 객체의 데이터(필드 등)를 외부에서 접근하게 한다면 클래스 내에 구현한 로직을 무시하고 데이터가 변경이 될 수 있기 떄문에 메서드를 통해 접근하는 것이 안전하다.
-> 또한 객체의 기능 중에서 외부에서 사용하지 않고 내부에서만 사용하는 기능이 있는데 이 역시 외부로부터의 접근을 막는 것이 좋다.
-> 예를 들어 자동차를 운전할 때 운전자는 시동 on/off, 액셀, 브레이크 정도의 기능만 알면 되지 엔진 조절 기능 까지 알 필요가 없다. 이런 기능까지 알기엔 너무 많은 것을 알아야 한다.
-> 데이터는 숨기고 필요한 기능만 노출하는 것이 좋은 캡슐화다.
-> 캡슐화 예제
-> 아래는 BankAcount 객체로 잔고를 필드로 가지고 있고, 입금, 출금, 잔액 조회, 입금 금액 검증 기능을 가지고 있다.
package access;
public class BankAccount {
private int balance; // 잔고
public BankAccount() {
this.balance = 0;
}
// public 메서드 - 입금
public void deposit(int amount) {
if (isAmountValid(amount)) {
balance += amount;
}else {
System.out.println("유효하지 않은 금액입니다.");
}
}
// public 메서드 - 출금
public void withdraw(int amount) {
if (isAmountValid(amount) && balance - amount >= 0) {
balance -= amount;
}else{
System.out.println("유효하지 않은 금액입니다. or 잔액이 부족합니다.");
}
}
// public 메서드 - 잔액 조회
public int getBalance(){
return balance;
}
// 입금 금액 검증 로직
private boolean isAmountValid(int amount) {
// 금액 > 0
return amount > 0;
}
}
-> 여기에서 사용자가 사용할 기능은 입금, 출금, 잔액 조회다. 입금 잔액 조회는 입금 기능을 제대로 수행하기 위해 검증하는 로직일 뿐 사용자가 필요로 하는 기능이 아니다 따라서 해당 메서드에는 private 접근 제어자를 붙였다.
package access;
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.deposit(10000)을 통해 입금을 하면 해당 참조값 위치에 있는 Account 클래스의 deposit()메서드로 접근하고, 매개변수로 받은 수가 입금을 정상적으로 수행할 수 있는 값인지 isAmountValid()메서드를 통해 확인한 후 입금 로직을 수행한다.
-> 또한 balance를 private로 설정하여 외부에서 접근할 수 없게 막아 balance가 로직을 무시하는 음수와 같은 유효하지 않은 값이 들어가는 상황을 방지한다.
★ 참고
'Java' 카테고리의 다른 글
final (0) | 2024.02.07 |
---|---|
자바 메모리 구조 & static (0) | 2024.02.06 |
생성자 (0) | 2024.02.01 |
객체 지향 프로그래밍 (0) | 2024.01.31 |
기본형 & 참조형 (0) | 2024.01.30 |