본문 바로가기
Java/Java Basic

[Java Basic] 21. 클래스 다형성

by Rosmary 2022. 7. 27.
728x90
반응형

 

 

 

 

Java 클래스는 다형성이라는 성질을 띄고 있다. 다형성(Polymorphism)은 여러(多, Poly) 형태(形, morphology)를 가질 수 있는 성질을 말한다. 다형성은 클래스로부터 만들어지는 인스턴스를 다른 클래스의 인스턴스로 변환할 수 있도록 만듦으로써, 하나의 객체가 여러 형태를 띌 수 있도록 만드는 것이다. 지금까지는 인스턴스 생성을 위해 각 클래스 이름으로 참조변수 선언과 생성을 진행해왔기 때문에 "무슨 귀신 씨나락 까먹는 소리를 하는가"라는 의문이 들 수도 있다(필자 역시 이 부분을 배울 때 그런 생각을 했다...)

 

사실 실습 없이 설명하기에는 어려운 부분이 많아 바로 예시와 함께 정리해보려 한다. 

 

 

 

1. 상속관계의 인스턴스 형 변환

 

필자는 기본 패키지에 Main, ParentLee , ChildLee  클래스 파일을 작성하려한다. 그리고 ParentLee 와 ChildLee 는 부모-자식 상속 관계로 맺으려 한다. ParentLee  클래스는 이름, 나이, 성별 및 부모 여부에 대해서만 멤버 변수로 지정한다.

 

 

 

Child 클래스는 ParentLee 클래스의 멤버변수와 매서드를 모두 상속받지만, 두 가지 부분에서 차이점을 가진다. 먼저 인스턴스 정보를 표시하는 printinfo() 매서드만 오버라이딩 시켰다. ParentLee 는 printinfo()에서 "Adult Info"라고 첫 줄에 출력되며, ChildLee 는 printinfo()에서 "Child Info"가 첫 줄에 출력된다. 두 번 째 차이점은 boolean status 값인데, ParentLee는 true를, ChildLee는 false 값으로 초기화가 되어 있다.

 

이 후 Main 함수에서 ParentLee 와 ChildLee  클래스 인스턴스를 하나씩 생성하고 인스턴스를 통해 printinfo() 매서드 및 status 변수 값을 출력해보았다.

 

 

 

 

여기까지는 지금까지 필자가 포스팅을 통해 설명해왔던 내용에서 크게 벗어나지 않는다. 이제, 조금 새로운 방식으로 인스턴스를 생성해보려 한다. ParentLee  클래스는 ChildLee  클래스의 부모 클래스이므로, ChildLee  인스턴스는 부모 클래스의 모든 멤버변수와 매서드를 사용할 수 있게 된다. 따라서, 위의 코드에서 주석처리된 부분 역시 주석을 해제하더라도 에러는 발생하지 않게 된다.

 

 

 

위의 코드를 보면 변수명 child2는 ParentLee 타입의 참조변수다. 그러나 인스턴스는 하위 클래스인 ChildLee 클래스를 호출하여 생성한다. 문제가 되지는 않는 것이 ParentLee Child2는 Parent 클래스 내의 멤버변수와 매서드에만 접근이 가능한데, ChildLee는 Parent의 멤버변수와 매서드를 동일하게 가지고 있기 때문이다.

 

즉, 클래스 사이 상속 관계가 형성되는 경우, 상위 클래스를 참조변수로 하위 클래스 인스턴스를 생성하는 것이 가능해진다. 하지만 반대로 하위 클래스 타입으로 상위 클래스 인스턴스를 생성하는 것은 불가능하다. 하위 클래스에는 상위클래스에 없는 멤버변수와 매서드가 추가로 정의될 수 있기 때문에 상속 관계의 두 클래스가 동일한 멤버변수와 매서드를 가지더라도 변환이 되지 않도록 의도적으로 막아두었기 때문이다.

 

 

 

 

2. 참조 타입과 인스턴스 연결 시, 멤버변수와 매서드 출력

 

그럼, 여기서 의문이 하나 생긴다. child2 변수를 통해 printinfo() 매서드를 호출하면, ParentLee의 printinfo()가 호출될까? 아니면 ChildLee의 printinfo()가 호출될까?

 

 

 

ParentLee 클래스를 통해 printinfo()를 호출했음에도, printinfo()는 ChildLee 클래스 값이 나타나는 것을 확인할 수 있다. 인스턴스가 ChildLee로 생성되었고, printinfo()가 오버라이딩 되었기 때문에, ChildLee의 printinfo()를 참조하게 된 것이다. 만약 ChildLee에 printinfo()가 오버라이딩 되어 있지 않다면, 당연히 ParentLee의 printinfo()가 호출된다.

 

그럼 멤버변수는 어떨까? 앞에서 매서드는 ChildLee를 참조했으니 당연히 ChildLee의 멤버변수값이 출력될 것 같지만, 의외로 Parent의 멤버변수 값이 출력된다.

 

 

 

정리하자면, 상속 관계의 클래스가 존재할 때, 부모 클래스를 참조 타입으로 자손 인스턴스를 생성하는 경우와 자손 클래스를 참조타입으로 자손 인스턴스를 생성하는 경우 멤버변수와 매서드 출력 결과는 서로 다르게 나타난다. 매서드는 오버라이딩이 되어 있지 않다면 참조 타입에 명시된 클래스로부터 매서드를 호출하며, 오버라이딩 된 경우에만 인스턴스 클래스의 매서드를 호출한다. 반면 멤버변수는 오버라이딩의 개념이 존재하지 않기 때문에 어떠한 상황에서도 참조 타입의 클래스로부터 변수 호출을 진행한다. 

 

위와 같이 참조 타입과 인스턴스 형이 불일치한 변수가 나타나는 경우, 클래스 작성 시 멤버 변수는 외부에서 접근을 하지 못하도록 private으로 지정하는 경우가 많다.  인스턴스 변수값과 참조 타입으로 호출한 값 사이 간극으로 인해 예상치 못한 결과가 나타날 수 있기 때문이다. 

 

 

 

3. instanceof 연산자

 

위에서 필자가 만든 인스턴스는 어떤 클래스로부터 나온 인스턴스일까? 위에서 필자는 Parent = new Parent, Child = new Child, Parent = new Child 의 세 가지 형태의 인스턴스를 생성했는데, 각각의 인스턴스가 어떤 클래스의 인스턴스인지 알아보려 한다. 이를 확인하려면 Java의 instanceof라는 연산자를 사용하면 된다. instanceof 연산자 사용 방법은 아래와 같다.

 

참조변수명 instanceof 클래스명

(반환값: boolean)

 

 

 

위의 결과를 보면 parent1은 Parent 클래스의 인스턴스에는 해당되나, Child 클래스 인스턴스는 아니라는 것이 확인된다. 반대로 child 변수들의 경우, 참조 타입(ParentLee, ChildLee)에 상관없이 모두 Parent와 Child의 인스턴스라는 결과가 나타난다. 

 

상속 관계의 클래스가 존재하고, 자식 클래스로부터 인스턴스가 생성되는 경우, 해당 인스턴스는 자식 클래스는 물론이거니와, 자손 클래스가 상속하는 모든 클래스의 인스턴스가 된다. 위의 예시에서는 ParentLee의 인스턴스 포함 결과만 나타났는데, Java의 최상위 클래스로 생성되는 모든 클래스의 부모 클래스가 되는 Object의 경우에도, parent와 child 변수들이 Object 클래스의 인스턴스라는 결과가 나타난다.

 

 

 

클래스의 상속관계를 사용하면, 서로 다른 객체 클래스의 인스턴스를 하나의 배열에 담는 것도 가능하다. 마지막으로 이 부분에 대해 확인해보자.

 

 

 

4. 여러 객체를 배열로 지정하기

 

여러 객체를 배열로 저장하는 것을 시연하기 위해, 한 명의 구매자가 물건을 구입하는 프로그램을 간략하게 만들어보려한다. 우선 Main 및 구매자(Buyer) 클래스를 기본 패키지에 생성하고, 모든 품목 클래스가 부모 클래스로 지정할 품목(Product) 클래스를 product라는 패키지에 별도로 작성한다. 구매할 수 있는 품목은 과일과 문구로 나뉘며, 이들 클래스는 각각 product.fruit, product.stationary 패키지에 작성한다.

 

- product.fruit : Apple, Banana, Cherry

- product.stationary: Pen, Pencil, Notebook

 

 

[ Buyer 클래스 - default package ]

Buyer 클래스 코드 1
Buyer 클래스 코드 2

 

[ Product 클래스 - product 패키지 ]

 

[ Banana 클래스 - product.fruite 패키지 ]

나머지 품목 클래스는 위와 동일한 형태를 띄고 있다.

 

 

Buyer 클래스 코드에서 특이할만한 사항은, 구매한 품목을 각 품목 클래스 배열로 받는 것이 아니라, 모든 품목의 상위 클래스로 지정된 Product를 배열의 참조타입으로 지정한다는 것이다. 따라서 Buyer 클래스의 Product는 Apple, Banana, Cherry, Notebook, Pen, Pencil 클래스의 인스턴스를 모두 저장하는 것이 가능해진다. 

 

Main 함수에서 Buyer와 각 품목 클래스의 인스턴스를 호출한 뒤, 물건을 구매해보자.

 

 

printPurchase() 는 Buyer 클래스 내에 구입 품목 배열인 items 내에 존재하는 Product 인스턴스 이름을 출력하는 역할을 한다. 금액이 초과된 바나나를 제외하면, 문구 품목 세 가지는 구매가 정상적으로 이루어져 모두 items 배열에 저장되어 있음을 마지막 코드에서 확인할 수 있다. 

 

 

 

구매 물품을 취소하고 새 제품을 구매하는 것 역시 정상적으로 이루어짐을 확인할 수 있다.  Java에서 클래스의 상속관계와 다형성을 이용하면, 서로 다른 객체도 하나의 배열로 관리할 수 있게 된다.

 

 

 


 

 

다음 포스팅에서는 Java의 추상클래스와 인터페이스에 대해 알아보려 한다.

 

 

 

Fin.

반응형

댓글