순수 배열과 ArrayList 클래스 모두 데이터 요소를 저장하고 관리할 때 사용한다.
순수 배열은 크기가 고정되어 있어 데이터의 추가에 제약이 있고 삭제가 불가능하지만, ArrayList 클래스는 배열의 한계를 해결하여 사용하기에 편리하다.
소스 코드를 살펴보며 ArrayList의 특징과 사용법, 주요 메서드를 알아보도록 하자!
💡순수 배열
// Create
int[] num1 = new int[3];
num1[0] = 10;
num1[1] = 20;
num1[2] = 30;
// Read
System.out.println(num1[0]);
System.out.println(num1[1]);
System.out.println(num1[2]);
System.out.println(num1.length);
// Update
num[0] = 20;
순수 배열은 타입과 길이가 명시되어 있다. (타입: int, 길이: [3])
순수 배열의 특징은 indexer 표기법을 사용하여 요소를 접근할 때 첨자(index)를 사용하며, 방의 삭제가 불가능하다는 특징이 있다.
💡ArrayList Class
- ArrayList(C) -> List(I) -> Collection(I)
ArrayList는 순수 배열과 가장 유사하여 사용빈도가 높은 편이다.
순수 배열과 컬렉션의 차이점은 배열과 같이 타입은 명시되어 있으나, 길이가 명시되어 있지 않다는 점이다. 그래서 컬렉션은 길이가 가변적이다. (타입: <Integer>)
그리고 순수 배열에서는 불가능한 요소 삭제가 컬렉션에는 가능하다는 특징이 있다.
ArrayList 배열 선언
ArrayList<String> list = new ArrayList<String>();
ArrayList 배열을 만들어보자.
제네릭의 타입 변수를 이용해 배열의 방의 타입을 설정한다.
내부 배열의 초기 길이 지정
ArrayList<Integer> list = new ArrayList<Integer>(10);
참고로 ArrayList 배열을 선언할 때, 컬렉션 초기 용량(내부 배열의 초기 길이)을 정해주는 게 가능하다.
ArrayList 배열에 데이터를 추가하는 방식은 초기에 지정한 배열의 길이를 초과할 때마다 이전의 데이터를 버리고 크기를 늘리는 과정을 거친다.
이러한 과정으로 배열의 길이가 점점 늘어나는 듯한 사용법을 구현한 것이므로, 처음부터 배열의 크기를 필요한 만큼 정한다면 낭비를 줄일 수 있다.
CRUD
Create (쓰기)
list.add("바나나");
list.add("딸기");
list.add("사과");
list.add("뀰");
list.add("파인애플");
데이터를 추가할 때에는 boolean add(T value) 메소드를 사용한다.
add()는 append와 같다. 이때 append는 마지막에 추가한다는 뜻으로 차례대로 마지막 방에 넣으라는 뜻이다.
처음 넣는 데이터는 0번째 방에 알아서 들어가며, 데이터를 추가할 때마다 순차적으로 들어가게 된다. 물론 방번호를 명시하여 데이터를 추가하는 방법도 존재한다.
Read (읽기)
System.out.println(list.get(0)); // 바나나
System.out.println(list.get(1)); // 딸기
System.out.println(list.get(2)); // 사과
데이터를 읽어올 때에는 T get(int index) 메서드를 사용한다.
System.out.println(list.size()); // 5
size() 메서드는 순수 배열의 length 기능을 하며, ArrayList는 계속 길이가 변화하므로 size() 메서드가 상당히 중요하다.
없는 방번호를 호출했을 경우 IndexOutOfBoundsException 오류가 발생한다.
Update (수정)
String temp = list.set(2, "포도");
System.out.println(list.get(2));
System.out.println(temp);
System.out.println();
데이터를 수정할 때에는 String set(int index, T newValue) 메소드를 사용한다.
사라지는 직전의 값을 return 해준다는 특징이 있지만, 평상시에는 리턴값을 받을 일이 많지 않다.
Delete (삭제)
System.out.println(list.get(1)); // 딸기
System.out.println(list.get(2)); // 포도
System.out.println(list.get(3)); // 뀰
list.remove(2);
// list.remove("포도");
System.out.println(list.get(1)); // 딸기
System.out.println(list.get(2)); // 뀰
System.out.println(list.get(3)); // 파인애플
순수 배열의 요소(방)는 삭제가 불가능하지만 컬렉션은 요소 삭제가 가능하다.
데이터를 삭제할 때에는 T remove(int index) 메소드 또는 boolean remove(T value) 메소드를 사용한다.
각각 방번호를 찾아서 삭제하는 것과 값을 찾아서 삭제하는 것의 차이이다.
위 소스코드에서 2번 인덱스 값을 지우기 전까지만 해도 3번째 방에는 뀰이 있었는데, 뀰 앞의 2번째 방의 포도를 지우자 뀰이 2번 방으로 이동했다. 비어져 있는 방을 채우기 위한 shift가 발생한 것이다!
이걸 조심해야 하는 이유는 3번째 방에 뀰이 있다고 믿고 있었는데, 나도 모르게 어딘가에서 요소가 지워지면 뀰의 위치가 이동하기 때문이다.
삭제된 방 이후의 모든 요소는 모두 방번호가 -1씩 감소한다는 사실을 기억하자.
list.remove("포도")
"포도"값을 이용해 list에서 값을 지웠는데 만약 포도가 2개 이상이라면 첫 번째 만나는 포도를 삭제한다.
그럼 두 번째 포도를 지우고 싶다면 어떻게 해야 할까? 안타깝게도 그건 불가능하다. 그래서 주로 값을 지울 때에는 인덱스를 이용해 지우는 편이다.
기타 메소드 활용
toString() 메서드 (덤프 메서드)
System.out.println(list.toString()); //[바나나, 딸기, 뀰, 파인애플]
System.out.println(list); //[바나나, 딸기, 뀰, 파인애플]
ArrayList 클래스는 toString을 미리 재정의했기 때문에 오브젝트를 찍기만 하면 값 목록이 출력된다.
이 결과는 toString()을 붙이지 않고 list만 출력해도 동일하다
add() 메서드 요소 삽입
list.add(2, "맹고");
System.out.println(list); // [바나나, 딸기, 맹고, 뀰, 파인애플]
배열의 원하는 위치에 요소를 추가할 때에는 오버로딩 되어있는 add() 메서드를 사용한다.
void add(index index, T value)와 같이 add() 메서드에 오버로딩이 되어있으며, 삽입된 방 이후의 모든 요소의 방번호가 +1 증가하고 요소를 삭제할 때처럼 shift가 발생한다.
indexOf() 메소드
System.out.println(list.indexOf("사과")); // -1
System.out.println(list.indexOf("뀰")); // 3
System.out.println(list.indexOf("맹고")); // 2
indexOf() 메서드로 값이 있는 위치를 반환할 수 있다.
만약 값이 없을 경우 -1을 반환한다.
claer(), isEmpty() 메소드
System.out.println(list.isEmpty()); // false
list.clear();
System.out.println(list.isEmpty()); // true
clear() 메서드는 배열을 깨끗하게 비워주며, isEmpty() 메소드는 리스트에 값이 비어있는지 물어본다.
헷갈릴 수 있는데, 비어있는지를 물어보는 것이므로 true는 데이터가 없는 것을 의미하고, false는 데이터가 있는 것을 의미한다.
trimToSize() 메소드
list.add(10);
list.trimToSize();
trimToSize() 메소드는 데이터가 들어있는 만큼의 길이로 재조정하는 역할을 한다.
foreach문 (향상된 for문)
for (변수 : 배열) {
}
for (String item : list) {
System.out.println(item);
}
foreach문은 for문과 달리 사용하는 데 있어 조건이 없고, 인덱스도 없다.
foreach문은 for보다 반복 과정은 훨씬 단순하다. 처음 반복문이 실행되면 list 집합을 찾아간다. 그리고 그 집합을 찾아본 다음에 무조건 첫 번째 값을 찾아 item에 복사한다.
반복문이 끝나면 다시 처음으로 올라가 다음 값을 찾아 item에 복사하고, 반복문 안에서 item을 사용한다. 그렇게 반복문을 실행하다가 배열에 남은 게 없으면 반복문을 나간다.
다차원 배열
// 1차원 배열
ArrayList<Integer> ms1 = new ArrayList<Integer>();
// 2차원 배열
ArrayList<ArrayList<Integer>> ms2 = new ArrayList<ArrayList<Integer>>();
// 3차원 배열
ArrayList<ArrayList<ArrayList<Integer>>> ms3 = new ArrayList<ArrayList<ArrayList<Integer>>>();
ArrayList 클래스는 순수 배열과 달리 클래스를 배열로 만들다 보니 다차원 배열에 가독성이 떨어진다.