프로그래밍/JAVA

[ JAVA ] 다형성이란?

리신 2022. 10. 27. 17:40
반응형

다형성이란?

객체지향 개념에서는 '여러 가지 형태를 가질 수 있는 능력'을 의미한다.

Java에서는 한 타입의 참조 변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.

구체적으로 말하자면, 부모 클래스 타입의 참조 변수로 자식 클래스의 인스턴스를 참조할 수 있도록 하였다는 것이다.

 

 

💡잠깐, 참조 변수 타입과 인스턴스 타입이 같아야 하는 것 아닌가❗❔

→ 답은 아니다! 참조 변수 타입과 인스턴스 타입이 일치하는 것이 보통이지만,

서로 상속 관계에 있을 경우, 다음과 같이 부모 클래스의 타입의 참조 변수로 자식 클래스의 인스턴스를 참조하도록 하는 것도 가능하다!

 

🙄 그러면 왜 이렇게 사용하는 것일까? 그리고 어떤 차이가 있을까?

→ 일단 어떤 차이가 있는지 알아보고 왜 이렇게 사용하는 것인지 예제로 알아보자!

 

💎예제💎

예제 그림

위 그림은 클래스 TV와 CaptionTV는 서로 상속관계이다.

이 두 클래스의 인스턴스를 생성하고 사용하기 위해서는 다음과 같이 할 수 있다.

TV t = new CaptionTV();
CaptionTV c = new CaptionTV();

위 코드는 CaptionTV 인스턴스 2개를 생성하고, 참조 변수 c와 t가 생성된 인스턴스를 하나씩 참조하였다.

 

하지만 여기서 t는 인스턴스의 타입이 CaptioCaptionTVnTV라고 할지라도 모든 멤버를 사용할 수 없다.

TV타입의 참조 변수로는  CaptionTV인스턴스 중에서 tv클래스의 멤버들(상속받은 멤버 포함)만 사용할 수 있다.

 

✅ 참고

둘 다 같은 타입의 인스턴스지만 참조 변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

 

💡그럼 반대로 자식 타입의 참조 변수로 조상 타입의 인스턴스를 참조하는 것은 가능할까?

→ 불가능하다. 아래 코드를 컴파일하면 에러가 발생한다.

CaptionTV c = new TV();

🙄왜?

→ 실제 인스턴스인 TV의 멤버 개수보다 참조 변수 C가 사용할 수 있는 멤버 개수가 더 많기 때문에 이를 허용하지 않는다!

 

이해가 되지 않을 수 있다. 그래서 자세히 예를 들어 설명해본다면,

위 예제 그림과 위 코드를 같이 보면, CaptionTV클래스에는 text와 caption()이 정의되어 있으므로 참조 변수 c로는 c.text, c.caption()을 사용할 수 있다.

하지만 c는 참조하고 있는 인스턴스는 TV타입이고, tv타입의 인스턴스에는 text와 caption()이 존재하지 않기 때문에 이들을 사용하려 하면 문제가 발생한다. 

 

그래서 자식 타입의 참조 변수로 부모 타입의 인스턴스를 참조하는 것은 존재하지 않는 멤버를 사용하고자 할 가능성이 있으므로 허용하지 않는 것이다.

따라서 참조 변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.

 

✅ 참고

1. 클래스는 상속을 통해서 확장될 수는 있어도 축소될 수는 없어서, 조상 인스턴스의 멤버 개수는 자손 인스턴스의 멤버 개수보다 항상 적거나 같다.

 

2. 모든 참조 변수는 null 또는 4byte의 주소 값이 저장되며, 참조 변수의 타입은 참조할 수 있는 객체의 종류와 사용할 수 있는 멤버의 수를 결정한다.

 

여기서 한 가지 더 알아두어야 할 내용이 있다!!

💡멤버변수가 부모 클래스와 자식 클래스에 중복으로 정의된 경우에 어떻게 사용 될까?

 참조변수의 타입에 따라 달라진다.

부모타입의 참조변수를 사용했을 때는 부모 클래스에 선언된 멤버변수가 사용되고,

자식 타입의 참조변수를 사용했을 때는 자식 클래스에 선언된 멤버변수가 사용된다.

 

💡위 내용 정리💡

부모 타입의 참조 변수로 자식 타입의 인스턴스를 참조할 수 있다.
반대로 자손 타입의 참조 변수로 조상 타입의 인스턴스를 참조할 수는 없다.
멤버변수가 부모 클래스와 자식 클래스에 중복으로 정의된 경우 참조변수의 타입에 따라 달라진다.

 


 

이제 뭐가 다른지는 이해했는데 왜 사용하는 건가요❗❔

아니 그래서 내용은 알겠는데 도대체 왜 쓰는 건데요? 

저도 이와 같은 의문이 들었습니다. 글을 찾아봐도 유지보수가 쉽다 재사용성이 증가한다.라는 장점이 있다는데

아니 정확히 어떤 면에서 유지보수가 쉽고 재사용성이 증가하는지는 나와있지 않아 생각을 해보았습니다.

 

그러던 중 저는 List <Object>  list = new ArrayList <Object>();이렇게 쓰던 게 기억이 났습니다!!

 

코딩하면서 ArrayList를 다들 사용해 보셨을 겁니다.

다들 아래 코드 중 첫 번째 말고 두 번째 방법으로 사용하셨을 거예요!!

ArrayList<Object> list = new ArrayList<Object>();
List<Object> list = new ArrayList<Object>();

 

저는.. 코더여서.. 두 번째 방법이 좋다고 하길래 저 방법으로 계속 사용했었습니다.

이유도 모르고.. 반성합니다 ㅎㅎ

 

여하튼 두 번째 방법이 좋은 이유는 아까 말한 다형성을 활용해서 의존도를 낮추고 코드의 재사용성을 높일 수 있다는 것입니다.

 

🙄어째서?

→ 만약이라는 가정을 했을 때, ArrayList는 빠른 탐색에 유리하다는 장점이 있어요

그런데 삽입/삭제에 유리하게 바꿔야 해서 LinkedList로 코드 변경이 일어나야 하는 상황이에요!

 

만약 ArrayList <Object>  list = new ArrayList <Object>();처럼 사용하였다면,

LinkedList로 변경해야 할 때, ArrayList에서는 지원하지만 LinkedList에서 지원하지 않는 메서드를 사용하였다면 싹 다..

갈아엎어 다시 짜야할 수도 있습니다. (끔찍)

 

💎외워두자! 

위 코드를 보고  변경에 유연하지 못한 구조라는 멋있는 말을 사용할 수 있다.

 

 

그래서  List <Object>  list = new ArrayList <Object>();  이렇게 List인터페이스를 사용했을 경우에는 

똑같은 상황에서 선언부 외에 다른 부분을 변경할 필요가 없다.

이런 부분에서 뒤에 인스턴스 타입만 바꿔주고 다른 코드는 바꾸지 않아도 된다.

 

💎구체적인 예제💎

class A {
	static int sizeOf(List<?> list){
    	return list.size();
    }
}
    List<Object> list = new LinkedList<Object>();
    int size = A.sizeOf(list);

만약 위 코드에서  ArrayList <Object>  list = newArrayList <Object>(); 를 사용했다면,

sizeOf() 호출하는 코드부터 다 고쳐야 한다.

sizeOf() 메서드는 parameter의 타입에 의존적이기 때문이다.

 

이런 이유로 결합도가 낮아지고 의존성을 분리하고, 유지보수의 장점과 다른 코드의 변화를 만들어내지 않을수록 생산성이 높아진다

등의 장점을 이해할 수 있을 것이다!

 

 

 

참고자료 : Java의 정석

https://stackoverflow.com/questions/9852831/polymorphism-why-use-list-list-new-arraylist-instead-of-arraylist-list-n

 

Polymorphism: Why use "List list = new ArrayList" instead of "ArrayList list = new ArrayList"?

Possible Duplicate: Why should the interface for a Java class be prefered? When should I use List<Object> list = new ArrayList<Object>(); ArrayList inherits from List, so if some

stackoverflow.com

 

 

반응형