Java

Java Collection Framework - ArrayList

Daesiker 2025. 12. 1. 10:41
반응형

Collection Framework란?

Collection Framework는 데이터를 효율적으로 저장하고 관리하기 위한 표준화된 인터페이스와 클래스의 모음입니다. 쉽게 말해 "데이터 보관함의 종류"라고 생각하면 됩니다.

주요 인터페이스:

  • List: 순서가 있고, 중복을 허용하는 데이터 집합 (예: ArrayList, LinkedList)
  • Set: 순서가 없고, 중복을 허용하지 않는 데이터 집합 (예: HashSet, TreeSet)
  • Map: 키(Key)와 값(Value)의 쌍으로 데이터를 저장 (예: HashMap, TreeMap)

ArrayList 완벽 가이드

ArrayList란?

ArrayList는 크기가 가변적인 배열입니다. 일반 배열은 크기가 고정되어 있지만, ArrayList는 필요에 따라 자동으로 크기가 늘어나거나 줄어듭니다.

핵심 특징:

  • 크기가 동적으로 변함
  • 순서가 있음 (인덱스 존재)
  • 중복 허용
  • null 값 저장 가능
  • 내부적으로 배열 사용
  • 인덱스 접근 빠름 (O(1))

언제 ArrayList를 사용할까?

ArrayList 사용:

  • 데이터 개수를 미리 알 수 없을 때
  • 데이터를 자주 추가/삭제할 때
  • 인덱스로 빠르게 접근해야 할 때
  • 순서가 중요할 때

배열 사용:

  • 데이터 개수가 확정되어 있을 때
  • 성능이 매우 중요할 때 (약간 더 빠름)
  • 기본 타입(int, double 등)을 직접 저장할 때

ArrayList 생성

import java.util.ArrayList;

// 기본 생성 (초기 용량 10)
ArrayList<String> list1 = new ArrayList<>();

// 초기 용량 지정
ArrayList<Integer> list2 = new ArrayList<>(100);

// 다른 컬렉션으로 초기화
ArrayList<String> list3 = new ArrayList<>(Arrays.asList("A", "B", "C"));

// 타입 추론 (Java 7+)
ArrayList<String> list4 = new ArrayList<>();  // 오른쪽 타입 생략 가능

제네릭(Generic): <> 안에 저장할 데이터 타입을 명시합니다. 타입 안전성을 보장하고, 형변환을 자동으로 해줍니다.

초기 용량: ArrayList는 내부적으로 배열을 사용합니다. 데이터가 많다는 것을 알면 초기 용량을 크게 설정하여 성능을 향상시킬 수 있습니다.


ArrayList 핵심 메서드

1. add() - 요소 추가

리스트에 요소를 추가하는 가장 기본적인 메서드입니다.

맨 뒤에 추가

ArrayList<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");
// 결과: [Apple, Banana, Cherry]

특정 위치에 추가

fruits.add(1, "Orange");  // 인덱스 1에 삽입
// 결과: [Apple, Orange, Banana, Cherry]

시간 복잡도

  • 맨 뒤 추가: O(1) - 대부분의 경우 매우 빠름
  • 중간 삽입: O(n) - 뒤의 요소들을 모두 이동해야 함

2. get() - 요소 가져오기

특정 인덱스의 요소를 가져옵니다.

ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));

String first = fruits.get(0);   // "Apple"
String second = fruits.get(1);  // "Banana"
String last = fruits.get(fruits.size() - 1);  // "Cherry"

시간 복잡도: O(1) - 배열처럼 인덱스로 즉시 접근 가능

주의사항: 존재하지 않는 인덱스에 접근하면 IndexOutOfBoundsException이 발생합니다.

// 안전한 접근
if (index >= 0 && index < fruits.size()) {
    String fruit = fruits.get(index);
}

3. set() - 요소 수정

특정 인덱스의 요소를 변경합니다.

ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
fruits.set(1, "Blueberry");  // 인덱스 1을 변경
// 결과: [Apple, Blueberry, Cherry]

시간 복잡도: O(1)

반환값: 변경 전의 값을 반환합니다.

 
 
java
String oldValue = fruits.set(0, "Apricot");
System.out.println(oldValue);  // "Apple"

4. remove() - 요소 삭제

요소를 삭제하는 방법은 두 가지입니다.

인덱스로 삭제:

ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
fruits.remove(1);  // 인덱스 1 삭제
// 결과: [Apple, Cherry]

값으로 삭제:

fruits.remove("Apple");  // "Apple" 삭제
// 결과: [Cherry]

시간 복잡도: O(n) - 삭제 후 뒤의 요소들을 앞으로 이동

중요: 값으로 삭제할 때는 첫 번째로 일치하는 요소만 삭제됩니다.

ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "A", "C"));
list.remove("A");
// 결과: [B, A, C] - 첫 번째 "A"만 삭제

Integer 리스트 주의사항:

ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(10, 20, 30));

numbers.remove(1);           // 인덱스 1 삭제 → [10, 30]
numbers.remove(Integer.valueOf(20));  // 값 20 삭제

5. size() - 크기 확인

리스트에 들어있는 요소의 개수를 반환합니다.

ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
int count = fruits.size();  // 3

배열과의 차이:

  • 배열: arr.length (속성)
  • ArrayList: list.size() (메서드)

실무 활용:

// 마지막 요소 접근
if (!list.isEmpty()) {
    String last = list.get(list.size() - 1);
}

// 반복문
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

6. isEmpty() - 비어있는지 확인

리스트가 비어있으면 true, 아니면 false를 반환합니다.

ArrayList<String> emptyList = new ArrayList<>();
System.out.println(emptyList.isEmpty());  // true

emptyList.add("Item");
System.out.println(emptyList.isEmpty());  // false

size()와의 비교:

// 방법 1
if (list.isEmpty()) { }

// 방법 2
if (list.size() == 0) { }

둘 다 같은 결과지만, isEmpty()가 의도가 더 명확합니다.

7. contains() - 요소 포함 여부 확인

특정 요소가 리스트에 있는지 확인합니다.

ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));

boolean hasApple = fruits.contains("Apple");    // true
boolean hasOrange = fruits.contains("Orange");  // false

시간 복잡도: O(n) - 리스트를 순회하며 비교

내부 동작: equals() 메서드를 사용하여 비교합니다.

// 커스텀 객체의 경우 equals() 오버라이드 필요
class User {
    String name;
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            return this.name.equals(((User) obj).name);
        }
        return false;
    }
}

8. indexOf() & lastIndexOf() - 요소 위치 찾기

indexOf(): 처음부터 검색하여 첫 번째 일치하는 인덱스 반환

ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "B", "D"));

int index = list.indexOf("B");  // 1 (첫 번째 "B")
int notFound = list.indexOf("Z");  // -1 (없으면 -1 반환)

lastIndexOf(): 뒤에서부터 검색하여 마지막 일치하는 인덱스 반환

int lastIndex = list.lastIndexOf("B");  // 3 (마지막 "B")

시간 복잡도: O(n)

실무 활용:

// 중복 요소 모두 찾기
ArrayList<Integer> positions = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
    if (list.get(i).equals("B")) {
        positions.add(i);
    }
}

9. clear() - 모든 요소 제거

리스트의 모든 요소를 삭제합니다.

ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
fruits.clear();
System.out.println(fruits.size());  // 0
System.out.println(fruits.isEmpty());  // true

시간 복잡도: O(n) - 모든 요소를 null로 설정

주의: 리스트 객체 자체는 남아있습니다. 다시 사용할 수 있습니다.

fruits.clear();  // 비움
fruits.add("Grape");  // 다시 사용 가능

10. toArray() - 배열로 변환

ArrayList를 일반 배열로 변환합니다.

ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));

// 방법 1: Object 배열
Object[] arr1 = fruits.toArray();

// 방법 2: 타입 지정 (권장)
String[] arr2 = fruits.toArray(new String[0]);

// 방법 3: 크기 지정
String[] arr3 = fruits.toArray(new String[fruits.size()]);

실무 팁: new String[0]을 사용하는 것이 가장 깔끔합니다. 내부적으로 알아서 크기를 맞춰줍니다.


ArrayList 순회 방법

1. 일반 for문

ArrayList<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));

for (int i = 0; i < fruits.size(); i++) {
    System.out.println(i + ": " + fruits.get(i));
}

장점: 인덱스를 알 수 있음

사용 시기: 인덱스가 필요하거나 조건부 수정이 필요할 때

2. 향상된 for문 (for-each)

for (String fruit : fruits) {
    System.out.println(fruit);
}

장점: 간결하고 읽기 쉬움

단점: 인덱스를 모르고, 요소 삭제 불가

사용 시기: 단순 조회만 할 때 (가장 권장)

3. Iterator

Iterator<String> iter = fruits.iterator();
while (iter.hasNext()) {
    String fruit = iter.next();
    System.out.println(fruit);
    
    // 안전한 삭제 가능
    if (fruit.equals("Banana")) {
        iter.remove();
    }
}

장점: 순회 중 안전하게 삭제 가능

사용 시기: 순회하면서 요소를 삭제해야 할 때

4. forEach (Java 8+)

fruits.forEach(fruit -> System.out.println(fruit));

// 메서드 참조
fruits.forEach(System.out::println);

장점: 매우 간결함

사용 시기: 람다식에 익숙하고, 단순 작업을 할 때


 

반응형