List
- 다형성과 OCP 원칙을 가장 잘 활용할 수 있는 곳 중 하나가 자료구조다.
- List : 순서가 있고, 중복을 허용하는 자료구조
- 배열과 비슷하지만 크기가 동적으로 변화하는 컬렉션을 다룰 때 사용한다.
- ArrayList와 LinkedList는 내부 구현만 다를 뿐 같은 기능을 제공하는 List
- 상황에 따라 성능은 다를 수 있지만 사용자 입장에서 보면 같은 기능을 제공한다.
- Collection 인터페이스 : 컬렉션 프레임워크의 핵심 인터페이스로 다양한 컬렉션 (데이터 그룹)을 다루기 위한 메소드를 정의하는 인터페이스이며 List, Set, Queue와 같은 다양한 하위 인터페이스가 있다.
- MyList는 인터페이스, MyLinkedList, MyArrayList는 MyList를 구현한 클래스.
public class BatchProcessor1 {
private final MyLinkedList<Integer> list = new MyLinkedList<>();
}
public class BatchProcessor2 {
private final MyList<Integer> list;
public BatchProcessor(MyList<Integer> list) {
this.list = list;
}
}
- 예시에서 BatchProcessor1 클래스는 MyLinkedList라는 구체적인 클래스에 의존하고 있다.
- 만약 LinkedList에서 ArrayList로 수정하고 싶다면 코드를 수정해야한다.
OCP 원칙에서 어긋난다.
- 만약 LinkedList에서 ArrayList로 수정하고 싶다면 코드를 수정해야한다.
- BatchProcessor2는 MyList라는 추상적인 인터페이스에 의존하고 있다.
- 만약 LinkedList에서 ArrayList로 수정하고 싶다면 코드를 수정할 필요 없다.
생성자에 의해서 의존성 주입(의존 관계 주입)이 되고 있기 때문에 어떤 구현체가 선택되는지 런타임에 생성자를 통해서 결정된다. OCP 원칙에 맞게 설계가 된 것.
- 만약 LinkedList에서 ArrayList로 수정하고 싶다면 코드를 수정할 필요 없다.
- 의존성 주입 (의존 관계 주입) - DI
- 의존 관계가 외부에서 주입되는 것을 의존성 주입, Dependency Injection (DI)이라고 부른다.
- 의존 관계를 미리 정하는 것이 아닌 런타임에 객체를 생성하는 시점으로 미루는 것.
- 의존성 주입을 통해 클라이언트 클래스는 컴파일 타임에 추상적인 것에 의존하고, 런타임에 의존성 주입을 통해 구현체를 주입받아 사용한다.
의존 관계
- 의존관계는 컴파일 타임 의존관계와 런타임 의존관계로 나뉜다.
- 컴파일 타임 (정적) - 코드 컴파일 시점을 의미하며 소스 코드 그자체만을 본다.
- 런타임 (동적) - 프로그램이 실행되는 동안의 시점
객체 생성, GC ...
- 컴파일 타임 의존 관계 : 자바 컴파일러가 보는 의존관계로 클래스에 모든 의존관계가 다 나타난다.
- 클래스에 바로 보이는 의존관계, 실행하지 않은 소스 코드에 정적으로 나타나는 의존관계를 말한다.
- 런타임 의존관계 : 실제 프로그램이 작동할 때 보이는 의존관계로 생성된 인스턴스와 그것을 참조하는 의존관계다.
- 프로그램이 실행될 때 인스턴스 간에 의존관계
- 런타임 의존관계는 프로그램 실행 중 계속 변할 수 있다.
- 보통 재사용성을 높이기 위해서 결정을 미래로 미룬다.
- 메소드로 만들어서 매개 변수로 받는 것.
- 추상적인 의존 관계를 지정함으로 미래에 구현체를 주입 받는 것.
- 제네릭 타입에서 타입 매개 변수를 지정해놓고 미래에 타입 인자를 받는 것.
- 순차적 접근 속도, 메모리 할당 및 해제 비용, CPU 캐시 활용도 등에 의해서 실제로 ArrayList가 LinkedList보다 성능상 유리하기에 실무에서 주로 ArrayList를 사용한다.
- 데이터를 앞쪽에 자주 추가, 삭제하는 경우에만 LinkedList를 고려하는 것이 좋다.
- 코드 작성 시 팁
//1번 코드
for (Item item : items) {
System.out.println("상품명: " + item.getName() + ", 합계: " + item.totalPrice());
totalAmount += item.totalPrice();
}
//2번 코드
for (Item item : items) { //출력을 위한 루프
System.out.println("상품명: " + item.getName() + ", 합계: " + item.totalPrice());
}
for (Item item : items) { //계산을 위한 루프
totalAmount += item.totalPrice();
}
- 예제와 같이 List에서 List 정보를 출력하고 계산을 해야한다고 가정
- 1번 코드는 한 번의 루프로 출력과 계산을 동시에 하기에 성능적인 측면에서 좋다.
- 하지만 최근에는 컴퓨터의 cpu, 메모리와 같은 성능들이 너무 좋기에 데이터의 양이 너무 많지 않다면 성능적인 측면보다는 유지 보수 관점에서 코드를 작성하는 것이 더 좋다.
- 2번 코드는 두 번의 루프를 사용하기에 성능적인 측면에서는 떨어질 수 있지만 유지보수 관점에서 본다면 각각의 기능별로 분리를 함으로 유지보수가 편리해지고 나중에 (메소드 추출) 메소드로 만들기에도 편해진다.
- 실무에서는 데이터의 양이 과도하게 많지 않다면 성능보다는 유지보수 관점에서 바라보고 코드를 작성하는 것이 더 좋다.
출처: [인프런 김영한 실전 자바 - 중급편]
'Java > [인프런 김영한 실전 자바 - 중급편]' 카테고리의 다른 글
[인프런 김영한 실전 자바 - 중급편] 컬렉션 프레임워크 - HashSet (2) | 2024.07.26 |
---|---|
[인프런 김영한 실전 자바 - 중급편] 컬렉션 프레임워크 - Hash (1) | 2024.07.26 |
[인프런 김영한 실전 자바 - 중급편] 컬렉션 프레임워크 - LinkedList (0) | 2024.07.25 |
[인프런 김영한 실전 자바 - 중급편] 컬렉션 프레임워크 - ArrayList (0) | 2024.07.25 |
[인프런 김영한 실전 자바 - 중급편] Generic 1, 2 (2) | 2024.07.25 |