[Java]ArrayList 구조 및 사용법

hansung's·2024년 2월 27일
1

🤔 ArrayList Collection


자바로 프로그램을 짜거나 코딩 테스트를 하면 쉽게 맞이할 수 있는 컬랙션 프레임워크에는 ArrayList가 존재한다.

ArrayList는 배열의 상위호환이라고 생각하면 되며, 기존의 배열로 자료를 담고 뺴고하는데 불편함이 존재하면서 나온 것이 ArrayList라고 한다.

자바의 List 인터페이스를 상속받는 여러 클래스 중 하나이다.

🙌 Collection Framework


ArrayList를 설명하기 앞서 Coolection Framework에 대해서 짧게나마 설명하고자 한다.
단순하게 이야기 하면, 자료 구조 종류의 형태(List 등)들을 자바 클래스로 구현한 모음집 이라고 보면 된다.

※ java.util 패키지 안에 자료구조와 관련된 클래스가 들어있다. 

간단하게 Collection Framework를 사용하는 장점에 대해 설명하면,

인터페이스와 다형성을 이용한 객체지향적 설계를 하여 표준화가 되어 있기 때문에, 사용법을 익히거나 재사용성이 높다.

  • 예로, vector 클래스와 ArrayList 클래스의 사용법이 비슷한 이유가 위와 같을 것이다.
!!!
컬렉션 프레임워크에 저장할 수 있는 데이터는 객체(Object)만 가능하다.

그래서 int형이나 double형과 같은 자바의 primitive 타입(원시 타입)은 적재하지 못한다.

따라서 primitive 타입을 wrapper 타입으로 변환해 
Integer나, Double 객체로 박싱(Boxing)하여 저장해야 한다.
ex) List<Integer> list = new List<>(); 이런것을 많이 봤을 것이다.

또한, 객체를 담는 다는 것은 결국 주소값을 담는 것이기 때문에, null 역시 저장할 수 있다.

✌ Collection Framework 종류


오늘은 ArrayList에 대해 설명하기로 했기 때문에, 이 정도만 있다는 수준으로 마무리 지으며 다음 시간에 따로 Collection Framework에 대해 조사해보겠다.

🎉 ArrayList 특징


ArrayList란?
배열(List)를 기반으로 한 컬렉션 중 하나이며, 데이터 추가, 삭제시 동적으로 길이를 조절해준다.


위의 그림을 보며 설명을 하면, 현재 저장공간은 8이지만 만약 8을 초과하게 된다면 가변적으로 크기를 변화시킬 수 있다.

즉, add와 같이 데이터가 추가되어 Capacity(용량)을 초과하게 된다면 자동적으로 저장 공간이 더 할당된다고 볼 수 있다.

특징을 조금 더 정리하자면

  1. 연속적인 데이터 리스트(중간에 빈 공간이 존재하지 않는다.)
  2. ArrayList 클래스는 내부적으로 Object[] 배열을 이용해 요소를 저장
    • 위 사진과 같이 Object[] 배열을 사용하는 것을 볼 수 있다.
  3. 배열을 이용하므로 인덱스를 통해 요소에 빠르게 접근
  4. 크기가 고정된 것이 아닌, 가변적으로 공간을 늘리고 줄인다.
    • 즉 - 리스트의 길이가 가변적. -> 이를 동적 할당(Dynamic allocation)이라고 한다.

🎈 ArrayList 장단점


그렇다면 이것을 왜 사용하는가? 에 대해 궁금할 수 있다.
먼저 장점에 대해 설명을 하자면,

장점:

  • 배열을 사용하기 때문에 인덱스를 통해 요소에 빠르게 접근이 가능
  • 조회를 많이 하는 경우 사용하기 좋다. (이유는 위와 같다.)
  • 데이터(element) 사이에 공간을 허용하지 않는다.
    • 즉, 인덱스 중간중간 비어있는 공간이 없다는 의미이다. 연속된 메모리 공간에서 시작된다는 말이다.

단점:

  • 배열 공간이 꽉 찰 때마다 배열을 copy(복사)하는 방식으로 늘리므로 이 과정에서 지연이 발생한다.
  • 데이터를 리스트 중간에 삽입 or 삭제 시, 중간에 빈 공간이 발생하지 않도록 요소들의 위치를 앞뒤로 자동으로 이동시키기 때문에 삽입/삭제 동작이 느리다.

사진에 보이듯이, Arrays.copyOf를 통해 복사를 통해 늘려나가는 모습을 볼 수 있다.

Arrays.copyOf란?
Arrays.copyOf(원본 배열, 원본 배열에서 복사하고 싶은 요소들의 길이)로 
원본배열에 원하는 길이만큼 배열을 복사하는 메서드이다. 

🎆 Arrays[] 배열과 ArrayList<>


흔하게 쓰는 배열[]의 특징에 대해서 먼저 알아보자

배열[] 특징:

  • 처음 선언한 배열의 길이(크기)는 변경할 수 없다. 이를 정적 할당(static allocation)이라고 한다.
    • ex) int[] arr = new int[5];
  • 크기를 변경할 수 없기에 크기가 정해져있으면 메모리 관리가 편하다.
  • 메모리에 연속적으로 나열되어 할당하기에 index를 통한 색인(검색)이 빠르다.
  • index에 위치한 하나의 데이터(element)를 삭제해도 해당 index에는 빈 공간이 남아 있다.
  • 배열의 크기를 변경할 수 없기 때문에, 처음에 너무 큰 크기로 설정해주게 되면 메모리 낭비가 될 수 있고, 반대로 너무 작은 크기로 설정해주면 공간이 부족해지는 경우가 발생
 Number[] arr = new Number[5];
 arr[0] = 1;
 arr[1] = 2;
 arr[2] = 3;
 arr[3] = 4;
 arr[4] = 5;

 System.out.println(Arrays.toString(arr));
 System.out.println("인덱스 2번에 대한 값은 : " + arr[2]);

 arr[2] = null;

 System.out.println(Arrays.toString(arr));
 System.out.println("인덱스 2번에 대한 값은 : " + arr[2]);

배열에는 remove와 같은 삭제 기능이 없기 때문에 해당 배열에 삭제 시 null이나 0을 이용해 값을 제거

ArrayList<>


배열과 비교했을 때의 ArrayList<>

  • 리스트의 길이가 가변적으로 이를 동적 할당(dynamic allocation)이라 한다.
  • 배열([] Array)과 달리 메모리에 연속적으로 나열되어 있지 않고, 주소로 연결되어 있는 형태(위에서 객체 데이터만 담을 수 있다고 설명) 이기 때문에 index를 통한 접근 속도가 배열보다는 느리다.
  • 객체로 데이터를 다루기 때문에 적은양의 데이터만 쓸 경우 배열에 비해 차지하는 메모리가 크다.
primitive 타입인 int형은 크기가 4Byte이다. 
하지만 Wrapper 타입인 Integer는 (32bit JVM)환경에서는
객체의 헤더(8Byte), 원시필드(4Byte), 패딩(4Byte)로 최소 16Byte를 차지한다고 한다.
또한 이러한 객체 데이터들을 다시 주소로 연결하기 때문에 16 + a가 된다. 
ArrayList<Number> arrList = new ArrayList<>();
arrList.add(1);
arrList.add(2);
arrList.add(3);
arrList.add(4);
arrList.add(5);

System.out.println(arrList);
System.out.println("인덱스 2번에 대한 값은 : " + arrList.get(2));

arrList.remove(2);

System.out.println(arrList);
System.out.println("인덱스 2번에 대한 값은 : " + arrList.get(2));

🎊 ArrayList 사용법


🎀 패키지 import 하기 -> 아래 코드를 이용해 사용할 수 있다.

import java.util.ArrayList;

🎍 ArrayList 객체 생성

ArrayList<String> arr1 = new ArrayList<>(); //문자열 객체만 적재 가능

ArrayList<String> arr2 = new ArrayList<>(5); // 초기 용량 5만큼 설정

ArrayList<String> arr3 = new ArrayList<>(Arrays.asList(1,2,3,4,5); // 배열에 담아 생성

ArrayList<String> arr4 = new ArrayList<>(arr3);
-> 사용용도

1. 데이터 복사: 
- list2 내용이 달라져도 list3에는 영향을 미치지 않음, 즉 데이터 복사본의 역할 수행

2. 컬랙션 변환:
- 다른 컬랙션 타입에서 ArrayList로 변환할 때 주로 사용
ex) LinkedList , HashSet 등과 같은 다른 컬랙션을 ArrayList로 변환할 때 사용
이는 API에서 ArrayList를 인자로 받는 경우 유용
여기서 <> 꺽쇠표시를 볼 수 있는데, 이는 제너릭 타입으로 간단하게 설명하면,
제너릭(Generic)은 '일반적인'이라는 뜻을 가지는데, 이를 조금 더 설명하면
데이터 형식에 얽매이지 않고, 하나의 값이 여러 데이터 타입들을 가질 수 있도록 하는 방법

🎋 ArrayList 데이터 추가

ArrayList는 <> '제너릭 타입'에 명시된 데이터들만 적재할 수 있다.

Type방법설명
booleanadd(E e)지정된 요소를 목록의 끝에 추가
voidadd(int index, E element)이 목록의 지정된 위치에 지정된 요소를 삽입
booleanaddAll(Collection c)컬랙션의 Iterator에서 반환된 순서대로 컬랙션의 모든 요소를 삽입
booleanaddAll(int index, Collection c)지정된 위치에서 시작해 컬랙션의 모든 요소를 삽입

add();


ArrayList<Integer> list = new ArrayList<>(5); //리스트의 용량이 총 5

list.add(1); // add로 데이터 추가에 성공시 true를 반환 
list.add(2);
list.add(3);
list.add(2, 4); // 지정된 인덱스 번호에 값을 삽입할 수 있다.

list.size(); // 크기는 4 (들어가 있는 요소의 총 개수를 의미)
System.out.println(list) // [1,2,4,3]

addAll();


ArrayList<Integer> list = new ArrayList<>(5); 

list.add(1); 
list.add(2);

ArrayList<Integer> list2 = new ArrayList<>(5); 

list2.add(3);
list2.add(4);

list.addAll(list2) // list에 list2의 내용을 추가

list.size(); // 크기는 4 (들어가 있는 요소의 총 개수를 의미)

addAll()를 통해 컬렉션 자체를 받아와 순서대로 다른 컬랙션의 모든 요소를 삽입할 수 있다.

💢 ArrayList 요소 추가 시 주의점

add(int index, element e)를 이용하게 될 시 나타날 수 있는 주의점으로
인덱스가 리스트의 용량(capacity)를 넘지 않도록 해야한다.

만약, 용량을 벗어난 인덱스에서 요소를 추가하려고 하면
IndexOutOfBoundsException 예외가 발생한다.

하지만, 리스트의 용량에 맞춰서 넣는다고 해도 마지막 위치값을 벗어난 인덱스에 값을 넣어도 똑같이 IndexOutOfBoundsException 예외가 발생한다.


그림으로 이를 표현하면 인덱스 4에 넣지않고, 5에 넣게 되면 예외가 발생한다는 의미이다.
그 이유는 ArrayList는 빈 공간이 없는 연속적인 데이터 리스트이기 떄문에 이러한 행위는 불가한 것이다.

현재 용량(capacity)가 6이더라도 현재 길이(size)는 3이므로 4번째 자리 이후로 접근하게 되면 IndexOutOfBoundsException 예외가 발생하는 것이다.

좀 더 정리해서 말하자면 데이터 길이가 3인 리스트에 데이터 길이가 1인 값을 넣었는데, 길이가 5가 된다면 논리적으로 맞지 않다. 그래서 예외가 발생한다고 볼 수 있다.

🎄 ArrayList 데이터 삭제

요소를 삭제하게 되면, 빈 공간이 발생하는데, arrayList는 연속된 메모리로 되어 있기에, 빈 공간이 발생하면 앞으로 움직여 공간을 매꾼다.

Type방법설명
Eremove(int index)지정된 위치에 있는 요소를 제거
booleanremove(Object o)지정된 요소가 처음으로 나타나는 경우 이를 제거
booleanremoveAll(Collection c)컬렉션에 포함된 모든 요소를 ​​이 목록에서 제거
voidclear()목록에서 모든 요소를 ​​제거
booleanremoveIf(Predicate<? super E> filter)조건을 만족하는 이 컬렉션의 모든 요소를 ​​제거

remove(int index)


ArrayList<String> list = new ArrayList<>(7); 

list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");

// 2번째 인덱스 자리의 요소 삭제
list.remove(2);

System.out.println(list); // [1, 2, 4, 5]

remove(Object o)


ArrayList<String> list = new ArrayList<>(7); 

list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("3");

// 3으로 시작하는 첫번째 요소 삭제
list.remove("3");

System.out.println(list); // [1, 2, 4, 5, 3]

clear()


ArrayList<String> list = new ArrayList<>(7); 

list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");

// 모든 값을 제거해줌, 그래서 반복문을 돌리면 일일이 삭제할 필요가 없다.
list.clear();

System.out.println(list); // []

removeIf(Predicate<? super E> filter)


JDK8에서 추가된 함수형 인터페이스와 람다를 사용해서 조건에 부합하는 요소를 삭제할 수 있습니다.
기타 설명보다 정리된 타 블로그를 참고하면 더 나을 것 같아서 블로그 주소로 대신
-> removeIf에 관한 블로그 정리글

💨 ArrayList 데이터 검색

Type방법설명
booleanisEmpty()목록에 요소가 없으면 true를 반환합니다 .
booleancontains(Object o)목록에 지정된 요소가 포함되어 있으면 true를 반환
intindexOf(Object o)요소가 처음 나타나는 인덱스를 반환, 포함 안되면 -1 반환
intlastIndexOf(Object o)요소가 마지막으로 나타나는 인덱스를 반환, 요소가 없으면 -1 반환
ArrayList<String> list = new ArrayList<>();
list.add("대한");
list.add("민국");
list.add("만세");
list.add("민국");

// list에 제국이 포함되어 있지 않으니 true 반환
list.isEmpty("제국");

// list에 민국이 포함되니 true반환
list.contains("민국"); 

// list에 민국이 있는지 순차적으로 검색하고 index를 반환 (만일 없으면 -1)
list.indexOf("민국"); // 1

// list에 민국이 있는지 역순으로 검색하고 index를 반환 (만일 없으면 -1)
list.lastIndexOf("민국"); // 3

💦 ArrayList 데이터 찾기

Type방법설명
Eget(int index)지정된 위치에 있는 요소를 반환
ListsubList(int fromIndex, int toIndex)지정된 fromIndex, 포함 및 toIndex, 제외 사이의 이 목록 부분에 대한 뷰를 반환

get(int index)


이는 단일 요소를 찾을 때 get을 이용할 수 있으며, 인덱스는 0부터 시작한다는 점 유의해서 사용

ArrayList<int> list = new ArrayList<>(5); 

list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);

list.get(1); // "2"
list.get(4); // "5"

subList(int fromIndex, int toIndex)


fromIndex부터 toIndex -1 까지의 범위만큼 요소를 가져 올 수 있다.

ArrayList<String> list = new ArrayList<>(10); 

list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");


// list[0] ~ list[4] 범위 반환
list.subList(0, 5); // [A, B, C, D, E]

// list[2] ~ list[4] 범위 반환
list.subList(2, 5); // [C, D, E]

// list[3] ~ list[4] 범위 반환
list.subList(3, 5); // [D, E]

💤 ArrayList 데이터 수정

Type방법설명
Eset(int index, E element)지정된 위치에 있는 요소를 지정된 요소로 수정
ArrayList<String> list = new ArrayList<>(5);

list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
 
// index 2번인 C를 Z로 변경
list.set(2, "Z"); 

System.out.println(list); // [A, B, Z, D, E]

🚑 ArrayList 정렬

Type방법설명
voidsort(Comparator<? super E> c)지정된 에 의해 유도된 순서에 따라 이 목록을 정렬합니다 Comparator.

sort(Comparator<? super E> c)


ArrayList<String> list = new ArrayList(5);
list.add("A");
list.add("B");
list.add("C");
list.add("a");
list.add("b");
 
// 오름차순 정렬
list.sort(Comparator.naturalOrder());
System.out.println(list); // [A, B, C, a, b]

// 내림차순 정렬
list1.sort(Comparator.reverseOrder());
System.out.println(list); // [b, a, C, B, A]
해당 방법은 JDK8부터 List에서 sort()메서드를 호출하여 사용할 수 있다.

Collections.sort()


💢 유의점

import java.util.Collections;

해당 패키지를 import를 해야 사용할 수 있다.
list.add("A");
list.add("B");
list.add("C");
list.add("a");
list.add("b");

// 오름차순으로 정렬
Collections.sort(list);
System.out.println(list); // [A, B, C, a, b]

// 내림차순으로 정렬
Collections.sort(list, Collections.reverseOrder());
System.out.println(list); // [b, a, C, B, A]

// 대소문자 구분없이 오름차순
Collections.sort(list, String.CASE_INSENSITIVE_ORDER);
System.out.println(list); // [a, A, b, B, C]

//대소문자 구분없이 내림차순
Collections.sort(list, Collections.reverseOrder(String.CASE_INSENSITIVE_ORDER));
System.out.println(list); // [C, b, B, a, A]

💜 참고자료

자바 ArrayList 구조 & 사용법 정리

자바 - ArrayList 사용방법 (개념, 특징, 메소드 및 예제)

Java Collections Framework 종류 💯 총정리

ArrayList(자바 플랫폼 SE 8

[Java] ArrayList 정렬하기 (오름차순, 내림차순, 사용자 정의)

profile
ABAPER를 꿈꾸는 개발자

0개의 댓글