Java Collection Framework, JCF
Java 언어에서 사용되는 데이터 구조를 관리하고 처리하기 위한 라이브러리 집합
Collection(List, Set, Queue)과 Map으로 구성 되어 있다.
- List:
- 순서가 있는 데이터를 저장
- 데이터의 중복을 허용
- ArrayList, LinkedList 등
- Set:
- 순서가 없는 데이터를 저장
- 데이터의 중복을 허용하지 않음
- HashSet, TreeSet 등
- Queue:
- 데이터를 순서대로 저장하고, 데이터를 추가할 때는 뒤쪽에, 삭제할 때는 앞쪽에서부터 처리
- LinkedList, PriorityQueue 등
- Map:
- Key-Value 쌍으로 데이터를 저장
- Key는 중복될 수 없다
- HashMap, TreeMap 등
Q. 왜 자바에서 배열보다 Collection 사용을 지향할까?
Java Collection을 사용하면 데이터를 조작할 때 다양한 api를 사용할 수 있다.
✨ Stream API
Java 8부터 추가된 기능
컬렉션(Collection)이나 배열(Array) 등의 요소(Element)들을 처리하며,
람다식(Lambda Expression)을 이용한 함수형 프로그래밍(Functional Programming)을 지원한다.
특징
- 원본 데이터를 변경하지 않는다.
- 외부 반복을 통해 작업하는 컬렉션과 달리 내부 반복(internal iteration)을 통해 작업을 수행한다.
- 재사용이 가능한 컬렉션과 달리 단 한번만 사용 가능
- 필터(filter)-맵(map) 기반의 API를 사용하여 지연(lazy) 연산을 통해 성능을 최적화 한다.
- parallelStream() 메서드를 통한 손쉬운 병렬 처리를 지원한다.
동작 흐름
- 스트림 생성(Stream Creation):
- 컬렉션(Collection), 배열(Array), 파일 등의 데이터 소스로부터 스트림(Stream)을 생성
- 중간 연산(Intermediate Operation):
- 생성된 스트림에 대해 여러 가지 중간 연산을 수행
- 이러한 중간 연산들은 필터링(Filtering), 변환(Mapping), 정렬(Sorting), 그룹핑(Grouping) 등 다양한 작업을 수행할 수 있다.
- 최종 연산(Terminal Operation):
- 중간 연산들을 거쳐 처리된 스트림을 최종적으로 처리
- 이러한 최종 연산들은 요소의 개수를 세는 count(), 요소들을 하나로 합치는 reduce(), 요소들을 출력하는 forEach() 등이 있다.
✍ 스트림 생성
Stream API는 다양한 데이터 소스에서 생성할 수 있다. 그 중 가장 일반적인 방법은 컬렉션(Collection)에서 스트림을 생성하는 것이다.
1) 컬렉션(Collection) - List 객체
List<String> list = Arrays.asList("Java", "Python", "C++", "JavaScript");
// List 객체에서 스트림 생성하기
Stream<String> stream = list.stream();
2) 배열(Array)
int[] arr = {1, 2, 3, 4, 5};
// int 배열에서 스트림 생성하기
IntStream stream = Arrays.stream(arr);
3) 파일(File)
텍스트 파일에서 한 줄씩 읽어와서 스트림을 생성하는 방법
Path path = Paths.get("file.txt");
// 파일에서 스트림 생성하기
Stream<String> stream = Files.lines(path);
위의 코드에서는 Files 클래스의 lines() 메소드를 호출하여 파일에서 스트림을 생성한다.
4) 지정된 범위의 연속된 정수
- IntStream.range() 메서드 : 시작값(start)을 포함하고 종료값(end)을 포함하지 않는 범위의 정수 스트림을 생성
IntStream.range(1, 5)
- IntStream.rangeClosed() 메서드: 시작값(start)과 종료값(end)을 모두 포함하는 범위의 정수 스트림을 생성
IntStream.rangeClosed(1, 4)
5) 람다 표현식
람다 표현식을 이용하여 스트림을 생성하는 방법은 다양하다.
- Stream.generate() 메서드 : generate() 메서드는 Supplier 함수형 인터페이스를 인자로 받아서, Supplier가 반환하는 값을 무한히 스트림으로 생성한다.
- 예를 들어, 1부터 시작하는 무한한 정수 스트림을 생성하는 코드는 다음과 같다.
Stream<Integer> infiniteStream = Stream.generate(() -> 1);
- Stream.iterate() 메서드를 : seed 값을 시작으로, UnaryOperator 함수형 인터페이스를 인자로 받아서, 해당 함수를 반복적으로 적용하여 스트림을 생성한다.
- 예를 들어, 1부터 시작하여 이전 값에 2를 더한 값으로 이루어진 스트림을 생성하는 코드는 다음과 같다.
Stream<Integer> stream = Stream.iterate(1, n -> n + 2);
- Stream.iterate() 메서드를 이용하여 무한한 스트림을 생성할 때는, 항상 종료 조건을 명시해주어야 한다.
- 그렇지 않으면 무한한 반복으로 인해 프로그램이 무한히 실행되거나, OutOfMemoryError가 발생할 수 있다.
- limit() 메서드 : 종료 조건을 명시하는 방법, 스트림의 크기를 제한하는 메서드로, 인자로 주어진 숫자만큼의 요소를 가진 스트림을 반환
- 예를 들어, 이전의 홀수 스트림 예제에서 처음 10개의 요소를 가진 스트림을 생성하는 코드는 다음과 같습니다.
Stream<Integer> stream = Stream.iterate(1, n -> n + 2).limit(10);
이외에도 Stream.of() 메소드를 사용하여 요소들을 직접 전달하거나 empty() 메소드를 통해 빈 스트림을 생성하는 방법이 있다.
🧮 중간 연산
요소(Element)를 가져와 처리한 후, 새로운 스트림을 반환하는 연산
중간 연산은 여러 개의 연속된 메소드 호출을 통해 적용할 수 있으며, 각 중간 연산은 다음 중간 연산이나 최종 연산(terminal operation)과 연결된다.
Stream 형태로 결과를 반환하기 때문에 연속적으로 연결하여 사용할 수 있다.
- 스트림의 중간 연산은 지연 평가(lazy evaluation)를 지원한다.
- 지연 평가는 요청이 있을 때만 연산을 수행하므로, 필요하지 않은 연산을 수행하지 않고 스트림 처리 속도를 높일 수 있다
- 스트림의 중간 연산은 스트림을 변환하지 않고 요소를 처리하기 때문에, 스트림의 처리 속도를 높이는 중요한 역할을 한다. 이를 통해 코드의 가독성을 높이고, 개발자가 스트림을 보다 쉽게 사용할 수 있습니다.
1) filter(Predicate<T> predicate)
주어진 조건(predicate)에 해당하는 요소만 남기고 나머지 요소는 제외한다.
ex) 문자열 스트림에서 길이가 5 이상인 요소만 남기는 경우
Stream<String> stream = Stream.of("Java", "Python", "C++", "JavaScript");
// 길이가 5 이상인 요소만 남기기
Stream<String> filteredStream = stream.filter(s -> s.length() >= 5);
2) map(Function<T, R> mapper)
각 요소를 주어진 함수에 인수로 전달하여, 그 반환값으로 이루어진 새로운 스트림을 반환한다.
ex) 문자열 스트림에서 각 문자열을 대문자로 변환하는 경우
Stream<String> stream = Stream.of("Java", "Python", "C++", "JavaScript");
// 각 문자열을 대문자로 변환하기
Stream<String> mappedStream = stream.map(s -> s.toUpperCase());
3) sorted(Comparator<T>comparator) :
비교자(comparator)를 이용하여 요소를 정렬, 없을 경우 오름차순
ex) 숫자 스트림을 오름차순으로 정렬하는 경우
IntStream stream = IntStream.of(3, 1, 4, 1, 5, 9, 2, 6, 5);
// 숫자를 오름차순으로 정렬하기
IntStream sortedStream = stream.sorted();
4)distinct() :
중복된 요소를 제거
ex) 숫자 스트림에서 중복된 숫자를 제거하는 경우
IntStream stream = IntStream.of(3, 1, 4, 1, 5, 9, 2, 6, 5);
// 중복된 숫자 제거하기
IntStream distinctStream = stream.distinct();
filter()나 distinct() 연산은 불필요한 요소를 제거하므로 스트림 처리 속도를 높일 수 있다.
map() 연산은 요소를 변환하여 스트림의 크기를 줄일 수 있으므로, 메모리 사용량을 줄일 수 있다.
그 외에도 flatMap, limit, skip, peek 등이 있다.
🧾 최종 연산
트림의 처리 결과를 반환하거나, 스트림의 요소를 처리하면서 최종 결과를 반환
최종 연산은 스트림을 소모하므로, 최종 연산 이후에는 스트림을 재사용할 수 없다.
Stream 인터페이스에 정의되어 있다.
- forEach() : 스트림의 모든 요소에 대해 주어진 동작을 수행
- count() : 스트림의 요소 개수를 반환
- reduce() : 스트림의 요소를 이용해 값을 계산하고, 결과를 반환
- collect() : 스트림의 요소를 수집해서 새로운 컬렉션을 생성하거나, 요소를 그룹화하거나, 통계 정보를 계산하는 등의 작업을 수행
- min() / max() : 스트림에서 최솟값 혹은 최댓값을 반환
- anyMatch() / allMatch() / noneMatch() : 스트림의 요소 중 하나라도 조건을 만족하는지, 모두 조건을 만족하는지, 하나도 조건을 만족하지 않는지 확인
- findFirst() / findAny() : 스트림의 첫 번째 요소를 반환
Stream API를 이용해 List에 저장된 문자열 중 길이가 5 이상인 문자열의 개수를 구하는 예시
List<String> words = Arrays.asList("apple", "banana", "orange", "watermelon", "grape");
long count = words.stream()
.filter(word -> word.length() >= 5)
.count();
System.out.println(count); // 출력 결과: 3
위 코드에서는 filter() 메소드로 길이가 5 이상인 문자열만 선택한 후, count() 메소드로 선택된 문자열의 개수를 계산한다.
이때, count() 메소드는 최종 연산이므로, 결과값을 반환하면서 스트림이 소모된다.
📖 reference
'Programming Language 👅' 카테고리의 다른 글
[Java] 자바는 Call by Value! (0) | 2023.05.19 |
---|---|
[Java] 제네릭(Generic) (0) | 2023.04.25 |
[Java] 자바의 컴파일 과정 (0) | 2023.03.27 |
[Java] 상속과 오버라이딩(inheritance & overriding) (0) | 2023.02.28 |
댓글