[Java] 스트림 생성 및 연산 종류

YoungMinKim·2020년 12월 15일
0
post-thumbnail

Goal

이번에는 스트림의 생성과 연결, 중간 연산, 최종 연산에 대해 정리 해보자.

01 스트림의 생성과 연결

기존에 스트림을 생성하던 두 가지 방식

  • 배열 기반 스트림 생성.
  • 컬렉션 인스턴스 기반 스트림 생성.

위와 같은 스트림 생성 방법은 기존에 존재하는 데이터를 기반으로 하여 스트림을 생성한다.

이전에 스트림을 생성하던 방식

// 배열 기반
String [] arr = {"Box", "Toy", "Rogo"};
Arrays.stream(arr)
	.filter(a -> a.length > 2)
	.forEach(a -> System.out.println(a + "\t"));
System.out.println();

// 컬렉션 인스턴스 기반
List<String> arr2 = Arrays.asList("Box", "Boy", "Gir");
arr2.stream()
	.filter(a -> a.length > 2)
	.forEach(a -> System.out.println(a + "\t"));
System.out.println();

01-1 다른 방식으로 스트림 생성

기존에 존재하는 데이터를 대상으로 스트림을 생성하는 것이 아닌, of() 메서드를 통해 새로운 스트림을 만들어보자.

public static void main(String[] args) {
	Stream.of(11, 22, 33, 44) // 네 개의 값으로 이뤄진 스트림 생성
		.forEach(n -> System.out.println(n + "\t"));
	System.out.println();

	Stream.of("So Simple") // 하나의 String 인스턴스로 이뤄진 스트림 생성
		.forEach(s -> System.out.println(s + "\t"));
	System.out.println();
	
	List<String> s1 = Arrays.asList("Toy", "Robot", "Box");
	Stream.of(s1) // 하나의 컬렉션 인스턴스로 이뤄진 스트림 생성
		.forEach(w -> System.out.println(w + "\t"));
	System.out.println();
}

기존에 존재하는 데이터를 기반으로 하여 스트림을 생성하지 않는다?

  • of : 기존에 존재하는 값이 없어도, 데이터를 of()에 넣어 초기화가 가능하다.
  • of : 인자로 전달된 대상을 가지고 스트림을 만든다.
  • of : 인자로 전달된 컬렉션 인스턴스(1개)를 대상으로 스트림을 형성 했다.

출력 결과

11   22   33   44
So Simple
[Toy, Robot, Box]

01-2 병렬 스트림으로 변경

 // 병령 스트림 메서드 예제
-----------------------------------------------------------------------
Stream<T> parallel()    // Stream<T>의 메소드
DoubleStream parallel() // DoubleStream의 메소드
IntStream paralle()     // IntStream의 메소드
LongStream paralle()    // Longstream의 메소드
-----------------------------------------------------------------------

public static void main(String[] args) {
	List<String> ls = Arrays.asList("Box", "Simple", "Complex", "Robot");
	Stream<String> ss = ls.Stream(); // 스트림 생성
	
	BinaryOperator<String> lc = (s1, s2) -> {
		if(s1.length() > s2.length())
			return s1;
		else
			return s2;
	}
	
	String str = ss.parallel()      // 병렬 스트림 생성
			.reduce("", lc); // 리덕션
	
	System.out.println(str);
} 
  • Stream<String> 인스턴스의 ss.parallel()를 호출하여 해당 스트림을 병렬 스트림으로 처리 하였다.
    1. 스트림이 병렬 스트림이냐, 아니다가 중요한 것이 아니다.
    2. 이후에 진행이 되는 중간, 최종 연산이 병렬로 처리 되는것.

출력 결과

Complex

01-3 스트림의 연결

public static void main(String[] args) {
	Stream<String> ss1 = Stream.of("Cake", "Milk");
	Stream<String> ss2 = Stream.of("Lemon", "Jelly");

	// 스트림을 하나로 묶은 후 출력(즉 연결 함수 -> concat을 통해)
	Stream.concat(ss1, ss2)
		.forEach(s -> System.out.println(s));
}
  • Stream 클래스에 존재하는 concat()를 통해 두 인자를 연결 한다.
  • 즉, 매개 변수 인자로 전달된 두 스트림 값을 → 하나의 스트림으로 생성 해준다.

02 맵핑(Mapping)에 대한 추가 정리

[Stream<T>의 map 시리즈 메소드들] 1:1 맵핑

  • <R> Stream<R> map(Function<T, R> mapper)
  • IntStream mapToInt(ToIntFunction<T> mapper)
  • LongStream mapToLong(ToLongFunction<T> mapper)
  • DoubleStream mapToDouble(ToDoubleFunction<T> mapper)
    • Int, Long, Double + Stream은 AutoBoxing, AutoUnBoxing을 피할수 있다.

[Stream<T>의 flaMap 시리즈 메소드들] 1:* 맵핑

  • <R> Stream<R> flatMap(Function<T, Stream<R>> mapper)
  • IntStream mapToInt(ToIntFunction<T> mapper)
  • LongStream mapToLong(ToLongFunction<T> mapper)
  • DoubleStream mapToDouble(ToDoubleFunction<T> mapper)
    • Int, Long, Double + Stream은 AutoBoxing, AutoUnBoxing을 피할수 있다.
  1. Stream<T>의 map, flatMap 메서드를 통해 새로운 값을 만든다 봐도 무방하다.
  2. 또한 위 메서드를 통해 불필요한 박싱(Boxing), 언박싱(UnBoxing)을 피할 수 있다.

02-1 맵핑(Mapping)에 대한 추가 정리

public static void main(String[] args) {
	Stream<String> ss1 = Stream.of("MY_AGE", "YOUR_LIFE");
	
	// 아래 람다식에서 스트림을 생성
	Stream<String> ss2 = ss1.flatMap(s -> Arrays.stream(s.split("_"))); // 중간 연산
	ss2.forEach(s -> System.out.println(s + "\t"))	
	System.out.println();
}

위 부분 설명해주실때 이해가 잘 되지 않았음 → 제대로 이해할 필요가 있음

02-2 맵핑(Mapping)에 대한 추가 정리

// 성적표 클래스 정의
class ReportCard {
	private int kor;
	private int eng;
	private int math;

	public ReportCard(int k, int e, int m) {
		kor = k;
		eng = e;
		math = m;
	}

	public int getKor() { return kor; }
	public int getEng() { return eng; }
	public int getMath() { return math; }	 
}

public static void main(String[] args) {
	// 배열에 3개의 성적표 인스턴스를 저장
	ReportCard[] cards = {
		new ReportCard(70, 80, 90),
		new ReportCard(90, 80, 70),
		new ReportCard(80, 80, 80)
	};

	// Reportcard 인스턴스로 이루어진 스트림 생성
	Stream<String> sr = Arrays.stream(cards); 
	
	// 위 배열을 통해 스트림 생성
	// 1 : 3의 맵핑을 수행 한다.
	
	// 학생들의 점수 정보로 이루어진 스트림 생성
	IntStream si = sr.flatMapToInt(r -> IntSream.of(r.getKor(), r.getEnf(), r.getMath()));

	// 평균을 구하기 위한 최종 연산 average 진행
	double avg = si.average().getAsDouble();
	System.out.println("avg. " + avg);
}
  • average() : OptionalDouble을 반환 형태로 갖는다.
  • getAsDouble() : 실제 실수형 숫자를 반환 해준다.

03 스트림의 정렬

// 정렬 메서드 구분
-----------------------------------------------------------------------
Stream<T> sorted(Comparator<? super T> comparator) // Stream<T>의 메서드
Stream<T> sorted()    // Stream<T>의 메서드
IntStream sorted()    // IntStream의 메서드
LongStream sorted()   // LongStream의 메서드
DoubleStream sorted() // DoubleStream의 메서드
-----------------------------------------------------------------------

public static void main(String[] args) {
	// 사전 편찬순으로 정렬을 수행 한다
	Steeam.of("Box", "Apple", "Robot")
		.sorted() // String 인스턴스는 Comparable<String> 인터페이스를 구현! 이를 기반으로 한 정렬 수행
		.forEach(s -> System.out.println(s + "\t"));
	System.out.println();

	Stream.of("Box", "Apple", "Rabit")
		.sorted((s1, s2) -> s1.length() - s2.length())
		.forEach(s -> System.out.println(s + "\t"));
	System.out.println();
}
  • String 클래스내부적으로 Compartor 인터페이스구현하고 있다(복습)
  • 위 같은 이유를 통해 자연스럽게 정렬을 수행 할 수 있다.

03-1 출력 결과

Apple  Box  Robot
Box  Apple  Rabit

03-2 루핑(Looping)

Stream의 forEach같은 경우 최종 연산인 반면, 이번에는 중간 연산peek() 메서드에 대해 알아보자.

public static void main(String[] args) {
	// 최종 연산이 생략된 스트림의 파이프라인
	IntSteam.of(1, 3, 5)
		.peek(d -> System.out.println(d + "\t"));
	System.out.println();

	// 최종 연산이 존재하는 스트림의 파이프 라인
	int a = IntSteam.of(1, 3, 5)
		.peek(d -> System.out.println(d + "\t"))
		.sum();
	System.out.println("a : " + a);	 
}
  • peek() : forEach문과 기능적으로 완전히 동일하다, 하지만 중간 연산 이라는 차이점이 있다.
  • 최종 연산이 진행되지 않은 중간 연산은 아무 의미가 없다, 즉 → 실행 결과를 얻을 수 없다.
    • ex) 나는 반복문을 통해 값을 얻는 동시에 합을 더하고 싶어.
    • 최종 연산인 forEach()는 위 같은 실행이 불가능하기에 peek()를 사용한다.

스트림의 중간 연산을 사용한 후 반드시 최종 연산 구문을 넣어줘야 실행을 할 수 있다.

04 스트림의 최종 연산

스트림의 최종 연산을 알아보기에 앞서, IntStream에 존재하는 메서드에 대해 정리 해보자.

public static void main(String[] args) {
	// 스트림에 존재하는 정수의 합 구하기
	int sum = IntStream.of(1, 3, 5, 7, 9)
			   .sum(); 
	System.out.println("sum : " + sum);

	// 스트림에 존재하는 정수의 개수 구하기
	long cnt = IntStream.of(1, 3, 5, 7, 9)
			    .count();
	System.out.println("count : " + count);

	// 평균
	IntStream.of(1, 3, 5, 7, 9)
		 .average() // 반환형이 Optional이니까 -> 이어서 ifPresent() 사용 가능하다.
		 .ifPresent(av -> System.out.println("avg : " + av));
	// 최소값
	IntStream.of(1, 3, 5, 7, 9)
		 .min()
		 .ifPresent(mn -> System.out.println("min : " + mn));
	// 최대값
	IntStream.of(1, 3, 5, 7, 9)
		 .max()
		 .ifPresent(mx -> System.out.println("max : " + mx));
}
  • int sum()
  • long count()
  • OptionalDouble average()
  • OptionalInt min()
  • OptionalInt max()

Optional 클래스 복습 차원

  • isPresent() : 만약 값이 존재한다면 → True, false 반환.
  • ifPresent() : 만약 값이 존재한다면 → 람다식 수행.
  • Optional.of : 상자를 만들 때 null허용하지 않는다.
  • Optional.ofNullable : 상자를 만들 때 null허용한다.
class StringOptional2 {
	public static void main(String[] args) {
		Optional<String> os1 = Optional.of(new String("Toy1"));
		Optional<String> os2 = Optional.ofNullable(new String("Toy2"));
		
		os1.ifPresent(s -> System.out.println(s)); // 람다식 버전
		os2.ifPresent(System.out::println); // 메소드 참조 버전
	}
}

참고 : https://velog.io/@ym1085/Optional-클래스

04-1 allMatch, anyMatch, noneMatch

public static void main(String[] args) {
	boolean b = IntStream.of(1, 2, 3, 4, 5)
			     .allMatch(n -> n % 2 == 0);
	System.out.println("모두 짝수이다. " + b);
	
	b = IntStream.of(1, 2, 3, 4, 5)
		     .anyMatch(n -> n % 2 == 0);
	System.out.ptinln("짝수가 하나는 있다. " + b);

	b = IntStream.of(1, 2, 3, 4, 5)
		     .noneMatch(n -> n % 2 == 0);
	System.out.ptinln("짝수가 하나도 없다. " + b);
}
  • allMatch() : 모든 인자가 조건에 맞아야 True를 반환.
  • anyMatch() : 하나의 인자라도 조건에 맞으면 True를 반환.
  • noneMatch() : 모든 인자가 조건에 맞지 않는다면 True를 반환.

04-2 collect: 스트림에 있는 데이터를 모아라

<R> R collect(Supplier<R> supplier,
		BiConsumer<R, ? super T> accumulator,
		BiConsumer<R, R> combiner)
// Stream의 collect 메서드 예제
public static void main(String[] args) {
	String [] words = {"Hello", "Box", "Robot", "Toy"};
	Stream<String> ss = Arrays.stream(words);
	
	List<String> ls = 
		ss.filter(s -> s.length() < 5)
		  .collect(() -> new ArrayList<>(),    // 저장소 생성
			(c, s) -> c.add(s),                  // 첫 번째 인자 통해 생성된 인스턴스 c, 스트림의 데이터 s
			(lst1, lst2) -> lst1.addAll(lst2));  // 순차 스트림에서는 의미 없음
	
	System.out.println(ls);
}
  • collect() 메서드를 사용하면 filter() 메서드를 통해 살아남은 데이터, 즉 걸러진 데이터를 따로 저장할 수 있다.

시나리오 정리

  1. Box, Toy만 걸러진다.
  2. collect의 람다식에 전달된 new ArrayList<>()를 통해 저장소 생성.
  3. 첫번째 인자 (c, s) → c : 저장소 (new ArrayList<>())를 의미.
  4. 두번째 인자 (c, s) → s : 스트림에 저장된 데이터를 의미(Box, Toy).

04-3 이러한 방식으로도 가능하지 않을까?

IntStream it = IntStream.of(1,3, 4, 5, 6);

int a = it.sum();
long b = it.count();    // 여기서 아래와 같은 컴파일 에러 발생
System.out.println(a);
System.out.println(b);
Exception in thread "main" java.lang.IllegalStateException: stream has already 
been operated upon or closed
at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
at java.util.stream.LongPipeline.<init>(LongPipeline.java:91)
at java.util.stream.LongPipeline$StatelessOp.<init>(LongPipeline.java:574)
at java.util.stream.IntPipeline$5.<init>(IntPipeline.java:261)
at java.util.stream.IntPipeline.mapToLong(IntPipeline.java:260)
at java.util.stream.IntPipeline.count(IntPipeline.java:430)
at com.java.study.stream.StreamOf.main(StreamOf.java:72)
  • 위 같은 선언은 절대적으로 가능하지 않다.
  • Stream참조 변수스트림을 참조하고 있지만, 중간, 최종 연산이 수행된 스트림은 소멸이 된다.
  • 위 같은 이유로 인해 한번 최종 연산을 수행한 스트림의 파이프라인을 되돌릴수는 없다.

04-4 병렬 스트림에서의 collect

class CollectParallelStringStream {
	public static void main(String[] args) {
		String [] words = {"Hello", "Box", "Robot", "Toy"};
		Stream<String> ss = Arrays.stream(words);

		List<String> ls = ss.parallel()
				    .filter(s -> s.length() < 5)
				    .collect(() -> new ArraytList<>(),
					     (c, s) -> c.add(s),
					     (lst1, lst2) -> lst1.addAll(lst2));
	}
}
profile
https://ym1085.github.io

0개의 댓글