Technology/Java

캡슐화(정보 은닉)를 위한 Java의 접근 제어자 이해하기

ikjo 2022. 2. 5. 16:36

캡슐화란?

객체 지향에는 대표적인 4대 특성이 있는데 바로 추상화, 상속, 다형성, 캡슐화이다. 이 중 캡슐화는 정보 은닉이라고도 하는데, 서로 관련 있는 속성과 메서드를 클래스에 하나로 묶고 사용자가 필요로 하는 인터페이스만 제공하고, 나머지 내부적으로 구현된 내용은 외부로부터 감추는 것을 의미한다.

 

이덕에 사용자 입장에서는 별도로 복잡한 코드를 실행하지 않고(사용자의 실수 방지) 특정 인터페이스만 호출함으로써 원하는 결과를 얻을 수 있게 하며, 객체 입장에서는 클래스 내부에 선언된 데이터를 보호(수정 방지)할 수 있게 된다. 아울러 외부에 공개되지 않은 멤버의 경우 해당 클래스 외부로의 파급 효과가 없으므로 수정이 용이하다는 장점도 있다.

 

이때 자바에서 구현 내용을 내부에 감추는 방법으로 접근 제어자(private, default, protected, public)를 사용할 수 있다.

 

 

접근 제어자란?

접근 제어자를 사용하면 멤버(속성, 메서드, 생성자) 또는 클래스에 대한 외부의 접근 권한을 지정할 수 있다. 자바에서 제공하는 접근 제어자로는 private, default, protected, public이 있으며 각각의 접근 제어자별 특징은 다음과 같다.

 

접근 제어자 외부의 접근 권한 특성
public 접근 제한 없음(어디서든 접근 가능)
default 같은 패키지 내의 클래스에서만 접근 가능
protected 서브 클래스가 슈퍼 클래스에 접근 가능하며,
같은 패키지 내의 클래스에서도 접근 가능
private 같은 클래스 내에서만 접근 가능

 

참고로 접근 제어자를 생략할 경우 default인 것으로 간주하며 접근 제어자 private의 경우 같은 클래스의 인스턴스끼리는 서로 접근 가능하다. 또한 접근 제어자 protected의 경우 자신(슈퍼 클래스)과 상속 관계에 있는 서브 클래스에서 접근 가능하지만 같은 패키지 내의 클래스에서도 접근이 가능하므로 접근 제어자 default와 잘 구별하여 사용하여야 한다.

 

 

멤버 접근 방법

우선 상황별 접근 제어자의 접근 권한 특성을 알아보기 전에 자바에서 임의의 멤버에 접근할 수 있는 방법에 대해 알아보자. 멤버에 접근하는 방법은 다음과 같이 총 4가지로 인스턴스 멤버에 접근하는지 또는 정적 멤버에 접근하는지에 따라 다르다.

 

이때 접근 권한이 허용된다는 가정하에 임의의 클래스에 멤버 변수 value(int형)가 있을 때 이 멤버 변수에 접근 하는 방법을 알아보자.

 

  1. value = 1;
    • 멤버 변수명만으로 지역 변수 또는 멤버 변수에 접근하는 방식으로 자기 자신의 클래스의 멤버 변수에 접근하거나 슈퍼 클래스의 멤버 변수에 접근할 때 사용된다. 이때 지역 변수명과 멤버 변수명이 동일할 때는 지역 변수에 우선하여 접근한다.
  2. this.value = 1;
    • this는 인스턴스 자신을 가리키는 참조 변수로서 "this.멤버 변수명" 형태로 멤버 변수에 접근하는 방식으로 자기 자신의 클래스의 멤버 변수에 접근하거나 슈퍼 클래스의 멤버 변수에 접근할 때 사용된다. 1번 방법과 별다른 차이가 없지만 매개변수와 멤버변수가 이름이 같을 경우 주로 사용된다. 다만 정적 메서드에서는 this를 사용할 수 없다.
  3. ClassA classA = new ClassA(); → classA.value = 1;
    • 클래스의 인스턴스(객체)를 생성한 후 "참조 변수명.멤버 변수명" 형태로 멤버 변수에 접근하는 방식으로 인스턴스 메서드나 정적 메서드에서와 상관없이 인스턴스 멤버 변수와 정적 멤버 변수에 모두 접근할 수 있다.
  4. ClassA.value = 1;
    • "클래스명.멤버 변수명" 형태로 멤버 변수에 접근하는 방식으로 인스턴스 메서드나 정적 메서드에서와 상관없이 정적 멤버 변수에 직접 접근할 때 사용할 수 있다.

 

※ 지금부터 각각의 접근 방식을 편의상 1 ~ 4 번호로 설명하고있으므로 각 번호별 접근 방식을 상기해주세요!

 

접근 제어자별 '인스턴스 멤버 변수' 접근하기

다음과 같이 두 개의 패키지가 있고 각각의 패키지 안에 클래스들이 존재하고 각각의 클래스(ClassA~ClassE)에는 인스턴스 멤버인 getValue() 메서드와 정적 멤버인 getStaticValue() 메서드가 있다고 가정했을 때, 각 클래스별 메서드별로 ClassA에 있는 각기 다른 접근 제어자가 선언된 4개의 인스턴스 멤버 변수(int형이라고 가정)에 대한 접근 권한 특성을 알아보자.

 

 

ClassA → ClassA

메서드 접근 제어자별 ClassA의 인스턴스 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() O O O O 1, 2, 3
getStaticValue() O O O O 3

 

ClassA 내부에서는 접근 제어자와 상관없이 ClassA 모든 인스턴스 멤버에 접근할 수 있다. 다만 인스턴스 멤버 메서드인 getValue()의 경우 3번 방법뿐만 아니라 1번, 2번의 방법으로도 접근할 수 있는 반면 정적 메서드인 getStaticValue()의 경우 3의 방법으로밖에 접근할 수 없다. 왜냐하면 정적 메서드는 기본적으로 인스턴스 멤버들에 접근할 수 없도록 되어있기 때문이다. 이는 정적 메서드는 인스턴스를 생성하지 않고도 호출될 수 있으므로 정적 메서드가 호출된 시점에 인스턴스가 존재하지 않을 수도 있기 때문이다. 그리하여 3번 방법처럼 인스턴스를 생성한 후에 인스턴스 멤버들에 접근하는 것은 가능한 것이다.

 

ClassB → ClassA

메서드 접근 제어자별 ClassA의 인스턴스 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() X O O O 3
getStaticValue() X O O O 3

 

ClassB는 ClassA와 상속 관계는 아니지만 같은 패키지 내에 있으므로 private인 경우를 제외하고 모든 경우에 접근이 가능하다. 다만 ClassB는 ClassA와 분리되어 있으므로 인스턴스 메서드나 정적 메서드와 상관없이 3번 방법처럼 ClassA의 인스턴스를 생성한 후 인스턴스 참조 변수명을 통해서만 인스턴스 멤버에 접근 가능하다.

 

ClassC → ClassA

메서드 접근 제어자별 ClassA의 인스턴스 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() X O O O 1, 2, 3
getStaticValue() X O O O 3

 

ClassC는 ClassA와 상속 관계이며 같은 패키지 내에 있으므로 private인 경우를 제외하고 모든 경우에 접근이 가능하다. 또한 ClassC는 ClassA의 인스턴스 멤버를 상속받아 마치 같은 클래스 내에서 접근하는 것과 같은 효과를 내어 인스턴스 메서드로 접근 시 1번, 2번 방법으로도 접근이 가능하다. 정적 메서드는 앞서 언급한대로 인스턴스 멤버에는 ClassA의 인스턴스 생성(3번 방법) 없이는 접근할 수 없다.

 

ClassD → ClassA

메서드 접근 제어자별 ClassA의 인스턴스 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() X X O O 1, 2, 3
getStaticValue() X X O O 3

 

ClassD는 ClassA와 다른 패키지에 있으므로 private인 경우와 default인 경우에는 접근이 불가능하다. 단, ClassA와 상속 관계이므로 protected인 경우에는 접근이 가능하다. ClassC가 접근할 때와 마찬가지로 ClassD 역시 ClassA의 인스턴스 멤버를 상속받아 마치 같은 클래스 내에서 접근하는 것과 같은 효과를 내어 인스턴스 메서드로 접근 시 1번, 2번 방법으로도 접근이 가능하다. 정적 메서드는 앞서 언급한대로 인스턴스 멤버에는 ClassA의 인스턴스 생성(3번 방법) 없이는 접근할 수 없다.

 

ClassE → ClassA

메서드 접근 제어자별 ClassA의 인스턴스 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() X X X O 3
getStaticValue() X X X O 3

 

ClassE는 ClassA와 다른 패키지에 있는데다가 상속 관계도 아니므로 public인 경우를 제외하고는 접근이 불가능하다. 또한 ClassE는 ClassA와 분리되어 있으므로 인스턴스 메서드나 정적 메서드와 상관없이 ClassA의 인스턴스를 생성한 후 인스턴스 참조 변수명을 통해서만(3번 방법) 인스턴스 멤버에 접근 가능하다.

 

 

접근 제어자별 '정적 멤버 변수' 접근하기

위에서와 동일하게 두 개의 패키지가 있고 각각의 패키지 안에 클래스들이 존재하고 각각의 클래스(ClassA~ClassE)에는 인스턴스 멤버인 getValue() 메서드와 정적 멤버인 getStaticValue() 메서드가 있다고 가정했을 때, 각 클래스별 메서드별로 ClassA에 있는 각기 다른 접근 제어자가 선언된 4개의 정적 멤버 변수(int형이라고 가정)에 대한 접근 권한 특성을 알아보자.

 

 

ClassA → ClassA

메서드 접근 제어자별 ClassA의 정적 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() O O O O 1, 2, 3, 4
getStaticValue() O O O O 1, 3, 4

 

ClassA 내부에서는 접근 제어자와 상관없이 ClassA 모든 인스턴스 멤버에 접근할 수 있다. 이때 인스턴스 메서드에서는 어떠한 방법으로도 정적 멤버 변수에 접근할 수 있는 반면, 정적 메서드는 this를 사용하는 방법(2번 방법)을 통해서는 정적 멤버 변수에 접근할 수 없다.

 

ClassB → ClassA

메서드 접근 제어자별 ClassA의 정적 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() X O O O 3, 4
getStaticValue() X O O O 3, 4

 

ClassB는 ClassA와 상속 관계는 아니지만 같은 패키지 내에 있으므로 private인 경우를 제외하고 모든 경우에 접근이 가능하다. 이때 ClassB는 ClassA와 분리되어 있으므로 인스턴스 메서드나 정적 메서드와 상관없이 ClassA의 인스턴스를 생성한 후 인스턴스 참조 변수명을 통해서 정적 멤버에 접근(3번 방법)하거나 클래스에 직접 접근(4번 방법)할 수 있다.

 

ClassC → ClassA

메서드 접근 제어자별 ClassA의 정적 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() X O O O 1, 2, 3, 4
getStaticValue() X O O O 1, 3, 4

 

ClassC는 ClassA와 상속 관계이며 같은 패키지 내에 있으므로 private인 경우를 제외하고 모든 경우에 접근이 가능하다. 또한 ClassC는 ClassA의 정적 멤버를 상속받아 마치 같은 클래스 내에서 접근하는 것과 같은 효과를 내어 인스턴스 메서드로 접근 시 this를 포함한 모든 방법을 통해서 접근이 가능하다. 반면, 정적 메서드는 this를 사용하는 2번 방법을 통해서는 정적 멤버 변수에 접근할 수 없다.

 

ClassD → ClassA

메서드 접근 제어자별 ClassA의 정적 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() X X O O 1, 2, 3, 4
getStaticValue() X X O O 1, 3, 4

 

ClassD는 ClassA와 다른 패키지에 있으므로 private인 경우와 default인 경우에는 접근이 불가능하다. 단, ClassA와 상속 관계이므로 protected인 경우에는 접근이 가능하다. ClassC가 접근할 때와 마찬가지로 ClassD 역시 ClassA의 정적 멤버를 상속받아 마치 같은 클래스 내에서 접근하는 것과 같은 효과를 내어 인스턴스 메서드로 접근 시 this를 포함한 모든 방법을 통해서 접근이 가능하다. 반면, 정적 메서드는 this를 사용하는 2번 방법을 통해서는 정적 멤버 변수에 접근할 수 없다.

 

ClassE → ClassA

메서드 접근 제어자별 ClassA의 정적 멤버 변수 접근 가능 유무 접근 방법
private default protected public
getValue() X X X O 3, 4
getStaticValue() X X X O 3, 4

 

ClassE는 ClassA와 다른 패키지에 있는데다가 상속 관계도 아니므로 public인 경우를 제외하고는 접근이 불가능하다. 또한 ClassE는 ClassA와 분리되어 있으므로 인스턴스 메서드나 정적 메서드와 상관없이 ClassA의 인스턴스를 생성한 후 인스턴스 참조 변수명을 통해서 정적 멤버에 접근(3번 방법)하거나 클래스명을 통해 클래스에 직접 접근(4번 방법)할 수 있다.

 

 

일관된 형식으로 정적 멤버 접근하기

지금까지 여러 상황에서 인스턴스 멤버와 정적 멤버에 접근해보았는데, 접근 권한이 허용된다는 가정하에 종합해보면 1번과 2번 방법은 상황에따라 접근할 수도 있고 못할 수도 있었다. 또한 인스턴스를 생성하여 참조 변수명을 통해서 접근하는 방식(3번 방법)은 인스턴스 멤버나 정적 멤버에 어느 상황에서나 접근할 수 있었는데, 만일 상속을 받지 않았고 인스턴스 멤버에 접근하는 경우 이 방식을 채택해야 한다. 마지막으로 클래스명을 통해 접근하는 방식(4번 방법)은 정적 멤버에 어느 상황에서나 접근할 수 있었다.

 

이때 정적 멤버에 접근할 때는 3번 방법 보다는 4번 방법을 통해 일관적으로 접근하는 것이 좋다. 왜냐하면 4번 방법을 통해 클래스명을 통해 접근하면 바로 정적 멤버에 접근할 수 있는데 굳이 해당 클래스의 인스턴스를 생성하고 인스턴스 참조 변수를 통해 접근한다면 메모리의 물리적 자원이 낭비되기 때문이다.

 

 

참고자료

  • https://ko.wikipedia.org/wiki/%EC%BA%A1%EC%8A%90%ED%99%94
  • 위키북스 "객체지향의 사실과 오해"
  • 위키북스 "스프링 입문을 위한 자바 객체 지향의 원리와 이해"
  • 도우출판 "자바의 정석"