다형성 (Polymorphism)
· 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미한다.
· 자바에서는 이러한 다형성을 상위 클래스 타입의 참조 변수로 하위 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현
하고 있다.
· 다형성은 상속, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나이다.
참조 변수의 다형성
※ Employee -> Regular, Daily, Sales
(상속)
· Employee emp = null;
· emp = new Regular(); // 업캐스팅
· emp = new Daily(); // 업캐스팅
· emp = new Sales(); // 업캐스팅
※ Employee 클래스에 정의된 메소드만 호출할 수 있다.
다음의 예제를 통해 다형성에 대해 이해해보자.
· 최상위(슈퍼) 클래스는 Employee 클래스이다.
· Regular 클래스는 Employee 클래스의 속성과 기능을 상속한다.
· Daily 클래스는 Employee 클래스의 속성과 기능을 상속한다.
· Sales 클래스는 Regular 클래스의 속성과 기능을 상속한다.
[다형성에 대한 예제1]
1. Employee 클래스 타입 변수 emp를 생성한다.
2. 변수 emp에 Regular 클래스의 인스턴스 정보를 저장한다.(업캐스팅)
3. Regular의 인스턴스 정보는 Regular 클래스의 매개변수 생성자에 데이터를 전달하여 regular 클래스에 정의된 인스턴스
변수 pay 말고도 나머지 기본 정보인 no, name, initDay 변수에 데이터를 저장하게된다.
4. printf 형식 출력문에 의해 emp.(점)으로 Employee 클래스에 접근하게되고 클래스 안에 payCheck() 메소드와
printEmployee() 메소드가 정의되어있지만 Regular 클래스에 두 메소드가 오버라이딩(재정의) 되어있어 재정의된 메소드를
불러오게되고 그에 맞게 출력값이 출력된다.
1. 위 예제에서 Regular 클래스에 대한 출력문까지의 흐름을 설명했으니 나머지 클래스도 위와 동일한 흐름으로 커서가 이동
되기때문에 한결 이해하기 쉬울것이다.
2. 한가지 다른 점인 Sales 클래스는 Employee 클래스를 직접 상속받는게 아닌 Regular 클래스를 상속받고 있다는 점이
다른데, Regular 클래스는 Employee 클래스를 상속받고 있기때문에 한단계 더 거쳐간다는거 이외에는 차이점이 없다.
[다형성에 대한 예제2]
· Poly1이라는 클래스 안에 main() 메소드를 시작으로 업캐스팅된 Regular 클래스의 인스턴스 정보를 전달하고,
printRegular() 메소드에서 Regular 클래스의 기본 정보를 출력하는 코드를 작성해보자.
1. main() 메소드와 printRegular() 메소드 모두 static 키워드를 사용하는 클래스 메소드이기때문에 데이터를 주고받을 때,
인스턴스를 생성할 필요가 없이 바로바로 전달이 가능하다.
2. 우선 프로그램의 시작점인 main() 메소드에서 업캐스팅을 했고, 그 정보를 가리키는 주소를 Employee 클래스 타입 변수
emp에 저장을 했다.
3. printRegular()에 데이터를 전달하기 위해서는 메소드를 호출해야하고 호출부안에 전달할 데이터인 emp 변수를 인자값으로
전달한다.
4. printRegular() 메소드에서 데이터를 전달받기위해 매개변수 1개가 있어야하고 매개변수의 타입은 변수 emp의 타입과
동일한 Employee 클래스 타입이어야한다.
5. Regular의 정보를 가리키는 주소를 알고 있는 변수 emp를 매개변수로 전달받았으니 이제 본론으로 돌아와 처음 Regular
클래스의 기본 정보를 출력하기만하면 된다.
6. printRegular() 메소드 안에서 printf 형식으로 Regular 클래스의 급여정보인 payCheck() 메소드를 호출한다.
(물론 최상위 클래스인 Employee 클래스에 정의된 payCheck() 메소드에 정의된 것을 Regular 클래스에서 오버라이딩한
정보를 가져온다는 사실을 잊지말아야한다!!)
7. 나머지 기본정보도 reg.(점)으로 Employee 클래스에 정의된 printEmployee() 메소드에 접근하게되고 Regular 클래스에서
printEmplyee() 메소드를 오버라이딩했기때문에 이 정보를 반환하게된다.
[다형성에 대한 예제3]
· 이번에는 Regular 클래스가 아닌 Daily 클래스가 Employee 클래스에 대해 업캐스팅을 하여 급여 정보와 기본 정보를 출력
하는 예제이다.
1. 캠쳐된 그림에서 주석처리된 코드는 무시하고 진행하자.
2. 이 문제는 예제2번과 동일한 코드의 흐름대로 흘러가는 예제이다.
3. 우선 printRegular() 메소드의 호출부에 Daily 클래스의 인스턴스 정보를 가리키는 주소를 가지고 있는 Employee 클래스
타입 변수 emp를 인자값으로 printRegular() 메소드에 전달한다.
4. 위 예제와 똑같이 printRegular() 메소드에서 데이터를 전달받기위해 매개변수 1개가 있어야하고 매개변수의 타입은 변수
emp의 타입과 동일한 Employee 클래스 타입이어야한다.
5. 나머지는 위의 예제와 같으니 넘어가겠다.
[다형성에 대한 예제4]
· 이번에는 예제2번과 예제3번을 동시에 진행하고 싶을 때 printEmployee() 메소드의 매개변수 타입을 무엇으로 잡아야할까?
1. 예제2번 문제와 예제3번 문제를 이해했다면 아주 간단한 문제이다.
2. Regular 클래스와 Daily 클래스는 Employee 클래스를 상속한다. 그리고 Regular 클래스의 인스턴스 정보를 가리키는 주소
가 저장된 변수 emp와 Daily 클래스의 인스턴스 정보를 가리키는 주소가 저장된 변수 emp1 모두 데이터 타입이 Employee
이다.
3. main() 메소드에서 인자값을 전달하는 변수명은 printEmployee() 메소드에 매개변수명과 동일하지 않아도 타입만
Employee라면 알아서 각 클래스에서 오버라이딩한 메소드에서 값을 처리하여 리턴(반환)된다.
∴ 이제 우리는 다형성의 개념을 알고나서 각 클래스마다 왜 같은 메소드를 오버라이딩(재정의) 해야하는지 알것이다.
참조 변수의 다형성
· Employee emp = new Regular();
· Regular emp1 = (Regular)emp; // 다운캐스팅
※ emp1 참조 변수를 사용하여 Regular 클래스에 정의된 메소드를 호출할 수 있다.
[다형성에 대한 예제5]
1. 프로그램의 시작은 언제나와 같이 main() 메소드이다.
2. main() 메소드에 163 line에서 Daily 클래스의 인스턴스를 업캐스팅하여 생성한다.
3. 164 line에서 printEmployee() 메소드 호출부에서 Daily 클래스의 인스턴스 정보를 가리키는 주소를 저장한 Employee
타입 변수 emp1을 전달한다.
4. printEmployee() 메소드는 인자값을 전달받아야하니 동일한 타입인 Employee 클래스 타입으로 데이터를 전달받는다.
5. 여기서 중요한데, 급여정보는 기본적으로 Employee 클래스에도 정의가 되어있다. 그런데 getWorkday() 라는 메소드는 눈을
씻고 찾아봐도 안보인다.
6. 어떻게 getWorkday() 메소드의 리턴(반환)값을 호출부에 리턴(반환)할 수 있을까? 정답은 Employee 클래스를 Daily
클래스로 다운캐스팅을 한다.(getWorkday() 메소드는 Daily 클래스에서 정의해두었기때문이다.
7. 다운캐스팅을 한 변수 d.(점)으로 Daily 클래스에 접근하여 해당 메소드에 진입하여 값을 메소드 호출부로 리턴(반환)
해주는것이다.
instanceof 연산자
· instanceof 연산자는 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용한다. 주로 조건문에 사용하고,
instanceof 좌측에는 인스턴스의 정보를 가지고있는 참조변수를 우측에는 참조하고있는 타입(클래스명)을 위치한다. 그리고
결과값은 true 또는 false로 반환한다.
instanceof 연산자에 대한 예제를 통해 자세히 알아보자.
[instanceof에 대한 예제1]
1. Sales 클래스의 인스턴스 정보를 업캐스팅한다.
2. 167 line의 코드는 boolean 타입의 변수 result에 변수 emp3의 인스턴스 타입이 Sales 클래스인지 확인하고 결과를
저장한다.
3. 저장된 결과값을 출력한다.
4. 인스턴스를 생성할 때 Sales 클래스의 인스턴스를 생성한 정보를 emp3에 담았기때문에 instanceof의 결과값은 true가
맞다.
[instanceof에 대한 예제2]
Q) 정규직, 일용직 사원에 관계없이 모든 사원의 급여를 출력하되, 일용직 사원인 경우에는 근무일수까지 출력하세요. (instanceof 연산자를 사용하세요.)
1. 정규직, 일용직에 관계없이 모든 사원의 급여 정보를 출력해야하니, 조건문 밖에서 출력문을 사용한다.
2. 문제에서 조건으로 걸어놓은 일용직 사원일 경우에는 근무일수를 출력하라고 했으니, 여기서 조건문에 조건을 instanceof
연산자를 사용하여 printEmployee() 메소드의 매개변수가 Daily의 인스턴스정보일 때 근무일수를 출력하면 되는거니까
매개변수 emp의 instanceof Daily라고 코드를 작성하면 된다. 좌측에는 인스턴스 정보를 가리키는 주소를 저장한 변수를
써주는것이고 우측에서 실제 인스턴스 정보를 생성했던 클래스명을 써주면된다.
3. 이후에는 조건문이 일치하면 getWorkday()메소드가 Daily 클래스에만 정의되어있으니까 변수 emp의 데이터 타입을
Employee 클래스에서 Daily 클래스로 다운캐스팅을 해야한다.
4. 그리고나서 출력문으로 다운캐스팅한 변수.(점)으로 Daily 클래스로 접근하여 getWorkday() 메소드를 사용하면된다.
[instanceof에 대한 예제3]
Q) 영업직 사원인 경우 판매실적, 커미션 정보를 추가적으로 출력하세요.
· 위 문제를 해결하기위해 Sales 클래스에 영업판매실적과 커미션 정보를 얻는 get() 메소드를 추가했다.
· 이 문제는 위 문제와 거의 비슷하다고 생각하면된다. printEmployee() 메소드 안에서 조건문을 생성하고 if 문에는
getWorkday() 메소드를 사용하기위해 instanceof 연산자를 사용하여 매개변수를 Daily 클래스로 다운캐스팅하면 되고,
else if 문에는 영업판매실적과 커미션 정보를 얻을 수 있는 get() 메소드를 호출하기위해 instanceof 연산자를 사용하여
Sales 클래스로 다운캐스팅하면된다.
· instanceof 연산자를 제대로 이해했다면 예제2번의 설명대로 이후의 그림의 코드를 이해할 수 있을것이다.
[다형성에 대한 예제6]
Q) 정규직, 일용직, 영업직 사원의 3가지 데이터를 배열에 저장하려면 데이터 타입을 어떤 타입으로 잡아야할까?
∴ 정답은 모든 사원의 상위 클래스인 Employee 클래스를 데이터 타입으로 잡아야한다. 다형성 개념으로 최상위 슈퍼 클래스인
Employee 클래스는 상속관계에 있는 서브 클래스 인스턴스 정보를 업캐스팅하여 저장할 수 있기때문이다.
(ex)Employee emp = new Regular("R001", "일길동", "2022-01-01, 3000000);)
[다형성에 대한 예제7]
Q)정규직, 일용직, 영업직 사원의 인스턴스를 배열에 저장하고 사원의 급여에 대한 정보만 출력하세요.
1. 바로 위 문제인 예제6번 문제를 이해했다면, Regular, Daily, Sales 클래스는 인스턴스를 생성하여 정보를 Employee 클래스 타입 변수 emps에 저장할 수 있다.
2. 이후 배열의 요소를 출력하기위해서 for each 문을 사용할 것이고, Employee 1차원배열 클래스 타입 변수 emps에 들어있는
요소의 타입은 Employee 클래스 타입이니까 그걸 전달받을 임시변수 emp의 데이터 타입도 Employee 클래스 타입이어야한다.
3. 나머지는 printf 형식에 의해 emp.(점)으로 Employee 클래스에 접근하여 메소드가 정의된 payCheck() 메소드를 호출
해오면 값을 출력할 수 있다.
다음 예제를 통해 인터페이스의 다형성 개념을 이해해보자.
[인터페이스에 대한 다형성 예제1]
· Volume 인터페이스에서 추상메소드를 정의할 때, public abstract 키워드를 적어주지않아도 되는데, 이는 컴파일 시
자바 컴파일러가 알아서 잡아주기때문에 생략이 가능하다.
· TV 클래스는 Volume 인터페이스를 구현한 구상 클래스이며, 반드시 인터페이스에 정의된 메소드를 오버라이딩(메소드
재정의) 해야한다.
· Raido 클래스는 TV 클래스와 마찬가지로 Volume 인터페이스를 상속받으며 구상 클래스이기때문에 Volume 인터페이스에
정의된 메소드를 오버라이딩(메소드 재정의) 해야한다.
· Volume 인터페이스 타입인 변수 obj는 선언 및 초기값을 null로 생성한다.
· TV 클래스와 Radio 클래스의 인스턴스를 생성한다.(각 클래스는 Volume 인터페이스에 생성된 인스턴스 변수의 값을 100으로
초기화한다.
· 위 그림에서 우리가 알아야할 사항은 TV 클래스와 Radio 클래스는 Volume 인터페이스를 상속받고있기때문에 업캐스팅의 개념을
가져와서 먼저 turnUp() 메소드와 turnDown() 메소드를 호출하게되는데 Volume 인터페이스엔 추상메소드로 정의만 되어있고
실제로는 각 클래스인 TV 클래스와 Radio 클래스에서 메소드 오버라이딩(메소드 재정의)한 메소드를 호출하며 값을 리턴
(반환)하여 출력한다.
[인터페이스에 대한 다형성 예제2]
· Volume 인터페이스를 상속받는 AdvancedVolume을 추가로 생성한다.
· 소리를 키는 기능인 turnOn() 추상메소드와 소리를 끄는 기능인 turnOff() 메소드를 생성한다.
· TV 클래스와 Radio 클래스는 Volume 인터페이스를 상속받는 AdvancedVolume 서브 인터페이스를 상속받고 새롭게 정의된
추상메소드를 오버라이딩(메소드 재정의)해야 컴파일 에러가 사라질것이다.
Q) Poly 클래스에서 turnUp() 메소드와 turnDown() 메소드만 호출하고 싶은경우 printvolume() 메소드에 매개인자의 데이터타입은?
1. turnUp() 메소드와 turnDown() 메소드만 호출하고 싶다면 printvolume() 메소드의 매개변수의 데이터타입은 Volume
인터페이스 타입이어야한다.
2. Volume 인터페이스에는 위 문제에서 필요한 메소드인 turnUp() 메소드와 turnDown() 메소드가 정의되어있기때문이다.
Q) 이번에는 Poly 클래스에서 위 예제의 두 메소드 외에 추가적으로 turnOn() 메소드와 turnOff() 메소드까지 호출하고 싶은경우 매개인자의 데이터타입은?
1. turnUp() 메소드와 turnDown() 메소드는 Volume 인터페이스에 정의되어있다.
2. turnOn() 메소드와 turnOff() 메소드는 AdvancedVolume 인터페이스에 정의되어있다.
3. AdvancedVolume 인터페이스는 Volume 인터페이스를 상속받고있다.
4. 모든 메소드를 호출하려면 결국 printvolume() 메소드의 매개변수는 AdvancedVolume 인터페이스 타입의 매개변수여야한다.
다음 예제를 통해 배열과 다형성 개념을 종합하여 이해해보자.
[배열에 대한 다형성 예제1]
· AdvancedVolume 인터페이스 타입의 1차원배열 변수 arr의 배열의 길이가 3개인 배열을 생성한다.
· 1차원배열 변수 arr의 0번째 인덱스에는 TV 클래스의 인스턴스를 생성하고, 1번째 인덱스에는 Radio 클래스의 인스턴스를
생성하고, 2번째 인덱스에는 다시 TV 클래스의 인스턴스를 생성한다.
Q) Poly 클래스에 print() 클래스 메소드에서 turnOn(),turnUp(),turnDown(),turnOff() 메소드를 모두 호출하고 싶다면 매개변수의 데이터타입은?
1. 모든 메소드를 호출할 수 있는 인터페이스는 위 예제에서도 살펴보았듯이 AdvancedVolume 인터페이스밖에 없다. 그렇기때문에 print() 클래스 메소드 매개변수의 데이터타입은 AdvancedVolume 인터페이스이며 여기서 한가지 추가되는건 배열의 개념이 추가되었기때문에 배열을 의미하는 여는 브랙킷([)과 닫는 브랙킷(])를 AdvancedVolme 인터페이스 뒤에 붙여줘야한다.
2. 이후 출력은 배열의 길이만큼 출력해주는 for each 문을 사용하여 임시변수 temp를 통해 해당 메소드를 호출하여 값을 리턴
받아온다.
다형성의 개념을 정리해보자.
1. 상위 클래스에는 참조형 변수를 통해서 모든 하위 인스턴스를 레퍼런스할 수 있다.
(상속, 추상클래스, 인터페이스의 다형성을 통해 상속받는 모든 인스턴스 정보를 레퍼런스할 수 있다.)
2. 각 클래스의 공통된 기능을 정의하여 사용하고싶을 경우에는 다형성 개념을 이용해서 코딩을 하면 좀 더 유연한 코딩을
할 수 있다.
3. 서브 클래스들의 인스턴스 정보를 동시에 하나의 변수에서 레퍼런스하고싶을 때는 모든 서브 클래스를 상속하는 최상위
클래스(Object 클래스 제외)를 통해 레퍼런스할 수 있다.