hanker

Java - List 인터페이스 (ArrayList, LinkedList, Vector 등) 본문

JAVA

Java - List 인터페이스 (ArrayList, LinkedList, Vector 등)

hanker 2025. 2. 12. 00:00
반응형

이번 글에서 List 인터페이스에 대해서 알아보려한다.

List 인터페이스는 여러 구현 클래스를 제공하는데, 해당 구현체들의 특징과 장단점 및 예제를 자세하게 알아보자!

 


1. List 인터페이스

 

List는 순서가 있는 데이터를 저장하고, 중복 요소를 허용하는 특징을 가지고 있다.

다양한 구현 클래스를 제공하고, 대표적으로 ArrayList, LinkedList, Vector 가 있다.

 

1-1. List 특징

- 순서 유지 : 입력된 순서대로 요소가 저장된다.

- 인덱스 접근 가능 : 요소를 index를 사용해 직접 접근 가능

- 중복 허용 : 동일한 값의 요소를 여러 개 저장 가능

 

1-2. 주요 구현 클래스

클래스 명 특징
ArrayList 조회 성능 우수, 삽입/삭제 속도 상대적으로 느림
LinkedList 조회 속도 상대적으로 느림, 삽입/삭제 성능 우수
Vector ArrayList와 유사하지만 동기화(Synchronized) 지원
특징 ArrayList LinkedList
데이터 구조 동적 배열 이중 연결 리스트
접근 속도 빠름  느림 
삽입/삭제 속도 느림 빠름
메모리 사용량 적음 (배열 크기만큼) 많음 (객체+포인터)

 


2. ArrayList

 

내부적으로 동적 배열을 사용하므로 인덱스 기반 접근이 빠르며, fail-fast iterator(for-each 사용시 데이터 변경되면 에러 발생) 특성을 가지고 있다.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // ArrayList 생성 및 요소 추가
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");

        // 인덱스를 통한 요소 접근
        System.out.println("첫 번째 과일: " + fruits.get(0));

        // for-each 문을 이용한 반복
        System.out.println("\nfor-each 반복:");
        for (String fruit : fruits) {
            System.out.println(fruit);
        }

        // Java 8의 forEach 메서드 활용
        System.out.println("\nJava 8 forEach:");
        fruits.forEach(System.out::println);

        // subList를 이용한 부분 리스트 생성
        List<String> subFruits = fruits.subList(1, 3); // 인덱스 1(포함)부터 3(미포함)까지의 요소
        System.out.println("\nSubList: " + subFruits);

        // fail-fast iterator 예제
        System.out.println("\nfail-fast iterator 예제:");
        try {
            for (String fruit : fruits) {
                // 반복 중 직접 리스트를 수정하면 ConcurrentModificationException 발생
                if ("Banana".equals(fruit)) {
                    fruits.add("Orange");
                }
            }
        } catch (Exception e) {
            System.out.println("예외 발생: " + e);
        }
    }
}

- add(), get() 메서드를 사용하여 요소를 추가하고 인덱스로 접근한다.

- subList 사용 : 원본 리스트의 일부를 뷰(View)로 반환하므로, 원본 리스트와 변경 사항이 공유된다.

- fail-fast interator : 반복 중 리스트를 직접 수정하면 ConcurrentModificationException 이 발생함을 보여준다.


2. LinkedList

 

LinkedList는 내부적으로 이중 연결 리스트(Doubly Linked List)를 사용하여, 삽입/삭제가 빠르지만 랜덤 액세스는 느리다.

import java.util.LinkedList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // LinkedList 생성 및 요소 추가
        List<Integer> numbers = new LinkedList<>();
        numbers.add(10);
        numbers.add(20);
        numbers.add(30);

        // LinkedList의 특화 메서드를 사용하려면 타입 캐스팅 필요 (LinkedList로 선언했으면 바로 사용 가능)
        LinkedList<Integer> linkedNumbers = (LinkedList<Integer>) numbers;

        // 리스트의 앞에 요소 추가
        linkedNumbers.addFirst(5);
        System.out.println("LinkedList (addFirst): " + linkedNumbers);

        // 리스트의 앞/뒤 요소 제거
        linkedNumbers.removeFirst();
        linkedNumbers.removeLast();
        System.out.println("LinkedList (removeFirst & removeLast): " + linkedNumbers);
    }
}

- addFirst / removeFirst / removeLast : LinkedList에서는 앞이나 뒤에 요소를 추가 또는 제거할 때 포인터 조작만으로 처리할 수 있다.

- 랜덤 액세스 : LinkedList는 특정 인덱스에 접근할 때 전체를 순회해야 하므로, 대규모 데이터에선 성능 차이가 날 수 있다.

 


3. Vector

 

Vector는 ArrayList와 유사하게 동적 배열을 사용하지만, 동기화(synchronized) 처리가 되어 있다.


import java.util.List;
import java.util.Vector;

public class Main {
    public static void main(String[] args) {
        // Vector 생성 및 요소 추가
        List<String> vector = new Vector<>();
        vector.add("One");
        vector.add("Two");
        vector.add("Three");

        System.out.println("Vector: " + vector);
    }
}

- 동기화 지원 : 멀티스레드 환경에서 사용할 수 있지만, 단일 스레드에서는 오버헤드가 발생할 수 있다.

- 최근에는 Collection.synchronizedListCopyOnWriteArrayList를 더 많이 사용한다.


4. CopyOnWriteArrayList

 

CopyOnWriteArrayList는 읽기 작업이 많은 멀티스레드 환경에서 유용하며, 쓰기에 있어 전체 배열을 복사하는 방식으로 동시성을 보장한다.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class Main {
    public static void main(String[] args) {
        List<String> cowList = new CopyOnWriteArrayList<>();
        cowList.add("Alpha");
        cowList.add("Beta");
        cowList.add("Gamma");

        // 반복 도중 리스트를 수정해도 ConcurrentModificationException이 발생하지 않습니다.
        System.out.println("CopyOnWriteArrayList 반복 및 수정 예제:");
        for (String s : cowList) {
            System.out.println("현재 값: " + s);
            if ("Beta".equals(s)) {
                cowList.remove(s); // 안전하게 삭제됨
            }
        }
        System.out.println("수정 후 리스트: " + cowList);
    }
}

- 반복중 안전하게 수정 : 일반 ArrayList에서는 반복 중에 수정 시 예외가 발생하지만, CopyOnWriteArrayList는 내부 배열을 복사하여 수정하므로 예외가 발생하지 않는다.

- 쓰기 성능 : 요소를 추가하거나 삭제할 때 전체 배열을 복사하기 때문에, 쓰기 작업이 많은 경우 비효율적이다.

 


5. subList 사용 시 주의사항

 

subList는 원본 리스트의 뷰(View)를 반환한다. 따라서 원본 리스트와 subList는 서로의 변경 사항에 영향을 미친다.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> letters = new ArrayList<>();
        letters.add("a");
        letters.add("b");
        letters.add("c");
        letters.add("d");

        // 인덱스 1부터 3까지 (b, c) 반환
        List<String> subLetters = letters.subList(1, 3);
        System.out.println("subList 생성 후: " + subLetters);

        // subList를 통한 변경은 원본 리스트에 반영됨
        subLetters.set(0, "B");
        System.out.println("subList 수정 후 원본 리스트: " + letters);

        // 원본 리스트의 구조 변경 후 subList 사용 시 주의!
        try {
            letters.add("e");  // 구조 변경 발생
            // 구조 변경 이후 subList에 접근하면 ConcurrentModificationException이 발생할 수 있음
            System.out.println("구조 변경 후 subList: " + subLetters);
        } catch (Exception e) {
            System.out.println("예외 발생: " + e);
        }
    }
}

- 뷰(View) 특성 : subList는 원본 리스트의 일부를 참조하기 때문에, subList에 적용된 변경은 원본에도 반영된다.

- 구조 변경 주의 : subList 생성 후 원본 리스트에 구조적 변경(추가, 삭제 등)을 하면 subList에 접근할 때 예외가 발생할 수 있다.


정리

 

위 예제 코드를 통해서 각 List 구현체의 특성과 주요 메서드 사용법 그리고 주의사항을 알아봤다.

- ArrayList : 빠른 인덱스 접근과 간편한 사용

- LinkedList : 삽입 / 삭제에 유리

- Vector : 동기화가 필요할 때 사용

- CopyOnWriteArrayList : 읽기 위주의 멀티스레드 환경에서 유용

 

상황에 맞게 적절한 List 구현체와 메서드를 선택하여 보다 효율적이고 안전하게 개발할 수 있다.

 

끝.

반응형