💡 해당 글은 『자바의 신 3판』을 복습하며 도서의 내용과 본인의 주관적인 생각을 정리한 글입니다.
☁️ 내용정리
JCF (Java Collection Framework)
자바에서 목록형 데이터를 처리하는 자료구조를 지원하는 프레임워크이다.
자료 구조란 하나의 데이터가 아닌 여러 데이터를 담을 때 사용하는 데이터 구조이다.
컬렉션이 왜 생겼을까?
정적으로 메모리를 할당하고 사용하는 배열은 실제 사용에서 불편한 점이 많다.
배열이 가득찬다면 배열을 카피하고 더 큰 메모리를 가지는 배열에 복사하는 로직이 필요할 것이고, 중복을 제거하고 싶디면, 배열을 순회하며 중복인 원소를 지워주는 로직이 필요할 것이다.
이러한 로직은 프로그램 개발에서 자주 쓰이기 때문에 미리 만들어 둔 것이다.
JCF에서는 크게 4가지로 분류된 자료구조를 제공한다.
List<E>
: 순서가 존재하는 List형 (ArrayList, LinkedList, Vector … )Queue<E>
: LIFO의 Queue형 (LinkcedList, PrioriyQueue, ArrayDeque … )Set<E>
: 값의 존재 유무가 중요한 Set형 (HashSet, TreeSet, LinkedHashSet … )Map<K, V>
: 키-값 쌍으로 저장하는 Map형이다. (HashMap, TreeMap, LinkdedHashMap … )
이 중 List, Queue, Set은 java.util.Collection
인터페이스를 구현한다. Map은 java.util.Map
으로 선언되어 있다.
Collection은 Iterable<E>
인터페이스를 확장한다.
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
컬렉션을 순회하며 각 요소에 접근할 수 있는 기능을 제공하는 Iterator 인터페이스를 반환하는 메서드가 존재하며
Java 8 이후 람다로 자주 사용되는 foEach(), 스트림에서 사용되는 spliterator를 반환하는 메서드가 제공된다.
List
public interface List<E> extends Collection<E>
**All Known Implementing Classes:**
AbstractList, AbstractSequentialList, ArrayList, AttributeList,
CopyOnWriteArrayList, LinkedList, RoleList, RoleUnresolvedList,
Stack, Vector
리스트는 배열처럼 순서가 존재하는 자료구조이다.
List를 구현하는 클래스 중 특히, ArrayList
, LinkedList
, Stack
, Vector
를 자주 사용한다.
ArrayList
ArrayList<E>
는 내부적으로 배열을 가지며, 이를 동적으로 확장하거나 축소하며 동작한다.
ArrayList에서 제공하는 유용한 메서드들은 아래와 같다.
Constructor
ArrayList(Collection<? extends E> c)
: c의 객체가 저장된 객체를 반환. (deep copy)ArrayList(int initialCapacity)
: initialCapacity만큼의 배열을 가지는 객체를 반환
데이터 얻기
int indexOf(Object o)
: o와 동일한 첫 번째 데이터의 위치를 반환int lastIndexOf(Object o)
: o와 동일한 마지막 데이터의 위치를 반환T[] toArray(T[] a)
: T타입 배열을 반환
List<Integer> list = List.of(1, 2, 3, 4, 5);
// 매개변수 배열의 크기가 리스트 내부 배열보다 클 경우 나머지는 원소는 null
Integer[] listArray = list.toArray(new Integer[0]);
데이터 삭제하기
E remove(int index)
: index의 데이터를 삭제하고 반환한다.boolean remove(Object o)
: o와 동일한 첫 번째 데이터를 삭제한다.boolean removeAll(Collection<?> c)
: c의 데이터와 동일한 모든 데이터를 삭제한다.
List<Integer> list = Collections.synchronizedList(new ArrayList<>(List.of(1, 2)));
Stack
stack은 LIFO 구조를 나타낼 때 사용한다. Vector를 확장하고 있기 때문에 thread-safe하다.
멀티 스레드 환경이 아닌 이상, 더 효율적인 ArrayDeque를 사용하는 것을 권장한다.
int search(Object o)
: o와 동일한 데이터의 위치를 반환한다.
☁️ 내 생각
컬렉션을 파라매터로 넘겨주는 경우 유의해야 할 사항
자바는 데이터를 전달하는 과정에서 call by value
만을 지원하긴 하지만 참조타입의 경우 해당 참조의 프로퍼티에 접근할 수 있다는 것이다.
public static void main(String[] args) {
List<String> list = new ArrayList<>();
addValue(list, "Hello");
for (String value : list) {
System.out.println(value); // Hello
}
}
// 메서드에서 메서드 외부에서 전달해준 참조의 프로퍼티에 접근하여 값을 추가할 수 있다.
public static void addValue(List<String> list, String value) {
list.add(value);
}
이를 인지하고 개발하지 않는다면, 프로그램이 원치 않은 결과를 반환할 수 있다.
따라서 외부에서 전달한 참조타입 혹은 컬렉션을 메서드에서 독립적으로 사용하고 싶다면 deep copy후 파라매터로 넘겨주거나, 메서드에서 deep copy후 사용해야 한다.
후자의 경우는 메서드 내부에서 외부로부터 넘어온 객체의 참조 주소를 알 수 있기 때문에 전자를 선호하는 편이다.
ArrayList를 사용할 때 유의해야 할 사항
private static final int DEFAULT_CAPACITY = 10;
내부 배열의 default_capacity
는 10이다. 이 수치를 설정하지 않은 채로 ArrayList
객체를 생성하고 원소를 추가했을 때, 내부 배열이 가득 찼다면, 배열을 복사해놓고 더 큰 메모리 공간을 할당한 뒤, 복사해둔 배열을 담는 grow()
메서드를 호출하게 된다.
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
이는 비교적 코스트가 높은 작업이기 때문에 지속해서 많은 양의 원소를 ArrayList에 추가해야 하는 경우 오버헤드가 발생할 수 있으므로, 생성자를 통해 배열에 적절한 크기를 할당해 주는 것이 좋다.
☁️ 질문
- JCF에서 지원하는 대분류의 자료구조 4가지는?
- List, Queue, Set, Map
- ArrayList는 내부적으로 어떻게 동작하는가?
- 내부적으로 배열을 가진다. 배열이 가득 찬 경우 원소를 추가하는 작업을 수행한다면, 현재 배열을 복사해두고 배열에 더 큰 공간을 가지는 배열을 할당한 뒤, 복사해 둔 배열을 할당한다.
- 위의 동작을 수행하면 오버헤드가 발생할 것 같은데 효울적으로 사용하는 방법은?
- ArrayList 생성 시 적절한 크기를 지정하고 사용하는 것이 효율적이다.
예전에 작성했던 ArrayList포스팅
'JAVA > 자바의 신' 카테고리의 다른 글
25장. 스레드는 개발자라면 알아두는 것이 좋아요 (1) | 2024.01.10 |
---|---|
23, 24장. 자바랭 다음으로 많이 쓰는 애들은 컬렉션 - Set, Queue, Map (2) | 2024.01.10 |
21장. 실수를 방지하기 위한 제네릭이라는 것도 있어요 (0) | 2024.01.09 |
20장. 가장 많이 쓰는 패키지는 자바랭 (1) | 2024.01.09 |
19장. 이쯤에서 자바의 역사와 JVM에 대해서 알아보자 (0) | 2024.01.08 |