본문 바로가기

Java/Chapter 09. 정보 은닉 그리고 캡슐화

[Java] 09.02 - 접근 수준 지시자(Access-level Modifiers)

들어가며

 앞서 인스턴스 변수를 대상으로
private 선언을 하였는데

 이러한 유형의 키워드를 가리켜
'접근 수준 지시자'라고 한다.
(Access-level Modifiers)

 이름 그래도 접근의 허용 수준을
결정할 때 선언하는 키워드이다.

 

4가지 종류의 '접근 수준 지시자'

 '접근 수준 지시자'의 종류는
다음과 같이 4가지이다.

 참고로 '참근 수준 지시자'는
다양한 한글 표현이 존재하므로

 영어 표현 'Access-level Modifiers'를
기억해 두는 것이 좋다.

public, protected private, default

 이 중에서 default는 키워드가
아닌,

 '아무런 선언도 하지 않은
상황'을 의미한다.

 비록 이는 키워드가 아닌
일종의 '상황'이지만

 이 역시 '접근 수준 지시자'의
한 종류로 구분을 한다.

 그리고 이러한 선언을 할
수 있는 대상은

 다음 2가지이다.

  • 클래스의 정의
  • 클래스의 인스턴스 변수와 메소드

 클래스의 정의를 대상으로는
다음 두 가지 선언이 가능하다.

public  class A { . . . }
default class B { . . . }

 그리고 인스턴스 변수와
메소드를 대상으로는

 다음 4가지 선언이 모두
가능하다.

public A;
protected B;
private C;
default D;

 그럼 먼저 클래스 정의 대상의

 public과 default 선언이 갖는
의미를 살펴보겠다.

 

클래스 정의 대상의 public과 default 선언이 갖는 의미

 다음과 같이 클래스가
public으로 선언되면

 위치에 상관없이 어디서든

 해당 클래스의 인스턴스를
생성할 수 있다.

public class AAA { . . . }    // 클래스 public 선언

 반면 다음과 같이 default로
선언되면

 동일 패키지로 묶인 클래스
내에서만 인스턴스 생성이
가능하다.

default class AAA { . . . }    // 클래스 default 선언

 정리하면, 클래스 정의에 대한

 public과 default 선언이
갖는 의미는 다음과 같다.

  • public     어디서든 인스턴스 생성이 가능하다.
  • default    동일 패키지로 묶인 클래스 내에서만 인스턴스 생성을 허용한다.

 이와 관련하여 다음 두
파일을 관찰하자.

 이를 통해서 public과
default 선언에 따른

 인스턴스 생성 가능 여부를
코드상에서 확인하자.

package zoo;

// Duck은 default로 선언되었으므로 동일 패키지 내에서만 인스턴스 생성 가능
class Duck{
    //  빈 클래스
}

// Cat은 public으로 선언되었으므로 어디서든 인스턴스 생성 가능
class Cat{
    public void makeCat(){
        // Duck과 같은 패키지로 묶여 있으니 Duck 인스턴스 생성 가능
        Duck quack = new Duck();
    }
}
package animal;

public class Dog {
    public void makeCat(){
        // Cat은 public으로 선언되었으므로 어디서든 인스턴스 생성 가능
        zoo.Cat yaong = new zoo.Cat();
    }

    public void makeDuck(){
        // Duck은 default로 선언되었으므로 이 위치에서 인스턴스 생성 불가
        zoo.Duck quack = new zoo.Duck();    // 컴파일 오류 발생 문장
    }
}

 위의 두 파일은 실제 컴파일이
가능하다.

 먼저 Cat.java를 다음과 같이
컴파일 한다.

 그러면 정상적으로 컴파일이
되어 패키지 이름대로

 디렉토리가 생성되고, 그
안에 클래스 파일이 담긴다.

C:\JavaStudy>javac -d . Cat.java

 이어서 다음과 같이 Dog.java를
컴파일 한다.

 이때 컴파일 오류가 발생한다.

 발생의 원인은 Dcuk 인스턴스의
생성 문장에 있다.

 따라서 이 문장을 주석 처리하고
컴파일을 하면

 오류 없이 컴파일을 완료할 수
있다.

 C:\JavaStduy>javac -d . Dog.java

 그리고 클래스의 public 선언과
관련하여

 다음 2가지 사항을 지켜야 한다.

 지키지 않을 경우, 컴파일 오류가
발생한다.

 그래서 위의 두 파일도, 이 2가지
사항을 지키고 있다.

  • 하나의 소스파일에, 하나의 클래스만 public으로 선언한다.
  • 소스파일의 이름과 public으로 선언된 클래스의 이름을 일치시킨다.

 이는 프로그램의 큰 틀을
분석하는 과정에서

 먼저 관찰하게 되는 public
클래스를 중심으로

 소스파일을 형성하기 위함이다.

 그러나 이러한 제약 사항이
없는 다른 객체지향 언어에서도

 오래전부터 이러한 형태로
클래스와 소스파일을 관리해
오고 있었다.

 다만 자바에서는 이를 반드시
지키도록

 문법으로 규정지었을 뿐이다.

 

인스턴스 맴버 대상의 public, protected, private, default 선언

 인스턴스 맴버는 다음과 같이
public, protected, private,
default 중 하나로 선언이 된다.

인스턴스 맴버는 인스턴스 변수 또는 메소드를 일컫는다.

 그리고 앞서 설명했듯이
default는 아무런 선언도
되지 않았음을 의미한다.

class AAA{
    public int num1;        // 인스턴스 변수의 public 선언
    protected int num2;     // 인스턴스 변수의 protected 선언
    private int num3;       // 인스턴스 변수의 private 선언
    int num4;               // 인스턴스 변수의 default 선언
    
    public void md1() {}    // 인스턴스 메소드의 public 선언
    protected void m2() {}  // 인스턴스 메소드의 protected 선언
    private void m3() {}    // 인스턴스 메소드의 private 선언
    void m4() {}            // 인스턴스 메소드의 default 선언
}

 이 중에서 public과 default
선언은

 앞서 클래스의 선언에도
사용됐는데,

 그 의미가 사실상 동일하게
인스턴스 멤버에 적용이 된다.

 그럼 인스턴스 맴버를
대상으로 하는

 이 두 선언의 의미부터
설명하겠다.

 

인스턴스 맴버의 public과 default 선언이 갖는 의미

 인스턴스 맴버 대상의 public과
default 선언이 갖는 의미는
다음과 같다.

  • public     어디서든 접근이 가능하다.
  • default    동일 패키지로 묶인 클래스 내에서만 접근이 가능하다.

 위에서 말하는 '접근'이
변수의 경우

 말 그대로 접근이 되지만,

 메소드의 경우 '호출'을
의미한다.

 그럼 이와 관련하여 다음의
코드를 보자.

package zoo;

public class Cat{
    // public으로 선언된 메소드, 따라서 어디서든 호출 가능
    public void makeSound() {System.out.println("애옹");}
    
    // default로 선언된 메소드, 따라서 동일 패키지로 묶인 클래스 내에서 호출 가능
    void makeHappy() {System.out.println("골골송");}
}
package animal;

public class Dog {
    public void welcome(zoo.Cat c){
        c.makeSound();  // 호출 가능 -> 컴파일 성공
        c.makeHappy();  // 호출 불가 -> 컴파일 에러
    }
}

 위 두 파일도 실제 컴파일이
가능하다.

 먼저 Cat.java를 다음과 같이
컴파일 한다.

C:\JavaStudy>javac -d . Cat.java

 이어서 다음과 같이 Dog.java를
컴파일 한다.

C:\JavaStudy>javac -d . Dog.java

 이때 makeHappy 메소드의
호출 문장에서 컴파일 오류가
발생한다.

 따라서 오류 없이 컴파일을
완료하기 위해서는

 다음 3가지 중 하나를 선택해
코드를 수정해야 한다.

  • Dog.java의 패키지를 zoo로 수정하여 컴파일 한다.
  • makeHappy 메소드를 public으로 선언한다.
  • makeHappy 메소드 호출문을 주석 처리한다.

 이 중에서 Dog.java의 패키지를
zoo로 수정하여 컴파일하면

 기존에 생성된 zoo 패키지에
Dog 클래스가 추가되어

 Cat 클래스와 동일한 패키지로
묶이게 된다.

 따라서 default로 선언된 메소드의
호출이 가능하게 된다.

 

인스턴스 맴버의 private 선언이 갖는 의미

 '정보 은닉'을 설명하면서
private 선언의 기능과

 그 의미를 이미 설명하였다.

 따라서 다음 클래스의
정의를 통해

 내용을 정리하는 수준에서
private에 대한 설명을
마무리하겠다.

public class Duck{
    private int numLeg = 2;    // 클래스 내부에서만 접근 가능

    public void md1(){
        System.out.println(numLeg);     // 접근 가능
        md2();  // 호출 가능
    }

    private void md2(){
        System.out.println(numLeg);     // 접근 가능
    }

    void m3(){
        System.out.println(numLeg);     // 접근 가능
        md2();  // 호출 가능
    }
}

 위의 클래스 정의에서 보이듯이,
private로 선언된 변수 numLeg는

 동일 클래스에 정의된 메소드
내에서만 접근이 가능하다.

 마찬가지로 private로 선언된
메소드 md2도

 Duck 클래스에 정의된 메소드
내에서만 호출이 가능하다.

 

인스턴스 맴버의 protected 선언이 갖는 의미

'상속'을 학습한 후에 공부하면 더 효과적이다.

 인스턴스 맴버의 protected
선언이 갖는 의미는

 다음과 같이 default 선언과
비교해서 생각하면 이해가
빠르다.

protected 선언은 default 선언이
허용하는 접근을 모두 허용한다.

더불어 protected는 default가 허용하지 않는
'한 영역'에서의 접근도 허용한다.

 따라서 인스턴스 맴버 중에,
default로 선언되면

 접근을 허용하지 않지만,
protected로 선언되면

 접근을 허용하는 그 '한 영역'이
어디인지만 알면

 protected에 대한 이해도
끝이 난다.

 그런데 그 한 영역은 '클래스
상속 관계'에서 만들어진다.

 따라서 protected 선언의
의미는

 '클래스의 상속'을 학습한
이후에 공부할 것을 권하고
싶다.

 하지만 지금 이해를 원하는
경우를 감안하여

 최대한 쉽게 설명을 이어가겠다.


 다음은 상속 관계에 있는
두 클래스를 보여준다.

 AAA클래스를 ZZZ클래스가
상속하고 있는 상태이다.

 이때 AAA 클래스의 모든
인스턴스 변수와 메소드들은

 상속으로 인하여 ZZZ
클래스의 맴버가 된다.

/* AAA.java */

public class AAA{
    int num;
}
/* ZZZ.java */

public class ZZZ extends AAA{   // extends AAA는 클래스 AAA의 상속을 의미함
    public void init(int n){
        num = n;    // 상속된 변수 num의 접근
    }
}

 ZZZ 클래스의 메소드 init을
보면

 자신의 멤버로 선언되지 않은
변수 num에 접근하고 있다.

 이것이 가능한 이유는 변수
num이 멤버로 상속되었기
때문이다.

 즉 AAA를 상속한 ZZZ의
인스턴스를 생성하게 되면

 인스턴스 내에는 ZZZ에
선언된 변수와 메소드만
존재하는 것이 아니라

 AAA에 선언된 변수와
메소드도 함께 존재하게
된다.


 그럼 이제 접근 관계에 대해
이야기하겠다.

 위와 같이 아무런 패키지 선언이
되지 않은 ZZZ 클래스는

 '디폴트 패키지(Default Package)'로
묶인 상태가 된다.

 즉 AAA와 ZZZ는 디폴트라는
이름의 동일 패키지로 묶인
관계이니

 AAA 클래스의 변수 num의
접근에는 문제가 없다.

 하지만 다음과 같이 두 클래스를
서로 다른 패키지로 선언하면

 상황은 달라진다.

/* AAA.java */

package alpha;

public class AAA{
    int num;
}
/* ZZZ.java */

// 클래스 AAA가 alpha 패키지로 묶였으므로 alpha.AAA가 되었다.
public class ZZZ extends alpha.AAA{
    public void init(int n){
        num = n;    // 상속된 변수 num의 접근
    }
}

 AAA 클래스가 패키지 alpha로
묶였다.

 반면에 ZZZ 클래스는 여전히
디폴트 패키지에 묶인 상태다.

 결론적으로 이 두 클래스는
서로 다른 패키지로 묶여
있는 상태이다.

 이 상태에서 먼저 다음과
같이 AAA.java를 컴파일한다.

C:\JavaStudy>javac -d . AAA.java

 그리고 이어서 다음과 같이
ZZZ.java를 컴파일 한다.

 그런데 이때 컴파일 오류가
발생한다.

 C:\JavaStudy>javac ZZZ.java

 오류의 원인은 다음과 같다.

 AAA와 ZZZ가 묶인 패키지가
다르기 때문에

 이러한 접근이 허용이 안 된다.

default로 선언된 클래스 AAA의 멤버
num을 클래스 ZZZ에서 접근하였다.

 그런데 AAA 클래스의 인스턴스
변수 num을

 다음과 같이 protected로 선언하면
정상적으로 컴파일이 된다.

protected int num;    // 상속 관계에 있는 클래스에서 접근 가능

 이제 default는 허용하지 않지만
protected는 허용하는,

 그 '한 영역'이 어디인지 확인이
되었다.

 그 '한 영역'에 대해 정리하면
다음과 같다.

protected로 선언된 멤버는 상속 관계에 있는
다른 클래스에서 접근 가능하다.

 그리고 이러한 접근은 상속
관계에 있는 두 클래스가

 서로 다른 패키지로 묶여
있어도 가능하다.

 

인스턴스 멤버를 대상으로 하는
public, protected, private, default 선언에 대한 정리

 지금까지 인스턴스 멤버를
대상으로 하는

 4가지 '접근 수준 지시자'에
대해서 설명했는데,

 이들 각각이 허용하는 접근의
수준을 정리하면 다음과 같다.

그림 50 - 접근 수준 지시자의 접근 허용 범위

 위의 표에서 말하는 '이외의
영역'은

 '다른 패키지에 속한 클래스'를
뜻한다.

 즉, 서로 다른 패키지에 속한
두 클래스 사이의 접근을
의미한다.

 그리고 위 표의 내용을 기준으로
접근 허용 범위에 대하여

 다음과 같이 이해하고 있는
것도 도움이 된다.

public > protected > default > private

참고 및 출처

윤성우의 열혈 Java 프로그래밍
국내도서
저자 : 윤성우
출판 : 오렌지미디어 2017.07.05
상세보기