Java (자바)

[Java] 배열 Array (2) - ArrayList / ArrayList 메서드

Oscar:) 2022. 9. 28. 22:11

 

배열에 대해 알아보았던 지난 포스팅에 이어서,

 

배열의 진화라고 볼 수 있는 ArrayList 에 대해 알아보자.

 

 


 

ArrayList

 

시간의 흐름에 따라 기술은 계속 향상되기 마련이다.

 

ArrayList는 기존의 배열의 단점을 극복하고,

배열을 더 효율적으로 관리할 수 있도록 도와주는 '클래스'이다.

 

그렇다면 ArrayList는 배열의 어떠한 단점을 극복했다는 걸까?

 


 

ArrayList와 비교한 배열(Array)의 단점

 

● 기존의 배열은 한 번 지정한 사이즈를 중간에 조정할 수 없었다.

 

- 배열의 길이보다 배열의 요소가 더 많아지면 배열을 재생성해야만 했다.

 

 

● 배열의 요소에 수정 & 삭제 등 변동이 생길 때, 관리가 필요했다.

 

- 예를 들어, 배열의 요소를 삭제했다면 해당 인덱스 값은 비어있게 되는데,

기존의 다른 요소들을 당겨오는 등의 코드를 구현해 주어야만 했다.

 

 

● 지정한 하나의 타입의 데이터만을 저장할 수 있다.

 

 


 

배열의 단점을 극복한 ArrayList

 

기존의 배열은, 데이터에 추가·수정·삭제 등의 변동이 생길 때마다 취약점을 드러내왔다.

 

ArrayList는 데이터의 변동에 따라 알맞게 크기가 변동된다는 특징을 가지고 있다.

 

 

이는 개발자가 배열 요소의 직접적인 관리를 따로 구현할 필요 없이,

바로 사용할 수 있는 최적의 메서드가 이미 구현되어 있기 때문이라고 볼 수 있다.

 

따라서, ArrayList의 길이보다 많은 양의 데이터가 추가되면

ArrayList의 길이가 자동으로 늘어나고,

ArrayList 중간에 있는 데이터를 삭제하더라도

기존의 요소들이 자동으로 빈 자리를 채우며 당겨질 수 있게끔 되었다.

 

 

또, 지정된 타입의 데이터만을 저장할 수 있었던 배열과는 다르게,

ArrayList는 객체에 한해서, 여러 타입의 데이터들을 관리할 수도 있다.

 

 


 

ArrayList 생성

 

위에서 ArrayList가 클래스라고 이미 언급했었다.

 

그리고 우리는, 클래스를 사용하기 위해 객체를 생성해야 함을 배웠다.

 

new 연산자를 사용하여 ArrayList 객체를 생성해보자.

ArrayList<타입> list = new ArrayList<타입>();

* ArrayList 클래스를 사용하기 위해서는

java.util.ArrayList를 import 해주어야 한다.

 

기본적인 문법은 위의 예제와 같다.

 

<> 부호는 생소할 수 있지만, 저장할 데이터의 타입을 작성해주는 곳이라고 보면 된다.

그리고 이를 제네릭(Generic) 이라고 한다.

 

* 타입을 지정하지 않고도 ArrayList를 생성해줄 수는 있지만,

안정성을 위해 타입을 지정할 것을 권장하고 있다.

 

 

위의 예제 말고도, ArrayList 객체를 생성할 때는 여러 방식을 사용할 수 있다.

	// 제네릭 없이 생성 가능. 하지만 권장되지는 않는 방식이다.
	// 실제로 코드 작성 시, 에러는 아니지만 경고 밑줄이 표시된다.
	ArrayList list = new ArrayList();
		
	// String 타입의 ArrayList.
	ArrayList<String> list1 = new ArrayList<String>();
		
	// int 타입의 ArrayList. 제네릭에는 int 타입의 포장 객체를 작성해준다.
	ArrayList<Integer> list2 = new ArrayList<Integer>();
		
	// new 연산자 뒷부분의 제네릭은 생략 가능하다.
	ArrayList<Integer> list3 = new ArrayList<>();
		
	// 소괄호(파라미터)에 값을 입력하여 초기 사이즈를 지정해줄 수 있다.
	ArrayList<Integer> list4 = new ArrayList<>(5);
    
        // 제네릭에 사전에 생성해둔 클래스를 사용할 수 있다.
        //해당 클래스의 객체만 사용 가능하다.
	ArrayList<Client> list5 = new ArrayList<>();

 

 

★ 3번째 코드를 보면, int 타입을 Integer로 작성한 것을 볼 수 있다.

ArrayList는 제네릭을 통해, 객체만을 타입으로 지정할 수 있다는 특징이 있다.
하지만 int 타입은 기본형 자료형이기 때문에, 객체로서 인정받지 못하는 것이다.
(여기서 말하는 객체는 참조 타입 자료형을 이야기한다)

따라서 ArrayList의 제네릭에 참조 타입을 지정해주기 위해서,
기본형 자료형을 Boxing(박싱 = 포장) 하여 참조 타입의 객체로 작성해 주는 것이다.

이렇게 포장된 객체는 Wrapper Class에 포함된다.

반대로 Wrapper Class의 객체가 사용될 때는,
UnBoxing 되어 포장 객체에서 기본형 타입의 값을 얻어내며 사용된다.


Boxing & UnBoxing 은 코드로 구현해야 할 때도 있지만,
대입되는 데이터가 Wrapper Class 객체의 타입과 일치하면,
자동 박싱(AutoBoxing)이 동작한다.

 

 

쉽게 설명한다고 나름 노력했지만, 이론적인 내용은 조금 어려울 수 있다.

 

요약하자면, ArrayList의 제네릭은 객체만을 취급하고,

기본형 자료 타입을 포장 객체로 만들어주는 AutoBoxing 덕분에

우리는 int 타입을 Integer 로 작성해 주기만 하면 된다는 것이다.

 

*char 타입도 Character 로 작성해야 하고,

나머지 기본형 자료 타입은 앞 글자만 대문자로 작성해주면 된다.

 

 


 

ArrayList 클래스가 보유한 메서드

 

ArrayList가 높은 효용성을 자랑할 수 있는 이유는,

ArrayList 클래스가 가지고 있는 최적의 메서드 덕분일 것이다.

 

이제 ArrayList의 다양한 메서드에 대해 알아보자.

 

 


 

● ArrayList에 데이터 추가

 

	ArrayList<String> list = new ArrayList<>();
        
        // 1번
	list.add(element);
        
        // 2번
        list.add(index, element);

 

데이터를 추가할 때는 add() 메서드를 사용한다.

 

add() 메서드는 위 처럼, 2가지 방식으로 작성할 수 있다.

 

① add(element)

파라미터에 타입에 알맞는 데이터를 입력해주면 된다.

위 처럼 따로 인덱스 값을 지정하지 않으면,
자동으로 ArrayList 맨 뒤에 추가된다.

 

② add(index, element)

첫 번째 파라미터에는 인덱스 값을 지정할 수 있고,
두 번째 파라미터에는 추가할 데이터를 입력하면 된다.

인덱스 값을 지정하기에, 자동으로 맨 뒤에 추가되지 않고
원하는 위치에 추가할 수 있다.

 

 

데이터가 잘 추가되는지 확인하기 위해 출력해 보겠다.

	ArrayList<String> list = new ArrayList<>();
		
	// 데이터 추가하기 전에 출력.
	System.out.println(list);
		
	list.add("Oscar");
		
	// 데이터 1개 추가 후 출력.
	System.out.println(list);
		
	list.add("Jayden");
	list.add("Miro");
	list.add("Taron");
		
	// 데이터 3개 더 추가 후 출력.
	System.out.println(list);
		
	list.add(2,"Pinni");
		
	// 지정 인덱스에 데이터 추가 후 출력.
	System.out.println(list);

 

출력 결과와 비교 가독성을 위해

출력 실행문에 주석을 작성했으니, 비교하며 확인해보자.

 

출력 결과는 다음과 같다.

 

데이터를 추가하기 전에는 비어있는 ArrayList 를 확인할 수 있다.

 

인덱스 값을 지정하지 않고 추가했을 때는

작성한 순서대로 뒤로 붙으며 추가된 것을 확인할 수 있다.

 

인덱스 값을 지정하였을 때는 해당 위치에 끼워져 추가되며,

그 뒤의 요소들은 1칸씩 밀려난 것을 확인할 수 있다.

 

 

 

그렇다면, 인덱스 값을 지정하는 것을 이용하여

데이터를 작성된 순서의 역순으로 추가할 수도 있을 것이다.

	ArrayList<String> list = new ArrayList<>();
		
	list.add(0, "Oscar");
	list.add(0, "Miro");
	list.add(0, "Taron");
	list.add(0, "Pinni");
		
	System.out.println(list);

 

 

출력 결과는 다음과 같다.

 

첫 번째 인덱스 값인 0의 자리에 계속 추가시키면서

코드를 작성한 역순으로 출력된 것을 확인할 수 있다.

 

추후에 데이터를 표시하는 순서를 고민할 때 도움이 될 것이다.

 

 


 

● ArrayList 데이터 수정

 

데이터를 수정하려면 수정할 데이터가 있어야 하므로,

위에서 배웠던 방법으로 데이터를 추가해준 뒤, 수정해 보겠다.

 

	ArrayList<String> list = new ArrayList<>();
		
	list.add("서울시");
	list.add("수원시");
		
	// list에 데이터 2개 추가 후 출력.
	System.out.println(list);
		
	list.set(0, "서울특별시");
	list.set(1, "수원특례시");
		
	// 데이터 수정 후 출력.
	System.out.println(list);

 

데이터를 수정할 때는 set(index, element) 메서드를 사용한다.

 

추가할 때와 마찬가지로,

첫 번째 파라미터에는 수정할 위치의 인덱스 값을 지정하고,

두 번째 파라미터에는 수정할 데이터를 입력하면 된다.

 

 

출력 결과는 다음과 같다.

 

작성한 인덱스 값에 알맞는 데이터가 수정되었다.

 

 


 

● ArrayList 데이터 삭제

 

데이터를 삭제하는 메서드는 2가지가 있다.

 

① remove(index)
해당 인덱스 값의 데이터만을 삭제한다.
해당 인덱스 뒤에 위치한 데이터들은 1칸씩 앞으로 당겨진다.

 

② clear()
해당 ArrayList의 모든 데이터를 삭제한다.

 

 

수정할 때와 마찬가지로 데이터를 추가해준 뒤, 삭제해 보겠다. 

	ArrayList<String> list = new ArrayList<>();
		
	list.add("청포도");
	list.add("대추");
	list.add("키위");
		
	// list에 데이터 3개 추가 후 출력.
	System.out.println(list);
		
	list.remove(1);
		
	// 인덱스 값 1의 데이터 삭제 후 출력.
	System.out.println(list);
		
	list.clear();
		
	// 모든 데이터 삭제 후 출력.
	System.out.println(list);

 

출력 결과는 다음과 같다.

 

3개의 데이터가 추가된 상태에서, remove() 메서드를 사용하여

두 번째 자리에 위치한 인덱스 값 1을 삭제하였고,

 

곧바로 clear() 메서드를 사용하여

모든 데이터를 삭제하였다.

 

 


 

● 이외의 자주 사용되는 편리한 메서드

 

ArrayList는 추가·수정·삭제를 제외하고도, 다양한 메서드를 제공한다.

 

- size() 메서드

- get() 메서드

- isEmpty() 메서드

- addAll() 메서드

- contains() 메서드

- indexOf() 메서드

 

위 6개의 메서드를 제외하고도 더 많은 메서드가 있지만,

자주 사용되는 메서드이니만큼, 오늘은 위 6가지만 알아보겠다.

 

 


 

· size() 메서드

 

ArrayList의 전체 길이를 리턴하는 메서드다.

 

기존의 배열에서는 length() 메서드를 사용했던 것과 차이점이 있다.

 

 

데이터를 추가해준 뒤, 위 메서드를 적용해 보겠다.

	ArrayList<String> list = new ArrayList<>();
		
	list.add("청포도");
	list.add("대추");
	list.add("키위");
		
	// list에 데이터 3개 추가 후 출력.
	System.out.println(list);
		
	int listSize = list.size();
		
	// 정수형 변수 listSize 출력
	System.out.println(listSize);

 

int 타입 변수를 선언하고 ArrayList의 size() 를 담아서 출력하였다.

 

출력 결과는 다음과 같다.

 

ArrayList에 데이터가 3개 있으므로, 3이 출력되는 것을 확인할 수 있다.

 

 


 

· get(index) 메서드

 

해당 인덱스 값에 위치한 데이터를 리턴해주는 메서드다.

 

ArrayList 형태가 아닌, 데이터만을 얻을 수 있다.

 

 

ArrayList를 생성해주고, 데이터를 뽑아와 보겠다.

	ArrayList<String> list = new ArrayList<>();
		
	list.add("청포도");
	list.add("대추");
	list.add("키위");
		
	// list에 데이터 3개 추가 후 출력.
	System.out.println(list);
		
	String data = list.get(1);
		
	// String 타입 변수 data 출력
	System.out.println(data);

 

String 타입 변수 data를 선언해주고,

get() 메서드로 해당 인덱스 값의 데이터를 담아주었다.

 

 

출력 결과는 다음과 같다.

 

ArrayList에 존재하는 3개의 데이터 중에서,

인덱스 값이 1인 데이터가 출력되었다.

 

 


 

· isEmpty() 메서드

*empty : 빈, 비어 있는

 

ArrayList 내 데이터의 존재 유무를 boolean 값으로 리턴해준다.

 

데이터가 있으면 false, 없으면 true를 반환한다.

 

	ArrayList<String> list = new ArrayList<>();
		
	list.add("청포도");
	list.add("대추");
	list.add("키위");
		
	// list에 데이터 3개 추가 후 출력.
	System.out.println(list);
		
	// isEmpty() 메서드를 출력문에 작성.
	System.out.println(list.isEmpty());
		
	list.clear();
		
	// 데이터를 모두 삭제한 뒤 출력.
	System.out.println(list);
		
	// isEmpty() 메서드를 출력문에 작성.
	System.out.println(list.isEmpty());

 

데이터를 추가해준 뒤, isEmpty() 메서드를 사용해보고

데이터를 모두 삭제한 뒤, isEmpty() 메서드를 다시 사용해 보았다.

 

 

출력 결과는 다음과 같다.

 

3개의 데이터가 존재할 때는 false가 출력되었고,

데이터가 존재하지 않을 때는 true가 출력된 것을 확인할 수 있다.

 

 


 

· addAll() 메서드

 

ArrayList에 다른 ArrayList를 통째로 더하는(합치는) 메서드다.

 

이 메서드 또한, 인덱스 값의 유무에 따라 2가지 방식으로 사용한다.

 

- addAll(Collection)

파라미터의 Collection은 생소할 수 있지만,
ArrayList의 상속 구조를 따라 올라가다 보면 존재하는 조상 격이라고 이해하면 된다.

단순히 생각해서, 저 파라미터에 다른 ArrayList를 작성해주면 된다.

 

- addAll(index, Collection)

데이터 추가·수정·삭제와 같은 개념으로 이해하면 쉽다.
인덱스 값으로, 합쳐질 ArrayList가 들어갈 위치를 지정할 수 있다.

 

 

ArrayList를 2개 생성하고, 합쳐 보겠다.

	ArrayList<String> list1 = new ArrayList<>();
	ArrayList<String> list2 = new ArrayList<>();
		
	list1.add("청포도");
	list1.add("대추");
	list1.add("키위");
		
	// list1에 데이터 3개 추가 후 출력.
	System.out.println(list1);
		
	list2.add("사과");
	list2.add("배");
		
	// list2에 데이터 2개 추가 후 출력.
	System.out.println(list2);
		
	list1.addAll(list2);
		
	// 리스트1에 리스트2를 합친 후 출력.
	System.out.println(list1);

 

 

출력 결과는 다음과 같다.

 

인덱스 값을 따로 지정하지 않았기에,

list1의 뒷부분에 list2가 더해진 모습을 확인할 수 있다.

 

*인덱스 값을 지정해주면 해당 위치에 list2가 모두 들어가고,

list1의 뒷부분 데이터가 list2의 길이 만큼 밀려난다.

 

 


 

· contains(Object) 메서드

*contains : 포함하다

 

ArrayList 내 해당 데이터의 포함 유무를 boolean 값으로 리턴해준다.

 

작성한 데이터가 있으면 true를, 없으면 false를 반환한다.

 

	ArrayList<String> list = new ArrayList<>();
		
	list.add("청포도");
	list.add("대추");
	list.add("키위");
		
	// list에 데이터 3개 추가 후 출력.
	System.out.println(list);
		
	// contains() 메서드를 출력문에 작성.
	System.out.println(list.contains("키위"));
		
	// contains() 메서드를 출력문에 작성.
	System.out.println(list.contains("사과"));

 

ArrayList에 있는 "키위" 와, ArrayList에 없는 "사과" 를 작성하였다.

 

출력 결과는 다음과 같다.

 

"키위" 는 ArrayList에 존재하므로, true가 출력되었고,

"사과" 는 ArrayList에 존재하지 않으므로, false가 출력되었다.

 

 


 

· indexOf(Object) 메서드

 

ArrayList 내 해당 데이터의 인덱스 값을 리턴해준다.

 

데이터가 존재하면 인덱스 값이 반환되고,

존재하지 않으면 -1이 반환된다.

(인덱스 초기값인 0은 사용할 수 없기 때문인 것 같다)

 

	ArrayList<String> list = new ArrayList<>();
		
	list.add("청포도");
	list.add("대추");
	list.add("키위");
		
	// list에 데이터 3개 추가 후 출력.
	System.out.println(list);
		
	// indexOf() 메서드를 출력문에 작성.
	System.out.println(list.indexOf("키위"));
		
	// indexOf() 메서드를 출력문에 작성.
	System.out.println(list.indexOf("사과"));

 

contains() 메서드 예제와 마찬가지로,

ArrayList에 있는 "키위" 와, ArrayList에 없는 "사과" 를 작성하였다.

 

 

출력 결과는 다음과 같다.

 

"키위" 는 ArrayList에 존재하므로, 해당 인덱스 값인 2가 출력되었고,

"사과" 는 ArrayList에 존재하지 않으므로, -1이 출력되었다.

 

 

 


 

 

이번 포스팅에서는 배열의 진화라고 볼 수 있는 ArrayList에 대해 알아보았다.

 

 

ArrayList를 사용하는 많은 사람들이 배열의 상위 호환이라고 느끼지만,

 

사실 ArrayList는 Array가 아닌, List와 Collention을 상속 받는 개념이다.

 

하지만 ArrayList의 구조와 사용처를 보면,

배열과 직접적인 관련이 있다고 느껴지는 것은 사실인 것 같다.

 

 

 

본인도 ArrayList에 대해 배우고 사용하며 기존의 배열을 더 쉽게 이해할 수 있었다.