JAVA - 다형성 집중 정리

DevSeoRex·2022년 12월 14일
1
post-thumbnail

❓ 다형성에 대해 - polymorphism

😂 그래서 다형성이 뭔지 설명 가능한가요?

다형성이 무엇이냐면, 다형성은 polymorphism 입니다.

면접관은 개발자를 뽑길 원하고 있고, 단순 번역기를 원한것이 아님에도 이런 부끄러운 대답을 한 적이 있는데(물론 모의 면접 연습 이였지만..) 생각해보면 아직도 다형성이 무엇인지 정확히 말하지 못하는 것 같습니다.

🐧 다형성이란 무엇일까?

다형성의 사전적 정의는 아래와 같습니다.

프로그램 언어의 다형성(polymorphism)은 그 프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, 프로그램 언어의 각 요소들이 다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킵니다. 반댓말은 단형성으로 프로그램 언어의 각 요소가 한가지 형태만 가지는 성질을 가리킵니다.


사전적 정의만으로는 이해하기가 너무 어렵습니다.
풀어 쓴다면 이렇게 말할 수 있을 것 같습니다.

’다형성 이란 같은 자료형에 여러가지 타입의 데이터를 대입하여 다양한 결과를 얻어낼 수 있는 성질을 의미한다.’

또 이렇게 표현하기도 합니다.

‘상위 클래스가 하위 클래스를 같은 메시지로 다른 동작을 하게 만드는 것’

결국 핵심 키워드는 ‘같은 메시지로 다른 동작’ , ‘여러가지 타입의 데이터를 대입하여 다양한 결과’ 와 같이 같은 자료형으로 다양한 결과를 내는 것을 다형성이라 할 수 있겠습니다.

Java에서는 대표적인 다형성의 활용 예시로 오버로딩, 오버라이딩, 업 캐스팅, 다운 캐스팅, 인터페이스, 추상 메서드, 추상 클래스와 같은 것들이 있습니다.

🐯 상속 관계에서의 다형성

class Phone {
	void sendCall() {} // 전화 걸기
	void sendMessage() {} // 메시지 보내기
}

class SmartPhone extends Phone {
		void getInternetConncetion() {}   // 인터넷 접속 얻기
		void play3DGame() {}              // 3D 게임실행
}

위의 예시를 보면 Phone과 SmartPhone은 부모 - 자식 상속 관계를 가지고 있습니다.
다형성을 공부하기 전에는 보통 같은 타입의 클래스로 객체를 생성해 사용했습니다.

Phone phone = new Phone();

SmartPhone smartPhone = new SmartPhone();

다형성을 배운 지금은 부모-자식과 같이 상속 관계에 있는 클래스들이라면 부모 타입의 참조변수로 자식 객체를 다룰 수 있다는 것을 알 수 있습니다.

// 부모 타입의 참조변수로 자식 객체 생성
Phone smartPhone = new SmartPhone();

상속의 경우에는 부모 클래스는 부모 클래스에 있는 멤버와 메서드들만 사용이 가능하고,
자식 클래스는 부모 클래스에 정의된 멤버와 메서드는 물론 자신이 가지고 있는 멤버와 메서드도 사용이 가능합니다.

따라서 부모의 참조 변수로 자식 클래스를 다루면 부모 클래스에 있는 멤버와 메서드들만 사용할 수 있습니다.

반대로, 자식의 참조 변수로 부모의 객체를 생성하는 것은 컴파일 에러를 발생시키게 됩니다.

// 부모의 참조 변수로 부모 객체 생성 -> 가능
Phone phone = new SmartPhone();

// 자식의 참조 변수로 부모 객체 생성 -> 불가능
SmartPhone smartPhone = new Phone(); // 컴파일 에러

// 업캐스팅을 활용하여 컴파일 오류 해결
SmartPhone smartPhone = (SmartPhone)phone;

업캐스팅을 사용하면 컴파일 오류는 해결할 수 있지만, 결론은 업캐스팅을 해도 저렇게 생성한 자식의 참조 변수로는 부모 클래스에 있는 공통된 멤버와 메서드만 사용할 수 있다는 것입니다.

🤯❓ 사용할 멤버도 줄어드는데 다형성이 이득 ❓ - 자료형 다형성

저도 처음에 다형성을 배울때는 이걸 왜 써야 하는지 이해를 할 수 없었습니다.
다형성을 조상 - 자손 관계에서 적용해 부모의 참조변수로 자식의 객체를 다루면 자식의 참조변수로 다룰때보다 사용할 수 있는 멤버의 개수만 줄어들기 때문이라고 생각했기 때문이였죠

위에서 했던 생각은 정말 제가 공부하던 자바의 정석의 회독을 거듭해갈수록 정말 단순한 생각이였다는 것을 깨닫게 됩니다.

지금 부터는 텍스트로 하는 설명대신 코드로 다형성의 장점을 설명하겠습니다.

class Animal {}

class Tiger extends Animal {}

class Dog extends Animal {}

class Lion extends Animal {}

class Elephant extends Animal {}

아까 작성한 코드보다 자식 클래스가 늘었습니다.

다형성은 부모 - 자식 관계일때 부모를 상속받은 클래스가 많을 때 그 빛을 발합니다.

만약에 이런 상황이 있다면 어떨까요?

동물 클래스들을 배열에 담아서 자주 조회하거나 꺼내야 할 일이 생긴다는 상황이 있다면 어떻게 해야 할까요?

만약 각 동물 클래스들이 상속을 받지 않았다고 가정하면 이렇게 코드를 작성해야 할 것입니다.

// Animal 클래스와 각 동물 클래스가 상속관계가 없을때 배열예제

Tiger[] tigerArr = new Tiger[3];
Dog[] dogArr = new Dog[3];
Lion[] lionArr = new Lion[3];
Elephant[] elephantArr = new Elephant[3];

tigerArr[0] = new Tiger();
dogArr[0] = new Dog();
lionArr[0] = new Lion();
elephantArr[0] = new Elephant();

각 동물 타입별로 배열을 생성하고, 각 배열마다 동물의 객체를 담아주어야 합니다.

호랑이, 개, 사자, 코끼리는 공통 분모가 있습니다. 바로 동물이라는 특징을 가지고 있죠.
동물 클래스들이 Animal 클래스를 모두 상속받으면 우리는 이 코드를 훨씬 유연하게 개선할 수 있습니다.

// Animal 클래스와 각 동물 클래스가 상속관계가 있을때 배열 예제
Animal[] animalArr = new Animal[10];

animalArr[0] = new Tiger();
animalArr[1] = new Dog();
animalArr[2] = new Lion();
animalArr[3] = new Elephant();

다형성을 활용하면 위와 같이 부모 타입의 배열로 자식 객체들을 자유롭게 다룰 수 있습니다.

😲 ❓사용할 멤버도 줄어드는데 다형성이 이득 ❓ - 매개변수 다형성

다형성의 특성은 변수의 타입 뿐만 아니라 인터페이스나 파라미터에서도 똑같이 적용됩니다.

class English {
		String message = "Hello";
}

class Korean {
		String message = "안녕";
}

class French {
		String message = "Bonjour";
}

class LanguageTranslator {
		
		// 메서드 오버로딩

		void speak(English english) {
				System.out.println(english.message);
		}

		void speak(Korean korean) {
				System.out.println(korean.message);
		}

		void speak(French french) {
				System.out.println(french.message);
		}
}

public class Translator {
	public static void main(String[] args) {
			
			English english = new English();
			Korean korean = new Korean();
			French french = new French();

			LanguageTranslator papago = new LanguageTranslator();

			papago.speak(english);      // Hello 출력
			papago.speak(korean);       // 안녕 출력
			papago.speak(french);       // Bonjour 출력
	}
}

이렇게 메서드를 오버로딩해서 사용할 수 있습니다.

위에서 작성한 코드의 단점이 있다면, 메서드를 오버로딩 할때 각 타입별로(영어, 한국어, 프랑스어) 해주었기 때문에 만약에 독일어가 추가 된다면 메서드를 하나 더 작성해야 하는 단점이 있습니다.

타입이 추가 되더라도 코드를 수정하지 않도록 코드를 개선해 보도록 하겠습니다.

interface Language {
	void speak();
}

class English implements Language {
		String message = "Hello";

		public void speak() {
			System.out.println(this.message);
		}
}

class Korean implements Language {
		String message = "안녕";

		public void speak() {
			System.out.println(this.message);
		}
}

class French implements Language {
		String message = "Bonjour";

		public void speak() {
			System.out.println(this.message);
		}
}

class LanguageTranslator {
		void translate(Language language) {
			language.speak();
		}
}

public class Translator {
	public static void main(String[] args) {
			
			English english = new English();
			Korean korean = new Korean();
			French french = new French();

			LanguageTranslator papago = new LanguageTranslator();

			papago.speak(english);      // Hello 출력
			papago.speak(korean);       // 안녕 출력
			papago.speak(french);       // Bonjour 출력
	}
}

이전 코드에서는 각 객체 타입마다 다른 메서드를 사용하기 위해 오버로딩을 통해 계속 메서드를 생성해야 했지만, 인터페이스를 통해 공통 메서드를 정의하고, 다른 클래스가 추가 되더라도 인터페이스를 구현 하는 것으로 구성하여 더 이상 Language 클래스의 수정 없이도 다른 결과를 출력할 수 있도록 설계를 유연하게 변경하였습니다.

❗ 다형성 이제 끝인가요?


네 아직 하나 남았습니다.

🐳 진짜 마지막! - 메서드 다형성

메서드를 확장하거나 재정의하는 Overloaing & Overriding 또한 메서드가 다형해지기 때문에 다형성의 특징 중 하나에 속한다고 볼 수 있습니다.

  • Overloading : 같은 이름의 메서드 여러개를 가지면서 매개변수의 유형과 개수가 다르도록 하는 것을 말하며, 컴파일시 일어나는 정적 다형성 입니다.
  • Overriding : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의해서 사용하는 것을 말하며, 런타임시 일어나는 동적 다형성 입니다.
// 메서드 다형성 예제

class Transportation {
		
		// Overloading
		public void print(int fee) {
			System.out.println("요금 출력 = " + fee);
		}
	
		// Overloading
		public void print(String destination) {
			System.out.println("목적지 출력 = " + destination);
		}
		
		public void discountFee(int fee, int percent) {
			System.out.println("할인 가격 = " + (fee * percent) + "원");
		}
}

class Bus extends Transportation {
			// 오버 라이딩 -> @Override 애너테이션을 사용하는 것이 좋다
			@Override
			public void discountFee(int fee, int percent) {
				System.out.println("버스 이용시 추가 할인 = " + (fee * percent * 2) + "원");
			}
	}

class Main {
		public void static main(String[] args) {
			Transportation t = new Transportation();
			t.print(1000);    // 결과 : 요금 출력 = 1000
			t.print("부산");  // 결과 : 목적지 출력 = 부산
			t.discountFee(1000, 0.2); // 결과 : 할인 가격 = 200원

			Transportation t = new Bus();
			t.discountFee(1000, 0.2);   // 결과 : 버스 이용시 추가 할인 = 400원(오버 라이딩)
		}
}

다형성을 이용해 메서드를 오버로딩 & 오버라이딩해서
같은 이름의 메서드를 사용하여 다른 출력을 만들어 내는 것을 확인 할 수 있습니다.

⚠️ 다형성을 다시 보고 나서 느낀 것

요즘 들어 자바를 복습하면서 기존에 놓쳤던 개념이나 깊게 보지 못했던 것에 대해 많은 생각이 들때가 있었습니다.

이렇게 자주 사용하면서 제대로 알지 못했던 개념과 동작 원리에 대해 하나 하나 파보는 것도 굉장히 매력있는 공부 같아 앞으로 기본을 다지자는 의미에서 자주 해보려 합니다.

오늘도 좋은 공부를 한 것 같습니다 앞으로도 화이팅 하겠습니다!

출처 :

https://inpa.tistory.com/entry/OOP-JAVA의-다형성Polymorphism-완벽-이해

0개의 댓글