다형성이란 하나의 메소드, 클래스가 있을 때 이것들이 다양한 방법으로 동작하는 것을 의미한다.
즉, 동일한 조작 방법으로 작동시키지만 동작 방법은 다른 것을 의미한다.
오버로딩은 다형성의 예이다.
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 PolymorphismOverloading{
public static void main(String[] args) {
O o = new O();
o.a(1);
o.a("one");
}
}
클래스O의 메소드 a는 두 개의 본체를 가지고 있다. 동시에 두개의 본체는 하나의 이름인 a를 쓰고 있다. 같은 이름이지만 서로 다른 동작 방법을 가지고 있으므로 오버로딩이 다형성의 예시가 될 수 있다.
class A{
public String x() {
return "x";
}
class B extends A{
public String y() {
return "y";
}
public class Polymorphism {
public static void main(String[] args) {
A obj = new B();
obj.x(); // 실행됨
obj.y(); // 실행되지 않음
}
}
클래스B는 클래스A를 상속하고 있다. 이런 경우 클래스B는 클래스A를 데이터 형으로 삼을 수 있다. A obj = new B();
클래스B는 메소드y를 가지고 있다. 그럼에도 메소드y가 마치 존재하지 않는 것처럼 실행되지 않는다. A obj = new B();
를 B obj = new B();
로 변경하면 obj.y();
가 실행된다.
클래스B의 데이터 형을 클래스A로 하면, 클래스B는 마치 클래스A인 것처럼 동작하게 된다. 클래스B를 사용하는 입장에서는 클래스B를 클래스A인 것처럼 사용하면 된다.
왜 이렇게 사용할까?
class A {
public String x() {
return "A.x";
}
}
class B extends A {
public String x() {
return "B.x";
}
}
public class Polymorphism {
public static void main(String[] args) {
A obj = new B();
System.out.println(obj.x()); // B.x
}
}
위의 코드는 클래스A의 메소드x를 클래스B에서 오버라이딩 하고 있다.
1. 클래스B의 데이터 타입을 클래스A로 인스턴스화하면, 클래스B의 메소드y는 마치 존재하지 않는 것처럼 실행되지 않음
⇒ 클래스B가 클래스A화 됨
2. 클래스B의 데이터 타입을 클래스A로 해서 인스턴스화 했을 때, 클래스B의 메소드를 실행하면 클래스A에서 정의된 메소드가 아닌 클래스B에서 정의된 메소드가 실행됨
⇒ 클래스B의 기본적인 성질은 그대로 간직하고 있음
클래스B를 클래스A의 데이터 타입으로 인스턴스화 했을 떄, 클래스A에 존재하는 멤버만이 클래스B의 멤버가 됨. 동시에 클래스B에서 오버라이딩한 멤버의 동작 방식은 그대로 유지함.
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 Polymorphism {
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
}
}
서로 다른 클래스B와 B2가 데이터타입A로 인스턴스화 되었지만, 메소드x를 호출한 결과가 서로 다르다.
⇒ "상속, 오버라이딩, 형 변환을 이용한 다형성"
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);
}
}
클래스 CalculatorDemo의 메소드execute는 Cal-Plus와 Cal-Minus 클래스의 메소드 run을 호출한다. 이 때, 메소드execute 내부에서는 매개변수로 전달된 객체의 메소드 run을 호출하고 있다.
만약 메소드execute의 매개변수 데이터 타입이 Calculator가 아니라면, 위와같은 로직을 처리할 수 없다. 메소드execute에서 매개변수로 전달된 값이 Calculator거나, 그 자식이라면 메소드run을 가지고 있다는 것을 보장 받을 수 있게 되는 것이다.
이러한 맥락에서 다형성이란, 하나의 클래스(Calculator)가 다양한 동작 방법(CalculatorDecoPlus, CalculatorDecoMinus)을 가지고 있는데 이것을 다형성이라 할 수 있을듯!
특정한 인터페이스를 구현하고 있는 클래스가 있을 때, 이 클래스의 데이터 타입으로 인터페이스를 지정할 수 있다.
interface I{}
class C implements I {}
public class Poly{
public static void main(String[] args) {
I obj = new C();
}
}
클래스 C의 데이터 타입으로 인터페이스I가 될 수 있다.
⇒ 다중 상속이 지원되는 인터페이스의 특징과 결합해서, 상속과는 다른 양상의 효과를 나타냄
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 Poly{
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();
와 objI3.A();
는 오류가 발생한다.
objI2.B();
는 objI2의 데이터 타입이 인터페이스I이기 때문이다. 인터페이스I는 메소드A만 정의하고 있고, I를 데이터 타입으로 하는 인스턴스는 마치 메소드A만 가지고 있는 것처럼 동작하기 때문이다.
인스턴스 objI2의 데이터 타입을 I2로 한다는 것은, 인스턴스를 외부에서 제어할 수 있는 조작 장치를 인스턴스I2의 멤버로 제한한다는 의미가 된다. 인스턴스 I2와 I3으로 인하여 하나의 클래스 다양한 형태를 띄게 되는 것이다.
Reference
1. 다형성