10분만 이 글에 투자해주시면
ArrayList를 List로 선언하는 이유를 이해시켜드리겠습니다..
(지적은 언제나 대환영입니다.)
본 아티클 시리즈를 정독하면, 아래 의문에 답을 얻을 수 있습니다.
1. 인터페이스와 구현체가 무엇인가? (List와 ArrayList의 차이) (클릭🔗)
2. ArrayList가 아닌 List 타입으로 선언하는 이유 (업캐스팅) -> 본편
3. Set - HashSet, Map - HashMap 의 차이 (클릭🔗)
ArrayList가 아닌 List 타입으로 선언하는 이유 (업캐스팅)
1편에서는 List와 ArrayList가 무슨 차이인지,
인터페이스와 구현체의 개념와 그 차이에 대해서 살펴보았습니다.
List<String> stringList = new ArrayList<>(); // 1번
ArrayList<String> stringList = new ArrayList<>(); // 2번
이제 위 구문을 살펴보겠습니다.
stringList 변수는 ArrayList 구현체로 생성되었습니다.
ArrayList 클래스 구현 코드를 살펴보면, 아무 인자가 없을 때 작동하는 생성자가 존재할 것이고,
그것에 따라 ArrayList 객체를 생성했고, stringList에 할당시켰을 것입니다.
그런데, 어차피 ArrayList를 생성해서 사용할 것이라면
ArrayLIst 타입으로 선언하면 되지 않을까요? (1번)
굳이 List로 선언할 이유가 있을까요?? (2번)
하지만 2번을 사용하는 것이 널리 알려져 있습니다.
그 이유에 대해서 살펴봅시다.
Yo DJ~ spin that shxx~
1. List의 메서드는 문제 없이 사용 가능하다!
우선, List로 선언한 변수에 ArrayList를 구현체를 생성해 할당하면
List 안에 선언된 추상 메서드들을 ArrayList가 구현한 방식대로 사용할 수 있습니다.
이해를 돕기 위해 예시를 들겠습니다.
List에 add, size 라는 추상 메서드가 선언이 되었다고 가정하겠습니다.
그리고 ArrayList에 add, size라는 메서드가 구현이 되었고,
추가로 addSize라는 메서드도 새로 구현이 되었다고 하겠습니다.
그럼 List 타입으로 선언된 stringList는 add와 size라는 메서드를 사용할 수 있습니다.
그리고 그것을 사용하면, ArrayList에서 구현한 방식대로 동작합니다.
하지만 여기서 문제가 발생하는데,
List에는 addSize라는 메서드가 선언되지 않았기 때문에,
stringList는 ArrayList의 addSize 메서드를 사용할 수 없습니다.
그렇지만, 대개 ArrayList에서 새로 선언한 메서드는 잘 사용되지 않고,
List 인터페이스에 선언된 메서드만으로 충분하기 때문에 큰 문제가 없다고 합니다!
2. (중요!) 다형성 부여 (+ 의존 관계 역전 원칙)
다형성이란 '하나의 객체가 여러 가지 타입을 가지는 것'을 의미합니다.
List<String> stringList = new ArrayList<>();
public int getFirstValueOfList(List stringList) {
return stringList.어쩌구();
}
위 코드를 살펴보겠습니다.
stringList는 List 타입이지만, ArrayList로 구현되었습니다.
그런데, 자료의 추가, 삭제를 빠르게 하려고 LinkedList를 사용하고 싶어진다면 어떡할까요?
// List<String> stringList = new ArrayList<>();
List<String> stringList = new LinkedList<>();
public int getFirstValueOfList(List stringList) {
return stringList.어쩌구();
}
구현체만 ArrayList에서 LinkedList로 변경해주면 끝입니다.
하지만, stringList의 타입이 List가 아닌 ArrayList였다면??
// List<String> stringList = new ArrayList<>();
ArrayList<String> stringList = new LinkedList<>(); // 컴파일 에러 발생
public int getFirstValueOfList(ArrayList stringList) {
return stringList.어쩌구();
}
ArrayList 타입에는 LinkedList 구현체를 할당할 수 없어서 컴파일 에러가 발생합니다.
위와 같은 코드를 구현했다면,
코드에 보이는 ArrayList를 모두 LinkedList로 수정해야 합니다.
// List<String> stringList = new ArrayList<>();
LinkedList<String> stringList = new LinkedList<>();
public int getFirstValueOfList(LinkedList stringList) {
return stringList.어쩌구();
}
코드가 복잡할수록 이 문제는 심각해집니다.
따라서 stringList를 List로 선언하고,
인자로 받을 때도 List로 받는 것입니다.
후에 stringList의 구현체가 변경되어도,
stringList와 관련한 메서드들을 수정할 필요가 없어지기 때문이죠.
이렇게 상위 개념으로 선언하고, 하위 개념을 생성해 할당하는 것을
업캐스팅(Up-casting)이라고 합니다.
(반대로 업캐스팅된 객체를 하위 개념 타입으로 변환하는 것은 다운캐스팅(Down-casting)이라고합니다.)
+ 추가내용)
이는 객체지향 SOLID 원칙의 D에 해당하는
의존 관계 역전 원칙(DIP : Dependency Inversion Principle)과도 연관이 있습니다.
저 원칙은 뜻이 되게 어려운데 (저에게는..)
저수준 모듈이 아닌 고수준 모듈에 의존해야 한다는 뜻을 가지고 있습니다.
즉, ArrayList가 아니라 List에 의존해야
다형성을 가질 수 있고, 다른 원칙을 위반하지 않을 수 있다는 것이죠.
관련 내용은 복잡하지만 중요해서, 추가로 찾아보기를 추천드립니다.
다음 아티클에서는
Set과 HashSet, Map과 HashMap의 차이에 대해서 다뤄보았습니다.
1, 2편 내용으로 충분히 이해되었을 내용이지만,
복습이 필요하시다면 읽어봐주세요.
감사합니다!
'Java' 카테고리의 다른 글
추상클래스란? 아니, 추상이란? - 무조건 이해되는 List와 ArrayList의 차이(4) (2) | 2022.11.01 |
---|---|
Set과 HashSet, Map과 HashMap의 차이 - 무조건 이해되는 List와 ArrayList의 차이(3) (0) | 2022.11.01 |
인터페이스와 구현체 - 무조건 이해되는 List와 ArrayList의 차이(1) (0) | 2022.11.01 |
Java 에러 - org.junit.platform.launcher.core.EngineDiscoveryOrchestrator lambda$logTestDescriptorExclusionReasons$7 (1) | 2022.10.28 |
IntelliJ JDK 버전 바꾸기 (m1 macOs) (0) | 2022.10.27 |