- Java 세팅 및 실습은 Windows 환경에서 IntelliJ를 통해 진행되었습니다.
5. 다형성 (Polymorphism) - 개요
- 다형성은 객체지향 프로그래밍의 중요한 개념 중 하나로, 같은 인터페이스를 공유하는 여러 객체가 다양한 형태로 동작할 수 있는 능력을 의미합니다.
- 다형성은 코드의 유연성을 높이고, 재사용성을 증가시키며, 유지보수를 쉽게 만들어줍니다.
5-1. 다형성의 개념 & 필요성
- 다형성(Polymorphism)은 그리스어에서 유래한 단어로, "많은 형태"를 의미합니다.
- 프로그래밍에서 다형성은 동일한 메시지가 여러 객체에서 다른 방식으로 처리될 수 있는 능력을 가리킵니다.
- 이를 통해 동일한 인터페이스나 부모 클래스를 공유하는 객체들이 다양한 방법으로 동작할 수 있게 됩니다.
다형성이 왜 필요한가?
- 코드 재사용성: 다형성을 통해 동일한 코드를 여러 클래스에서 사용할 수 있습니다.
- 유연성: 다형성은 코드가 서로 다른 객체 타입에 대해 더 유연하게 동작할 수 있도록 합니다.
- 유지보수성: 다형성을 통해 새로운 클래스나 기능을 추가하더라도 기존 코드를 변경할 필요가 적어 유지보수가 용이해집니다.
5-2. 다형적 참조와 메서드 오버라이딩
- 다형적 참조(Polymorphic Reference)는 부모 클래스 타입의 변수로 자식 클래스의 객체를 참조하는 것을 의미합니다.
- 이는 자식 클래스가 부모 클래스를 상속받고 메서드를 오버라이딩(재정의)함으로써 가능합니다.
- 메서드 오버라이딩(Method Overriding)은 자식 클래스가 부모 클래스의 메서드를 재정의하여 자신의 방식으로 동작하게 만드는 것입니다.
- 이때 부모 클래스의 참조 변수를 사용해도 자식 클래스의 오버라이딩된 메서드가 호출됩니다.
class Animal {
public void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal();
Animal myDog = new Dog();
Animal myCat = new Cat();
myAnimal.sound();
myDog.sound();
myCat.sound();
}
}
- 위의 예시에서 Animal 클래스의 참조 변수로 Dog와 Cat 객체를 참조할 수 있으며, 메서드 호출 시 자식 클래스에서 오버라이딩된 메서드가 호출됩니다.
5-2-참고. 업캐스팅과 다운캐스팅
- 업캐스팅(Upcasting)과 다운캐스팅(Downcasting)은 다형성과 관련된 캐스팅 연산입니다.
업캐스팅(Upcasting): 자식 클래스 타입의 객체를 부모 클래스 타입으로 변환하는 것입니다.
- 업캐스팅은 암시적(자동적)으로 이루어지며, 다형성을 구현하는 데 중요한 역할을 합니다.
Animal myDog = new Dog();
- 업캐스팅은 메모리 구조상 오류에 대해 안전하게 동작하며, 부모 클래스의 참조 변수로 자식 클래스의 객체를 참조할 수 있습니다.
- 그러나 업캐스팅을 하면 부모 클래스에서 선언된 멤버만 사용할 수 있습니다.
- 자동 형변환도 일종의 업캐스팅이라 볼 수 있습니다.
다운캐스팅(Downcasting): 부모 클래스 타입으로 업캐스팅된 객체를 다시 자식 클래스 타입으로 변환하는 것입니다.
- 다운캐스팅은 명시적으로 수행해야 하며, 잘못된 다운캐스팅은
ClassCastException을 일으킬 수 있습니다.
Animal myAnimal = new Dog();
Dog myDog = (Dog) myAnimal;
- 다운캐스팅을 통해 자식 클래스의 고유 메서드나 속성에 접근할 수 있습니다.
Animal myAnimal = new Dog();
if (myAnimal instanceof Dog) {
Dog myDog = (Dog) myAnimal;
myDog.bark();
}
5-3. instanceof 연산자
instanceof 연산자는 객체가 특정 클래스의 인스턴스인지 여부를 확인하는 데 사용됩니다. 이를 통해 안전하게 다운캐스팅할 수 있습니다.
Animal myAnimal = new Dog();
if (myAnimal instanceof Dog) {
Dog myDog = (Dog) myAnimal;
myDog.bark();
} else {
System.out.println("The object is not a Dog.");
}
- 위 예시에서 instanceof 연산자는 myAnimal이 Dog 타입의 인스턴스인지 확인하여, 안전한 다운캐스팅을 보장합니다.
- 쉽게 말해서 오른쪽의 있는 타입에 왼쪽에 있는 인스턴스의 타입이 들어갈 수 있는지를 대입해보면 됩니다.
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
Animal myAnimal = new Animal();
System.out.println("myDog instanceof Dog: " + (myDog instanceof Dog));
System.out.println("myDog instanceof Animal: " + (myDog instanceof Animal));
System.out.println("myDog instanceof Cat: " + (myDog instanceof Cat));
System.out.println("myCat instanceof Cat: " + (myCat instanceof Cat));
System.out.println("myCat instanceof Animal: " + (myCat instanceof Animal));
System.out.println("myCat instanceof Dog: " + (myCat instanceof Dog));
System.out.println("myAnimal instanceof Animal: " + (myAnimal instanceof Animal));
System.out.println("myAnimal instanceof Dog: " + (myAnimal instanceof Dog));
System.out.println("myAnimal instanceof Cat: " + (myAnimal instanceof Cat));
}
}
마무리
- 다형성은 객체지향 프로그래밍에서 중요한 개념으로, 객체가 여러 형태를 가질 수 있도록 하는 능력을 의미합니다.
- 이를 통해 코드의 유연성, 재사용성, 유지보수성을 크게 향상시킬 수 있습니다.
- 다형적 참조, 메서드 오버라이딩, 업캐스팅과 다운캐스팅, 그리고 instanceof 연산자는 다형성을 구현하는 데 중요한 요소입니다.
- 이러한 개념들을 이해하고 활용하면, 보다 유연하고 확장 가능한 객체지향 프로그램을 작성할 수 있습니다.
- 다음 포스팅에서는 다형성을 활용하는 추상클래스와 인터페이스에 대해서 다루고, 더 나아가 좋은 객체 프로그래밍이란 무엇인지에 대해서 다루어 보면서 초급과정의 정리를 마치려합니다.