공부/Java

자바 접근 제어자

Stair 2024. 8. 31. 22:17
반응형

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

 

스피커의 음량은 절대로 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("음량을 증가합니다.");
        }
    }

    void volumeDown(){
        volume -= 10;
        System.out.println("볼륨 다운 호출");
    }

    void showVolume(){
        System.out.println("volume = " + volume);
    }
}

 

package access;

public class SpeakerMain {

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

        speaker.volumeDown();
        speaker.showVolume();
    }
}

 

이 스피커는 볼륨을 100 이상 올릴 수 없게 설게되어 있다. 이 소리를 100 이상으로 올리게 하려면 어떡해야할까

 

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

    speaker.volumeDown();
    speaker.showVolume();

    System.out.println("volume 필드 직접 접근 수정");
    speaker.volume = 200;
    speaker.showVolume();
}

 

speaker 클래스의 volume 필드를 직접 건드려서 볼륨 소리를 200으로 올릴 수 있었다.

하지만 이 스피커는 최대 볼륨이 100까지인 스피커이다. 앞서 volumeUp()과 같은 메서드를 만들어서 음량이 100을 넘지 못하도록 기능을 개발했지만 소용이 없었다.

 

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

 

접근 제어자를 통해 이 문제를 해결할 수 있다.

volume 필드를 Speaker 클래스 외부에서는 접근하지 못하게 막는 것이다.

public class Speaker {
    private int volume;

 

private 접근 제어자는 모든 외부 호출을 막고 해당 클래스 내부에서만 수정할 수 있게 된다.

만약 이를 무시하고 실행하게 된다면

java: volume has private access in access.Speaker

 

와 같은 에러가 나타나게 된다.

 

 

 

이제 외부에서 직접 접근 하는 것은 불가능하게 변하였다.

 

이러한 접근 제어자는 총 4종류가 있다.

private : 모든 외부 호출을 막는다.

default : 같은 패키지 안에서 호출만 허용한다.

protected : 같은 패키지 안에서 호출은 허용하고, 패키지가 달라도 상속 관계라면 호출을 허용한다.

public : 모든 외부 호출을 허용한다.

 

private -> default -> protected -> public

 

접근 제어자를 명시하지 않으면 같은 패키지 안에서 호출을 허용하는 default 접근 제어자가 적용된다.

default 라는 용어는 해당 접근 제어자가 기본값으로 사용되기 때문에 붙여진 이름이지만, 실제로는 package-private가 더 정확한 표현이다. 해당 접근 제어자를 사용하는 멤버는 동일한 패키지 내의 다른 클래스에서만 접근이 가능하기 때문이다.

 

접근 제어자 사용 위치는 필드와 메서드, 생성자에 사용된다.

추가로 클래스 레벨에도 일부 접근 제어자를 사용할 수 있다.

 

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

 

 

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

** 이제부터는 패키지 위치가 매우 중요하다. 패키지 위치에 주의하자**

 

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();
    }
}

 

패키지는 access.a 이다.

마지막 innerAccess()는 내부 호출을 보여준다. 내부 호출은 자기 자신에게 접근하는 것이다.

 

package access.a;

public class AccessInnerMain {
    public static void main(String[] args) {
        AccessData data = new AccessData();

        data.publicField = 1;
        data.publicMethod();


        data.defaultField = 2;
        data.defaultMethod();

        data.privateField = 3;
        data.privateMethod();
        
        data.innerAccess();


    }
}

 

다음과 같은 main문을 실행할 때 같은 패키지이기에  public과 default는 정상적으로 동작하나 privateField와 privateMetho()에선 에러가 나는 것을 볼 수 있다

 

C:\Users\한정우\IdeaProjects\java-start\src\access\a\AccessInnerMain.java:14:13
java: privateField has private access in access.a.AccessData

 

저 두 줄을 제외하고 data.innerAccess()를 호출해보자

innerAccess()는  AccessInnerMain 입장에서는 public 메서드이다.

innerAccess에서는 자신의 내부 메서드와 필드에 접근할 수 있기 때문에 private도 정상적으로 호출이 된다.

 

 

package access.b;

import access.a.AccessData;

public class AccessOuterMain {

    public static void main(String[] args) {
        AccessData data = new AccessData();

        data.publicField = 1;
        data.publicMethod();


//        data.defaultField = 2;
//        data.defaultMethod();

//        data.privateField = 3;
//        data.privateMethod();

        data.innnerAccess();

    }
}

다음은 access.b패키지에 소속된 코드이다.

 

public엔 접근이 가능하나 다른 패키지인 defaultField, defaultMethod() 그리고 당연하게도 private까지 접근이 불가하다.

"default는 다른 패키지이기 때문에 호출이 불가능하다."

 

하지만 마찬가지로 AccessOuterMain 입장에서 data.innerAccess() 메서드는 public 메서드이기때문에 호출 가능하다.

**참고로 생성자도 접근 제어자 관점에서 메서드와 동일하다

 

 

클래스 레벨은 접근 제어자를 딱 두가지만 사용할 수 있는데

public 과 default이다. private 및 protected는 사용할 수 없다.

 

public 클래스는 반드시 파일명과 이름이 같아야 한다.

1. 하나의 자바 파일에 public 클래스는 하나만 만들 수 있다(여지껏 해 왔던 class들이 public클래스)

2. 하나의 자바 파일에 default 접근 제어자를 사용하는 클래스는 무한정 만들 수 있다.

 

 

package 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{

}

 

다음과 같은 public 클래스와 default 클래스를 만들었다.

PublicClass는 public접근제어자이기에 파일명과 클래스의 이름이 반드시 같아야 한다. 이 클래스는 public이기 떄문에 외부에서 접근할 수 있다.

DefaultClass1, DefaultClass2는 default 접근 제어자이다. 이 클래스는 default이기 때문에 "같은 패키지 내부에서만 접근할 수 있다"

 

package access.b;

import access.a.DefaultClass1;
import access.a.DefaultClass2;
import access.a.PublicClass;

public class PublicClassOuterMain {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        DefaultClass1 defaultClass1 = new DefaultClass1();
        DefaultClass2 defaultClass2 = new DefaultClass2();
    }
}

 

외부에 있는 b 패키지에서는 접근이 불가능하다

 

C:\Users\한정우\IdeaProjects\java-start\src\access\b\PublicClassOuterMain.java:3:16
java: access.a.DefaultClass1 is not public in access.a; cannot be accessed from outside package

 

 

private : 클래스 내부에서만 가능

default : 같은 패키지에서만 가능

protected : 같은패키지 및 상속받은 클래스에서만 가능

public : 어디에서나 접근 가능

 

 

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

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

 

이전에 데이터와 데이터를 처리하는 메서드를 하나로 모으는 것에 초점을 맞추었다. 여기서 캡슐화를 안전하게 완성할 수 있게 해주는 장치가 접근 제어자이다.

 

1. 데이터를 숨겨라

캡슐화에서 가장 필수로 숨겨야 하는 것은 속성(데이터)이다. 데이터를 외부에서 함부로 접근하면 모든 로직을 무시하고 데이터를 변경할 수 있다.

ex)자동차로 예를 들어보면 우리가 차를 탈 때 엑셀만 밟으면 나머지는 자동차가 알아서 처리해주는 것과 유사하다

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

 

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("유효하지 않은 금액입니다.");
        }
    }

    private boolean isAmountValid(int amount){
        return amount > 0;
    }

    public void withdraw(int amount){
        if(isAmountValid(amount) && balance >= amount){
            balance -= amount;
        }else {
            System.out.println("유효하지 않은 금액이거나 잔액이 부족합니다.");
        }
    }

    public int getBalance(){
        return balance;
    }
}

 

public static void main(String[] args) {
    BankAccount account = new BankAccount();
    account.deposit(-10000);
    account.withdraw(3000);
    System.out.println("balance = " + account.getBalance());


}

 

사용자는 deposit, withdraw, getBalance 메서드 세개만 사용하면 되고 나머지 로직은 알 필요가 없다.

 

 

반응형

'공부 > Java' 카테고리의 다른 글

자바 final  (1) 2024.09.02
자바 메모리 구조와 static  (2) 2024.09.02
자바 패키지  (0) 2024.08.31
자바 생성자  (1) 2024.08.30
자바 객체 지향 프로그래밍  (0) 2024.08.30