⚡️다형성(Polymorphism)
다형성이란 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미한다.
자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 필드나 메소드를 참조할 수 있도록 하여 구현하고 있다.
다형성을 구현하기 위해서는 자식 클래스가 부모 클래스의 메소드나 변수를 오버라이딩 하거나
자식 클래스가 부모 타입으로 업캐스팅하여 구현할 수 있다.
📌 오버라이드(Override), 오버라이딩(Overriding)
오버라이드는 사전적 의미로써 위에 덮어쓰다라는 뜻을 갖는다.
단어의 의미대로 상위 클래스에서 정의된 변수와 메소드를 하위 클래스에서 입맛대로 변경해 재정의하는 것이다.
결국 부모 클래스의 틀은 가져가되 자식 클래스의 상황에 맞는 기능을 수행토록 하는 것이다.
예를 들어 내가 부모 클래스인 붕어빵을 팔고있는데 차별화를 주고 싶어
붕어빵의 필드인 속재료를 팥과 더불어 슈크림이나 치즈로 오버라이딩 해서 판매할 수 있을 것이다.
간단한 예시를 보자.
class Menu {
int price;
Menu(int price){ // 생성자 정의
this.price = price;
}
void menuName() {
System.out.println("메뉴 이름");
}
}
여기 부모 클래스로 사용할 Menu가 있다.
필드로 가격인 price가 있고 생성자를 통해 가격을 지정할 수 있도록 했다.
그리고 메소드는 메뉴의 이름을 출력할 menuName()이 있다.
class Steak extends Menu{
Steak(){
super(30000); // 부모 클래스의 생성자를 통해 가격 책정
}
public void menuName() { // 부모 클래스 메소드 오버라이딩
System.out.println("1번 메뉴 : 스테이크");
}
}
class Salad extends Menu{
Salad(){
super(10000); // 부모 클래스의 생성자를 통해 가격 책정
}
public void menuName() { // 부모 클래스 메소드 오버라이딩
System.out.println("2번 메뉴 : 샐러드");
}
}
class Drink extends Menu{
Drink(){
super(5000); // 부모 클래스의 생성자를 통해 가격 책정
}
public void menuName() { // 부모 클래스 메소드 오버라이딩
System.out.println("3번 메뉴 : 음료");
}
}
스테이크와 샐러드 음료 3가지 클래스들은 메뉴 클래스로부터 상속받는다.
생성자와 super(가격)을 통해 부모클래스로부터 상속 받은 price 값을 지정해준다.
그 다음 부모 클래스의 menuName()을 오버라이딩 함으로써 각 음식의 이름을 출력하도록 하였다.
public class OverriedTest {
public static void main(String[] args) {
Steak steak = new Steak();
Salad salad = new Salad();
Drink drink = new Drink();
steak.menuName(); salad.menuName(); drink.menuName();
}
메인함수에서는 각 음식들을 인스턴스화 해주고 오버라이드한 메소드를 실행한다.
오버라이드한 각각의 메소드가 잘 실행된 것을 결과로 알 수 있다.
📌 캐스팅(Casting)
캐스팅이란 타입을 변환하는 것을 뜻하며, 자바에서는 상속 관계에 있는 부모와 자식 클래스 서로 간 캐스팅이 가능하다.
자식 클래스가 부모 클래스의 타입으로 캐스팅되는 것을 업캐스팅(UpCasting)이라 하고,
반대로 부모 클래스가 자식 클래스로 캐스팅 되는 것을 다운캐스팅(DownCasting)이라 한다.
🔎 업캐스팅
다시, 업캐스팅이란 자식 클래스의 객체가 부모 클래스 타입으로 형변환 되는 것이다.
아까 위에서 사용한 코드를 다시 사용해 예로 들면 부모 클래스는 Menu, 자식 클래스는 Steak이다.
아래 코드 main에서 선언한 Menu steak = new Steak(); 부분이 steak를 부모 타입으로 업캐스팅 한 부분이다.
업캐스팅을 하게 되면 steak는 부모 클래스인 Menu의 멤버에 접근이 가능하지만 Steak의 멤버에는 접근이 불가능하다.
아래의 코드를 예로 Steak가 Menu로 부터 상속받은 멤버들은 이용가능하지만
자식 클래스만 갖고 있는 값인 String errorCheck; 는 사용이 불가능하다.
class Menu { 위 코드와 같음 }
class Steak extends Menu{
String errorCheck; // 부모 클래스에 없는 변수
Steak(){ super(30000); }
public void menuName() {
System.out.println("1번 메뉴 : 스테이크"); }
}
public class CastingTest {
public static void main(String[] args) {
Menu steak = new Steak(); // 업캐스팅
steak.menuName(); // 성공적으로 작동
steak.errorCheck = "컴파일 에러"; // 컴파일 에러 발생
}
그러면 업캐스팅의 개념은 알았으니 어떤 상황에서 유용하게 쓰일지 위에서 사용한 코드를 간단하게 각색해서 알아보자.
class Menu {
int price;
Menu(int price){ this.price = price; }
}
class Steak extends Menu{
Steak(){ super(30000); }
public String toString() { return "스테이크"; }
}
class Salad extends Menu{
Salad(){ super(10000); }
public String toString() { return "샐러드"; }
}
class Drink extends Menu{
Drink(){ super(5000); }
public String toString() { return "음료"; }
}
class Customer { // 고객 클래스
void choice(Menu menu) {
System.out.println(menu.toString()+ "의 가격은 " + menu.price + "원 입니다.");
}
}
public class UpCastingTest {
public static void main(String[] args) {
Menu steak = new Steak(); //업캐스팅
Menu salad = new Salad(); //업캐스팅
Menu drink = new Drink(); //업캐스팅
Customer c = new Customer();
c.choice(steak); c.choice(salad); c.choice(drink);
}
}
아까 사용했던 코드에 고객 클래스와 업캐스팅을 추가한 코드이다.
고객 클래스에는 choice(Menu menu) 메소드를 이용해 음식의 가격을 알아볼 수 있다.
3가지 음식의 인스턴스를 모두 부모 클래스 타입으로 업캐스팅하고 고객의 메소드를 호출하면 결과는 성공적으로 나타날 것이다.
만약 업캐스팅을 사용하지 않는다면 클래스별 타입별로 조건문을 작성해야 한다는 번거로움이 있을 것이다.
이런 상황에서 메뉴가 적으면 다행이지만 많은 경우에는 감당할 수 없을 것이다.
if(Steak steak) {
System.out.println(스테이크 가격은 얼마...);
}
else if(Salad salad){
System.out.println(셀러드 가격은 얼마...);
}
.
.
.
🔎 다운캐스팅
우선 업캐스팅할 때 묵시적 형변환이 허용되지만 다운캐스팅은 아래와 같이 캐스트할 타입을 명시해주어야 한다.
// 업캐스팅 >> 묵시적 형변환
Menu menuSteak = new Steak();
// 다운캐스팅
Steak steak = (Steak)menuSteak;
다운캐스팅은 자식 클래스의 객체가 자신만의 특성을 잃었을 때 복구시켜주는 역할을 한다.
아까 업캐스팅에서 컴파일 오류를 일으켰던 예제를 다운캐스팅을 통해 알아보자.
class Menu { 위 코드와 같음 }
class Steak extends Menu{
String errorCheck; // 부모 클래스에 없는 변수
Steak(){ super(30000); }
public void menuName() {
System.out.println("1번 메뉴 : 스테이크"); }
}
public class CastingTest {
public static void main(String[] args) {
Menu steakMenu = new Steak(); // 업캐스팅
steak.menuName(); // 성공적으로 작동
Steak steak = (Steak)steakMenu; // 다운캐스팅
steak.errorCheck = "Success"; // 다운캐스팅을 통해 성공
}
위에서 업캐스팅을 통해 컴파일 오류를 일으켰던 부분이 다운캐스팅을 통해
타입을 Menu -> Steak로 바꾸며 Steak의 필드를 사용할 수 있게 되었다.
'Java, JSP' 카테고리의 다른 글
[Java/자바] interface 인터페이스 사용법 (0) | 2022.01.01 |
---|---|
[Java/자바] 추상메소드와 추상클래스 (0) | 2021.12.31 |
[Java/자바] final 의미와 사용법 (0) | 2021.12.30 |
[Java/자바] 입력 BufferedReader와 Scanner의 차이 (2) | 2021.12.26 |
[Java/자바] static 정적 변수와 메소드 (0) | 2021.12.23 |
댓글