참조변수와 인스턴스의 연결
조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자손 클래스에 중복으로 정의했을 때,
조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우는
서로 다른 결과를 얻는다.
메소드의 경우 조상 클래스의 메소드를 자손의 클래스에서 오버라이딩한 경우에도 참조 변수의 타입에 관계없이
항상 실제 인스턴스의 메소드(오버라이딩된 메소드)가 호출되지만,
멤버변수의 경우 참조변수의 타입에 따라 달라진다.
결론부터 말하자면, 멤버변수가 조상과 자손클래스에 중복으로 정의된 경우, 다음과 같이
조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고,
자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.
다음 예시를 살펴보자.
타입은 다르지만 참조변수 p와 c는 모두 Child인스턴스를 참조하고 있다.
그리고 Parent, Child모두 서로 같은 멤버들을 정의하고 있다.
메소드인 method()의 경우 참조변수의 타입에 관계없이 항상 실제 인스턴스의 타입인 Child클래스에 정의된 메소드가 호출되지만, 인스턴스변수인 x는 참조변수의 타입에 따라 달라지는 것을 알 수 있다.
만일, 다음과 같이 Child클래스가 비어있다고 가정해보자.
자손클래스가 비어있다면 모두 조상클래스의 멤버를 사용할 것이다
그래서 다음과 같은 결과를 얻을 수 있다.
이처럼 자손 클래스에서 조상 클래스의 멤버를 중복으로 정의하지 않았을 때는 참조변수의 타입에 따른 변화는 없다.
전에 공부했던 super를 이용하여 이 예제와 응용해보자.
자손 클래스 Child에 선언된 인스턴스변수 x와 조상 클래스 Parent로부터 상속받은 인스턴스변수 x를 구분하는데
참조변수 super와 this가 사용된다.
super.x는 조상 클래스인 Parent에 선언된 인스턴스변수 x를 뜻하며,
this.x또는 x는 Child클래스의 인스턴스변수 x를 뜻한다.
그리고 메소드인 method()는 참조변수의 타입에 관계없이 항상 실제 인스턴스의 타입인 Child클래스에 정의된 메소드가 호출된다.
하지만, Child클래스에 메소드이름이 method2() 처럼 달라진다면 참조변수 p로 호출할 수 없다.
왜냐하면 p는 아무리 Child클래스를 참조하고 있어도 실제 참조타입은 Parent이기 때문이다.
매개변수의 다형성
참조변수의 다형적인 특징은 메소드의 매개변수에도 적용된다.
아래와 같이 Product, Tv, Computer, Audio, Buyer클래스가 정의되어 있다고 가정하자.
class Product{
int price;
int bonusPoint;
}
class Tv extends Product { }
class Computer extends Product { }
class Audio extends Product { }
class Buyer{
int money = 1000;
int bonusPoint = 0;
}
Product클래스는 Tv, Audio, Computer클래스의 조상이며,
Buyer클래스는 제품을 구입하는 사람을 클래스로 표현한 것이다.
Buyer클래스에 물건을 구입하는 기능의 메소드를 추가한다고 가정하면,
구입할 대상이 필요하므로 매개변수로 구입할 제품을 넘겨 받아야 한다.
Tv를 산다고 하면
void buy(Tv t) { } ;
Computer를 산다고 하면
void buy(Computer c) { };
하지만 이렇게 되면, 제품의 종류가 늘어날 때마다 Buyer클래스에는 새로운 buy메소드를 추가해주어야 할 것이다.
따라서 메소드의 매개변수에 다형성을 적용하여 아래와 같이 하나의 메소드로 간단히 처리해야 한다.
void buy(Product p) {
money = money - p.price;
bonusPoint = bonusPoint - p.bonusPoint;
}
매개변수가 Product타입의 참조변수라는 것은, 메소드의 매개변수로 Product클래스의 자손타입의 참조변수면
어느 것이나 매개변수로 받아들일 수 있다는 뜻이다.
그리고 Product클래스에 price와 bonusPoint가 선언되어 있기 때문에
참조변수 p로 price, bonusPoint를 사용할 수 있기에 이와 같이 할 수 있다.
다음 프로그램을 살펴보자.
고객(Buyer)이 buy(Product p) 메소드를 이용해서 Tv와 Computer를 구입하고,
고객의 잔고와 보너스점수를 출력하는 예제이다.
여기서 볼 수 있는 것은 Product타입인 참조변수 p로 Tv클래스와, Computer클래스를 참조했다는 것이다.
이것이 가능한 이유는 모두 Product클래스의 자손타입이기 때문이다.
매개변수의 다형성의 또 다른 예로 PrintStream클래스에 정의되어있는 print(Object obj)메소드를 살펴보자.
print(Object obj)는 매개변수로 Object타입의 변수가 선언되어 있는데
Object는 모든 클래스의 조상이므로 이 메소드의 매개변수로 어떤 타입의 인스턴스도 가능하므로,
이 하나의 메소드로 모든 타입의 인스턴스를 처리할 수 있는 것이다.
여러 종류의 객체를 배열로 다루기
위의 코드에서 구매하는 제품을 배열로 다루면 편할 것이다.
위 그림을 보면 구입한 제품을 저장하기 위한 배열 item을 만들었다.
구입한 제품을 item이라는 배열에 저장하고,
summary메소드를 통해 총 구매한 금액과, 구매한 제품들을 출력할 수 있다.
하지만, 배열의 크기를 10으로 했기 때문에 11개 이상의제품을 구입할 수 없는 것이 문제다.
이런 경우, Vector클래스를 사용하면 된다.
Vector는 동적으로 크기가 관리되는 객체배열이다.
Vector관련된 메소드는 다음과 같다.
Vector() | 10개의 객체를 저장할 수 있는 Vector인스턴스를 생성. (자동적으로 크기 증가) |
boolean add(Object o) | Vector의 객체를 추가. 성공하면 true, 실패하면 false를 반환 |
boolean remove(Object o) | Vector의 객체를 제거 |
boolean isEmpty() | Vector가 비어있는지 검사. 비어있으면 true, 아니면 false를 반환 |
Object get(int index) | 지정된 위치의 객체를 반환. 반환타입이 Object이므로 적절한 형변환 필요 |
int size() | Vector에 저장된 객체의 개수를 반환 |
그럼 Vector를 이용하여 Buyer클래스를 다음과 같이 수정해보겠다.
구입한 제품을 저장하는데 사용될 객체 Vector를 사용했다.
add메소드를 통해 구입한 제품을 Vector에 저장하는 역할을 해주었고,
remove메소드를 통해 구입한 제품을 환불하는 기능을 추가하였다.
또한 summary메소드에 get메소드를 통해 해당 위치의 값을 반환하고,
그 반환타입이 Object타입이기 때문에 Product타입으로 적절히 형변환까지 해주었다.
'JAVA > Basic' 카테고리의 다른 글
[JAVA-basic] 인터페이스 (0) | 2021.08.31 |
---|---|
[JAVA-basic] 추상클래스 (0) | 2021.08.27 |
[JAVA-basic] 다형성 (0) | 2021.08.24 |
[JAVA-basic] 제어자 (0) | 2021.08.20 |
[JAVA-basic] 오버라이딩 (0) | 2021.08.19 |