관리 메뉴

클라이언트/ 서버/ 엔지니어 "게임 개발자"를 향한 매일의 공부일지

상속 12 - 업 캐스팅과 instanceof 연산자 본문

프로그래밍 언어/자바

상속 12 - 업 캐스팅과 instanceof 연산자

huenuri 2024. 12. 1. 22:17

새벽에 수업으로 들었던 업캐스팅과 다운캐스팅에 대한 학습을 진행해 보겠다. 캐스팅(casting)이란 타입변환을 말한다. 자바에서 클래스에 대한 캐스팅은 업 캐스팅(upcasting)과 다운 캐스팅(downcasting)으로 나뉜다.


 

 

 

 

업 캐스팅

다음 그림을 살펴보자. 사람을 생물이라고 불러도 되며, 생물을 넣는 박스에 코끼리나 사람을 넣고 박스에 생물을 가리키는 팻말을 사용해도 무방하다. 왜냐하면 사람은 생물을 상속받았기 때문이다.

 

 

이와 비슷하게 자바에서 서브 클래스는 슈퍼 클래스의 속성을 상속받기 때문에 서브 클래스의 객체는 슈퍼 클래스의 멤버를 모두 가진다. 그러므로 서브 클래스의 객체를 슈퍼 클래스의 객체로 취급할 수 있다. 서브 클래스의 객체에 대한 레퍼런스를 슈퍼 클래스 타입으로 변환하는 것을 업캐스팅이라고 한다.

업캐스팅은 슈퍼 클래스의 레퍼런스로 서브 클래스의 객체를 가리키게 한다. 다음 그림의 사례를 살펴보자. 업캐스팅은 main()의 다음 코드에서 발생한다.

Person p;
Student s = new Student();
p = s;		// 업캐스팅

 

 

이 코드에서 슈퍼 클래스 타입의 레퍼런스 p가 서브 클래스 객체(s)를 가리키도록 치환되는 것이 업캐스팅이다. 업캐스팅을 통해 Person 타입의 p는 Student 객체를 가리킨다. 그러나 레퍼런스 p로는 Person 클래스의 멤버만 접근할 수 있다. 왜냐하면 p는 Person 타입이기 때문이다.

왜냐하면 p는 Person 타입이기 때문이다. grade 필드는 Person 클래스의 멤버가 이니기 때문에 다음 문장은 오류이다.

p.grade = "A";	// 컴파일 오류. grade는 Person의 멤버가 아님

 

 

업 캐스팅한 레퍼런스로는 객체 내에 모든 멤버에 접근할 수 없고 슈퍼 클래스의 멤버만 접근할 수 있다. Student 객체가 Person 타입으로 업캐스팅되면, Person 타입의 객체로 취급되며 Student 클래스의 필드나 메서드는 접근할 수 없게 된다. 그리고 업 캐스팅은 다음과 같이 명시적 타입 변환을 하지 않아도 된다. 그것은 Student 객체는 Person 타입이기도 하기 때문이다.

P = (Person)s;	// (Person)을 생략하고, p = s;로 해도 됨

 

이 내용이 솔직히 이해가 되지 않아서 관련 자료를 찾아보며 학습을 진행했다.


 

 

코드 이해

1. p와 s의 관계

  • Person p: Person 타입의 레퍼런스 변수를 선언
    • 이 변수는 Person 클래스 타입의 객체를 참조할 수 있음
  • Student s: Student 타입의 객체를 생성하고 이를 s 변수에 저장
    • Student 클래스는 Person 클래스를 상속받았으므로, Student는 Person의 자식 클래스

2. p = s;

  • 의미: Student 타입 객체 s를 Person 타입 레퍼런스 변수 p로 참조
    • 참조만 변환하는 것이며, 실제 s 객체는 여전히 Student 객체
    • 즉, p는 Student 객체를 가리키지만, Person 타입으로 바라본다.

 

 

"p가 서브 클래스 객체(s)를 가리킨다"는 의미

  • p = s;의 동작
    1. s는 실제로 Student 객체를 참조
    2. p는 Person 타입으로 s(즉, Student 객체)를 참조하게 됨
    3. 따라서 p를 통해 객체에 접근하면, Student 객체를 부모 클래스 Person 타입으로 제한된 관점에서 바라보는 것
  • 즉, *p가 서브 클래스 객체(s)를 가리킨다"**는 말은
    • p가 실제로 Student 객체를 참조하고 있지만, 부모 클래스인 Person 타입으로 사용된다는 뜻

 

 


 

 

 

 

 

다운 캐스팅

업 캐스팅과 반대로 캐스팅하는 것을 다운 캐스팅이라고 하고, 아래 그림은 다운 캐스팅의 사례를 보여준다. 다음은 Student 객체를 Person 타입의 레퍼런스로 가리키는 업캐스팅 코드이다.

Person p = new Student("이재문");	// 업캐스팅

 

 

다운 캐스팅은 이와 반대로 Person 타입의 레퍼런스를 Student 타입의 레퍼런스로 변환하는 것으로, 다음 코드와 같다.

Student s = (Student)p;	  // 다운 캐스팅, (Student)의 타입 변환을 반드시 표시

 

 

이 결과 s를 통해 아래 그림과 같이 Student 객체 전체를 접근할 수 있게 된다. 다운 캐스팅은 업 캐스팅과 달리 명시적으로 타입 변환을 지정해야 한다.

 


 

 

 

 

 

업 캐스팅과 instanceof 연산자

앞의 그림 5-13에서 생물 팻말(레퍼런스)이 가리키는 박스에 들어 있는 객체의 타입이 무엇인지 팻말만 보고는 알 수 없는 것처럼, 업 캐스팅을 한 경우 레퍼런스가 가리키는 객체의 진짜 클래스 타입을 구분하기 어렵다.

다음과 같이 Person 클래스와 이를 상속받는 클래스들이 있다고 하자.

 

 

 

 

다음 코드들은 Person 클래스를 상속받은 객체들을 업캐스팅을 통해 Person 클래스의 레퍼런스 p로 가리키는 것으로, 모두 정상적인 코드이다.

Person p = new Person();
Person p = new Student();	// 업캐스팅
Person p = new Researcher();	// 업캐스팅
Person p = new Professor();	// 업캐스팅

 

 

그렇다면 Person 타입의 레퍼런스 p가 있을 때, p가 Person 객체를 가리키는지, Stuent, Resercher, Professor 객체를 가리키는지 알 수 없다. 다음 그림을 참조해 보자. 이 그림에서 print(Person person) 메서드가 호출되면,

print(p);

 

 

print(Person person) 메서드는 매개변수로 전달받은 person에 어떤 클래스의 객체가 전달되어 왔는지 알 수 없다.

void print(Person person) {
	// person이 가리키는 객체가 Person 타입일 수도 있고,
        // Student, Researcher, 혹은 Professor 타입일 수도 있다.
    .....
}

 

 

오직 아는 것은 Person을 상속받은 객체가 업 캐스팅되어 넘어왔다는 사실이다. 그러므로 print(Person person) 메서드에서는 매개변수 person에 전달된 객체가 어떤 클래스의 객체인지 구별할 방법이 필요하다.

 


 

 

 

 

instanceof 연산자 사용

레퍼런스가 가리키는 객체가 어떤 클래스 타입인지 구분하기 위해, 자바에서는 instanceof 연산자를 두고 있다. instanceof은 이항연산자로서 다음과 같이 사용된다.

레퍼런스 instanceof 클래스명

 

 

 

instanceof 연산자의 결과값은 boolean 값으로 레퍼런스가 가리키는 객체가 해당 클래스 타입의 객체이면 true이고 아니면 false로 계산한다.

그림 5-16을 참고하여 다음 코드를 통해 instanceof를 이해해 보자.

Person jee = new Student();
Person kim = new Professor();
Person lee = new Researcher();
if (jee instanceof Person)	// jee는 Person 타입이므로 true
if (jee instanceof Student)	// jee는 Student 타입이므로 true
if (kim instanceof Student)	// kim은 Stuent 타입이 아니므로 false
if (kim instanceof Professor)	// kim은 Professor 타입이므로 true
if (kim instanceof Researcher)	// Professor 객체는 Researcher 타입이기도 하므로 true
if (lee instanceof Professor)	// lee는 Professor 타입이 아니므로 false

 

 

intanceof는 클래스에만 적용되므로 다음은 오류이다.

if(3 instanceof int)	// 문법 오류. instanceof 객체에 대한 레퍼런스만 사용

 

 

다음 instanceof 연산은 true이다.

if("java" instanceof String)	// true

 

 

사람의 예로 instanceof를 적용해 보자. 황기태가 사람의 인스턴스인가라고 하면 예이다. 또한 황기태는 생물의 인스턴스인가에 대해서도 예이다.


 

 

 

 

instanceof 연산자 활용

instanceof 연산자를 이용하여 상속 관계에 따라 레퍼런스가 가리키는 객체의 타입을 알아본다. 실행 결과는 무엇인가?

 

 

 


 

 

 

 

 

핵심 체크 문제 풀기

 

 

 

문제 1번

코드 분석

  1. A a = new B();
    • B 객체를 부모 클래스 타입인 A로 참조. (업캐스팅)
    • 정상 동작.
  2. B b = (B) a;
    • a가 실제로 B 객체이므로, 다운캐스팅은 정상적으로 수행된다.
  3. if (a instanceof B)
    • a는 B 객체를 참조하고 있으므로, instanceof 결과는 true.
    • 출력: "GO".
  4. if (b instanceof A)
    • b는 B 타입 객체이고, B는 A를 상속받았으므로, instanceof 결과는 true.
    • 출력: "STOP"

 

if (a instanceof b)이다. instanceof 연산자의 오른쪽은 클래스 타입이 와야 한다.


 

오류 설명

  • instanceof의 규칙:
    • instanceof 연산자는 객체(a)가 특정 클래스나 인터페이스의 인스턴스인지 확인하기 위해 사용된다.
    • 오른쪽에는 반드시 클래스 타입 또는 인터페이스 타입이 와야 한다.
    • 하지만, 코드에서는 b(변수)가 들어가 있어, 문법적으로 잘못된 사용이다.

 

 

 

직접 코드 작성하기

 

 

 

 

 

 


 

 

 

 

문제 2

 

주어진 클래스 계층

  • class A : 최상위 클래스
  • class B extends A : A를 상속
  • class C extends A : A를 상속
  • class D extends C : C를 상속

 

조건 분석

  1. b instanceof A
    • b는 B 타입이고, B는 A를 상속하므로, 결과는 true
  2. a instanceof C
    • a는 D 객체를 참조하고 있으며, D는 C를 상속하므로, 결과는 true
  3. a instanceof java.lang.Object
    • 모든 클래스는 Object를 상속받으므로, 결과는 true
  4. (new A()) instanceof D
    • new A()는 A 객체를 생성
    • A는 D와 관계가 없으므로, 결과는 false
  5. (new C()) instanceof A
    • new C()는 C 객체를 생성
    • C는 A를 상속하므로, 결과는 true
  6. (new C()) instanceof C
    • new C()는 C 객체를 생성
    • 당연히 C 객체이므로, 결과는 true

instanceof 결과가 false인 조건은 4번. (new A()) instanceof D


 

 

 

 

학습을 마치고

정말 이해하기 어려운 단원이라 공부하는데 2시간 넘게 걸렸다. 그래도 개념을 어느 정도 이해할 후에 다음 학습으로 넘어가고 싶었다. 공부를 하다가 핵심 체크문제도 코드를 직접 작성하면 좋을 것 같아 앞에서 풀었던 문제도 그런 식으로 코드를 작성한 후 붙여 넣었다.

이 내용은 다음에 한번 더 복습을 해야 할 것 같다.