(지적은 언제나 대환영입니다.)
본 아티클 시리즈를 정독하면, 아래 의문에 답을 얻을 수 있습니다.
1. 인터페이스와 구현체가 무엇인가? (List와 ArrayList의 차이) (클릭🔗)
2. ArrayList가 아닌 List 타입으로 선언하는 이유 (업캐스팅) (클릭🔗)
3. Set - HashSet, Map - HashMap 의 차이 -> 본편
Set과 HashSet, Map과 HashMap의 차이
Set<String> set = new HashSet<>();
Map<String, Integer> map = new HashMap<>();
위와 같은 코드를 자주 보셨을 거라 생각합니다.
여기서 한 가지 의문이 듭니다.
왜 HashSet 객체를 Set으로 선언하는 거지?
왜 HashMap 객체를 Map으로 선언하는 거지?
여기서 Set과 Map은 인터페이스입니다.
인터페이스는 실제로 구현되지 않은 추상메서드만 가지고 있습니다.
하지만 HashSet과 HashMap은 해당 인터페이스들을 구현(implements)한 클래스입니다.
이러한 클래스를 해당 인터페이스의 구현체, 구현 객체 라고 합니다.
비유하자면,
인터페이스는 Set(집합)으로서 갖춰야 할 규격, 지켜야 할 가이드와 같은 개념입니다.
HashSet은 그 규격에 맞춰 실제로 설계된 물건입니다.
(아래에서는 편의를 위해 Set만 언급하겠습니다. Map으로 치환해도 내용은 동일합니다.)
위와 같이 set 변수를 Set으로 선언하면,
Set 인터페이스에 선언된 메서드를 사용할 수 있습니다.
그리고 그 동작은 HashSet에서 구현된 로직을 통해 이루어집니다.
그런데, Set 인터페이스에는 없지만
HashSet에는 구현되어 있는 메서드가 있을 수도 있습니다.
그리고 Set 타입인 set 인스턴스는 그 메서드를 사용할 수 없습니다.
그럼에도 불구하고 set을 Set 타입으로 선언하는 이유가 무엇일까요?
아래에서 그 이유를 다뤄보겠습니다.
HashSet, HashMap이 아닌 Set, Map으로 선언하는 이유
바로 다형성 때문입니다.
다형성은 하나의 객체에 여러 가지 타입을 적용할 수 있는 것입니다.
사실 HashSet을 많이 사용하지만,
Set 인터페이스의 구현체에는 TreeSet, LinkedHashSet 등도 있습니다.
(당연히 아실 수도 있지만, 저는 몰랐습니다..)
Set<String> set = new HashSet<>();
public int getFirstValueOfSet(Set<String> set) {
return set.어쩌구();
}
만약 위와 같이 구현된 코드에서,
HashSet이 아닌 TreeSet을 사용하고 싶다고 해봅시다.
그럼 아래와 같이 HashSet만 TreeSet으로 수정하면 됩니다.
Set<String> set = new TreeSet<>();
public int getFirstValueOfSet(Set<String> set) {
return set.어쩌구();
}
하지만 아래 set 객체가 Set이 아닌 HashSet으로 선언되었다면?
HashSet<String> set = new TreeSet<>(); // 컴파일 에러 발생
public int getFirstValueOfSet(HashSet<String> set) {
return set.어쩌구();
}
컴파일 에러가 발생합니다.
게다가 수정을 위해서는 HashSet을 전부 TreeSet으로 바꿔줘야 합니다.
TreeSet<String> set = new TreeSet<>(); // 변경 과정이 복잡하다
public int getFirstValueOfSet(TreeSet<String> set) {
return set.어쩌구();
}
코드가 복잡할수록 이러한 문제도 더욱 심각해집니다.
그렇기에 set을 Set으로 선언하고,
인자로 받을 때도 Set 타입으로 받는 것이죠.
이렇게 상위 개념으로 선언하고, 하위 개념을 생성해 할당하는 것을
업캐스팅(Up-casting)이라고 합니다.
(반대로 업캐스팅된 객체를 하위 개념 타입으로 변환하는 것은 다운캐스팅입니다.)
이는 객체지향의 SOLID 원칙 중 D(Dependence Inversion Priciple : 의존 관계 역전 원칙)와도 연관이 깊은 내용입니다.
관련한 내용은 깊고 중요해, 따로 찾아보시는 것을 추천드립니다.
본 편에서는 1, 2편의 내용을 Set과 Map에도 적용해보았습니다.
1, 2편을 읽지 않아도 대략적인 이해가 되리라 생각합니다만
이해가 어렵거나 보다 더 이해하고 싶으시다면,
1, 2편을 차례로 읽어보시길 추천드립니다.
1편 - 인터페이스와 구현체가 무엇인가? (List와 ArrayList의 차이) (클릭🔗)
2편 - ArrayList가 아닌 List 타입으로 선언하는 이유 (업캐스팅) (클릭🔗)
마지막 4편에서는 추상클래스에 대해 가볍게 다뤄보겠습니다.
이 '추상', '추상화'라는 개념이 프로그래밍에서 쓰이는 것와 일상에서 쓰이는 뜻이 너무 달라 저에게는 너무 어렵게 느껴졌습니다.
관련해서 느꼈던 점과 이해한 방식을 소개해보고자 합니다.
감사합니다!