본문 바로가기
Java/Basic

다형성(polymorphism) - 참조변수와 인스턴스의 연결

by 최로이 2021. 4. 1.

부모 타입의 참조변수와 자식 타입의 참조변수의 차이는 사용할 수 있는 멤버의 개수다. 만약 부모 클래스에 선언된 멤버변수와 동일한 이름의 인스턴스변수가 자식 클래스에도 정의되어 있을 때, 부모 타입의 참조변수로 자식 인스턴스를 참조하는 경우와 자식 타입의 참조변수로 부모 타입의 인스턴스를 참조하는 경우에는 서로 다른 결과를 얻는다.

메소드의 경우 부모 클래스의 메소드를 자식 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메소드(오버라이딩한 메소드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.

static메소드는 static변수처럼 참조변수의 타입에 영향을 받는다. 참조변수의 타입에 영향을 받지 않는 메소드는 인스턴스 메소드뿐이다. 그래서 static메소드는 반드시 참조변수가 아닌 '클래스명.메소드()'로 호출해야 한다.

즉, 멤버변수가 부모 클래스와 자식 클래스에 중복으로 정의된 경우에는 다음과 같다.

① 부모타입의 참조변수를 사용했을 때는 부모 클래스에 선언된 멤버변수가 사용된다.
② 자식타입의 참조변수를 사용했을 때는 자식 클래스에 선언된 멤버변수가 사용된다.

참조변수 타입에 따른 멤버변수 결과값 예제)

class BindingTest{
	public static void main(String[] args) {
		Parent p = new Child();
		Child  c = new Child();

		System.out.println("p.x = " + p.x);
		p.method();

		System.out.println("c.x = " + c.x);
		c.method();
	}
}

class Parent {
	int x = 100;

	void method() {
		System.out.println("Parent Method");
	}
}

class Child extends Parent {
	int x = 200;

	void method() {
		System.out.println("Child Method");
	}
}
-----------------
//결과
p.x = 100
Child Method
c.x = 200
Child Method

참조변수 p와 c의 타입은 서로 다르지만 두 개의 참조변수 모두 Child 인스턴스를 참조하고 있다. 그리고 Parent, Child클래스는 서로 같은 멤버들을 정의하고 있다. 이 때 부모타입의 참조변수 p와 자식타입의 참조변수 c에서 Child인스턴스의 멤버들을 사용하는데 차이가 있다는 걸 알 수 있다.

메소드인 method()의 경우 참조변수의 타입에 관계없이 항상 실제 인스턴스의 타입인 Child클래스에 정의된 메소드가 호출되지만 인스턴스 변수인 x는 참조변수의 타입에 따라서 달라진다.

 

참조변수 타입과 상관없는 메소드의 결과값과 자식 타입의 멤버가 정의되어 있지 않을 때의 부모 멤버 상속 예제)

class BindingTest2 {
	public static void main(String[] args) {
		Parent2 p = new Child2();
		Child2  c = new Child2();

		System.out.println("p.x = " + p.x);
		p.method();

		System.out.println("c.x = " + c.x);
		c.method();
	}
}

class Parent2 {
	int x = 100;

	void method() {
		System.out.println("Parent Method");
	}
}

class Child2 extends Parent2 { }
-----------------
//결과
p.x = 100
Parent Method
c.x = 100
Parent Method

만약 Child클래스에 아무런 멤버도 정의되어 있지 않다면 부모로부터 멤버들을 상속받아 사용한다. 위 예제는 자식과 부모 클래스의 멤버를 중복으로 정의하지 않았을 때 참조변수의 타입에 따른 변화는 없는 것을 볼 수 있다. 부모와 자식간의 어떤 멤버가 호출되어야 할 지 헷갈릴 필요 없이 선택의 여지가 없기 때문이다. 참조변수의 타입에 따라 결과가 달라지는 경우는 부모 클래스의 멤버변수와 같은 이름의 멤버변수를 자식 클래스에 중복해서 정의한 경우뿐이다.

 

마지막으로 부모와 자식타입의 참조변수에 같은 인스턴스를 가리키고 있고 같은 이름의 멤버변수를 가지고 있을 때 수월하게 구분할 수 있는 방법은 무엇일까? 바로 super와 this다. 자식인 Child클래스에서 부모 클래스에 선언된 인스턴스변수를 사용하려면 'super.참조변수'명의 형태를 사용하고, Child클래스의 인스턴스변수를 사용하기 위해서는 'this.참조변수'와 같이 사용해야 한다.

 

super, this 사용 예제)

class BindingTest3{
	public static void main(String[] args) {
		Parent5 p = new Child5();
		Child5  c = new Child5();

		System.out.println("p.x = " + p.x);
		p.method();
		System.out.println();
		System.out.println("c.x = " + c.x);
		c.method();
	}
}

class Parent5 {
	int x = 100;

	void method() {
		System.out.println("Parent Method");
	}
}

class Child5 extends Parent5 {
	int x = 200;

	void method() {
		System.out.println("x=" + x);  // this.x와 같다.
		System.out.println("super.x=" + super.x);
		System.out.println("this.x="  + this.x);
	}
}
-----------------------
//결과
p.x = 100
x=200
super.x=100
this.x=200

c.x = 200
x=200
super.x=100
this.x=200

다만 멤버변수들은 주로 private으로 접근을 제한하고, 외부에서는 메소드를 통해서만 멤버변수에 접근할 수 있기 때문에 보통은 외부 클래스에서 참조변수를 통해 직접적으로 인스턴스변수에 접근할 수 있도록 설계하지 않는다. 만약 인스턴스변수에 직접 접근하면 참조변수의 타입에 따라 사용되는 인스턴스변수가 달라질 수 있으므로 주의해야 한다.

댓글