다형성(Polymorphism)이라는 주제에 대해서 알아보자. 다형성이란 하나의 메소드나 클래스가 있을 때 이것들이 다양한 방법으로 동작하는 것을 의미한다. 키보드의 키를 통해서 비유를 들어보겠다. 키보드의 키를 사용하는 방법은 '누른다'이다. 하지만 똑같은 동작 방법의 키라고 하더라도 ESC는 취소를 ENTER는 실행의 목적을 가지고 있다. 다형성이란 동일한 조작방법으로 동작시키지만 동작방법은 다른 것을 의미한다.
package com.yuri.javatutorials.polymorphism;
class O {
public void a(int param) {
System.out.println("숫자출력");
System.out.println(param);
}
public void a(String param) {
System.out.println("문자출력");
System.out.println(param);
}
}
public class PolymorphismOverloadingDemo {
public static void main(String[] args) {
O o = new O();
o.a(1);
o.a("one");
}
}
오버로딩은 가장 이해하기 쉬운 다형성의 예라고 할 수 있다. 클래스 O의 메소드 a는 두개의 본체를 가지고 있다. 동시에 두개의 본체는 하나의 이름인 a를 공유하고 있다. 같은 이름이지만 서로 다른 동작 방법을 가지고 있기 때문에 오버로딩은 다형성의 한 예라고 할 수 있다.
package com.yuri.javatutorials.polymorphism;
class A {
public String x() {
return "A.x";
}
}
class B extends A {
public String x() {
return "B.x";
}
public String y() {
return "y";
}
}
class B2 extends A {
public String x() {
return "B2.x";
}
}
public class PolymorphismDemo1 {
public static void main(String[] args) {
A obj = new B();
A obj2 = new B2();
System.out.println(obj.x()); //B.x
System.out.println(obj2.x()); //B2.x
}
}
상속과 오버라이딩 그리고 형변환을 이용한 다형성이다.
package com.yuri.javatutorials.polymorphism;
abstract class Calculator {
int left, right;
public void setOprands(int left, int right) {
this.left = left;
this.right = right;
}
int _sum() {
return this.left + this.right;
}
public abstract void sum();
public abstract void avg();
public void run() {
sum();
avg();
}
}
class CalculatorDecoPlus extends Calculator {
public void sum() {
System.out.println("+ sum :" + _sum());
}
public void avg() {
System.out.println("+ avg :" + (this.left + this.right) / 2);
}
}
class CalculatorDecoMinus extends Calculator {
public void sum() {
System.out.println("- sum :" + _sum());
}
public void avg() {
System.out.println("- avg :" + (this.left + this.right) / 2);
}
}
public class CalculatorDemo {
public static void execute(Calculator cal) {
System.out.println("실행결과");
cal.run();
}
public static void main(String[] args) {
Calculator c1 = new CalculatorDecoPlus();
c1.setOprands(10, 20);
Calculator c2 = new CalculatorDecoMinus();
c2.setOprands(10, 20);
execute(c1);
execute(c2);
}
}
/*
실행결과
+ sum :30
+ avg :15
실행결과
- sum :30
- avg :15
*/
클래스 CalculatorDemo의 execute 메소드는 CalculatorDecoPlus와 CalculatorDecoMinus 클래스의 메소드 run을 호출하면서 그것이 '실행결과'라는 사실을 화면에 표시하는 기능을 가지고 있다. 이 때 메소드 execute 내부에서는 매개변수로 전달된 객체의 메소드 run을 호출하고 있다.
만약 메소드 execute의 매개변수 데이터 타입이 Calculator가 아니라면 어떻게 해야할까? 위와 같은 로직을 처리 할 수 없을 것이다. 메소드 execute 입장에서는 매개변수로 전달된 값이 Calculator이거나 그 자식이라면 메소드 run을 가지고 있다는 것을 보장 받을 수 있게 되는 것이다.
이 맥락에서의 다형성이란 하나의 클래스(Calculator)가 다양한 동작 방법(ClaculatorDecoPlus, ClaculatorDecoMinus)을 가지고 있는데 이것을 다형성이라고 할 수 있겠다.
하위 클래스를 인스턴스화 시킬 때 그것의 데이터 타입으로 상위 클래스를 지정할 수 있다. 인터페이스를 구현하고 있는 클래스가 있을 때 이 클래스의 데이터 타입으로 인터페이스를 지정 할 수 있다. 말이 어렵다면 코드를 통해서 무슨 말인지 알아보자.
package com.yuri.javatutorials.polymorphism;
interface I {
}
class C implements I {
}
public class PolymorphismDemo2 {
public static void main(String[] args) {
I obj = new C();
}
}
클래스 C의 데이터 타입으로 인터페이스 I가 될 수 있다는 점이다. 이것은 다중 상속이 지원되는 인터페이스의 특징과 결합해서 상속과는 다른 양상의 효과를 만들어낸다.
C라고 하는 클래스가 인스턴스화 될때 데이터 타입이 I인 이유는 클래스 C가 인터페이스 I를 구현하고 있기 때문이다.
package com.yuri.javatutorials.polymorphism;
interface I2 {
public String A();
}
interface I3 {
public String B();
}
class D implements I2, I3 {
public String A() {
return "A";
}
public String B() {
return "B";
}
}
public class PolymorphismDemo3 {
public static void main(String[] args) {
D obj = new D();
I2 objI2 = new D();
I3 objI3 = new D();
obj.A();
obj.B();
objI2.A();
// objI2.B();
// objI3.A();
objI3.B();
}
}
주석처리된 메소드 호출은 오류가 발생하는 것들이다. objI2.b()에서 오류가 발생하는 이유는 objI2의 데이터 타입이 인터페이스 I이기 때문이다. 인터페이스 I는 메소드 A만을 정의하고 있고 I를 데이터 타입으로 하는 인스턴스는 마치 메소드 A만을 가지고 있는 것처럼 동작하기 때문이다.
이것은 인터페이스의 매우 중요한 특징 중의 하나를 보여준다. 인스턴스 objI2의 데이터 타입을 I2로 한다는 것은 인스턴스를 외부에서 제어할 수 있는 조작 장치를 인스턴스 I2의 맴버로 제한한다는 의미가 된다. 인스턴스 I2와 I3로 인해서 하나의 클래스가 다양한 형태를 띄게 되는 것이다.
나는 스마트폰을 전화기, 핸드폰, 브라우저로도 사용하고 있다. 이것을 부모님에게 제공할 때는 "이거 전화기예요" 라고 한다면 부모님은 스마트폰이라고 하는 클래스를 전화기라고 하는 인터페이스로 사용하기 때문에 전화기 이외의 나머지 기능들은 부모님이 사용하지 못한다. 수많은 기능들을 뒤로 하고 전화기라는 기능에 초점을 맞출 수 있다. 두번째 예로는 아이에게 이 스마트폰을 게임기라고 한다면 아이는 게임기인 줄만 알고 전화를 사용하지 않을 것이다. 스마트폰이라고 하는 클래스를 제공할 때 게임이라는 인터페이스로 제공하게 되면 아이 입장에서는 게임에 집중할 수 있다.
package com.yuri.javatutorials.polymorphism;
interface father {
}
interface mother {
}
interface programmer {
public void coding();
}
interface believer {
}
class Steve implements father, programmer, believer {
public void coding() {
System.out.println("fast");
}
}
class Rachel implements mother, programmer {
public void coding() {
System.out.println("elegance");
}
}
public class Workspace {
public static void main(String[] args) {
programmer employee1 = new Steve();
programmer employee2 = new Rachel();
employee1.coding();
employee2.coding();
}
}
Steve와 Rachel의 사용자인 직장에서는 Steve와 Rachel의 인터페이스인 programmer를 통해서 두사람과 관계하게 된다. 두 사람이 어떤 종교나 가족관계를 가졌건 인터페이스 programmer을 가지고 있다면 고용할 수 있다. 회사에서는 코딩을 할 수 있는 사람이 필요하고 어떤 사람이 programmer라는 인터페이스를 구현하고 있다면 그 사람은 반드시 coding이라는 메소드를 구현하고 있을 것이기 때문이다. 또 두 사람에게 업무를 요청 할 때는 programmer라는 인터페이스의 메소드인 coding을 통해서 요청하면 된다. 하지만 두 사람의 성향이나 능력에 따라서 그 업무를 수행한 결과는 다른데 Steve는 빠르게 코딩하고 Rachel은 우아하게 코딩하고 있다.
즉 같은 방식으로 요청을 하지만 다른 결과물을 만들어내고 있다. 이를 다형성이라고 한다.
자바는 굉장히 고전적인 언어이고, 나는 자바를 배우다.. 말다.. 배우다 .. 말다를 반복했다. 마주한 그 고비를 넘지 못하는 느낌이랄까? 😥 그 고비를 넘어보고 싶고 그래서 다시 공 부하는 중이다. 이런 개념까지 왜 필요한거지?..라며 생각했던 부분들을 이번 강의를 통해 이해할 수 있었다. 결론은 이고잉님 사랑해요.. 🧡 과거에도, 현재에도 좌절을 마주했을 때 도와주셨던 빛같은 존재입니다...
이 글은 생활코딩의 자바 강좌를 바탕으로 정리한 내용입니다.