컬렉션 프레임워크(Collection Framework)
다수의 객체를 다루기 위한 표준화된 프로그램 방식을 뜻한다.
크게 3가지로 나눌 수 있는데 계층 구조로 보면 다음과 같다.
해당 계층 구조에서 핵심은 List, Set, Map이다.
List
순서가 있는 데이터 집합으로 중복을 허용한다.
List의 종류로 ArrayList, LinkedList, Stack, Vector 등이 있다.
List의 예시로 대표적인 ArrayList를 사용해보자.
ArrayList는 말 그대로 Array(배열)의 특징과 List(리스트)의 특징이 혼합된 자료구조를 의미한다.
먼저 ArrayList의 내부를 살펴보면 List 인터페이스를 구현하게 된다.
그 다음으로 살펴볼 것은 ArrayList의 길이는 처음 생성했을 때 기본적으로 10의 길이가 주어지고, 내부적으로 Object 배열을 이용하여 값을 저장한다.
해당 코드와 같이 배열을 이용하기 때문에 인덱스를 통해서 요소에 빠르게 접근할 수 있다.
크기가 고정되어 있는 배열과 달리 가변적으로 공간을 늘리거나 줄일 수 있다. 하지만 배열의 공간이 꽉 찰 때마다 배열을 복사하는 방식으로 늘리기 때문에 이 과정에서 지연이 발생하게 된다.
데이터를 중간에 삽입 및 삭제를 하는 경우 요소들의 위치를 앞뒤로 자동으로 이동시키기 때문에 삽입 삭제 동작이 느리다.
ArrayList arrayList = new ArrayList();
arrayList.add(111);
arrayList.add("111");
ArrayList도 하나의 클래스이기 때문에 사용하기 위해서는 먼저 객체를 생성해줘야 한다.
위의 예시 코드에서 이상한 점을 살펴보면 하나의 ArrayList에 여러 타입을 저장하고 있다.
배열은 원래 한 가지의 타입으로 이루어진 데이터들을 넣어야 되는데 어떻게 가능할까 생각해보면 List의 특성이 있기 때문이다. List는 타입이 달라도 저장할 수 있어서 위의 예시 코드와 같이 동작하는 것도 가능하다.
하지만 저렇게 각각 다른 타입으로 ArrayList에 저장하게 되면 값을 사용할 때 타입마다 다르게 처리해야 되는 문제가 생긴다.
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(111);
arrayList.add("111"); // 에러 발생
앞서 설명한 문제점을 해결하기 위해서 제네릭을 사용하게 된다. 타입 변수로 Integer를 지정해서 ArrayList에 저장되는 값들의 타입을 강제할 수 있다.
이제 다음으로는 ArrayList가 가지고 있는 몇 가지 기능들을 살펴보자.
add / remove
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(111);
arrayList.add(222);
arrayList.add(333);
arrayList.add(2, 444);
arrayList.remove(2);
가장 기본적인 값을 저장하는 것과 삭제하는 기능을 사용한 예시다.
해당 코드를 실행하면 결과값으로 [111, 222, 333]이 나오는데 여기서 확인해볼게 있다.
add() 메서드는 값을 줘서 저장할 수 있지만, 또 다른 방법으로 인덱스와 값을 매개변수로 줘서 저장할 수도 있다. 그렇다면 2번 인덱스에 444 값을 저장했는데 왜 출력값에는 444가 없고, 333이 있을까?
ArrayList를 처음 설명할때 언급했듯이 데이터를 추가하거나 삭제할 경우 List의 특성을 따르게 된다. 2번 인덱스에 444값을 저장했다면 기존에 있었던 333값이 인덱스 3으로 이동하게 되고 2번 인덱스 위치에 444가 새롭게 추가된다.
그 후 2번 인덱스를 삭제했기 때문에 444값이 없어지고 다시 333 값이 2번 인덱스로 이동한다.
get / size
Integer result = arrayList.get(2);
int size = arrayList.size();
System.out.println(result);
System.out.println(size);
위의 코드를 통해 get과 size 메서드를 확인할 수 있다.
먼저 get() 메서드의 매개변수로 인덱스 값을 넘겨줘서 해당 인덱스에 위치한 값을 가져온다. 앞서 2번 인덱스에 위치했던 444값을 삭제했으므로 get(2)를 실행하게 되면 333 값이 출력된다.
size() 메서드를 확인해보면 현재 ArrayList의 길이를 확인할 수 있다.
예제를 보면 get() 메서드를 호출하고 난 뒤에 반환되는 값은 Integer 래퍼 클래스인데 size() 메서드는 기본형인 int 타입으로 반환된다. 왜 그런지 생각해보면 답은 간단하다.
ArrayList의 타입은 여러 가지가 될 수 있다. Integer, String, Double 등 타입이 달라질 수 있지만 size는 ArrayList에 어떤 타입이 오든지 간에 길이만 반환해주면 되기 때문에 int 타입으로 고정된다.
contains / indexOf
boolean contains = arrayList.contains(222);
int indexof = arrayList.indexOf(333);
System.out.println("contains = " + contains);
System.out.println("indexof = " + indexof);
마지막으로 살펴볼 메서드는 contains()와 indexOf() 메서드이다.
먼저 contains() 메서드는 매개변수로 주어진 값이 현재 ArrayList에 있는 값인지 확인하는 메서드로 반환 값은 boolean 타입이다.
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
contains() 메서드 내부를 살펴보면 코드가 간단한데 indexOf 메서드를 내부적으로 호출하여 매개변수로 입력받은 값이 ArrayList에 있는지 확인하게 된다.
그 다음으로 indexOf() 메서드를 살펴보면 매개변수로 주어진 값이 있는 인덱스를 반환해준다.
public int indexOf(Object o) {
return indexOfRange(o, 0, size);
}
int indexOfRange(Object o, int start, int end) {
Object[] es = elementData;
if (o == null) {
for (int i = start; i < end; i++) {
if (es[i] == null) {
return i;
}
}
} else {
for (int i = start; i < end; i++) {
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
indexOf() 메서드의 코드를 살펴보면 매개변수로 전달받은 값과 인덱스의 시작점인 0, 인덱스의 끝 지점인 size를 통해서 값이 현재 ArrayList에 있는지 확인하는 코드이다.
앞서 살펴본 contains() 메서드가 이 indexOf() 메서드를 사용하게 된다.
그 외 메서드 목록
메서드 | 설명 |
addAll(Collection c) | ArrayList에 또 다른 자료구조의 엘리먼트를 넣는다. |
forEach(Consumer<? super E> action) | for 문을 좀 더 효율적으로 사용하는 방법으로 람다식으로 표현이 가능하다. |
isEmpty() | ArrayList에 값이 있으면 true, 없으면 false를 반환한다. |
lastIndexOf(Object o) | ArrayList의 마지막 인덱스를 반환한다. |
removeAll(Collection c) | ArrayList에서 또 다른 ArrayList에 있는 엘리먼트를 전부 제거한다. |
toArray() | 배열로 반환해준다. |
set(int index, E element) | 지정한 인덱스에 사용자가 원하는 값으로 변경한다. |
Set
순서를 유지하지 않는 데이터의 집합으로 중복을 허용하지 않는다.
Set의 종류로 HashSet, TreeSet 등이 있다.
Map
키와 값의 쌍으로 이루어진 데이터 집합으로 순서는 유지되지 않으며 키는 중복을 허용하지 않지만, 값은 중복을 허용한다.
Map의 종류로 HashMap, TreeMap, HashTable 등이 있다.
대표적인 HashMap을 살펴보자.
ArrayList나 Set과 같은 자료구조는 하나의 Collection 인터페이스를 구현하게 되는데 HashMap이 있는 Map 인터페이스는 Collection 인터페이스와 연결되어 있지 않고 독립적으로 존재한다.
HashMap<Integer, String> hashMap = new HashMap<>();
HashMap을 생성할 때는 두 개의 타입 변수가 필요하다. 하나는 key의 타입, 또 다른 하나는 value의 타입을 지정해줘야 한다.
put / get
hashMap.put(1, "Kim");
hashMap.put(2, "Yang");
hashMap.put(3, "Park");
hashMap.put(4, "Lee");
String getStr1 = hashMap.get(1);
String getStr2 = hashMap.get(2);
System.out.println("getStr1 = " + getStr1);
System.out.println("getStr2 = " + getStr2);
put() 메서드를 사용하여 key 값과 value 값을 설정해준다. 이 때 위에서 HashMap을 만들 때 지정했던 타입 변수와 해당 타입이 일치해야 한다.
그 다음 get() 메서드를 통해서 값을 가져오게 되는데 메서드의 매개변수로 key 값을 넘겨주면 해당 key에 설정된 value 값을 반환해준다.
containsKey / size
boolean contain = hashMap.containsKey(3);
int size = hashMap.size();
System.out.println("contain = " + contain);
System.out.println("size = " + size);
containsKey() 메서드를 통해서 매개변수로 받는 key 값이 존재하는지 확인할 수 있다.
size() 메서드는 HashMap의 길이를 반환해주는데 이 때 맵의 key-value 쌍 또는 매핑 수를 참조하는 맵의 크기를 반환해준다.
remove / replace
String remove = hashMap.remove(2);
System.out.println("remove = " + remove);
System.out.println(hashMap.get(2));
hashMap.replace(3, "Park", "Jung");
System.out.println(hashMap.get(3));
remove() 메서드를 사용하여 매개변수로 받는 key와 해당 key에 있는 value 값을 삭제해준다. 삭제하고 난 뒤에는 해당 값을 반환해줘서 확인이 가능하다.
앞선 ArrayList와 다르게 해당 2번 key를 삭제하면 3번 key가 앞으로 이동하는 것이 아닌 2번 key를 null로 바꿔준다.
replace() 메서드는 key에 해당하는 값을 사용자가 원하는 값으로 변경해주는 메서드이다.
그 외 메서드 목록
메서드 | 설명 |
isEmpty() | HashMap이 비어있으면 true, 값이 있으면 false를 반환한다. |
containsValue(Object value) | HashMap에 지정된 값이 있으면 true, 없으면 false를 반환한다. |
clear() | 모든 key-value 쌍을 제거한다. |
Iterator
List와 Set에는 Iterable이라는 인터페이스를 구현하게 되는데 해당 인터페이스는 컬렉션에 저장된 데이터를 접근하는 데 사용된다.
여기서 주의할 점으로는 Map 인터페이스는 Iterable 인터페이스가 없기 때문에 다른 메서드를 사용해서 값을 가져와야 한다.
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
해당 코드는 ArrayList 자료구조에 들어있는 요소를 하나씩 확인해보는 코드로 iterator() 메서드를 사용하여 요소를 확인할 수 있다. 하지만 문제는 iterator() 메서드는 일회용으로 한 번 사용하면 다시 객체를 생성하여 사용해야되는 단점이 있다.
Comparator와 Comparable
객체를 정렬하는데 필요한 메서드(정렬 기준을 제공)를 정의한 인터페이스이다.
여기서 Comparator는 기본 정렬기준 외에 다른 기준으로 정렬하고자 할 때 사용되고, Comparable은 기본 정렬 기준을 구현하는데 사용한다.
Comparable
기본 정렬 기준을 구현하는데 사용되는 인터페이스이다.
기본 정렬 기준을 구현한다는 것은 객체 자체가 어떻게 정렬될지를 알고 있고 객체가 자연스러운 순서를 가지고 있어야 할 경우에 사용된다.
this 참조를 사용하여 지정된 객체와 비교하게 된다.
Collections.sort() 또는 Arrays.sort() 메서드를 사용하여 자동으로 정렬할 수 있으며, 객체는 CompareTo 메서드에서 정의한 순서에 따라 정렬된다.
하나의 비교만 사용할 수 있다.
Comparator
기본 정렬 기준 외에 다른 기준으로 정렬하고자할 때 사용되는 인터페이스이다.
기본 정렬 기준을 사용하지 않고 별도의 클래스를 통해서 정렬하게 된다.
두 가지 다른 클래스 객체를 비교하게 된다.
주어진 유형에 대해 필요한 만큼 다양한 비교가 가능하다. 즉, 여러 속성을 기반으로 정렬할 수 있기 때문에 더 유연한 비교 기준을 제공하며 다양한 정렬 방식을 적용할 수 있다.
'자바' 카테고리의 다른 글
[Java] - 람다 (0) | 2023.11.17 |
---|---|
[Java] - 제네릭 (0) | 2023.11.15 |
[Java] - 애너테이션 (0) | 2023.11.13 |
[Java] - 내부 클래스 (0) | 2023.11.11 |
[Java] - Exception (0) | 2023.11.11 |