다형성(polymorphism)은 하나의 메소드나 클래스가 특정 타입이나 매개변수의 수 등에 의해 다른 여러 동작을 보여주는 것을 의미합니다.
드라이버에 일자 침을 달면 일자 드라이버가 되고, 십자 침을 달면 십자 드라이버가 되고, 모터를 달면 전동 드라이버가 되듯이 전달하는 재료에 따라 실행의 성능이 달라집니다. 하지만 나사를 돌려서 뺀다라는 사용 방법은 동일하죠.
아무튼 자바
에서는 다형성
을 구현하기 위해 자동 타입 변환과 메소드 오버라이딩을 통해 구현하고 있습니다. 그리고 다형성을 구현할 때 상속
과 인터페이스
를 이용합니다.
멤버 변수 다형성
은 타입은 같으나 대입되는 객체가 달라 실행결과가 달라지는 것을 의미합니다. 다음 코드를 보고 멤버 변수 다형성을 이해해봅시다.
public class ScrewdriverHead {
public void use() {
System.out.println("드라이버 빙글빙글");
}
}
public class Crosshead extends ScrewdriverHead {
@Override
public void use() {
System.out.println("십자 드라이버 빙글빙글");
}
}
public class Flathead extends ScrewdriverHead{
@Override
public void use() {
System.out.println("일자 드라이버 빙글빙글");
}
}
public class Screwdriver {
public ScrewdriverHead head;
public void use() {
head.use(); //멤버 변수 head에 대입된 객체의 use() 호출
}
}
public class Main {
public static void main(String[] args) {
Screwdriver screwdriver = new Screwdriver();
screwdriver.head = new ScrewdriverHead(); //기본 드라이버
screwdriver.use();
screwdriver.head = new Crosshead(); //십자 드라이버
screwdriver.use();
screwdriver.head = new Flathead(); //일자 드랑리버
screwdriver.use();
}
}
멤버 변수 head
에 대입된 객체 타입이 무엇이냐에 따라 기본, 십자, 일자 드라이버에 대한 메소드를 호출합니다.
메소드가 객체 타입을 매개변수로 받는 경우 명시된 객체 타입을 전달할 수도 있지만 자식 객체 타입을 전달할 수도 있습니다. 이때 같은 메소드를 다른 타입의 매개변수를 통해 이용하게 되므로 다형성이 성립합니다.
이게 가능한 이유는 자식 객체 타입은 부모 객체 타입을 포함하고 있기에 자동 타입 변환이 발생하기 때문입니다.
위에서 사용했던
ScrewdriverHead, CrossHead, Flathead
는 그대로 사용합니다.public class ScrewdriverHead { public void use() { System.out.println("드라이버 빙글빙글"); } }
public class Crosshead extends ScrewdriverHead { @Override public void use() { System.out.println("십자 드라이버 빙글빙글"); } }
public class Flathead extends ScrewdriverHead{ @Override public void use() { System.out.println("일자 드라이버 빙글빙글"); } }
이 드라이버를 사용할 User
클래스를 만들어줍니다. 그리고 Crooshead와 Flathead의 부모 클래스가 되는 Screwdriber 타입을 매개변수로 받는 메소드를 만들어줍니다.
public class User {
public void useDriver(ScrewdriverHead head) {
head.use(); //head의 타입에 맞는 객체의 use()를 호출
}
}
public class Main {
public static void main(String[] args) {
User user = new User();
Crosshead crosshead = new Crosshead();
user.useDriver(crosshead);
Flathead flathead = new Flathead();
user.useDriver(flathead);
}
}
user 객체가 useDriver()메소드의 어떤 매개변수 타입을 전달하냐에 따라 동작이 달라집니다.
상속을 이용한 다형성과 마찬가지로 인터페이스를 사용해서 다형성을 구현할 수 있습니다. 그리고 실제로는 다형성을 구현할 때 인터페이스를 사용하는 경우가 더 많다고 합니다.
인터페이스 변수를 이용해서 필드를 선언하고 다형성을 구현할 수 있습니다. 이 과정에서 인터페이스 자동 형변환이 사용됩니다.
public interface ScrewdriverHead {
void use(); //추상 메소드
}
public class Crosshead implements ScrewdriverHead {
@Override
public void use() {
System.out.println("십자 드라이버 빙글빙글");
}
}
public class Flathead implements ScrewdriverHead {
@Override
public void use() {
System.out.println("일자 드라이버 빙글빙글");
}
}
public class Screwdriver {
public ScrewdriverHead head = new Crosshead();
//자동 타입 변환으로 ScrewdriverHead 인터페이스 타입이 Crosshead 객체 타입으로 변환
public void use() {
head.use();
}
}
public class Main {
public static void main(String[] args) {
Screwdriver screwdriver = new Screwdriver();
screwdriver.use();
screwdriver.head = new Flathead();
screwdriver.use();
}
}
매개변수 타입을 인터페이스 타입으로 선언하면 자동 형변환이 일어나 인터페이스를 구현한 객체와 그 자손들을 매개변수로 사용할 수 있습니다.
ScrewdriverHead 인터페이스, Crosshead 구현 클래스, Flathead 구현 클래스는 위와 동일합니다.
public class User {
public void useDriver(ScrewdriverHead screwdriverHead) {
screwdriverHead.use();
}
}
public class Main {
public static void main(String[] args) {
User user = new User();
Crosshead crosshead = new Crosshead();
Flathead flathead = new Flathead();
user.useDriver(crosshead);
user.useDriver(flathead);
}
}