단순히 여러 엘리먼트들을 단일 단위로 그룹화하는 객체이다. 때때로 컨테이너라고도 한다. 컬랙션은 Aggregate 데이터를 저장, 검색, 조작 및 전달하는데 사용된다.
일반적으로 포커 핸드, 메일 폴더 또는 전화반호부와 같이 자연스러운 그룹을 형성하는 데이터 아이템들을 나타낸다.
컬렉션 프레임워크는 컬렉션을 표현하고 조작하기 위한 통합 아키텍처다. 모든 컬렉션 프레임워크엔 다음이 포함된다.
- 인터페이스 : 컬렉션을 나타내는 추상 데이터 타입이다. 인터페이스를 사용하면 표현(Representation)의 세부 사항과 관계없기 컬렉션을 조작할 수 있다. 객체 지향 언어에서 인터페이스는 일반적인 계층 구조를 형성한다.
- Implementations : 컬렉션 인터페이스의 구체적인 구현체로, 본질적으로 재사용 가능한 data structures다.
- 알고리즘 : 컬렉션 인테페이스를 구현하는 객체에 대해 검색 및 정렬과 같은 유용한 계산을 수행하는 방법이다. 알고리즘은 다형성이라고 한다. 적절한 컬렉션 인터페이스의 다양한 구현에서 동일한 방법을 사용할 수 있다. 본질적으로 알고리즘은 재사용이 가능한 기능이다.
# representation은 컬렉션이 내부적을 데이터를 저장하고 관리하는 방식 또는 구현 방법을 의미한다. 즉 인터페이스를 사용하면 컬렉션의 내부 구현 방법에 관계없이 동일한 방식으로 컬렉션을 조작할 수 있다는 것을 의미한다.
예를들어 List 인터페이스는 ArrayList나 LinkedList와 같은 다양한 구현 클래스가 어떻게 내부적으로 데이터르를 저장하는 지에 관계없이, 동일한 메서드들을 통해 리스트를 조작할 수 있게 해준다.
개발자는 구체적인 구현방법을 신경쓰기 않고도 일관된 방식으로 컬렉션을 사용할 수 있다.
자바 컬렉션 프레임워크는 다음과 같은 이점을 제공한다.
- Reduces Programming effort : 컬렉션 프레임워크는 유용한 data structures와 알고리즘을 제공함으로써 프로그램 작동에 필요한 하위 수준의 배관이 아닌 프로그램의 중요한 부분에 집중할 수 있게 해준다. 자바 컬렉션 프레임워크는 관련되지 않는 API 간의 상호 운용성을 촉진함으로써 API를 연결하기 위해 어댑터 객체나 변환 코드를 작성할 필요가 없도록 해준다.
- Increases Program speed and Quality : 유용한 data strucutres 및 알고리즘의 고성능, 고품질 구현을 제공한다. 각 인터페이스의 다양한 구현은 상호 교환이 가능하므로 컬렉션 구현을 전환하여 프로그램을 쉽게 조정할 수 있다. 자신만의 데이터 구조를 작성하는 번거로움에서 벗어난 프로그램의 품지로가 성능을 개선하는데 더 많은 시간을 할애할 수 있다.
- 관련 없는 API들 간의 상호 운용성을 허용한다 : 컬렉션 인터페이스는 API들이 컬렉션을 주고 받는 일종의 공영오이다. API들은 독립적으로 작성되었더라도 원할하게 상호 운용될 수 있다.
- 새로운 API를 배우고 사용하는데 드는 노력을 줄여준다 : 많은 API들이 자연스럽게 입력으로 컬렉션을 받고 출력으로 컬렉션을 제공한다. 과거에는 이러한 API마다 자체 컬렉션을 조작하는 작은 하위 API가 있었고, 이러한 임시 컬렉션 하위 API들 간에 일관성이 거의 없었기에 사용시 실수를 하기 쉬웠다. 표준 컬렉션 인터페이스가 등장하면서 이 문제는 사라졌다.
- 새로운 API를 설계하는데 드는 노력을 줄여준다 : 앞선 장점의 반대 측면으로 설계자와 구현자는 컬렉션에 의존하는 API를 만들 때마다 바퀴를 새로 발명할 필요는 없다. 대신 표준 컬렉션 인터페이스를 사용할 수 있다.
- 소프트웨어 재사용을 촉진한다 : 표준 컬렉션 인터페이스를 준수하는 새로운 Data Structures는 본질적으로 재상용 가능하다. 이러한 인터페이스를 구현하는 객체에 대해 작동하는 새로운 알고리즘도 마찬가지이다.
인터페이스
인터페이스를 다양한 타입의 컬렉션을 캡슐화한다. 이러한 인터페이스를 사용하면 represntation의 세부 사항과 관계없이 컬렉션을 조작할 수 있다. 핵심 컬렉션 인터페이스는 자바 컬렉션 프레임워크의 기초이다.
핵심 컬렉션 인터페이스는 계층 구조를 형성한다.
집합(Set)은 특정한 종류의 컬렉션이며, 정렬된 집합(SortedSet)은 특별한 종류의 집합이다.
맵은 진정한 컬렉션이 아니다.
모든 핵심 컬렉션 인터페이스는 제너릭이다.
public interface Collection<E> {
// 인터페이스 메서드 선언
}
<E>는 해당 인터페이스가 제너릭임을 나타낸다. Collenction 인스턴스를 선언할 때 컬렉션에 포함된 객체의 타입을 지정할 수 있으며, 그리고 지정해야한다. 타입 아규먼트를 지정하면 컴파일러가 컴파일 시간에 컬렉션에 넣는 객체의 타입이 올바른지 확인할 수 있어 런타임 오류를 줄을 수 있다.
핵심 컬렉션 인터페이스의 수를 관리 가능한 수준으로 유지하기 위해, 자바 플랫폼은 각 컬렉션 타입의 변형마다 별도의 인터페이스를 제공하지 않는다. 대신 각 인터페이스의 수정 작업은 선택 사항으로 지정된다. 특정 구현은 모든 작업을 지원하지 않을 수 있다. 지원되지 않는 작업이 호출되면 컬렉션은 UnsupportedOperationException을 던진다. 구현은 자신이 지원하는 선택적 작업을 문서화할 책임이 있다. 자바 플랫폼의 모든 범용 구현은 모든 선택적 작업을 지원한다.
- Collection : 컬렉션 계층의 루트이다. 컬렉션읜 해당 엘리먼트로 알려진 객체들의 그룹을 나타낸다. 컬렉션 인터페이스는 모든 컬렉션이 구현하는 최소 공통 분모이며, 최대의 일반화를 원할 때 컬렉션을 전달하고 조작하는데 사용된다. 일부 타입의 컬렉션은 중복 엘리먼트를 허용하고, 일부는 허용하지 않는다. 일부는 순서가 있으며, 다른 일부에는 순서가 없다. 자바 플랫폼은 이 인터페이스의 직접적인 구현을 제공하지 않지만, Set 및 List와 같은 더 구체적인 하위 인터페이스의 구현을 제공한다.
- Set : 중복 엘리먼트를 포함할 수 없는 컬렉션이다. 이 인터페이스는 수학적 집합 추상화를 모델링 하며 집합을 나타내는데 사용된다.
- List : 순서가 있는 (Ordered) 컬렉션 (시퀸스)이다. 리스트는 중복 엘리먼트를 포함할 수있다. 리스트의 사용자는 일반적으로 각 엘리먼트가 리스트에 삽입되는 위치를 정확하게 제어할 수 있으며, 정수 인덱스(위치)를 사용하여 엘리먼트에 접근할 수 있다.
- Queue : 특정 작업을 처리하기 전에 여러 엘리먼트를 보관하는데 사용되는 컬렉션이다. 기본적인 컬렉션 작업 외에도, 추가적인 삽이브 추출, 검사 작업을 제공한다. 일반적으로 엘리먼트를 FIFO 방식으로 정렬한다. 물론 제공된 비교자나 엘리먼트의 기본 정렬 순서에 따라 엘리먼트를 정렬하는 우선순위 큐가 있다. 사용되는 정렬 방식에 상관없이, 큐의 머리 부분(Head)는 remove 또는 poll 호출에 의해 제거될 엘리먼트이다. FIFO 규에서 모든 새로운 엘리먼트가 큐의 꼬리 부분(Tail)에 삽입된다. 다른 종류의 큐는 다른 배치 규칙을 사용할 수 있다. 모든 큐 규현은 자신의 정렬 속성을 명시해야 한다.
- Deque : 특정 작업을 처리하기 전에 여러 엘리먼트를 보관하는데 사용되는 컬렉션이다. 기본적인 컬렉션 작업 외에도, Deque는 추가적인 삽입, 추출, 검사 작업을 제공한다. Deque는 FIFO와 LIFO 방식 모두를 사용할 수 있다. Deque에서는 모든 새로운 엘리먼트를 양 끝에서 삽입, 검색 및 제거할 수 있다.
- Map : Key를 Value에 매핑하는 객체이다. Map은 중복 키를 포함할 수 없으며, 각 키는 최대 하나의 값에만 매핑될 수 있다.
HashSet
중복 없는 엘리먼트들의 집합을 저장하는데 사용된다. 내부적으로 HashMap을 사용하여 엘리먼트를 저장한다. 모든 엘리먼트는 HashMap의 키로 저자오디면 값은 항상 동일한 상수(PRESENT)이다.
엘리먼트의 순서를 보장하지 않고, 중복 엘리먼트를 허용하지 않는다, null 엘리먼트도 하나만 허용한다,
Set<String> hashSet = new HashSet<>();
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("apple"); // "apple"은 이미 존재하므로 추가되지 않음
HashMap
Key-Vaule Pair을 저장하는데 사용된다. 각 키와 값은 해시 테이블에 저장된다.
키는 중복을 허용하지 않지만, 값은 중복을 하용한다. 키는 순서를 보장하지 않는다. null 키를 하나 허용하고, null 값은 여러 개 허용한다.
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("apple", 1);
hashMap.put("banana", 2);
hashMap.put("apple", 3); // "apple" 키의 값이 3으로 대체됨
HashSet와 HashMap은 둘다 자바 컬렉션 프레임워크에서 제공하는 data structures로 hash table을 기반으로 한다.
HashSet는 중복 없는 집합을 다룰 때, HashMap은 키-값 쌍을 다룰 떄 유용하다.
- SortedSet : 엘리먼트를 오름차순으로 유지하는 Set이다. 정렬의 이점을 활용하기 위해 여러 추가 작업이 제공된다. 정렬된 집합은 단어 목록이나 회원 명부와 같은 자연스럽게 정렬된 집합에 사용된다.
- SortedMap : 매핑을 키의 오름차순으로 유지하는 Map이다. 이는 SortedSet의 Map 버전이다. 정렬된 맵은 사전, 전화번호부 같이 키/값 쌍의 자연스럽게 정렬된 컬렉션에 사용된다.
컬렉션 인터페이스
엘리먼트라고 불리는 객체들의 그룹을 나타낸다. 컬렉션 인터페이스는 최대한 일반적으로 객체들의 컬렉션을 전달할 때 사용된다.
관례적으로 모든 범용 컬렉션 구현(Set, Map 등)은 컬렉션 인터페이스 구현체인 아규먼트를 취하는 생성자를 가지고 있다.
이 생성자는 변환 생성자라고 하며, 지정한 컬렉션이 어떤 하위 인터페이스나 구현 타입이든 상관없이 새 컬렉션을 지정한 컬렉션의 모든 엘리먼트를 포함하도록 초기화한다. 이 생성자는 컬렉션의 타입을 변환할 수 있게 해 준다.
List<String> list = new ArrayList<String>(c);
List<String> list = new ArrayList<>(c); // JDK 7 이상부터 사용
컬렉션 인터페이스는 아래와 같은 기본 작업을 수행하는 메서드가 포함되어 있다.
int size(),
boolean isEmpty(),
boolean contains(Object element),
boolean add(E element),
boolean remove(Object element),
Iterator<E> iterator()
또한 전체 컬렉션을 대상으로 하는 메서드도 포함되어 있다.
oolean containsAll(Collection<> c),
boolean addAll(Collection<? extends E> c),
boolean removeAll(Collection<?> c),
boolean retainAll(Collection<?> c),
void clear()
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BulkOperationsExample {
public static void main(String[] args) {
// 기본 리스트 생성
List<String> targetList = new ArrayList<>(Arrays.asList("apple", "banana", "cherry", "date", "fig"));
// containsAll 예제
List<String> listToCheck = Arrays.asList("apple", "banana");
boolean containsAllResult = targetList.containsAll(listToCheck);
System.out.println("containsAll result: " + containsAllResult); // true
// addAll 예제
List<String> listToAdd = Arrays.asList("grape", "honeydew");
targetList.addAll(listToAdd);
System.out.println("After addAll: " + targetList); // [apple, banana, cherry, date, fig, grape, honeydew]
// removeAll 예제
List<String> listToRemove = Arrays.asList("apple", "fig");
targetList.removeAll(listToRemove);
System.out.println("After removeAll: " + targetList); // [banana, cherry, date, grape, honeydew]
// retainAll 예제
List<String> listToRetain = Arrays.asList("banana", "date");
targetList.retainAll(listToRetain);
System.out.println("After retainAll: " + targetList); // [banana, date]
// clear 예제
targetList.clear();
System.out.println("After clear: " + targetList); // []
}
}
Object[] toArray() 및 <T> T[] toArray(T[] a) 와 같은 배열 작업을 위한 추가 메서드도 존재한다.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ToArrayExample {
public static void main(String[] args) {
// 리스트 생성
List<String> list = new ArrayList<>(Arrays.asList("apple", "banana", "cherry", "date"));
// 충분한 크기의 배열을 제공하여 toArray 호출
String[] array1 = list.toArray(new String[list.size()]);
System.out.println("Array1: " + Arrays.toString(array1));
// 작은 크기의 배열을 제공하여 toArray 호출
String[] array2 = list.toArray(new String[0]);
System.out.println("Array2: " + Arrays.toString(array2));
// 결과 배열을 직접 사용하는 예제
for (String fruit : array2) {
System.out.println("Fruit: " + fruit);
}
}
}
JDK 8 및 이후 버전에서는 컬렉션 인터페이스가 Stream<E> stream() 및 Stream<E> parallelStream() 메서드도 노출하여 기본 컬렉션에서 순차 또는 병렬 스트림을 얻을 수 있다.
컬렉션 인터페이스는 객체 그룹을 나타내는 컬렉션에 기대할 수 있는 작업들을 수행한다. 컬렉션에 몇 개의 엘리먼트들이있는지 알려주는 메서드(Size, isEmpty), 특정 객체가 컬렉션에 있는지 확인하는 메서드 (Contains), 컬렉션에 엘리먼트를 추가하고 제거하는 메서드(add, remove), 컬렉션에 대한 반복자(iterator)를 제공하는 메서드를 포함한다.
add 메서드는 중복을 허용하는 컬렉션과 허용하지 않는 컬렉션 모두에서 의미 있게 사용할 수 있도록 일반적으로 정의되어 있다. 이 메서드는 호출이 완료된 후 지정된 엘리먼트가 컬렉션에 포함되도록 일반적으로 정의되어 있다. 이 메서드는 호출이 완료된 후 지정된 엘리먼트가 컬렉션에 포함되도록 보장하며, 호출 결과 컬렉션이 변경되면 true를 반환한다.
remove 메서드는 지정된 엘리먼트 단일 인스턴스를 컬렉션에세 제거하도록 설계되어 있으며, 해당 엘리먼트가 컬렉션에 포함되어 있을 떄 이 메서드를 호출하면 컬렉션이 수정된 경우 true를 반환한다.
import java.util.*;
public class CollectionExample {
public static void main(String[] args) {
// Collection 인터페이스의 인스턴스 생성
Collection<String> c = new HashSet<>();
c.add("apple");
c.add("banana");
c.add("cherry");
// Collection을 ArrayList로 변환하는 변환 생성자 사용
List<String> list = new ArrayList<>(c);
// Collection 인터페이스의 기본 메서드 사용 예시
System.out.println("Size: " + c.size()); // 컬렉션의 크기
System.out.println("Is empty: " + c.isEmpty()); // 컬렉션이 비어 있는지 여부
System.out.println("Contains 'apple': " + c.contains("apple")); // 특정 객체가 컬렉션에 포함되어 있는지 여부
c.add("date"); // 컬렉션에 요소 추가
System.out.println("Added 'date': " + c);
c.remove("banana"); // 컬렉션에서 요소 제거
System.out.println("Removed 'banana': " + c);
// Iterator 사용하여 컬렉션 반복
Iterator<String> iterator = c.iterator();
while (iterator.hasNext()) {
System.out.println("Iterator value: " + iterator.next());
}
// 전체 컬렉션을 대상으로 하는 메서드 사용 예시
Collection<String> otherCollection = Arrays.asList("apple", "fig");
System.out.println("Contains all: " + c.containsAll(otherCollection)); // 모든 요소가 포함되어 있는지 여부
c.addAll(otherCollection); // 다른 컬렉션의 모든 요소 추가
System.out.println("After addAll: " + c);
c.removeAll(otherCollection); // 다른 컬렉션의 모든 요소 제거
System.out.println("After removeAll: " + c);
c.retainAll(otherCollection); // 다른 컬렉션에 있는 요소만 유지
System.out.println("After retainAll: " + c);
c.clear(); // 모든 요소 제거
System.out.println("After clear: " + c);
// 배열 작업을 위한 추가 메서드 사용 예시
c.add("grape");
c.add("honeydew");
Object[] array = c.toArray();
System.out.println("Array: " + Arrays.toString(array));
String[] stringArray = c.toArray(new String[0]);
System.out.println("String array: " + Arrays.toString(stringArray));
// Stream 메서드 사용 예시 (JDK 8 이상)
Stream<String> stream = c.stream();
stream.forEach(System.out::println);
Stream<String> parallelStream = c.parallelStream();
parallelStream.forEach(System.out::println);
}
}
컬렉션 순회 (Traversing)
컬렉션을 순회하는 방법으 세 가지가 있다.
- aggregate 연산을 사용하는 방법
- for-each 구문을 사용하는 방법
- Iterator를 사용하는 방법
Aggregate Operations
JDK 8 및 이후 버전에서는 컬렉션을 반복하는 선호되는 방법은 스트림을 얻고 aggregrate operations을 수행하는 것이다.
aggregate operations는 종종 람다 표현식과 함께 사용되어 프로그래밍을 더 표현력 있게 만들고, 코드 라인을 줄일 수 잇다.
아래의 코드는 도형 컬렉션을 순차적으로 반복하며 빨간색 객체를 출력한다.
myShapesCollection.stream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
만약 켈럭션이 충분히 크고 컴퓨터에 충분한 CPU 코어가 잇다면 병렬 스트림을 쉽게 요청할 수 있다.
myShapesCollection.parallelStream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
이 API를 사용하여 데이터를 수집하는 다양한 방법이 있다.
예를 들어 아래의 코드는 컬렉션의 엘리먼트들을 String 객체로 변환하고, 이를 쉼표로 구분하여 결합한다.
String joined = elements.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
또는 모든 직원의 급여를 합산할 수도 있다.
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
컬렉션 프레임워크는 항상 API 일부로서 여러 Bulk Operation를 제공해 왔다. 여기서에는 containsAll, addAll, removeAll등과 같이 전체 컬렉션을 대상으로 하는 메서드들이 포함된다.
이러한 메서드들과 JDK 8에 도입된 aggergate operations를 혼동해선 안된다.
새로운 aggergate operation와 기존의 Bulk Operations의 주요 차이점은, 기존의 메서드들은 모두 변경 가능하다는 점이다.
이들은 모두 기본 컬렉션을 수정한다. 반면에 새로운 aggergate operations는 기본 컬렉션을 수정하지 않는다. 새로운 aggergate opertaions와 람다 표현식을 사용할 때, 나중에 병렬 스트림에서 코드가 실행될 경우 문제를 일으키지 않도록 변경을 피하는 것이 중요하다.
# Bulk Operations의 Bulk는 대량의, 일괄적인라는 의미로 사용되엇다. 즉 Bulk Operations는 한번에 전체 컬렉션에 대해 수행되는 작업을 의미한다.
예를 들어 containsAll, addAll, removeAll 메서드는 컬렉션의 모든 엘리먼트에 대해 한 번만 적용되는 작업이다.
import java.util.*;
import java.util.stream.Collectors;
import java.awt.Color;
class Shape {
private String name;
private Color color;
public Shape(String name, Color color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public Color getColor() {
return color;
}
@Override
public String toString() {
return name;
}
}
class Employee {
private String name;
private int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public int getSalary() {
return salary;
}
}
public class AggregateOperationsExample {
public static void main(String[] args) {
// 도형 컬렉션 생성
Collection<Shape> myShapesCollection = Arrays.asList(
new Shape("Circle", Color.RED),
new Shape("Square", Color.BLUE),
new Shape("Triangle", Color.RED),
new Shape("Rectangle", Color.GREEN)
);
// 순차 스트림을 사용하여 빨간색 도형 출력
myShapesCollection.stream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
// 병렬 스트림을 사용하여 빨간색 도형 출력
myShapesCollection.parallelStream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
// 도형 이름을 쉼표로 구분하여 결합
String joinedNames = myShapesCollection.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
System.out.println("Joined names: " + joinedNames);
// 직원 컬렉션 생성
Collection<Employee> employees = Arrays.asList(
new Employee("John", 50000),
new Employee("Jane", 60000),
new Employee("Jack", 55000)
);
// 직원 급여 합산
int totalSalary = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary));
System.out.println("Total salary: " + totalSalary);
}
}
for-each 생성자
for loop를 통해 컬렉션이나 배열을 간결하게 순회할 수 있다. 향상된 for 구문이라고도 한다.
for (Object o : collection)
System.out.println(o);
하지만 Iterator 메서드를 구현해야만 사용할 수 있다.
List<String> li = new ArrayList<>();
li.add("korea");
li.add("japan");
li.add("usa");
for (String e : li) {
System.out.println(e);
}
첫 번째 looping에서 ArrayList 클래스가 구현한 Iterator 인터페이스의 iterator 메서드가 호출되면서 ArrayList 클래스의 inner class인 Itr 클래스 객체를 생성한다.
# Itr 클래스는 Iterator<E> 인터페이스를 구현한 구체이다.
public Iterator<E> iterator() {
return new Itr();
}
그리고 Itr 클래스가 구현한 Iterator 인터페이스의 hasNext 메서드가 호출된다.
public boolean hasNext() {
return cursor != size;
}
hasNext 메서드 호출 이후, Iterator 인터페이스의 Next 메서드가 호출된다.
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
이터레이터 (Iterators)
컬렉션을 순회하고, 원하는 경우 선택적으로 컬렉션에서 엘리먼트를 제거할 수 있게 해주는 객체이다. 컬렉션의 iterator 메서드를 호출하여 Iterator를 얻을 수 있다.
public interface Iterator<E> {
boolean hasNext();
E next();
void remove(); //optional
}
hasNext 메서드는 반복할 엘리먼트가 더 있는 경우 true를 반환하면, next 메서드는 반복의 다음 엘리먼트를 반환한다.
remove 메서드는 next에 의해 마지막으로 반환된 엘리먼트를 기본 컬렉션에서 제거한다. remove 메서드는 next를 호출할 때 한 번만 호출할 수 있으며, 이 규칙을 위반하면 예외가 발생한다.
Iterator.remove는 반복 중에 컬렉션을 수정하는 유일한 안전한 방법이다.
반복이 진행되는 동안 기본 컬렉션이 다른 방식으로 수정되면 동작은 정의되자 않는다.
다음과 같은 경우에는 for-each 구문 대신 Iterator를 사용해야 한다.
- 현재 엘리먼트를 제거해야 할때. for-each 구문은 iterator를 숨기기 때문에 remove를 호출할 수 없다. 따라서, for-each 구문은 필터링에 사용할 수 없다.
- 여러 컬렉션을 병렬로 순회해야 할때
아래 코드는 for-each 구문을 사용하지 않고 엘리먼트를 삭제하고 출력한다.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (element.equals("banana")) {
iterator.remove(); // "banana" 요소를 제거
}
}
for (String fruit : list) {
System.out.println(fruit); // "apple"과 "orange"만 출력
}
}
}
아래 코드는 두 개의 컬렉션을 병렬로 순회하며 출력한다.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ParallelIterationExample {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("apple");
list1.add("banana");
list1.add("orange");
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
Iterator<String> iterator1 = list1.iterator();
Iterator<Integer> iterator2 = list2.iterator();
while (iterator1.hasNext() && iterator2.hasNext()) {
String element1 = iterator1.next();
Integer element2 = iterator2.next();
System.out.println(element1 + " - " + element2);
}
}
}
아래 메서드는 Iterator를 사용하여 임의의 컬렉션을 필터링하는 방법을 보여준다.
즉, 컬렉션을 순회하며 특정 엘리먼트를 제거하는 방법이다.
static void filter(Collection<?> c) {
for (Iterator<?> it = c.iterator(); it.hasNext(); )
if (!cond(it.next()))
it.remove();
}
해당 메서드를 적용한 코드는 아래와 같다.
import java.util.Collection;
import java.util.Iterator;
public class CollectionFilter {
public static <T> void filter(Collection<T> collection, T elementToRemove) {
Iterator<T> iterator = collection.iterator();
while (iterator.hasNext()) {
T element = iterator.next();
if (element.equals(elementToRemove)) {
iterator.remove(); // 특정 엘리먼트 제거
}
}
}
public static void main(String[] args) {
// 예시로 ArrayList를 사용
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");
list.add("banana");
System.out.println("Before filtering: " + list);
// "banana" 엘리먼트를 필터링 (제거)
filter(list, "banana");
System.out.println("After filtering: " + list);
}
}
이 코드는 다형성을 가지며, 이는 구현에 상관없이 모든 컬렉션에서 작동한다는 것을 의미한다.
또한 자바 컬렉션 프레임워크를 사용하여 다형적인 알고리즘을 얼마나 쉽게 작성할 수 있는 지를 보여준다.
컬렉션 인터페이스 벌크 오퍼레이션
벌크 오퍼레이션은 전차 컬렉션에 대한 작업을 수행한다. 이러한 간편한 작업들을 기본 작업을 사용하여 구현할 수 있지만, 대부분의 경우 이러한 구현은 덜 효과적이다.
- containsAll : 지저오딘 컬렉션의 모든 엘리먼트가 타겟 컬렉션에 포함되어 있으면 true를 반환한다,
- addAll : 지정된 컬렉션의 모든 엘리먼트를 타겟 컬렉션에 추가한다.
- removeAll : 지정된 컬렉션에도 포함된 모든 엘리컨트를 타켓 컬렉션에서 제거한다.
- retainAll : 지정된 컬렉션에도 포함되지 않은 모든 엘리먼트 타겟 컬렉션에서 제거한다, 지정된 컬렉션에도 포함된 엘리먼트만 타겟 컬렉션에 유지한다.
- clear : 지정된 컬렉션의 모든 엘리먼트를 제거한다.
addAll, removeAll, retainAll 메서드는 작업을 실행하는 과정에서 대상 컬렉션이 수정된 경우 true를 반환한다.
아래 코드는 벌크 오퍼레이션의 강력함을 보여주는 간단한 예시이다.
컬렉션 c에 지정된 엘리먼트 e의 모든 인스턴스를 제거한다.
c.removeAll(Collections.singleton(e));
아래의 코드는 컬렉션에서 모든 null 엘리먼트를 제거한다.
c.removeAll(Collections.singleton(null));
아래의 코드는 지정된 요소만 포함하는 불변 Set를 반환하는 static factory mehod인 Collection.singleton을 사용한다.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class BulkOperationsExample {
public static void main(String[] args) {
// 기본 컬렉션 생성
Collection<String> targetCollection = new ArrayList<>(Arrays.asList("apple", "banana", "cherry", "date", "fig"));
// containsAll 예제
Collection<String> collectionToCheck = Arrays.asList("apple", "banana");
boolean containsAllResult = targetCollection.containsAll(collectionToCheck);
System.out.println("containsAll result: " + containsAllResult); // true
// addAll 예제
Collection<String> collectionToAdd = Arrays.asList("grape", "honeydew");
targetCollection.addAll(collectionToAdd);
System.out.println("After addAll: " + targetCollection); // [apple, banana, cherry, date, fig, grape, honeydew]
// removeAll 예제
Collection<String> collectionToRemove = Arrays.asList("apple", "fig");
targetCollection.removeAll(collectionToRemove);
System.out.println("After removeAll: " + targetCollection); // [banana, cherry, date, grape, honeydew]
// retainAll 예제
Collection<String> collectionToRetain = Arrays.asList("banana", "date");
targetCollection.retainAll(collectionToRetain);
System.out.println("After retainAll: " + targetCollection); // [banana, date]
// clear 예제
targetCollection.clear();
System.out.println("After clear: " + targetCollection); // []
}
}
컬렉션 인터페이스 배열 오퍼레이션
toArray 메서드는 입력 시, 배열이 필요한 이전 API간의 브릿지 역할을 한다. 배열 작업을 통해 컬렉션의 내용을 배열로 변환할 수 있다. 아규먼트가 없는 단순한 형태인 c.toArray()는 Object 타입의 새로운 배열을 생성한다.
더 복잡한 형태는 호출자가 배열을 제공하거나 출력 배열의 런타임 타입을 선택할 수 있게 한다.
만약 c가 컬렉션이라면, 다음 코드는 c의 내용을 엘리먼트의 개수와 동일한 길이의 새로 할당된 Object 배열에 덤프한다.
Object[] a = c.toArray();
c가 문자열만 포함하고 있다고 가정한다면, 다음 코드는 c의 내용을 cdml 엘리먼트 개수와 동일한 길이의 새로 할당된 Stirng 배열에 덤프한다.
'자바 튜토리얼' 카테고리의 다른 글
JDBC API (0) | 2024.08.02 |
---|---|
제너릭 (Generics) [4] (0) | 2024.07.18 |
제너릭 (Generics) [3] (0) | 2024.07.17 |
제너릭 (Generics) [2] (0) | 2024.07.16 |
제너릭 (Generics) [1] (0) | 2024.07.16 |