JAVA

JAVA - 접근 제어자

jonghyeon6084 2024. 1. 4. 20:45
728x90

1. 접근 제어자 이해 1

 

자바는 public, private과 같은 접근 제어자(access modifier)를 제공한다. 접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드에 접근하는 것을 허용하거나 제한할 수 있다.

 이런 접근 제어자는 왜 필요할까?? 예제를 통해 접근 제어자가 필요한 이유를 알아보자.

 

만약 내가 스피커에 들어가는 소프트웨어를 개발하는 개발자. 

스피커의 음량이 절대로 100이 넘으면 안 된다는 요구사항이 있다.(100이 넘어가면 스피커가 고장난다는 가정.)

 

스피커 객체를 만들고, 기능은 볼륨을 올리고 내리고, 음량을 확인하는 단순한 기능을 제공한다.

package access;

public class Speaker {
    int volume;

    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("volumeDown 호출");
    }

    void showVolume() {
        System.out.println("현재 음량 : " + volume);
    }
}

 

생성자로 초기 음량 값을 지정해주게 했다.

public class SpeakerMain {
    public static void main(String[] args) {
        Speaker speaker = new Speaker(90);
        speaker.showVolume();

        speaker.volumeUp();
        speaker.showVolume();

        speaker.volumeUp();
        speaker.showVolume();
    }
}

100이 넘어가면 최대 음량이라고 출력되고, 현재 음량이 출력된다.

 

근데 만약 오랜 시간이 흘러 새로운 개발자가 업그레이드 다음 버전 스피커의 코드를 개발하게 되었는데 100 이상 올라가지 않아 Speaker 객체에 접근해서 volume을 200으로 설정하고 코드 실행한 순간 스피커가 고장 나버렸다.

 

현재 Speaker 객체를 사용하는 사용자는 Speaker의 필드와 메서드에 모두 접근가능하다.

앞서 메서드를 만들어 음량이 100이 넘지 못하도록 기능을 개발하였지만 소용이 없다 왜냐 volume 필드에 접근해서 원하는 값을 설정할 수 있기 때문이다.

 이런 문제를 근본적으로 해결하기 위해서는 volume 필드의 외부 접근을 막을 수 있는 방법이 필요하다.

2. 접근 제어자 이해 2

이 문제를 해결하는 방법은 volume 필드를 Speaker 클래스 외부에서 접근하지 못하게 막는 것이다.

public class Speaker {
    private int volume; // private 사용
    ...
}

이렇게 변경하게 된다면,

 

volume필드를 private을 사용해서 Speaker 내부로 숨겼다. 이제 외부에서 volume필드에 직접 접근할 수 없게 막았다.

 

이 상태에서 외부(SpeakerMain)에서 volume 필드로 직접 접근하려고 하면 컴파일 오류가 발생한다.

public class SpeakerMain {
    public static void main(String[] args) {
        ...

        //필드 직접 접근
        System.out.println("volume 필드 직접 접근 수정");
        speaker.volume = 200; // 컴파일 오류 발생
        speaker.showVolume();
    }
}

3. 접근 제어자 종류

자바는 4가지 종류의 접근 제어자를 제공한다.

  • private : 모든 외부 호출을 막는다.
  • default(package-private) : 같은 패키지안에서 호출은 허용한다.
  • protected : 같은 패키지안에서 호출은 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.(상속관계 배울 때 더 자세히 알아보는 걸로)
  • public : 모든 외부 호출을 허용한다.

접근 제어자 예시

public class Speaker {// 클래스 레벨
    private int volume;// 필드

    Speaker(int volume) {} // 생성자

    void volumeUp() {} // 메서드
    void volumeDown() {}
    void showVolume() {}
}

접근 제어자의 핵심은 속성과 기능을 외부로부터 숨기는 것이다.

  • 'private' 은 나의 클래스 안으로 속성과 기능을 숨길 때 사용, 외부 클래스에서 해당 기능을 호출할 수 없다.
  • 'default' 는 나의 패키지 안으로 속성과 기능을 숨길 때 사용, 외부 패키지에서 해당 기능을 호출할 수 없다.
  • 'protected' 는 상속 관계로 속성과 기능을 숨길 때 사용, 상속 관계가 아닌 곳에서 해당 기능을 호출할 수 없다.

  • 'public' 은 기능을 숨기지 않고 어디서든 호출할 수 있게 공개한다

 

4. 접근 제어자 사용 - 필드, 메서드

다양한 상황에 따른 접근 제어자를 확인해 보자.

package access.a;

public class AccessData {
    public int publicField;
    int defaultField;
    private int privateField;

    public void publicMethod() {
        System.out.println("publicMethod 호출 " + publicField);
    }

    void defaultMethod() {
        System.out.println("defaultMethod 호출 " + defaultField);
    }

    private void privateMethod() {
        System.out.println("privateMethod 호출 " + privateField);
    }

    public void innerAccess() {
        System.out.println("내부 호출");
        publicField = 100;
        defaultField = 200;
        privateField = 300;
        publicMethod();
        defaultMethod();
        privateMethod();
    }
}

마지막에 innerAccess()가 있는데, 이 메서드는 내부 호출을 보여준다. 내부 호출은 자기 자신에게 접근하는 것이다. 따라서 private을 포함한 모든 곳에 접근할 수 있다.

 

이제 외부에서 이 클래스에 접근해 보자.

package access.a;

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은 모든 접근을 허용하기 때문에 필드, 메서드에 접근 가능하다.
  • dafault는 같은 패키지에서 접근 가능하으로 접근할 수 있다.
  • private은 AccessData 내부에서만 접근할 수 있으므로 호출 불가이다.

다른 패키지에서 접근하는 예시도 보며 비교해 보자.

package access.b;

import access.a.AccessData;

public class AccessOuterMain {
    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();

    }
}

5. 접근 제어자 사용 - 클래스 레벨

클래스 레벨의 접근 제어자 규칙

  • 클래스 레벨의 접근 제어자는 public, default만 사용할 수 있다.
    • private, protected는 사용할 수 없다.
  • public 클래스는 반드시 파일명과 이름이 같아야 한다.
    • 하나의 자바 파일에 public 클래스는 하나만 등장할 수 있다.
    • 하나의 자바 파일에 default 접근 제어자를 사용하는 클래스는 무한정 만들 수 있다.

6. 캡슐화

캡슐화(Encapsulation)는 객체 지향 프로그래밍의 중요한 개념 중 하나다. 캡슐화는 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어서 외부에서의 접근을 제한하는 것이다. 캡슐화를 통해 데이터의 직접적인 변경을 방지하거나 제한할 수 있다.

 캡슐화는 쉽게 이야기해서 속성과 기능을 하나로 묶고, 외부에 꼭 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것이다.

 

이전에 객체 지향 프로그래밍을 설명할 때의 캡슐화는 속성과 기능을 하나로 묶는 거에 초점을 맞췄다면, 지금은 거기에 더하여 안전하게 완성(접근 제어자)할 수 있는 것까지 해보자.

 

1. 데이터를 숨겨라

객체에는 속성(데이터)과 기능(메서드)이 있다. 캡슐화에서 가장 필수로 숨겨야 하는 것은 속성(데이터)이다.

객체의 데이터는 객체가 제공하는 기능인 메서드를 통해서 접근해야 한다.

 

2. 기능을 숨겨라

객체의 기능 중에서 외부에서 사용하지 않고 내부에서만 사용하는 기능들이 있다.

사용자 입장에서 꼭 필요한 기능만 외부에 노출하자. 나머지 기능은 모두 내부로 숨기자.

 

캡슐화가 잘 된 예제를 하나 보자.

package access;

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 boolean isAmountValid(int amount) {
        //금액이 0 보다 커야함
        return amount > 0;
    }
}

은행 계좌 기능을 다룬다. 다음과 같은 기능을 가지고 있다.

private

  • balance : 데이터 필드는 외부에 직접 노출하지 않는다. BankAccount가 제공하는 메서드를 통해서만 접근할 수 있다.
  • isAmountValid() : 입력 금액을 검증하는 기능은 내부에서만 필요하다. 

public

  • deposit() : 입금
  • withdraw() : 출금
  • getBalance(): 잔고