Lesson: Introduction to Collections 1

2024. 6. 10. 16:29High Level Programming Language/Collections

[공식 튜토리얼]

 

collection(때때로 컨테이너라고도 함)은 단순히 여러 엘리먼트들을 단일 단위로 그룹화하는 객체입니다. Collections은 Aggregate 데이터를 저장, 검색, 조작 및 전달하는 데 사용됩니다. 일반적으로 포커 핸드(카드 모음), 메일 폴더(편지 모음) 또는 전화번호부(이름과 전화번호 매핑)와 같이 자연스러운 그룹을 형성하는 데이터 아이템들을 나타냅니다. Java 프로그래밍 언어 또는 다른 프로그래밍 언어를 사용해 본 적이 있다면 이미 collections에 익숙할 것입니다.

 

What Is a Collections Framework?

컬렉션 프레임워크는 컬렉션을 표현하고 조작하기 위한 통합 아키텍처입니다. 모든 컬렉션 프레임워크에는 다음이 포함됩니다.

  • Interfaces: 컬렉션을 나타내는 추상 데이터 타입입니다. 인터페이스를 사용하면 표현[representation]의 세부 사항과 관계없이 컬렉션을 조작할 수 있습니다. 객체 지향 언어에서 인터페이스는 일반적으로 계층 구조를 형성합니다.
  • Implementations: 컬렉션 인터페이스의 구체적인 구현체입니다. 본질적으로 재사용 가능한 data structures 입니다.
  • 알고리즘: 컬렉션 인터페이스를 구현하는 객체에 대해 검색 및 정렬과 같은 유용한 계산을 수행하는 방법입니다. 알고리즘은 다형성이라고 합니다. 즉, 적절한 컬렉션 인터페이스의 다양한 구현에서 동일한 방법을 사용할 수 있습니다. 본질적으로 알고리즘은 재사용 가능한 기능입니다.

representation은 컬렉션이 내부적으로 데이터를 저장하고 관리하는 방식 또는 구현 방법을 의미합니다. 즉, 인터페이스를 사용하면 컬렉션의 내부 구현 방법에 관계없이 동일한 방식으로 컬렉션을 조작할 수 있다는 것을 의미합니다. 예를 들어, List 인터페이스는 ArrayList나 LinkedList와 같은 다양한 구현 클래스가 어떻게 내부적으로 데이터를 저장하는지에 관계없이, 동일한 메서드들을 통해 리스트를 조작할 수 있게 해줍니다.

이를 통해, 개발자는 구체적인 구현 방법을 신경 쓰지 않고도 일관된 방식으로 컬렉션을 사용할 수 있습니다.

 

자바 컬렉션 프레임워크 외에도 가장 잘 알려진 컬렉션 프레임워크의 예로는 C++ 표준 템플릿 라이브러리(STL)와 스몰토크의 컬렉션 계층 구조가 있습니다. 역사적으로 컬렉션 프레임워크는 매우 복잡해서 학습 곡선이 가파르다는 평판을 얻었습니다. 이 장에서 배우겠지만, 자바 컬렉션 프레임워크는 이 전통을 깨뜨린다고 믿습니다.

 

Benefits of the Java Collections Framework

자바 컬렉션 프레임워크는 다음과 같은 이점을 제공합니다:

  • Reduces programming effort: 컬렉션 프레임워크는 유용한 data structures 와 알고리즘을 제공함으로써 프로그램 작동에 필요한 하위 수준의 "배관"이 아닌 프로그램의 중요한 부분에 집중할 수 있도록 해줍니다. Java Collections Framework는 관련되지 않은 API 간의 상호 운용성을 촉진함으로써 API를 연결하기 위해 어댑터 객체나 변환 코드를 작성할 필요가 없도록 해줍니다.
  • Increases program speed and quality: 이 컬렉션 프레임워크는 유용한 data structures 및 알고리즘의 고성능, 고품질 구현을 제공합니다. 각 인터페이스의 다양한 구현은 상호 교환이 가능하므로 컬렉션 구현을 전환하여 프로그램을 쉽게 조정할 수 있습니다. 자신만의 데이터 구조를 작성하는 번거로움에서 벗어나 프로그램의 품질과 성능을 개선하는 데 더 많은 시간을 할애할 수 있습니다.
  • Allows interoperability among unrelated APIs(관련 없는 API들 간의 상호 운용성을 허용합니다): 컬렉션 인터페이스는 API들이 컬렉션을 주고받는 일종의 공용어입니다. 예를 들어, 내 네트워크 관리 API가 노드 이름의 컬렉션을 제공하고, 당신의 GUI 도구킷이 열 머리글의 컬렉션을 기대한다면, 우리 API들은 독립적으로 작성되었더라도 원활하게 상호 운용될 수 있습니다.
  • Reduces effort to learn and to use new APIs(새로운 API를 배우고 사용하는 데 드는 노력을 줄여줍니다): 많은 API들이 자연스럽게 입력으로 컬렉션을 받고 출력으로 컬렉션을 제공합니다. 과거에는 이러한 API마다 자체 컬렉션을 조작하는 작은 하위 API가 있었고, 이러한 임시 컬렉션 하위 API들 간에 일관성이 거의 없었습니다. 따라서 각 API를 처음부터 배워야 했고, 사용 시 실수를 하기가 쉬웠습니다. 표준 컬렉션 인터페이스가 등장하면서 이 문제는 사라졌습니다.
  • Reduces effort to design new APIs(새로운 API를 설계하는 데 드는 노력을 줄여줍니다): 이는 앞선 장점의 반대 측면입니다. 설계자와 구현자는 컬렉션에 의존하는 API를 만들 때마다 바퀴를 새로 발명할 필요가 없습니다. 대신 표준 컬렉션 인터페이스를 사용할 수 있습니다.
  • Fosters software reuse(소프트웨어 재사용을 촉진합니다): 표준 컬렉션 인터페이스를 준수하는 새로운 data structures는 본질적으로 재사용 가능합니다. 이러한 인터페이스를 구현하는 객체에 대해 작동하는 새로운 알고리즘도 마찬가지입니다.

 

Lesson: Interface

핵심 컬렉션 인터페이스는 아래 그림에 표시된 다양한 타입의 컬렉션을 캡슐화합니다. 이러한 인터페이스를 사용하면 representation의 세부 사항과 관계없이 컬렉션을 조작할 수 있습니다. 핵심 컬렉션 인터페이스는 Java 컬렉션 프레임워크의 기초입니다. 다음 그림에서 볼 수 있듯이 핵심 컬렉션 인터페이스는 계층 구조를 형성합니다.

The core collection interface

 

집합(Set)은 특별한 종류의 컬렉션(Collection)이며, 정렬된 집합(SortedSet)은 특별한 종류의 집합(Set)입니다. 또한, 이 계층 구조는 두 개의 독립된 트리로 구성되어 있다는 점을 주목하십시오 — 맵(Map)은 진정한 컬렉션이 아닙니다.

 

모든 핵심 컬렉션 인터페이스가 제네릭이라는 점을 주목하십시오. 예를 들어, 다음은 Collection 인터페이스의 선언입니다.

public interface Collection<E> {
    // 인터페이스 메서드 선언
}

 

<E>는 해당 인터페이스가 제네릭임을 나타냅니다. Collection 인스턴스를 선언할 때 컬렉션에 포함된 객체의 타입을 지정할 수 있으며, 그리고 지정해야 합니다. 타입 아규먼트를 지정하면 컴파일러가 컴파일 시간에 컬렉션에 넣는 객체의 타입이 올바른지 확인할 수 있어 런타임 오류를 줄일 수 있습니다. 제네릭 타입에 대한 정보는 제네릭 레슨을 참조하십시오.

 

이러한 인터페이스들을 사용하는 방법을 이해하면, 자바 컬렉션 프레임워크에 대해 알아야 할 대부분을 알게 될 것입니다. 이 장에서는 어떤 인터페이스를 언제 사용해야 하는지를 포함하여 인터페이스를 효과적으로 사용하는 일반적인 지침에 대해 논의합니다. 또한 각 인터페이스를 최대한 활용할 수 있도록 프로그래밍 관용구에 대해서도 배우게 될 것입니다.

 

핵심 컬렉션 인터페이스의 수를 관리 가능한 수준으로 유지하기 위해, 자바 플랫폼은 각 컬렉션 타입의 변형마다 별도의 인터페이스를 제공하지 않습니다. (이러한 변형에는 불변, 고정 크기, 추가 전용 등이 포함될 수 있습니다.) 대신 각 인터페이스의 수정 작업은 선택 사항으로 지정됩니다. 특정 구현은 모든 작업을 지원하지 않을 수 있습니다. 지원되지 않는 작업이 호출되면, 컬렉션은 UnsupportedOperationException을 던집니다. 구현은 자신이 지원하는 선택적 작업을 문서화할 책임이 있습니다. 자바 플랫폼의 모든 범용 구현은 모든 선택적 작업을 지원합니다.

다음 목록은 핵심 컬렉션 인터페이스를 설명합니다:

  • Collection — 컬렉션 계층의 루트입니다. 컬렉션은 해당 엘리먼트로 알려진 객체들의 그룹을 나타냅니다. Collection 인터페이스는 모든 컬렉션이 구현하는 최소 공통 분모이며, 최대의 일반화를 원할 때 컬렉션을 전달하고 조작하는 데 사용됩니다. 일부 타입의 컬렉션은 중복 엘리먼트를 허용하고, 일부는 허용하지 않습니다. 일부는 순서가 있으며, 다른 일부는 순서가 없습니다. 자바 플랫폼은 이 인터페이스의 직접적인 구현을 제공하지 않지만, Set 및 List와 같은 더 구체적인 하위 인터페이스의 구현을 제공합니다. 또한 Collection 인터페이스 섹션을 참조하십시오.
  • Set — 중복 엘리먼를 포함할 수 없는 컬렉션입니다. 이 인터페이스는 수학적 집합 추상화를 모델링하며, 포커 핸드를 구성하는 카드, 학생의 시간표를 구성하는 과목, 또는 머신에서 실행 중인 프로세스와 같은 집합을 나타내는 데 사용됩니다. 또한 Set 인터페이스 섹션을 참조하십시오.
  • List — 순서가 있는[ordered] 컬렉션(때로는 시퀀스라고도 함)입니다. 리스트는 중복 엘리먼트를 포함할 수 있습니다. 리스트의 사용자는 일반적으로 각 엘리먼트가 리스트에 삽입되는 위치를 정확하게 제어할 수 있으며, 정수 인덱스(위치)를 사용하여 엘리먼트에 접근할 수 있습니다. Vector를 사용해본 적이 있다면, List의 일반적인 특성에 익숙할 것입니다. 또한 List 인터페이스 섹션을 참조하십시오.
  • Queue — 특정 작업을 처리하기 전에 여러 엘리먼트를 보관하는 데 사용되는 컬렉션입니다. 기본적인 Collection 작업 외에도, Queue는 추가적인 삽입, 추출, 검사 작업을 제공합니다. Queue는 일반적으로(하지만 반드시 그런 것은 아니지만) 엘리먼트를 FIFO(선입선출) 방식으로 정렬합니다. 예외로는 제공된 비교자나 엘리먼트의 기본 정렬 순서에 따라 엘리먼트를 정렬하는 우선순위 큐가 있습니다. 사용되는 정렬 방식에 상관없이, 큐의 머리 부분(head)은 remove 또는 poll 호출에 의해 제거될 엘리먼트입니다. FIFO 큐에서는 모든 새로운 엘리먼트가 큐의 꼬리 부분(tail)에 삽입됩니다. 다른 종류의 큐는 다른 배치 규칙을 사용할 수 있습니다. 모든 Queue 구현은 자신의 정렬 속성을 명시해야 합니다. 또한 Queue 인터페이스 섹션을 참조하십시오.
  • Deque — 특정 작업을 처리하기 전에 여러 엘리트를 보관하는 데 사용되는 컬렉션입니다. 기본적인 Collection 작업 외에도, Deque는 추가적인 삽입, 추출, 검사 작업을 제공합니다. Deque는 FIFO(선입선출)와 LIFO(후입선출) 방식 모두로 사용할 수 있습니다. Deque에서는 모든 새로운 엘리먼트를 양 끝에서 삽입, 검색 및 제거할 수 있습니다. 또한 Deque 인터페이스 섹션을 참조하십시오.
  • Map Key를 Value에 매핑하는 객체입니다. Map은 중복 키를 포함할 수 없으며, 각 키는 최대 하나의 값에만 매핑될 수 있습니다. Hashtable을 사용해본 적이 있다면, 이미 Map의 기본 사항에 익숙할 것입니다. 또한 Map 인터페이스 섹션을 참조하십시오.
     

HashSetHashMap은 둘 다 Java의 컬렉션 프레임워크에서 제공하는 data structuers로, hash table을 기반으로 합니다. 그러나 그들의 용도와 기능에는 중요한 차이점이 있습니다.

HashSet

  목적: 중복 없는 엘리먼트들의 집합을 저장하는 데 사용됩니다.
  구조: 내부적으로 HashMap을 사용하여 엘리먼트를 저장합니다. 모든 엘리먼트는 HashMap의 키로 저장되며,

            값은 항상 동일한 상수 (PRESENT)입니다.
  특징:
    엘리먼트의 순서를 보장하지 않습니다.
   중복 엘리먼트를 허용하지 않습니다.
   null 엘리먼트를 하나만 허용합니다.
  사용 예:

Set<String> hashSet = new HashSet<>();
  hashSet.add("apple");
  hashSet.add("banana");
  hashSet.add("apple"); // "apple"은 이미 존재하므로 추가되지 않음


HashMap
  목적: Key-Value 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을 사용하여 구현됩니다.
   HashMap은 키-값 쌍을 저장합니다.
  중복 허용:
   HashSet은 중복 엘리먼트를 허용하지 않습니다.
   HashMap은 키의 중복을 허용하지 않지만, 값의 중복은 허용합니다.
  저장 방법:
   HashSet에서는 엘리먼트가 HashMap의 키로 저장되며, 모든 값은 동일한 상수로 저장됩니다.
   HashMap에서는 키와 값이 모두 저장됩니다.
  null 허용:
   HashSetnull 엘리먼트를 하나 허용합니다.
   HashMapnull 키를 하나 허용하고, null 값을 여러 개 허용합니다.

이러한 차이점을 이해하면, 상황에 맞게 적절한 자료구조를 선택하는 데 도움이 됩니다. HashSet은 중복 없는 집합을 다룰 때, HashMap은 키-값 쌍을 다룰 때 유용합니다.


 

마지막 두 개의 핵심 컬렉션 인터페이스는 단순히 Set 및 Map의 정렬된 버전입니다.

  • SortedSet — 엘리먼트를 오름차순으로 유지하는 Set입니다. 정렬의 이점을 활용하기 위해 여러 추가 작업이 제공됩니다. 정렬된 집합은 단어 목록이나 회원 명부와 같은 자연스럽게 정렬된 집합에 사용됩니다. 또한 SortedSet 인터페이스 섹션을 참조하십시오.
  • SortedMap - 매핑을 키의 오름차순으로 유지하는 Map입니다. 이는 SortedSet의 Map 버전입니다. 정렬된 맵은 사전이나 전화번호부와 같이 키/값 쌍의 자연스럽게 정렬된 컬렉션에 사용됩니다. 또한 SortedMap 인터페이스 섹션을 참조하십시오.

정렬된 인터페이스가 엘리먼트의 순서를 어떻게 유지하는지 이해하려면, Object Ordering 섹션을 참조하십시오.

 

 

The Collection Interface

Collection은 엘리먼트라고 불리는 객체들의 그룹을 나타냅니다. Collection 인터페이스는 최대한 일반적으로 객체들의 컬렉션을 전달할 때 사용됩니다. 예를 들어, 관례적으로 모든 범용 컬렉션 구현은 Collection 인터페이스 구현체인 아규먼트를 취하는 생성자를 가지고 있습니다. 이 생성자는 변환 생성자라고 하며, 지정된 컬렉션이 어떤 하위 인터페이스나 구현 타입이든 상관없이 새 컬렉션을 지정된 컬렉션의 모든 엘리먼트를 포함하도록 초기화합니다. 다시 말해, 이 생성자는 컬렉션의 타입을 변환할 수 있게 해줍니다.

 

예를 들어 List, Set 또는 다른 종류의 Collection일 수 있는 Collection<String> c가 있다고 가정합니다. 이 관용 코드는 처음에 c의 모든 엘리먼트를 ​​포함하는 새로운 ArrayList(List 인터페이스의 구현)를 생성합니다.

List<String> list = new ArrayList<String>(c);

 

또는 JDK 7 이상을 사용하는 경우 다이아몬드 연산자를 사용할 수 있습니다.

List<String> list = new ArrayList<>(c);

 

 

Collection 인터페이스에는

int size(),
boolean isEmpty(),
boolean contains(Object element),
boolean add(E element),
boolean remove(Object element),
Iterator<E> iterator()

와 같은 기본 작업을 수행하는 메서드가 포함되어 있습니다.

또한 

boolean 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 및 이후 버전에서는 Collection 인터페이스가 Stream<E> stream() 및 Stream<E> parallelStream() 메서드도 노출하여 기본 컬렉션에서 순차 또는 병렬 스트림을 얻을 수 있습니다. (스트림 사용에 대한 자세한 내용은 "Aggregate Operations"라는 레슨을 참조하십시오.)

Collection 인터페이스는 객체 그룹을 나타내는 컬렉션에 기대할 수 있는 작업들을 수행합니다. 컬렉션에 몇 개의 엘리먼트들이 있는지 알려주는 메서드(size, isEmpty), 특정 객체가 컬렉션에 있는지 확인하는 메서드(contains), 컬렉션에 엘리먼트를 추가하고 제거하는 메서드(add, remove), 컬렉션에 대한 반복자(iterator)를 제공하는 메서드(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 Collections

컬렉션을 순회하는 방법은 세 가지가 있습니다:

(1) aggregate 연산을 사용하는 방법

(2) for-each 구문을 사용하는 방법

(3) Iterator를 사용하는 방법

 

Aggregate operations

JDK 8 및 이후 버전에서는 컬렉션을 반복하는 선호되는 방법은 스트림을 얻고 aggregate operations를 수행하는 것입니다. aggregate operations는 종종 람다 expression과 함께 사용되어 프로그래밍을 더 표현력 있게 만들고, 코드 라인을 줄일 수 있습니다. 다음 코드는 도형 컬렉션을 순차적으로 반복하며 빨간색 객체를 출력합니다:

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)));

 

이들은 스트림과 aggregate operations으로 할 수 있는 몇 가지 예일 뿐입니다. 더 많은 정보와 예시는 "Aggregate Operations"라는 레슨을 참조하십시오.

Collections 프레임워크는 항상 API의 일부로서 여러 "bulk operations"를 제공해 왔습니다. 여기에는 containsAll, addAll, removeAll 등과 같이 전체 컬렉션을 대상으로 하는 메서드들이 포함됩니다. 이러한 메서드들과 JDK 8에 도입된 aggregate operations를 혼동하지 마십시오. 새로운 aggregate operations와 기존의 bulk operations(containsAll, addAll 등)의 주요 차이점은, 기존의 메서드들은 모두 변경 가능하다는 점입니다. 즉, 이들은 모두 기본 컬렉션을 수정합니다. 반면에, 새로운 aggregate operations는 기본 컬렉션을 수정하지 않습니다. 새로운 aggregate 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 Construct

for-each construct(또는 enhanced for loop 라고도 함)을 사용하면 for loop를 통해 컬렉션이나 배열을 간결하게 순회할 수 있습니다 — 자세한 내용은 The for Statement를 참조하십시오. 다음 코드는 for-each 구문을 사용하여 컬렉션의 각 엘리먼트를 별도의 라인에 출력합니다:

for (Object o : collection)
    System.out.println(o);

 

※ 예제를 만들어서 for-each 구문을 디버깅하시길 바랍니다.

List<String> li = new ArrayList<>();
li.add("korea");
li.add("japan");
li.add("usa");

for (String e : li) {
    System.out.println(e);
}

 

첫번째 looping에서 ArrayList 클래스가 구현한 Iterable 인터페이스의 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 메서드를 호출하여 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 구문은 필터링에 사용할 수 없습니다.
 여러 컬렉션을 병렬로 순회해야 할 때.

 

다음은 Iterator를 사용하여 를 엘리먼트를 제거하고 여러 컬렉션을 병렬로 순회하는 샘플 코드입니다.

element 제거 샘플 코드:

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를 사용하여 "banana" 엘리먼트를 제거하는 코드와 두 개의 리스트를 병렬로 순회하는 코드를 보여줍니다.

 

다음 메서드는 Iterator를 사용하여 임의의 Collection을 필터링하는 방법을 보여줍니다. 즉, 컬렉션을 순회하면서 특정 엘리먼트를 제거하는 방법입니다.

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);
    }
}

 

이 간단한 코드는 다형성(polymorphic)을 가지며, 이는 구현에 상관없이 모든 Collection에서 작동한다는 것을 의미합니다. 이 예제는 Java Collections Framework를 사용하여 다형적인 알고리즘을 얼마나 쉽게 작성할 수 있는지를 보여줍니다.

 

Collection Interface Bulk Operations

Bulk operations는 전체 Collection에 대한 작업을 수행합니다. 이러한 간편한 작업들을 기본 작업을 사용하여 구현할 수 있지만, 대부분의 경우 이러한 구현은 덜 효율적일 것입니다. 다음은 bulk operations입니다:

  • containsAll - 지정된 Collection의 모든 엘리먼트가 타겟 Collection에 포함되어 있으면 true를 리턴합니다.
  • addAll - 지정된 Collection의 모든 엘리먼트를 타겟 Collection에 추가합니다
  • removeAll - 지정된 Collection에 포함된 모든 엘리먼트를 타겟 Collection에서 제거합니다
  • retainAll - 지정된 Collection에도 포함되지 않은 모든 엘리먼트를 타겟 Collection에서 제거합니다. 즉, 지정된 Collection에도 포함된 엘리먼트만 타겟 Collection에 유지합니다.
  • clear - 지정된 Collection의 모든 엘리먼트를 제거합니다

addAll, removeAll, 그리고 retainAll 메서드는 작업을 실행하는 과정에서 대상 Collection이 수정된 경우 true를 반환합니다.

bulk operations의 강력함을 보여주는 간단한 예로, Collection c에서 지정된 엘리먼트 e의 모든 인스턴스를 제거하는 다음 관용 코드를 고려하십시오.

c.removeAll(Collections.singleton(e));

 

좀 더 구체적으로, Collection에서 모든 null 엘리먼트를 제거하고 싶다고 가정해봅시다.

c.removeAll(Collections.singleton(null));


이 관용구는 지정된 요소만 포함하는 불변 Set을 반환하는 static factory method인 Collections.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); // []
    }
}

 

Collection Interface Array Operations

toArray 메서드는 입력 시, 배열이 필요한 이전 API 간의 브릿지 역할을 합니다. 배열 작업을 통해 컬렉션의 내용을 배열로 변환할 수 있습니다. 아규먼트가 없는 단순한 형태[c.toArray()]는 Object 타입의 새로운 배열을 생성합니다. 더 복잡한 형태는 호출자가 배열을 제공하거나 출력 배열의 런타임 타입을 선택할 수 있게 합니다.

예를 들어, c가 Collection이라고 가정해 봅시다. 다음 코드 조각은 c의 내용을 엘리먼트의 개수와 동일한 길이의 새로 할당된 Object 배열에 덤프합니다:

Object[] a = c.toArray();

 

c가 문자열만 포함하고 있다고 가정해 봅시다(아마도 c가 Collection<String> 타입이기 때문일 것입니다). 다음 코드 조각은 c의 내용을 c의 엘리먼트 개수와 동일한 길이의 새로 할당된 String 배열에 덤프합니다.

String[] a = c.toArray(new String[0]);