본문 바로가기

Programming/JAVA

상속(5) - protected 접근 제한자(★★)

java logo image

 

 

 

접근 제한자의 종류에는 public, default, private와 더불어 protected가 있습니다. 이 중 public/default/private는 잘 알려져 있지만, protected의 경우에는 상속과 밀접한 관련이 있는 키워드입니다. protected 접근 제한자가 어떤 식으로 상속 클래스에서 동작하는지에 대해서 살펴보도록 하겠습니다. 

 

우선, public과 private의 경우 접근 구분이 명확할 것입니다. public은 대부분의 제한 없이 사용할 수 있으며, private는 오직 해당 클래스에서만 호출과 사용이 가능하다는 점 때문에 기억하기 쉽습니다. default의 경우에는 사용이 public에 가깝지만, 반드시 "같은 패키지 내에서"만 사용이 가능하다는 특징을 갖고 있습니다. 즉 별도의 접근 제한자 선언이 없다면 같은 패키지 내부에서 사용이 가능하다는 것이죠. 

 

접근 제한자 protected는 그 특성이 조금 독특합니다. 일단, 기본적으로 default와 마찬가지로 '기본적으로는 다른 패키지에서는 사용할 수 없습니다.' 하지만, 여기에 예외가 발생합니다. 다른 패키지에 있는 클래스라고 하더라도, 그 클래스가 원본 클래스의 자식 클래스라면 접근이 가능하다는 이야기가 됩니다. 그래서 일각에서는 'public과 default의 중간 정도의' 접근 제한자라는 표현을 사용하기도 하는 것이죠.

 

그리고 한 가지, 더 주의해야 할 특성이 있습니다. 부모 클래스의 생성자가 만일 protected 접근 제한자를 갖는다면, 자식 클래스에서 부모 생성자를 new를 이용해서 호출할 수 없습니다. 앞서 배웠던 내용 중, 자식 클래스의 생성자에서 super( )를 사용하여 직접 부모 생성자를 호출하는 방식을 사용해야 합니다. 

 

 

 


 

간단하지만, 살짝 복잡한 예제를 보겠습니다. 우선 클래스 A와 B는 같은 패키지이며, C와 D는 같은 패키지입니다. 단, A/B와 C/D의 패키지는 각각 다르게 선언했습니다. 

 

package chap06.class00;

public class A {
	protected String field = "a field";
	
	
	protected A() {
		System.out.println("A의 생성자 실행");
		
	}
	
	protected void method() {
		System.out.println("클래스 A의 메서드 실행");
	}
}

 

package chap06.class00;

public class B {
	
	public void method() {
		A a = new A();
		a.field = "value";
		a.method();
	}
	
}

 

package chap06.class01;
import chap06.class00.A;

public class C {
	/*public void method() {
		A a = new A();
		a.field = "value";
		a.method();
	}*/
}

 

package chap06.class01;
import chap06.class00.A;

public class D extends A{
	
	public D() {
		super();
		this.field = "D 생성자에서 대입한 value";
		this.method();
	}
	
}

 

우선 A의 생성자, 필드, 메서드는 모두 protected로 선언했습니다. 우선 클래스 B는 같은 패키지이므로 모두 A의 필드 등을 호출 할 수 있습니다. 하지만 클래스 C의 경우, A클래스를 임포트 진행했지만 사용할 수 없습니다. 하지만 클래스 D의 경우 다른 클래스임에도 불구하고 A를 상속해 자식 클래스가 되었기 때문에 A의 요소들을 호출할 수 있습니다. 

 

아래와 같이 실행 클래스를 실행해 보겠습니다.

 

package chap06.class00;
import chap06.class01.*;

public class ExampleMain {	
	public static void main(String[] args) {
		D d = new D();
		System.out.println(d.field);
		
		A a = new A();
		System.out.println(a.field);
	}
}

/* 출력
A의 생성자 실행
클래스 A의 메서드 실행
D 생성자에서 대입한 value
A의 생성자 실행
a field
*/

 

참고로 생성자 D에서 this를 사용하고 있습니다. 여기서 this는 클래스 D의 객체 인스턴스를 의미하게 되고, 해당 인스턴스가 갖고 있는 field와 method( )를 의미하게 되는데요. 알다시피 클래스 D에는 어떠한 필드와 메서드도 없습니다. 하지만 A를 상속했기 때문에 클래스 D의 객체 인스턴스는 field와 method( )를 자신의 필드와 메서드로서 사용할 수 있게 됩니다.

 

상속 개념에서 설명한 내용이지만, 자식 클래스 D의 객체가 생성되면서 A의 객체 역시 생성되고 자식 클래스 D의 객체 인스턴스는 A의 인스턴스도 함께 연결하여 사용하는 그림을 떠올려 주시면 됩니다. 

 

A라는 클래스의 객체 인스턴스를 별도로 생성하게 되면 이는 별개의 A 클래스의 객체 인스턴스가 되는 것이죠.