Java에서는 기본적으로 여러 데이터를 다룰 때 배열을 사용합니다.
그러나 배열은 정적이므로, 한 번 생성되면 크기를 변경할 수 없습니다. 배열은 처리할 데이터의 개수가 정해진 경우에 유용하게 사용할 수 있지만 데이터 개수가 고정되어 있지 않다면 더 큰 배열을 새로 생성하거나, 복사를 해야 합니다.
이러한 작업은 꽤나 비용이 많이 들기 때문에 처음부터 배열의 길이를 넉넉하게 잡아줘서 새로운 배열을 생성하는 상황을 가능한 적게 발생하도록 합니다. 그러나 길이를 너무 크게 잡으면 메모리를 낭비하게 됩니다.
이와 같은 불편한 점을 개선하기 위해 나온 클래스가 바로 "ArrayList"입니다.
ArrayList 란 ❓
Java에서의 ArrayList는 List 인터페이스를 상속받은 동적 배열을 구현한 클래스로, 크기가 가변적으로 변하는 선형 자료 구조를 제공하여 기본 배열의 단점을 보완한 자료구조입니다.
즉 각 데이터에 순차적으로 접근 가능한 연속된 자료구조입니다.
배열을 기반으로 한 Collection 중 하나이며, 데이터를 추가하여 저장 용량(capacity)을 초과한다면 자동으로 부족한 크기만큼 저장 용량이 늘어난다는 특징이 있습니다.
ArrayList 동작 방식
1. 처음 ArrayList가 생성될 때 일정한 용량(Capacity)을 갖고 할당됩니다.
2. 할당된 용량이 넘어서 요소를 추가하면, 기존의 용량에 2배 크기로 새로 배열을 할당합니다.
3. 기존 배열의 요소를 새로운 배열에 복사합니다.
ArrayList 특징 ❕
1. 동적 할당 : 크기가 동적으로 할당되기 때문에 초기화할 때 크기를 지정할 필요가 없습니다.
2. 연속적인 데이터의 리스트 : 데이터는 연속적으로 리스트에 있어야 하며 빈 공간이 없습니다.
3. 인덱스 기반 접근 : 인덱스를 이용해 요소에 빠르게 접근할 수 있습니다.
4. 데이터를 중간에 삽입/삭제할 경우에 빈 공간을 채워야 하기 때문에 요소를 앞뒤로 자동으로 이동시키므로 성능이 떨어질 수 있습니다.
5. 성능저하 : 용량 변경 시 배열을 새로 생성하고 복사해야 하는 동작방식 때문에 큰 데이터를 다룰 때 성능 저하가 발생할 수 있습니다.
6. 요소를 추가하는 데 O(n) 시간이 걸립니다.
ArrayList 사용법
import java.util.ArrayList;
ArrayList를 사용하기 위해서는 위와 같이 ArrayList 패키지를 명시하여 import 해야 합니다.
ArrayList<참조 타입> list = new ArrayList<>(초기 용량);
ArrayList<Integer> list1 = new ArrayList<>(); // 타입 지정 : Integer 객체만 적재가능
ArrayList<Integer> list2 = new ArrayList<>(10); // 초기 용량(Capacity) 설정
ArrayList<Integer> list3 = new ArrayList<>(list1); // 다른 Collection값으로 초기화
ArrayList<Integer> list4 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); // 배열을 넣어 생성
ArrayList는 객체 참조만을 저장할 수 있기 때문에, 기본 데이터 타입(primitive data types)은 래퍼 클래스(wrapper classes)를 통해 저장됩니다. 예를 들어, int 대신 Integer 클래스를 사용해야 합니다.
위와 같이 제네릭을 의미하는 <> 기호를 사용하여 타입을 지정할 수 있습니다.
제네릭 안에 타입을 기재하면 ArrayList 클래스 자료형의 타입은 해당 타입으로 지정되어 해당 타입만 리스트에 적재할 수 있습니다.
제네릭(Generics) : 자바에서 제네릭(Generics)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법
ArrayList 메서드
메소드 | 설명 |
boolean add(E e) | 지정된 제네릭 타입 데이터를 추가한다. 추가에 성공하면 true를 반환 |
void add(int index, E element) | 특정 인덱스 위치에 데이터를 추가한다. |
boolean addAll(Collection<? extends E> c) | Collection을 현재 ArrayList에 모두 추가한다. |
void clear() | 모든 데이터를 초기화한다. |
boolean contains(Object o) | 파라미터로 전달된 객체를 포함하고 있는지 검사한다. 없으면 -1 반환 |
E get(int index) | 특정 인덱스 위치의 제네릭 타입의 데이터를 가져온다. |
int indexOF(Object o) | 지정된 객체(obj)가 저장된 위치를 찾아 반환한다. 없으면 -1 반환 |
Object set(int index, Object obj) | 기존에 추가된 값을 변경한다. |
Iterator<E> iterator() | 순차 데이터 처리를 하려고 Iterator 객체를 가져온다. |
boolean remove(int index) | 특정 인덱스 위치의 데이터를 삭제한다. |
int size() | ArrayList 객체에 포함된 데이터의 크기를 리턴한다. |
List subList(int fromIndex, int toIndex) | 인덱스 범위를 지정하여 해당 범위 요소를 가져온다. |
add() : 요소 삽입
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// add() method
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add(3, "A");
System.out.println(list);
}
}
add() 기본적으로 리스트의 가장 끝에 값을 추가합니다.
add() 인자에 인덱스를 지정하면 해당 인덱스에 값이 추가되고, 그 인덱스부터 뒤로 1 칸씩 밀리게 됩니다.
remove() : 요소 삭제
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// add() method
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.remove(2); // 2번째 인덱스 자리의 요소 삭제
System.out.println(list);
}
}
삭제할 때는 요소의 인덱스를 입력하거나, 요소를 직접 입력할 수 있습니다.
또한 인덱스를 통해 삭제할 경우에 삭제되는 요소를 반환받을 수 있기 때문에 값을 삭제하는 동시에 필요한 경우 삭제되는 값을 반환받아 사용할 수 있습니다.
clear() : 요소 전체 삭제
ArrayList<String> list1 = new ArrayList<>();
list1.add("1");
list1.add("2");
list1.add("3");
list1.clear(); // list1의 데이터를 모두 비운다.
System.out.println(list1); // []
하나씩 remove() 하지 않고 전체 리스트를 삭제하고 싶을 때는 clear() 메서드를 호출하면 됩니다.
ArrayList 요소 검색
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("A");
list.add("B");
list.contains("A"); // list에 A가 있는지 검색 : true
list.indexOf("B"); // 1
list.lastIndexOf("A"); // 3
}
}
ArrayList 요소 얻기
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(10);
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.get(0); // "A"
list.get(3); // "D"
}
}
개별 단일 요소를 얻고자 할 경우에는 get() 메서드를 사용하여 값을 가져올 수 있습니다.
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(18);
list.add("A");
list.add("R");
list.add("R");
list.add("A");
list.add("Y");
list.add("L");
list.add("I");
list.add("S");
list.add("T");
list.subList(0, 5); // [A,R,R,A,Y]
list.subList(5,7); // [L,I]
}
}
위와 같이 단일 요소가 아닌 범위를 지정해서 요소를 가져올 수 있습니다. JavaScript에서는 slice() 메서드와 비슷합니다.
subList() 메서드는 fromIndex ~ toIndex -1 사이에 저장된 객체를 반환합니다.
ArrayList 요소 변경
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(18);
list.add("A");
list.add("B");
list.add("C");
System.out.println(list1); // [A, B, C]
list.set(1, "b");
System.out.println(list1); // [A, b, C]
}
}
주어진 객체(obj)를 지정한 위치 (index)에 저장합니다.
이때 자리에 있던 기존의 데이터는 삭제되고, 새로운 데이터가 대체되어 들어갑니다.
ArrayList 복사
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
ArrayList<Integer> cloneList = (ArrayList<Integer>) list.clone();
System.out.println(list); // [1,2,3]
System.out.println(cloneList); // [1,2,3]
}
}
ArrayList는 내부적으로 Object [] 배열 형태로 저장하기 때문에 형변환이 필요합니다.
ArrayList 배열 변환
ArrayList를 다시 배열로 변환해야 하는 일이 자주 발생합니다. 이때 사용할 수 있는 메서드는 다음과 같습니다.
메서드 | 설 명 |
Object[] toArray() | ArrayList에 저장된 모든 객체들을 배열로 반환합니다. |
Object[] toArray(Object[] objArr) | ArrayList에 저장된 모든 객체들을 배열 objArr에 담아서 반환합니다. |
모든 원소를 하나씩 접근하면서 복사할 수 있지만 너무 번거롭고 비효율적이기 때문에 toArray() 메서드를 자주 사용합니다.
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.add("JavaScript");
list.add("Python");
// 방법 1 : 배열로 변환하고 반환
String[] listToArr1 = list.toArray();
// 방법 1 : 매개변수로 지정된 배열에 담아 바환
String[] listToArr2 = new String[list.size()]; // 먼저 리스트 사이즈에 맞게 배열 생성
//String[] listToArr2 = new String[new String[0]];
}
}
방법 1 : toArray()
먼저 List 클래스의 인스턴스 메서드인 toArray()를 사용하여 배열로 변환하고 Object 타입의 배열로 반환합니다.
방법 2: toArray(T [] a)
toArray(T [] a)는 T타입 배열을 반환합니다.
파라미터 a의 길이는 0으로 지정하면 알아서 list의 길이에 맞춰서 설정되어 배열에 저장됩니다.
방법 3: stream
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
int[] arr = list.stream().mapToInt(Integer::intValue).toArray();
}
}
값 타입 배열을 얻기 위해서는 int 값을 꺼내서 배열로 저장해야 하는데, 이때 사용할 수 있는 메서드가 바로 stream()입니다.
- list.stream() : Stream <Integer>를 반환합니다.
- mapToInt(Integer::intValue) : Integer의 intValue() 메서드를 참조해서 int로 언박싱합니다.
- toArray() : IntStream의 원소를 배열로 변환합니다.
Stream은 다음 포스팅에서 좀 더 자세히 다뤄보도록 하겠습니다.
ArrayList 순회
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 기본 for문
for(int i = 0 ; i < list.size() ; i++){
System.out.print(list.get(i) + " ");
}
// 결과 : 1 2 3
// 향상된 for문 (forEach)
for(Integer i : list) {
System.out.println(i);
}
}
}
보통 ArrayList의 요소를 순회해야 한다면 위와 같이 기본 for문이나 향상된 for문으로 처리하는 것이 일반적일 것입니다.
다만 몇몇 컬렉션(List, Set, Queue...)에서는 저장된 요소를 Iterator 인터페이스로 순회할 수 있습니다.
import java.util.ArrayList;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.add("JavaScript");
list.add("Python");
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
/* 실행 결과 */
// Java
// JavaScript
// Python
}
}
위 코드를 설명하자면 먼저 add() 메서드를 사용하여 ArrayList에 요소를 추가해줍니다. 그리고 ArrayList를 Iterator 객체로 생성해 주고, Iterator 인터페이스가 제공하는 함수를 사용하여 요소를 하나씩 뽑아 출력하게 됩니다.
Java에서 Iterator는 Collection framework에서 값을 가져오거나 삭제할 때 사용합니다.
Iterator 인터페이스가 제공하는 메서드는 3가지로 구분됩니다.
사용방법은 다음과 같습니다.
Iterator<데이터타입> iterator명 = 컬렉션.iterator();
1. hashNext()
: 다음 요소가 존재하는지 확인합니다. 존재하면 → true 존재하지 않으면 → false
2. next()
: 다음 요소를 가져옵니다.
3. remove()
: next()로 호출된 요소를 제거합니다.
Iterator에서 내부적으로 호출하는 메서드의 순서는 hashNext() → next() → remove()입니다.
Iterator 장점
- 모든 Collection Framework에서 공통으로 사용이 가능합니다.
- 3개의 메서드만 알면 되기 때문에 사용하기에 쉽습니다.
Iterator 단점
- 단방향 반복만 가능합니다.
- 값을 추가하거나 변경하지 못합니다.
- 대량의 데이터를 제어할 때 속도가 느립니다.
'자료구조' 카테고리의 다른 글
[자료구조 JAVA] LinkedHashSet 알아보기 (1/2) (0) | 2024.06.15 |
---|---|
[자료구조 JAVA] 스택 Stack 컬렉션 알아보기 (1/2) (0) | 2024.06.13 |
[JAVA] HashMap 이란 ❓ (1/2) (1) | 2024.06.05 |
[자료구조] 스택 stack 알아보기 With JavaScript (0) | 2024.04.28 |
[자료구조] HashTable 알아보기 (JavaScript) (1) | 2024.04.21 |