Stream API
Stream API는 람다식과 함께 Java 8버전부터 지원하는 기능으로 객체지향 언어였던 자바에서도 함수형 프로그래밍을 가능하게 해준다.
여기서 말하는 Stream은 IO의 Stream과는 다른 개념이다.
Stream이란 Array, List, Map등의 자료구조를 처리하는 연속적인 데이터의 흐름이다.
Stream은 다음과 같은 특징을 가진다.
- 원본의 데이터를 변경하지 않는다.
- 일회용이다.
- 내부 반복으로 데이터를 처리한다.
Stream API를 사용하면 Array, List, Map등의 자료구조를 for문으로 다둘 때보다 매우 간결한 코드로 다룰 수 있게 된다.
List의 각요소를 2배로 만드는 상황을 보자.
1. 전통적인 for문을 사용하는 경우
import java.util.ArrayList;
import java.util.List;
public class ForLoopExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
numbers.add(i);
}
List<Integer> doubledNumbers = new ArrayList<>();
for (int number : numbers) {
doubledNumbers.add(number * 2);
}
System.out.println(doubledNumbers);
}
}
2. stream api를 사용하는 경우
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
numbers.add(i);
}
List<Integer> doubledNumbers = numbers.stream()
.map(number -> number * 2)
.collect(Collectors.toList())
.forEach(System.out::println);
}
}
Stream API 메서드
Stream API는 크게 3가지로 나눌 수 있다.
1. 생성
2. 중간 연산
3. 최종 연산
1. 생성
생성은 말그대로 Stream을 생성하는 단계이다.
Stream을 생성하는 Case는 크게 3가지이다.
1. 직접생성하기
Stream.of(T... values)메소드를 통해 stream을 생성할 수 있다.
// 여러 값으로부터 스트림 생성
Stream<String> stream = Stream.of("apple", "banana", "cherry", "date");
2. 배열을 통해 생성하기
Arrays.stream(T[] array)메소드를 통해 stream을 생성할 수 있다.
String[] fruits = {"apple", "banana", "cherry", "date"};
Stream<String> stream = Arrays.stream(fruits);
3. 컬렉션을 통해 생성하기
Collection.stream()메소드를 통해 stream을 생성할 수 있다.
List<String> fruitsList = Arrays.asList("apple", "banana", "cherry", "date");
Stream<String> stream = fruitList.stream();
2. 중간 연산
중간 연산 단계는 생성된 스트림을 원하는 형식으로 가공하는 형태이다.
1. 필터링하기
스트림에서 특정 조건을 통해 원하는 데이터를 솎아내는 과정이다.
filter(Predicate<T>)메소드를 사용한다.
예를 들어 특정 정수형리스트에서 짝수만 골라내려고 할 때, filter()메소드를 다음과 같이 활용할 수 있다.
import java.util.Arrays;
import java.util.List;
public class NumberFilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 필터링: 짝수만 선택
numbers.stream()
.filter(number -> number % 2 == 0)
.forEach(System.out::println);
}
}
2. 데이터 변환하기
스트림의 각 데이터를 특정 방식으로 변환해내는 과정이다.
map(Function<T, R> mapper) 메소드를 사용한다.
예를 들어, 각 문자열을 대문자로 변환하려고 할 때, map()메소드를 다음과 같이 활용할 수 있다.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MapExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
// 각 문자열을 대문자로 변환
List<String> upperCaseWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
upperCaseWords.forEach(System.out::println);
}
}
3. 데이터 정렬하기
스트림의 데이터를 정렬한다.
sorted(Comparator<T>) 메소드를 사용한다.
예를 들어, 각 문자열을 알파벳순으로 정렬할 때, sorted()메소드를 사용하면 다음과 같이 활용할 수 있다.
import java.util.Arrays;
import java.util.List;
public class SortedExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("banana", "apple", "cherry", "date");
// 문자열을 알파벳 순서로 정렬
words.stream()
.sorted()
.forEach(System.out::println);
}
}
4. 중복 제거하기
스트림의 데이터 중 중복된 값을 제거한다.
distinct() 메소드를 사용한다.
리스트에서 중복 값을 제거하려고 할 때, distinct()메소드를 사용하면 다음과 같이 활용할 수 있다.
import java.util.Arrays;
import java.util.List;
public class DistinctExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4);
// 중복을 제거하여 출력
numbers.stream()
.distinct()
.forEach(System.out::println);
}
}
5. 원하는 갯수만큼 데이터 뽑기
스트림의 데이터중 앞에 데이터 n개 만큼 뽑는다.
limit(long)메소드를 사용한다.
리스트의 첫 요소 3개만 반환하는 예시는 다음과 같다.
import java.util.Arrays;
import java.util.List;
public class LimitExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "fig");
// 처음 3개의 요소만 선택하여 출력
words.stream()
.limit(3)
.forEach(System.out::println);
}
}
6. 요소 건너뛰기
스트림의 데이터 중 처음 n개의 데이터를 건너뛰고 뽑는다.
skip(long) 메소드를 사용한다.
처음 2개의 요소를 건너뛰고 반환하는 예시는 다음과 같다.
import java.util.Arrays;
import java.util.List;
public class SkipExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "fig");
// 처음 2개의 요소를 건너뛰고 나머지 요소를 출력
words.stream()
.skip(2)
.forEach(System.out::println);
}
}
3. 최종 연산
중간 연산을 통해 가공된 스트림을 최종적으로 사용한다.
1. 최댓값/최솟값/합/평균/갯수
스트림에서의 최댓값, 최솟값, 합, 평균, 갯수를 반환한다.
- 최댓값 - max()
- 최솟값 - min()
- 합 - sum() (int 타입)
- 평균 - avg() (double 타입)
- 갯수 - count() (long 타입)
최댓값을 max()를 통해 반환하는 예시
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class MaxExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3, 5, 2, 10, 6);
// 리스트에서 최대값 찾기
Optional<Integer> maxNumber = numbers.stream()
.max(Integer::compareTo);
maxNumber.ifPresent(System.out::println);
}
}
2. 데이터 수집
스트림 요소를 컬렉션으로 수집하고 싶을 때 사용한다.
collect(Collector)메소드를 사용한다.
매개변수인 Collector 인터페이스를 통해 처리하는데 자주 사용하는 작업은 Colletors의 정적메서드를 통해 사용할 수 있다.
지금부터 제공되는 Collectors 정적메서드에 대해 몇가지 알아보자.
1. toList(): 리스트로 수집
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ToListExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
List<String> result = words.stream()
.filter(word -> word.length() > 5)
.collect(Collectors.toList());
result.forEach(System.out::println);
}
}
2. toMap(): Map으로 수집. 매개변수에 keyMapper, valueMapper를 넣어 key, value값을 설정한다.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ToMapExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Tom", 35),
new Person("Alice", 28)
);
Map<String, Integer> result = people.stream()
.collect(Collectors.toMap(Person::getName, Person::getAge));
result.forEach((name, age) -> System.out.println(name + ": " + age));
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
3. joining(String): 스트림의 요소를 문자열로 연결. 매개변수를 기준으로 연결한다.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class JoiningExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
String result = words.stream()
.collect(Collectors.joining(", "));
System.out.println(result);
}
}
4. groupingBy(): 데이터를 특정 기준으로 그룹화 해 그 기준을 key로, 그 기준에 해당하는 값들을 리스트로 반환한다.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class GroupingByExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Tom", 35),
new Person("Alice", 28),
new Person("Bob", 25)
);
Map<Integer, List<Person>> result = people.stream()
.collect(Collectors.groupingBy(Person::getAge));
result.forEach((age, group) -> {
System.out.println("Age " + age + ":");
group.forEach(person -> System.out.println(" - " + person.getName()));
});
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
5. partioningBy(): 스트림의 요소를 특정 기준으로 두가지로 분리한다. key값이 boolean(기준에 부합하면 true, 그렇지 않으면 false) value값이 각 기준에 부합하는 요소들의 리스트가 된다.
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class PartitioningByExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Tom", 35),
new Person("Alice", 28),
new Person("Bob", 25)
);
Map<Boolean, List<Person>> result = people.stream()
.collect(Collectors.partitioningBy(person -> person.getAge() >= 30));
System.out.println("30세 이상:");
result.get(true).forEach(person -> System.out.println(" - " + person.getName()));
System.out.println("30세 미만:");
result.get(false).forEach(person -> System.out.println(" - " + person.getName()));
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
3. 조건 검사
stream의 요소들이 특정 조건을 만족하는지 검사하고 싶을 때 사용
총 세가지가 존재한다.
- anyMatch(): 하나라도 조건에 만족하는지
- allMatch(): 모두 조건에 만족하는지
- noneMatch(): 모두 조건에 만족하지 않는지
1. anyMatch()
리스트에 짝수가 하나라도 있는가
import java.util.Arrays;
import java.util.List;
public class AnyMatchExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 8);
boolean hasEven = numbers.stream()
.anyMatch(number -> number % 2 == 0);
System.out.println("리스트에 짝수가 하나라도 있는가? " + hasEven);
}
}
2. allMatch()
리스트의 모든 요소가 양수인지
import java.util.Arrays;
import java.util.List;
public class AllMatchExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 9);
boolean allPositive = numbers.stream()
.allMatch(number -> number > 0);
System.out.println("리스트의 모든 요소가 양수인가? " + allPositive);
}
}
3. noneMatch()
리스트의 모든 요소가 음수가 아닌지
import java.util.Arrays;
import java.util.List;
public class NoneMatchExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 9);
boolean noneNegative = numbers.stream()
.noneMatch(number -> number < 0);
System.out.println("리스트의 모든 요소가 음수가 아닌가? " + noneNegative);
}
}
4. 각 요소에 대한 동작 정의
스트림의 각 요소에 대해 주어진 동작을 수행하는데 사용된다.
forEach(Consumer<T>)메소드를 사용한다.
Consumer는 함수형 인터페이스로, 리턴값을 반환하지 않는 함수만 작성이 가능하다.
리스트에 대해 각 요소를 제곱하고 출력한다면
import java.util.Arrays;
import java.util.List;
public class ForEachSquareExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 각 숫자의 제곱을 계산하고 출력
numbers.stream()
.forEach(number -> {
int square = number * number;
System.out.println("Square of " + number + " is " + square);
});
}
}
참고:
[Java] Stream API에 대한 이해 - (1/5)
1. Stream API에 대한 이해 [ Stream API에 대한 소개 ] Java는 객체지향 언어이기 때문에 기본적으로 함수형 프로그래밍이 불가능하다. (함수형 프로그래밍에 대해 이해가 부족하다면 이 글을 참고하길
mangkyu.tistory.com
[Java] Stream API의 활용 및 사용법 - 기초 (3/5)
1. Stream 생성하기 앞서 설명한대로 Stream API를 사용하기 위해서는 먼저 Stream을 생성해주어야 한다. 타입에 따라 Stream을 생성하는 방법이 다른데, 여기서는 Collection과 Array에 대해서 Stream을 생성하
mangkyu.tistory.com
'Java' 카테고리의 다른 글
[Java] 스레드(Thread) (0) | 2024.07.27 |
---|---|
[Java] 해시기반 컬렉션의 동등성 비교(hashCode() 메서드의 필요성) (0) | 2024.07.25 |
[Java] 람다식의 이해 (0) | 2024.07.25 |
[Java] HashMap - value값을 기준으로 정렬 (0) | 2024.07.23 |
[Java] HashSet - 객체의 속성으로 중복 체크 (0) | 2024.06.21 |