Java Stream

박은빈·2023년 5월 17일
0

자바

목록 보기
22/25
post-custom-banner

스트림(Stream)이란

  • 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것 (컬렉션, 배열 등)

기존의 방식에서는 각 데이터소스별로 다루기위한 메서드나 방법이 전부 달랐기때문에 활용하기 어려웠다
하지만 java8에서 Stream이 등장하면서 모든 데이터소스를 다루는 방법이 통일되었다

Stream이라는 이름에서부터 나왔듯이 각 데이터 소스들이 물길처럼 연속적인 흐름이 이어진다.
이 흐름을 따라가며 작성하면 결과를 얻을 수 있다

Stream 특징

  • 데이터 소스로부터 데이터를 읽기만할 뿐 변경하지 않는다.
List<Integer> list = Arrays.asList(5,2,1,4,3);

List<Integer> sortedList = list.stream().sorted().collect(Collectors.toList());

System.out.println(list);
System.out.println(sortedList);

//list컬렉션을 변경하는게 아닌 데이터를 읽어서 새로운 list를 만들어 내었다
  • Iterator처럼 일회용이다 (필요할경우 다시 스트림을 생성해야 한다)
  • 최종 연산 전까지 중간연산이 수행되지 않는다. 지연된 연산 Lazy Evaluation
IntStream intStream = new Random().ints(1,46); //1~45범위의 무한 스트림

intStream.distinct().limit(6).sorted()				//중간 연산
		.forEach(i -> System.out.print(i + ", "));	//최종 연산
        
/*
위의 코드만 보면 말이 안된다
무한으로 생성되는 스트림을 중복제거(distinct())를 하고 6개만 제한(limit(6))하면서
오름차순 정렬 sorted())을 하는것은 불가능하다.
하지만 stream의 Lazy Evaluation을 통해 어떤 연산이 필요한지 미리 파악을한다
이를통해 limit이 있다는 것을 파악하고 제한을 해둔다음 중복제거를 한다음 정렬한다
*/
  • 스트림은 작업을 내부 반복으로 처리한다
    for와 같은 반복문을 forEach와 같은 내부 반복문으로 처리한다
  • 작업을 병렬으로 처리가 가능하다 (멀티 스레드)
Stream<String> strStream = Stream.of("dd","aaa","CC","cc","b");

int sum = strStream.parallel() //병렬 스트림으로 전환
					.mapToInt(s -> s.length())
                    .sum();

/*
기본적으로 자바는 하나의 스레드로만 작업을 처리한다.
이 방식은 빅데이터를 처리하기에는 느리기때문에 멀티 스레드를 활용해서 처리하면 효율이 높아진다
그리고 stream은 `parallel()`을 이용해 지원한다
*/
  • 기본형 스트림 - IntStream, LongStream, DoubleStream 등
    - 오토박싱&언박싱의 비효율이 제거된다 (Stream<Integer> 대신 IntStream사용)
    - 숫자와 관련된 유용한 메서드를 Stream<T>보다 더 많이 제공한다 (sum, max, min등)

Stream의 구조

컬렉션(list, set, map), 배열 등 -> stream생성 -> 중간 연산 0~n번 -> 최종 연산 0~1번 -> 결과

사용방법

Stream 생성

//컬렉션
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream();

//배열
String[] arr = {"a","b","c"};
Stream<String> strStream = Stream.of(arr);

//iterator(반복자)
Stream<Integer> evenStream = Stream.iterate(0, n->n+2); //0,2,4,6...

//람다식
Stream<Double> randomStream = Stream.generate(Math::random);

중간 연산

  • filter
  • map, mapToInt, mapToLong, mapToDouble
  • flatmap
  • limit
  • skip
  • sorted
  • distinct
  • peek
  • boxed
  • parallel

filter

이름에서부터 알 수 있듯이 스트림 내 요소들을 조건에 맞게 필터링 할 수 있다.
내부 메서드로 Prediacte<T>의 test를 사용하는데 이는 조건에따라 boolean으로 반환하는 메서드이다. 해당 test메서드를 override하여 사용
해도 되지만 람다식으로 하는게 편하다

//Predicate 인터페이스
public interface Predicate<T> {
  boolean test(T t);
 ...
}
List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream();
  
intStream.filter(n -> n<4);
//4이상의 수는 필터에서 걸러버린다

map

스트림 내의 요소를 특정 형태로 변환할 수 있다
내부 인터페이스로는 Function<T,R>을 사용한다

intStream.map(n -> n+1);
//각 요소에 1을 더해주었다
  • mapToInt(), mapToLong(), mapToDouble()
    기존 Stream<T>에서 기본형 스트림인 IntStream, LongStream, DoubleStream으로 변환시켜준다.
    이 기본형 스트림으로 바꾸는 이유는 오토박싱이 필요없고 기본형 스트림에만 존재하는 연산이 있기때문이다
stream.mapToInt(n -> n);
stream.mapToLong(n -> n);
stream.mapToDouble(n -> n);
stream.mapToInt(s -> Integer.parseInt(s));

flatmap

역시 이름에나와있듯이 'flat'평평하게 만들어 준다는 스트림이다
2차원배열과같은 고차원 컬렉션과 배열을 평평하게 1차원으로 바꿔주는 메서드이다.

String[][] arr = {{"apple", "banana"}, {"kim", "park"}};
Stream<String[]> arrStream = Stream.of(arr);

List<String> flatMapList = arrStream
						.flatMap(Arrays::stream)
            			.collect(Collectors.toList());
            
System.out.println(flatMapList);
//[apple, banana, kim, park]

limit

스트림 내의 요소 개수를 제한할 수 있다

Stream<Integer> evenStream = Stream.iterate(0, n->n+2);

List<Integer> list = evenStream
					.limit(5)
					.collect(Collectors.toList());
                    
System.out.println(list);
//[0, 2, 4, 6, 8]

0부터 시작해 무한히 짝수를 나타내는 스트림을 limit을 이용해 5까지만 나오게 만들었다

skip

스트림 내의 첫번째 요소부터 전달된 요소까지 건너뛸 수 있다

String[] arr = {"a","b","c"};
Stream<String> strStream = Stream.of(arr);

List<Integer> list = strStream
					.skip(2)
                    .collect(Collectors.toList());
                    
System.out.println(list);
//c

sorted

스트림 내 요소를 정렬할 수 있다

List<Integer> list = Arrays.asList(2,1,5,3,4);
Stream<Integer> intStream = list.stream();

intStream.sorted(); //1,2,3,4,5

intStream.sorted(Comparator.reverseOrder()); //5,4,3,2,1

distinct

스트림 내의 요소의 중복을 제거한다.
기본형 타입일경우 값으로 비교하지만 객체일경우 Object.equals로 비교한다

List<Integer> list = Arrays.asList(1,2,2,3,3);
Stream<Integer> intStream = list.stream();

intStream.distinct() //1,2,3

peek

스트림 내의 각각의 요소를 대상으로 특정 연산을 수행하게 된다
for문과 비슷하게 동작하지만 중요한 점으로는 중간연산이기때문에 반환값이 stream이다. 이로인해 결과를 나타내는 연산이 아닌 중간에 확인용 연산으로 적합하다

List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream();
  
intStream
	.peek(System.out::println)
	.forEach(System.out::println);
//이를 통해 최종 연산은 불가능하다
//최종연산이 없을경우 peek또한 동작하지 않는다

boxed

IntStream, LongStream, DoubleStream과 같은 기본형 스트림을 일반 스트림으로 변환할 수 있다

IntStream intStream = IntStream.range(0,5);

Stream<Integer> boxedStream = intStream.boxed();

parallel

기본적인 for나 while과 같은 반복문은 하나의 스레드로만 동작한다.
하지만 빅데이터를 분석할때나 크기가 큰 작업을 수행할 때 하나의 스레드로만 작업한다면 비효율적일뿐만 아니라 속도도 느리다.
이를 위해 스트림에서는 parallel이라는 메서드를 이용하면 멀티 스레드로 연산을 수행하게 만들어준다

intStream
	.parallel()
    .filter(n -> n<4)
    .map(n -> n+1)
    .distinct()
    .forEach(System.out::println);

최종 연산

  • forEach
  • reduce
  • min, max
  • sum, average
  • count
  • collect
  • findFirst, findAny
  • anyMatch, allMatch, noneMatch

forEach

스트림을 순회하며 지정된 연산이나 조건을 실행한다
for문과 비슷한 역할이라고 보면 된다

List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream();

intStream.forEach(System.out::println);

하지만 만약 병렬 스트림 parallel을 사용했다면 forEach의 순서를 보장할 수 없기때문에 이때는 forEachOrdered를 사용한다

intStream.parallel().forEachOrdered(System.out::println);

reduce

모든 스트림 요소를 소모하여 결과를 구할 수 있다.

Optional<Integer> result = intStream.reduce((a, b) -> a+b); //6

int result2 = intStream.reduce(1, (a, b) -> a+b); //7

인자가 하나일경우에는 첫번째 원소와 두번째 원소를 가지고 연산을 수행하게 되고, 인자가 2개일 경우 첫번째 인자에는 초기값이, 두번째 인자부터 원소를 가지고 연산을 하게된다

min, max

해당 연산을 이용하게되면 쉽게 최소값, 최대값을 구할 수 있다

List<Integer> list = Arrays.asList(1,2,3,4,5);
List<String> strList = Arrays.asList("apple","banana","watermelon");

String strMin = strList.stream().min(Comparator.comparing(String::length)).get();
//apple
//객체형식일경우 Comparator.comparing을 이용해 비교를 하여 최소값, 최대값을 구한다

String strMax = strList.stream().max(Comparator.comparing(String::length)).get();

int min = list.stream().mapToInt(n -> n).min().getAsInt();
//1
//기본형일경우 기본형 스트림으로 변환 후 연산을 수행한다

int max = list.stream().mapToInt(n -> n).max().getAsInt();

sum, average

해당 연산은 기본형 스트림에서만 가능하다
애초에 총합과 평균은 숫자에서만 가능하기때문이다

int[] arr = {1,2,3,4,5};

int sum = IntStream.of(arr).sum(); //15
double average = IntStream.of(arr).average().getAsDouble()); //3.0

지금 연산을 할때 어떤 연산은 바로 나오지만 어떤 연산은 Optional로 감싸져서 나온다.
이 이유는 sum같은경우 다 더하는거라 값이 정해져있기때문에 Optional이 상관없지만 average같은경우 값이 어떻게 나올지 모르기때문에 Optional로 감싸진 것이다

count

스트림의 요소들의 개수를 구해주는 메서드이다
배열의 length 컬렉션의 size와 같다고 보면 된다

int count = IntStream.of(arr).count(); //5
int strCount = strList.stream().count(); //3

collect

스트림의 요소들을 원하는 자료형으로 변환해 준다

  • Collectors.toSet() : set자료형으로 변환
  • Collectors.toList() : list자료형으로 변환
  • Collectors.toCollection() : collection자료형으로 변환
  • Collectors.joining() : 요소들을 합쳐서 리턴한다
String[] fruits = {"banana", "apple", "mango", "kiwi", "peach", "cherry", "lemon"};

Set<String> set = Stream.of(fruits).collect(Collectors.toSet());
//[banana, apple, cherry, kiwi, lemon, peach, mango]

List<String> lists = Stream.of(fruits).collect(Collectors.toList());
//[banana, apple, mango, kiwi, peach, cherry, lemon]

HashSet<String> hashSet = Stream.of(fruits).collect(Collectors.toCollection(HashSet::new));
//[banana, apple, cherry, kiwi, lemon, peach, mango]

String join = Stream.of(fruits).collect(Collectors.joining());
//bananaapplemangokiwipeachcherrylemon
String join2 = Stream.of(fruits).collect(Collectors.joining(", "));
//banana, apple, mango, kiwi, peach, cherry, lemon

findFirst, findAny

두 메서드 모두 스트림에서 첫번째 요소를 참조하는 Optional객체를 반환한다
findFirst는 가장 앞에 있는 요소를 리턴하지만
findAny는 Stream을 처리할때 가장 먼저 찾은 요소를 리턴한다
그렇기때문에 병렬 스트림에서 차이가 있다

int[] arr = {1,2,3,4,5};

OptionalInt findFirst = IntStream.of(find).findFirst(); //1
OptionalInt findAny = IntStream.of(find).parallel().findAny(); //3

anyMatch, allMatch, noneMatch

조건에 따라 모두 boolean으로 리턴이된다

  • anyMatch : 조건에 부합하는 요소가 하나라도있으면 true
  • allMatch : 조건에 모두 부합할경우 true
  • noneMatch : 모두 조건에 만족하지 않을경우 true
int[] match = {1,2,3,4,5};

IntStream.of(match).anyMatch(n -> n==5); // true
IntStream.of(match).allMatch(n -> n<=0); // false
IntStream.of(match).noneMatch(n -> n==0); // true
profile
안녕하세요
post-custom-banner

0개의 댓글