[JAVA] 객체 지향 프로그래밍 Ⅱ 정리 - (3) : 다형성

DongGyu Jung·2022년 1월 26일
0

자바(JAVA)

목록 보기
14/60
post-thumbnail

🏃‍♂️ 들어가기 앞서..

본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕

*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.



🎨 다형성 ( polymorphism )

" 여러가지 형태를 가질 수 있는 능력 "
( Java에서는 "한 타입의 참조변수"로 여러 타입 객체를 참조할 수 있도록 )

구체적으로는
" 조상클래스 타입참조변수 " 로
" 자손클래스 인스턴스 " 참조할 수 있도록 하는 것이다.
( 자손클래스 인스턴스 → 조상클래스 타입 참조변수 )

<반대의 경우>
자손타입의 참조변수로
조상타입의 인스턴스 참조하는 것은 불가능하다.
(자손클래스는 확장클래스이기 때문에 " 자신보다 더 소규모 "인 클래스타입 인스턴스 참조 불가)

class Tv {
    boolean power ; 
    int channel ;
    
    void power() { power = !power; }
    void channelUp() { ++channel }
    void channelDown() { --channel }
}

class SmartTv extends Tv {
    String text ;
    void caption() {
        // ~~~
    }
}

위와 같이
조상클래스Tv가 있고
자손클래스SmartTv가 있다.

여태까지 우리는
인스턴스를 생성하고 다루기 위해
" 인스턴스 타입과 일치하는 타입의 참조변수 "를 사용했다.

Tv t = new Tv() ;
SmartTv s = new SmartTv() ;

보통 위와 같이 타입이 일치하지만
만약
서로 " 상속관계 "에 있다면
맨 처음 설명했던 사용이 가능하다.

SmartTv s = new SmartTv() ;
Tv t = new SmartTv() ; // 조상 타입 참조변수로 "자손 타입 인스턴스" 참조

그렇다고 완전한 자손 타입 인스턴스를
사용할 수는 없다.

생성된 자손클래스 인스턴스 멤버 중
" 조상클래스에 정의되지 않은 멤버 "는 사용 불가하다.

즉,
둘 다 같은 타입의 인스턴스이긴 하지만
" 참조변수의 타입 "에 따라 사용가능한 멤버는 달라진다.

🔴 참조변수

서로 상속 관계에 있는 클래스 사이에서
참조변수도 형변환이 가능하다.
(" 조상으로의 형변환 "은 다룰 수 있는 멤버의 개수가 줄어들기 때문에 항상 안전함.)

(희망형변환 클래스) 참조변수 형태로 형변환

  • "자손"타입 참조변수 ▶ 조상타입 참조변수 (O)

  • "조상"타입 참조변수 ▶ 자손타입 참조변수 (O)

  • "자손"타입 참조변수 ▶ 조상의 조상(최상위 Object클래스)타입 참조변수 (O)

  • [동일 조상 상속] 자손타입 참조변수 ▶ 자손타입 참조변수 (X)

class Car {  }
class Benz extends Car {  }
class Audi extends Car {  }
// Benz와 Audi클래스는 상속관계가 아님.


Audi a = new Audi();

Car c = (Car) a; // 조상타입으로 형변환 : 생략 "가능" _ Car c = a ; 가능
Audi a2 = (Audi) c; // 자손타입으로 형변환 : 생략 "불가" _ Audi a2 = c ; 불가능
Benz b = (Benz) a ; // "불가능" 에러 _ Audi클래스 타입 참조변수 a

위와 같이 형변환이 가능하고
보면 알 수 있듯이
자손타입으로의 형변환에서는 생략 불가능이다.



🔍 instanceof 연산자

(참조변수) " 참조하고 있는 인스턴스의 실제 타입을 알아보는 연산자 "

주로 《 조건문 》에 사용되는 연산자로

(참조변수) instanceof (타입_클래스명) 형식으로 사용된다.

  • true : " 검사한 타입으로 형변환 가능하다. "
  • false : 불가능 / 참조변수 값이 null일 때

<" 조상타입참조변수 " → " 자손타입 인스턴스 ">를 참조할 수 있기때문에
두 요소의 타입이
항상 일치하는 것은 아니다.

"" 조상타입 참조변수로는
실제 인스턴스 멤버들을 모두 사용할 수 없다
""고
앞서 배웠을 것이다.

그렇기 때문에
실제 인스턴스 타입과
" 같은 타입의 참조변수로 형변환 "을 해야만!
해당 인스턴스의 모든 멤버들을 사용할 수 있다.



🟡 매개변수

다형성은
특정 메서드" 매개변수 "에도 적용이 되는데

우선 물건을 사는 상황으로 예시를 들어보자.
그렇다면
먼저 팔 물건이 필요할 것이다.

class Product {
    int price ; // 가격 변수 멤버
    int bonuspoint ; // 적립포인트 변수 멤버
    
    Product(int price) {
        this.product = price ;
        bonuspoint = (int)(price/10.0) // 적립금 : 제품가의 10% -> 잔돈은 소숫점 너무 많으면 안됨 _ (int)
    }
}

팔 물건은 매우 다양할 것이고
각 물건들의 가격과 적립포인트는
각기 다를 것이다.

" 서로 다른 물건이더라도 모두 팔 물건에 해당되니까 "
extends(상속) 시켜서 각각의 물건 class를 만든다

class Tv extends Product {
	Tv() { super(100) ; }
    
    // 무엇을 구매했는지 출력할 경우를 위해
    // toString() : Object클래스 메서드 (오버라이딩 참고)
    public String toString() { return "TV" ; }
}
class Radio extends Product {
	Radio() { super(50) ; }
    public String toString() { return "Radio" ; }
}

이렇게
팔 물건들에 대해 처리가 되었다면

이젠
가장 중요한 살 사람이 필요하다.

class Buyer {
    int money = 1000 ; // 가진 돈
    int bonuspoint = 0 ; // 구매자의 현재 적립 포인트
}

이렇게 팔 물건 & 구매자에 대해 각 클래스로 선언했다면

구매 행위를 함수로 선언해야 할 것이다.

/* class Buyer 멤버 메서드 */
void buy(Tv t) {
    money = money - t.price // 잔액 계산
    bonuspoint = bonuspoint + t.bonuspoint // 적립금 계산
}
void buy(Radio r) {
    money = money - r.price // 잔액 계산
    bonuspoint = bonuspoint + r.bonuspoint // 적립금 계산
}

흠...
이렇게 선언한다 했을 때,
" 만약 물건이 10,000가지 라면..? "

위 함수를 10000개를 만들게 되는 것이다.
단순노동을 하려면 공사판으로 출근하는 것이 빠를 것이다.

buy메서드를 하나로 통일시키고 싶다면
매개변수의 타입이 어떻든 입력되게끔 해야한다

이 때,
가뭄에 비 내리는 듯한 달콤한 성질이 " 매개변수의 다형성 "이다.

Tv와 Radio로 나누지말고
" 팔 물건 "으로 선언했던
Product 클래스 타입으로 매개변수를 설정하면 된다.

void buy(Product p) {
    money = money - p.price // 잔액 계산
    bonuspoint = bonuspoint + p.bonuspoint
	
    System.out.println(p + "을/를 구입") ;
}

이렇게 메서드의 매개변수가
" 조상클래스 타입 "이라면
해당 메서드의 매개변수로 " 자손클래스 타입 변수들 "은
모두 받아들일 수 있다.

그렇게
아래와 같은 코드 실행에는 문제없게 된다.

Buyer b = new Buyer() ;
Tv t = new Tv() ;
Radio r = new Radio() ;

b.buy(t) ; // Tv을/를 구입 - 출력
b.buy(r) ; // Radio을/를 구입 - 출력

System.out.println(b.money + "원 남았습니다.") ; // 850원 남았습니다. - 출력
System.out.println("적립금은 " + b.bonuspoint + "점 입니다.") ; // 적립금은 15점 입니다. - 출력


※ " 배열 "로 다루기

" 조상타입의 참조변수로 자손타입의 객체를 참조하는 것이 가능하다 "는 것을
위에서 살펴봤는데
배열로 다룬다는 것은 단순하다.

조상타입 배열
" 자손타입 객체들을 묶어서 사용하는 것 "이다.

/*
Product 클래스를 상속받은 Tv, Radio, Computer 클래스 타입 
*/
Product p1 = new Tv() ;
Product p2 = new Radio() ;
Product p3 = new Computer() ;

// 배열로 간단하게 처리
Product p[] = new Product[3] ;
p[0] = new Tv() ;
p[1] = new Radio() ;
p[2] = new Computer() ;

이렇게 배열로 저장하게 된다면
" 객체들을 특징별로 나눠서 한번에 관리하며 사용할 수 있다. "
예를 들면
팔 물건, 전시품, 사은품 등을 따로 관리한다고 할 때,

각 클래스를 상속받는 자손클래스 타입 객체들이 다양하게 생성될텐데
위 3가지 특징별로 나누어 배열로 관리하면 편할 것이다.

뭐, 어쨋든
배열로 다룰 땐,
객체들의 상속관계를 따져서
" 가장 가까운 공통조상 클래스 타입의 참조변수 배열 "을 생성하여
객체들을 저장하면 된다.

직전에 살펴봤던 Buyer클래스를 수정해보자.

class Buyer {
    int money = 1000 ; // 가진 돈
    int bonuspoint = 0 ; // 구매자의 현재 적립 포인트
    Product[] basket = new Product [10] ; // 구입한 물건 담을 바구니 _ "10개로 제한된 바구니"
	iont i = 0 ; //cart호출 인덱스로 사용할 변수

    void buy(Product p) {
    money = money - p.price // 잔액 계산
    bonuspoint = bonuspoint + p.bonuspoint
	cart[i++] = p ;
    System.out.println(p + "을/를 구입") ;
    }
}

위 예시코드에서는 new Product[10]으로 10개만 받을 수 있게 제한되어있다.
만약 더 적든 많든
들어온 만큼 유동적으로 정하고 싶으면
Vector클래스》를 사용하면 된다.

* Vector클래스는 내부적으로 Object타입의 배열을 갖고 있어서
해당 배열에 객체 추가/제거를 할 수 있게 작성되어 있다.
" 동적 크기관리 가능한 객체배열 "

public class Vector extend AbstractList
	implements List, Cloneable, java.io.Serializable {
    protected Object elementData[] ;
    ...
    ..
    .
}    

0개의 댓글