처음부터 차근차근

[Java] 제어자, 접근 제어자 본문

Language/Java

[Java] 제어자, 접근 제어자

HangJu_95 2023. 12. 26. 15:15
728x90

제어자(modifier)란?

클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여합니다.

 

제어자는 클래스나 멤버 변수와 메서드에 주로 사용되며, 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능합니다.

  • 접근 제어자 : public, protected, (default), private
  • 그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

접근 제어자 access modifier

멤버 또는 클래스, 메서드에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 합니다.

접근 제어자 종류

  • private : 같은 클래스 내에서만 접근이 가능하다. (모든 외부 호출을 막는다.)
  • default(package-private) : 같은 패키지안에서 호출은 허용한다.
    • 접근 제어자를 명시하지 않으면 같은 패키지 안에서 호출을 허용하는 default 접근 제어자가 적용됩니다.
  • protected: 같은 패키지안에서의 호출은 허용하며, 패키지가 달라도 상속 관계의 호출은 허용한다.
  • public : 모든 외부 호출을 허용한다.

순서대로 private이 가장 많이 차단하고, public이 가장 많이 허용한다

private -> default -> protected -> public

 

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

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

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

먼저 같은 패키지 내 외부 클래스에서 사용해보겠습니다.

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();
    }
}
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();
    }
}
  • 패키지 위치는 package access.a이다. 패키지 위치를 꼭 맞추어야 한다. 주의하자.
  • public은 모든 접근을 허용하기 때문에 필드, 메서드 모두 접근 가능하다.
  • default는 같은 패키지에서 접근할 수 있다. AccessInnerMain은 AccessData와 같은 패키지이다. 따라서 default 접근 제어자에 접근할 수 있다.
  • private은 AccessData 내부에서만 접근할 수 있다. 따라서 호출 불가다.
  • AccessData.innerAccess()메서드는 public이다. 따라서 외부에서 호출할 수 있다.
  • innerAccess()메서드는 외부에서 호출되었지만 innerAccess()메서드는 AccessData에 포함되어있다. 이 메서드는 자신의 private 필드와 메서드에 모두 접근할 수 있다.

실행 결과

publicMethod 호출 1
defaultMethod 호출 2
내부 호출
publicMethod 호출 100
defaultMethod 호출 200
privateMethod 호출 300

 

이제 다른 패키지에서 호출해보겠습니다.

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

        // 다른 패키지여서 defaultField 호출 불가
//        data.defaultField = 2; // 호출 불가
//        data.defaultMethod(); // 호출 불가

        //private 호출 불가
//        data.privateField = 3; // 접근 불가
//        data.privateMethod(); // 접근 불가
        data.innerAccess();
    }
}
  • 패키지 위치는 `package access.b` 이다. 패키지 위치를 꼭 맞추어야 한다. 주의하자.
  • public은 모든 접근을 허용하기 때문에 필드, 메서드 모두 접근할 수 있다.
  • default는 같은 패키지에서 접근할 수 있다. access.b.AccessOuterMain 은
  • access.a.AccessData와 다른 패키지이다. 따라서 default접근 제어자에 접근할 수 없다.
  • private은 AccessData내부에서만 접근할 수 있다. 따라서 호출 불가다.
  • AccessData.innerAccess() 메서드는 public이다. 따라서 외부에서 호출할 수 있다.
  • innerAccess()메서드는 외부에서 호출되었지만 해당 메서드 안에서는 자신의 private 필드와 메서드에 접근할 수 있다.
publicMethod 호출 1
내부 호출
publicMethod 호출 100
defaultMethod 호출 200
privateMethod 호출 300

접근 제어자 사용 - 클래스

  • 클래스 레벨의 접근 제어자는 public, default만 사용할 수 있습니다.
  • public 클래스는 반드시 파일명과 이름이 같아야 합니다.
    • 하나의 자바 파일에 public 클래스는 하나만 등장할 수 있습니다.
    • 하나의 자바 파일에 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 {

}
  • 패키지 위치는 package access.a 이다. 패키지 위치를 꼭 맞추어야 한다. 주의하자.
  • PublicClass 라는 이름의 클래스를 만들었다. 이 클래스는 public 접근 제어자다. 따라서 파일명과 이 클래스의 이름이 반드시 같아야 한다. 이 클래스는 public 이기 때문에 외부에서 접근할 수 있다.
  • DefaultClass1, DefaultClass2는 default접근 제어자다. 이 클래스는 default이기 때문에 같은 패키지 내부에서만 접근할 수 있다.
  • PublicClass의 main()을 보면 각각의 클래스를 사용하는 예를 보여준다.
    • PublicClass는 public 접근 제어다. 따라서 어디서든 사용할 수 있다.
    • DefaultClass1, DefaultClass2와는 같은 패키지에 있으므로 사용할 수 있다.

같은 패키지 내 클래스에서 호출 시

package access.a;

public class PublicClassInnerMain {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        DefaultClass1 class1 = new DefaultClass1();
        DefaultClass2 class2 = new DefaultClass2();
    }
}

다른 패키지 내 클래스에서 호출 시

package access.b;

import access.a.PublicClass;

public class PublicClassOuterMain {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        // 다른 패키지 접근 불가
//        DefaultClass1 class1 = new DefaultClass1();
//        DefaultClass2 class2 = new DefaultClass2();
    }
}

접근 제어자는 왜 사용하는 걸까?

  • 외부로부터 데이터를 보호하기 위해서
  • 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해서

우리가 음악 플레어를 만드는 개발자라고 생각을 해보겠습니다.

만약 이러한 캡슐화가 없다면 코드는 이렇게 작성할 수 있습니다.

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);
    }
}
package access;

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

volume을 0 ~ 100까지 값만 가지고 싶게 설계했는데, 다른 개발자가 볼륨이 작다면서 해당하는 필드에 바로 접근해버릴 수 있습니다.

이것은 Speaker를 만든 개발자의 의도와 맞지 않습니다.

친절하게 메서드를 만들어놨는데, 메서드를 사용하지 않고 필드에 직접 적용하였기 때문에 메서드가 필요 없어집니다.

이런 문제를 근본적으로 해결하기 위해 접근 제어자를 사용해서 막을 수 있습니다.

private int volume;

필드에 접근 제어자를 적용한다면

그림과 같이 외부에서 volume 필드에 직접 접근할 수 없게 막을 수 있습니다. 

만약 직접 접근할려고 한다면 에러가 발생합니다.

캡슐화

캡슐화는 객체 지향 프로그래밍의 중요한 개념 중 하나입니다.

데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어서 외부에서의 접근을 제한하는 것을 의미합니다.

 

위와 같이, 음악 플레이어의 Volume을 필드를 통해 직접 변경하는 것을 막은 것처럼, 캡슐화를 통해 데이터의 직접적인 변경을 방지하고 제한할 수 있습니다.

그렇다면 어떤 것을 숨기고 어떤 것을 노출해야 할까요??

  1. 데이터를 숨겨라
    • 객체에는 속성과 기능이 있는데, 캡슐화에서 가장 필수로 숨겨야 하는 것은 속성(데이터)입니다.
    • 자동차를 운전할 때 자동차 부품을 다 열어서 그 안에 있는 속도계를 직접 조절하지 않습니다. 단지 자동차가 제공하는 엑셀 기능을 사용해서 엑셀을 밟으면 자동차가 나머지는 다 알아서 하는 것입니다.
    • 따라서 객체의 데이터는 객체가 제공하는 기능인 메서드를 통해서 접근해야 합니다.
  2. 기능을 숨겨라
    • 객체의 기능 중에서 외부에서 사용하지 않고 내부에서만 사용하는 기능들이 있습니다. 이런 기능은 모두 감추는 것이 좋습니다.
    • 자동차가 제공하는 복잡한 엔진 조절 기능, 배기 기능을 우리가 알 필요는 없습니다. 단지 엑셀과 핸들 정도의 기능만 알고 있으면 충분합니다.
    • 기능은 꼭 필요한 기능만 노출하는 것이 좋은 캡슐화입니다.

참고

 

김영한의 실전 자바 - 기본편 강의 - 인프런

실무에 필요한 자바 객체 지향의 핵심 개념을 예제 코드를 통해 쉽게 학습합니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문법을 안다

www.inflearn.com

자바의 정석_기초편

'Language > Java' 카테고리의 다른 글

[Java] static  (2) 2023.12.29
[Java] Java 메모리 구조  (1) 2023.12.29
[Java] Package  (1) 2023.12.26
[Java] 생성자, this  (1) 2023.12.26
[Java] 객체 지향 프로그래밍  (1) 2023.12.24