시작하기에 앞서
- 본 글은 국비지원을 수강하는 학생이 공부한 내용을 바탕으로 '정리를 위해' 작성한 글입니다. 학생이 쓴 글이기 때문에 내용에 오류가 있을 수 있습니다!
- 본 글은 생활코딩의 java-다형성 강의, 책 이젠 나도! 자바의 상속 파트, 프로그래밍 언어 튜토리얼 사이트 tutorialspoint의 java-polymorphism을 바탕으로 작성 하였습니다!
다형성(Polymorphism)이란
다형성은 하나의 메소드나 클래스가 다양한 방법으로 동작할 수 있는 것을 의미한다. 이를테면 코드상에 존재하는 a라는 메소드를 사용할 때, 어떤 식으로 사용하느냐에 따라(매개 변수를 다르게 한다던지 - 오버로딩, 상속을 통해 구현부를 수정한다던지 - 오버라이딩) a의 동작 방법이 달라질 수 있다는 것이다. 텍스트로 설명하면 난해하고 이해가 가지 않을 수 있으니 아래의 코드를 한번 살펴보자.
class Ex {
pulbic void a(int number) {
System.out.print("기입된 숫자 :");
System.out.println(number);
}
public void a(String str) {
System.out.print("기입된 문자열 :");
System.out.println(str);
}
}
public class PhlymorphismEx01 {
public static void main(String[] args) {
Ex example = new Ex();
example.a(1); // (1)
example.a("하나"); // (2)
}
}
위의 코드는 Ex 클래스를 인스턴스로 삼은 example이 자신의 멤버 메소드 a()를 실행한 코드이다. 주석으로 (1)와 (2)로 표시된 메소드 실행의 결과는 다음과 같다.
- (1) 기입된 숫자 : 1
- (2) 기입된 문자열 : 하나
위의 결과를 통해 분명 a()라는 같은 이름의 메소드를 호출하였음에도 결과가 달라질 수 있음을 확인했다. 위의 예는 하나의 메소드를 오버로딩한 것으로, 이는 객체지향언어에서 추구하는 다형성이라는 특성의 예라고 할 수 있다. 앞서 언급한 바와 같이 다형성이란 말 그대로 '하나의 메소드나 클래스가 다양한 방법으로 동작할 수 있는 것'을 의미하기 때문이다.
클래스와 다형성
다음의 코드를 한번 살펴보자. 한번 유심히 보고 무언가 이상한 점은 없는지 확인해보자.
class A { }
class B extends A { }
public class PolymorphismEx02 {
public static void main(String[] args) {
A object = new B();
}
}
이상한 점을 발견하였는가? 아마 클래스와 객체까지 공부를 한 분들이라면 이상한 점을 눈치챌 수 있었을지도 모르겠다. 이상한 점은 코드의 6번째 줄로, 보통의 경우였다면 object의 데이터타입이 생성자와 같은 B여야 했을텐데, 이 경우는 B가 아니라 A로 표기되어 있다. 어떻게 이런 일이 가능한 것일까?
자바에서는 다형성을 위해 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 허용한다. 즉, 원래였다면 자식 클래스를 데이터 타입으로 가져야했던 것을 부모의 클래스로 갖는 것을 허용하는 것이다.
다음은 위와 같은 클래스 형태의 다형성을 보여주는 예제이다.
class Parent { }
class Child extends Parent { }
public class PolymorphismEx03 {
public static void main(String[] args) {
Parant a = new Parent(); // (1) 허용
Child b = new Child(); // (2) 허용
Parent c = new Child(); // (3) 허용
Child d = new Parent(); // (4) 허용 X 오류 발생
}
}
왜 이런 문법이 사용되는 것일까? 다음의 예제를 통해 도대체 왜 이런 문법이 사용되는지, 또 이러한 문법의 사용법이 무엇인지 한번 살펴보자.
class A {
public String x() {
return "저는 A 클래스의 x 메소드입니다.";
}
}
class B extends A {
public String y() {
return "저는 B 클래스의 y 메소드입니다.";
}
}
public class PolymorphismEx04 {
public static void main(String[] args) {
A object = new B();
System.out.println(object.x()); // (1)
System.out.println(object.y()); // (2)
}
}
위 예제 코드를 실행하면,
- (1) 저는 A 클래스의 x 메소드입니다.
- (2) (오류 발생)
다음과 같은 결과값을 얻을 수 있다. 1번, 즉 object.x()는 실행이 되는데 object.y()는 실행이 되지 않고 오류가 발생하는 것이다. 왜 이런일이 발생할까?
위의 object 인스턴스는 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조한 것이다. 부모 클래스인 A에는 y() 메소드가 없기 때문에, 자바는 위의 코드를 실행시킬 때 y() 메소드는 없는 것으로 간주한다. 따라서 y() 메소드는 실행되지 않는다.
어려울 수 있을 것 같다. 만약 이 부분이 어렵다면 한 가지만 기억해두어도 괜찮을 것 같다. 그건 바로,
- 클래스를 인스턴스화시킬 때, 그 클래스를 담을 수 있는 데이터타입은 자기자신일 수도, 부모 클래스일 수도 있다는 것이다.
자, 그러면 한 단계 더 나아가보자. 만약 바로 위의 코드에서 B 클래스에 x() 메소드가 오버라이딩 되어 있다면 어떤 일이 발생할까?
class A {
public String x() {
return "저는 A 클래스의 x 메소드입니다.";
}
}
class B extends A {
public String x() {
return "저는 B 클래스의 x 메소드입니다.";
}
public String y() {
return "저는 B 클래스의 y 메소드입니다.";
}
}
public class PolymorphismEx05 {
public static void main(String[] args) {
A object = new B();
System.out.println(object.x()); // (1)
}
}
결과값은
- (1) 저는 B 클래스의 x 메소드입니다.
이다.
대단한 결과이지 않은가(개인적으로 이 부분에서 대단한 경이를 느꼈다). 이를 통해 얻을 수 있는 인사이트는 무엇일까? 우리는 이를 통해 어떤 기능을 구현할 수 있을까? 이를 통해 구현할 수 있는 한 가지 실용적인 방법론에 대해 언급하고 이 글을 마치도록 하자. 아래의 실전 예제는 생활코딩의 다형성 수업의 예제로 이 곳으로 들어가면 관련 강의를 들을 수 있다.
(관련 강의 : https://www.youtube.com/watch?v=nyV5akYR5A4)
다형성을 통한 실전 예제
여기 Calculator이라는 추상클래스가 있다고 가정해보자. Calculator은 자신의 멤버 메소드로 sum(), avg() 그리고 run()을 갖는다. 이때 sum과 avg는 추상메소드이고 run은 실제적인 메소드이다. run 메소드는 sum과 avg를 순차적으로 호출하는 메소드이다.
Calculator를 부모클래스로 둔 클래스로는 CalculatorDecoPlus와 CalculatorDecoMinus 클래스가 있다. 각각의 클래스는 sum과 avg를 자신의 특성에 맞게 정의하였다.
자 이제, 위에 그림으로 처리된 개념이 아래 코드상에 구현되어 있다고 가정하고 (코드를 볼 때 마음의 눈으로 위의 그림대로 코드가 있다고 상상해주시라) 아래의 코드를 보도록 하자.
public class CalculatorDemo {
public static void execute(Calculator cal) {
System.out.println("실행결과");
cal.run();
}
public static void main(String[] args) {
Calculator c1 = new CalculatorDecoPlus();
...
Calculator c2 = new CalculatorDecoMinus();
...
execute(c1);
execute(c2);
}
}
여기서 가장 중요한 것은 execute 메소드가 매개변수로 Calculator 클래스 데이터타입을 가지고 있다는 것이다. 이 부분은 아주 중요하고도 인사이트가 있다고 여겨지는 부분인데 그 이유는 코드가 매우 깔끔하기 때문이다.
자식클래스의 데이터타입을 부모클래스로 지정하면 이처럼 깔끔한 코드를 짤 수 있다. 만약 CalculatorDecoPlus와 CalculatorDecoMinus 클래스가 각각 자신의 데이터타입을 가졌다면 execute 메소드는 데이터타입을 각각 CalculatorDecoPlus와 CalculatorDecoMinus로 갖는 두 가지의 형태로 구현했어야 했을 것이다. 즉 execute 메소드를 오버로딩하여 두 가지의 형태로 구현해야 하는 것인데, 문제가 있는 코드는 아니지만 구현부가 완전히 동일한 코드를 중복해서 사용하는 것은 유지보수적인 면에서 아무래도 전자보다는 취약한 모습을 보일 것이다. Calculator의 자식클래스가 두 개였으니 execute 메소드를 두 가지 형태로 구현하면 되었지, 만약 자식 클래스가 수십 개였다면 코드는 악몽같이 길어졌으리라.
결국 이와 같은 방법을 통해 코드를 훨씬 간결하고 가독성 있게 짤 수 있게 된다. 자바에서 클래스의 다형성을 고려하고 코드를 작성한 경우와 그렇지 않은 경우의 차이점은 단기적으로는 미미할지 몰라도, 코드의 양이 많아지고 프로그램이 무거워지면 무거워질수록 양자의 퀄리티 차이는 비약적으로 발생할 수 밖에 없다고 생각한다. 만약 특정 구간에서 버그가 생긴다고 해도, 다형성을 고려한 코드는 그렇지 않은 코드보다 훨씬 보수하기 용이할 것이다.
'JAVA' 카테고리의 다른 글
스프링의 역사 | 스프링은 왜 탄생했는가 (2) | 2021.11.07 |
---|---|
컬렉션 프레임워크(collection framework) 상속 관계 도표 사이트 (0) | 2021.09.23 |
JAVA 문법 : 클래스와 객체 (0) | 2021.09.15 |
2021. 08. 07 Java 연습 문제 풀기 & 모르는 문제 (4) | 2021.08.07 |
Java로 복리 계산기 만들기 (0) | 2021.08.06 |