JAVA

JAVA 스트림(Stream) 정리

병렬처리

  • 프로그램 내에서 영역을 여러 개로 나누어 각각의 계산을 여러 프로세스에서 동시에 수행하는 것이다.
  • 자바에서는 병렬처리를 Stream API를 통해 집계 또는 연산을 할 수 있도록 제공하며 손쉽게 구현할 수 있도록 클래스와 메소드를 제공한다.
  • 자바에서의 Stream API는 주로 배열이나 Collection 등의 집합체를 바탕으로 값의 집계나 데이터를 사용한 처리 API이다.

Stream API

  • 스트림의 구조는 크게 3가지로 나뉜다.
    • 스트림 생성
    • 중개 연산
    • 최종 연산
    • [예시]
      Collection집합.스트림생성().중개연산().최종연산();
    • 계속해서 .으로 연계할 수 있게 하는 방법을 파이프라인이라고 한다.
  • Stream은 재사용이 불가능하다.
  • 병렬 스트림인 parallelStream의 경우 여러 쓰레드가 작업한다.

Stream 생성

  • java.util.stream.Stream의 of()
  • java.util.Array의 stream()
  • java.util.Collection의 stream()
  • 위 세 가지의 메소드를 통해 만들 수 있으며, 대상 객체를 Stream으로 반환하게 되면 Map, Reduce등의 집계를 간편하게 구현할 수 있다.
public class Test {

	public static void main(String[] args) {
		List<String> list =Arrays.asList("사원1","사원2","사원3","사원4");
		String [] array = {"관리자1","관리자2","관리자3","관리자4"};
		
		//1. 리스트를 스트림으로 변환(병렬 작업 위해서)
		Stream<String> stream1=list.stream();
		//2. 배열을 스트림으로 변환
		Stream<String> stream2=Arrays.stream(array);
		
		//3. 문자열 여러개를 래퍼클래스로 변환을 해서 스트림으로 구현
		Stream<String> stream3=Stream.of("1","2","3","4","5");
		Stream<String> stream4=Stream.of(array);
		//4. 출력
		PrnStream(stream1);
		PrnStream(stream2);
		PrnStream(stream3);
		PrnStream(stream4);
		
		Stream<Integer> stream5=Stream.of(1,3,2,4,5,6);//래퍼 클래스로 변환하는 것이니
		//Int를 Integer로 바뀐다.
		PrnStream01(stream5);
	}
	private static void PrnStream01(Stream<Integer> my_stream) {
		my_stream.forEach(e->System.out.printf("%5d",e));
		System.out.println("\n===========================");
	}
	private static void PrnStream(Stream<String> my_stream) {
		my_stream.forEach(e->System.out.printf("%5s",e));
		System.out.println("\n===========================");
	}
}
//출력
//사원1  사원2  사원3  사원4
//===========================
//관리자1 관리자2 관리자3 관리자4
//===========================
//  1    2    3    4    5
//===========================
//관리자1 관리자2 관리자3 관리자4
//===========================
//  1    3    2    4    5    6
//===========================

객체 출력

스트림 변환 후 검색, 집계, 연산 등을 구현한 다음 마지막에 출력할 때 사용한다.

  • void forEach(Consumer <? super T> consumer)
  • void forEachOrder(Consumer <? super T> consumer)
  • Consumer는 값을 받아 처리하지만 반환 값을 리턴하지 않는 인터페이스이다.

Stream 내부 작업

1. map(중개 연산)

Stream 인스턴스의 각 요소를 조건에 맞춘 새로운 stream으로 검색하거나, 다른 데이터 요소를 가진 stream 인스턴스를 생성하는 작업을 한다.

  • filter(Predicate<? super T> predicate)
    • prediacte에 정의된 boolean 리턴 값이 true에 해당하는 요소만 Stream<T>로 변환
  • limit(long maxSize)
    • 요소의 처음부터 maxSize까지의 요소의 Stream을 리턴
  • distinct()
    • 요소끼리 equals 메소드에서 비교하여 중복 항목을 제외한 Stream으로 리턴
  • map(Function<? super T, ? extends R> mapper)
    • 지정된 값을 변경한 새로운 Stream을 Return
    • 입력 T를 R로 변환하는 함수 인터페이스이며 Function을 매개인자로 갖는다.
  • flapMap(Function <? super T, ? extends Stream<? extends R>> mapper)
    • 입력된 T를 복수개의 R로 변환하는 map 메소드로 flatMap에서 만들어진 Stream은 자동으로 close된다.
  • peek(Consumer<? super T> action)
    • 변환하는 작업을 테스트할 때 사용하는 메소드이다.
    • 디버깅 용도로 어떤 값이 들어 있는지 확인하고 싶을 때 사용한다.
    • 요소의 수에 어떤 영향도 주지 않는다
public class MapTest {

	public static void main(String[] args) {
		
		Integer [] array = {1,2,3,4,5,1,2,3,4,5};
		//메소드 참조 class_name::method_name=static
		System.out.println(" 1. filter 메소드로 짝수만 출력해보자. ");
		Arrays.stream(array).filter(value -> value%2==0)
		.forEach(System.out::println);
		//filter안에는 predicate 안에 들어 있는 test가 들어갔다.
		
		
		System.out.println(" 2. limit 메소드로 3개의 요소만 출력해보자. ");
		//메소드 참조 class_name::method_name=static
		Arrays.stream(array).limit(3).forEach(value->System.out.println("value: "+value));
		
		System.out.println(" 3. distinct 메소드로 중복되지 않는 요소만 출력해보자");
		Arrays.stream(array).distinct().forEach(System.out::println);
		
		
		System.out.println(" 4. map을 활용하여 대문자로 만들어 보자.");
		Stream<String> stream= Stream.of("java","jsp","spring","jqeury");
		stream.map(s->s.toUpperCase()).forEach(System.out::println);
		//map은 값을 다른 값 또는 다른 메서드로 변환하는 메서드이다.
		
		System.out.println(" 5. map() / peek() 을 사용하여 값을 체크해보자.");
		Stream<String> stream01= Stream.of("루리","루세","루오","폴리");
		stream01.peek(s->System.out.printf("체크한 결과: %s",s)).map(t-> "\t실제 값:"+t)
		.forEach(System.out::println);;
		
		//데이터를 복수 개 리턴
		System.out.println(" 6. flatMap()을 사용하여 세번째 값에 있는 것을 이용하여 반복 출력하자.");
		List<String> list= Arrays.asList("사과3팩","멜론2팩","딸기3팩","망고2팩");
		//list에 있는 것을 하나씩 넣는다 list.stream
		List<String> result=list.stream().flatMap(
			s->{
				String c=s.substring(0,2);
				int num=Integer.parseInt(s.substring(2,3));
				String [] array2= new String[num];
				Arrays.fill(array2, c); //array 칸 모두를 c로 채운다.
				return Stream.of(array2);//리턴 값은 스트림이다.}
			}).collect(Collectors.toList());
		//collect 이용하면 list나 Set 사료형 가능
		System.out.println(result);
	}
}
//결과
//1. filter 메소드로 짝수만 출력해보자. 
//2
//4
//2
//4
//2. limit 메소드로 3개의 요소만 출력해보자. 
//value: 1
//value: 2
//value: 3
//3. distinct 메소드로 중복되지 않는 요소만 출력해보자
//1
//2
//3
//4
//5
//4. map을 활용하여 대문자로 만들어 보자.
//JAVA
//JSP
//SPRING
//JQEURY
//5. map() / peek() 을 사용하여 값을 체크해보자.
//체크한 결과: 루리	실제 값:루리
//체크한 결과: 루세	실제 값:루세
//체크한 결과: 루오	실제 값:루오
//체크한 결과: 폴리	실제 값:폴리
//6. flatMap()을 사용하여 세번째 값에 있는 것을 이용하여 반복 출력하자.
//[사과, 사과, 사과, 멜론, 멜론, 딸기, 딸기, 딸기, 망고, 망고]

2. Reduce(최종 연산)

리턴 받은 stream 인스턴스의 요소에서 어떤 결과를 얻거나 요소를 사용하여 출력하는 등의 작업을 담당한다. 

  • get()
    • Optional <T>에 들어 있는 값을 리턴한다.
    • 포함하고 있는 값이 업는 경우 NoSuchElementException 예외가 발생한다.
  • isPresent()
    • 값이 포함되어 있는 경우 true, 포함되지 않는 경우 false를 리턴한다.
  • orElse(T other)
    • 값이 포함되어 있는 경우에는 그 값을 리턴하고, 저장되어 있지 않은 경우네는 인수 other에 저장한다.
  • orElseGet(Supplier<? extends T> other)
    • 값이 포함되어 있는 경우는 그 값을 리턴하고 포함되지 않는 경우 supplier가 생성한 값을 리턴한다.
  • orElseThrow(Supplier<? extends X> exceptionSupplier)
    • 값이 포함되어 있는 경우는 그 값을 반환하고 포함되지 않는 경우 supplier가 발생 시키는 예외를 생성한다.
  • of(T value)
  • ofNullable(T value)
    • Optional의 static 메소드로 인수 value가 null이 아닌 경우,그 value를 포함하는 Optional을 리턴하고 null의 경우는 Optional의 empty 메소드로 생성되는 Optional을 리턴한다.
  • empty()
  • ifPresent(Consumer<? super T> consumer)
    • Optional 값을 포함하고 있는 경우 consumer 작업을 수행하고
      Optional이 Empty인 경우에는 아무것도 하지 않는다.
  • reduce(BinaryOperator<T> accumulator)
    • accumulator  처리 할  BinaryOperator결과와 요소을 사용하여  처리를 반복하고 처리 결과를  optional로 리턴한다.  만일 요소가 없는 등의 결과가 없는 경우는 empty 의  optional를 리턴한다. 
  • T reduce (T identifier,BinaryOperatorl <T>accumulator)
    • 처음 설정 값으로 제 1 인수에 T를 설정하여 T를 첫 번째 값으로 BinaryOperator 처리를 반복하고, 그 결과를 T로 리턴, Stream에 요소가 없는 경우는 T의 identifier 값을 리턴 한다
  • U reduce (U identifier,BinaryFunction <U? super T, U>accumulator, BinaryOperator <U>combiner)  
    • stream 가지는 형태와 결과로 반환하는 형식이 다른 경우에 사용하는 reduce 메소드. 
      제 1 인수를 첫 번째 값으로 U를 받고 BinaryFunction에서 Stream 요소 T를 받은 U를 사용하여 처리하고,
      제 1 인수와 같은 U의 형태로 변환하여 결과를 리턴한다. 
      병렬 처리의 경우, 제 3 인수 BinaryOperator 분산된 BinaryFunction 결과 U를 받고 결과를 
      정리해 생성하는 처리를 한다. Stream에 요소가 없는 경우 제 1 인수의 값이 리턴된다.

 

package com.test;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

//reduce accumulator 처리 할 BinaryOperator 결과와
//reduce 요소를 사용하여 처리를 반복하고 처리 결과를 optional로 리턴
//만일 요소 가 없는 등 결과가 없는 경우 empty의 optional로 리턴
public class ReduceTest {

	public static void main(String[] args) {
		//Optional<T> reduce(BinaryOperator<T> accumulator)
		System.out.println("1. reduce를 이용해서 멤버의 합을 구해보자. ");
		List<Integer> numbers=Arrays.asList(10,20,30,40,50);
		Optional<Integer> sum= numbers.stream().reduce(Integer::sum);
		if(sum.isPresent()) {
			System.out.println(sum.get());// get Optional <T>에 있는 값 리턴
		}else {
			System.out.println("novalue");
		}
		
		// T reduce(T identity, BinaryOperator<T> accumlulator)
		System.out.println("2. 매개인자 2개로 받아 T로 리턴하는 메소드를 활용");
		List<Integer> numbers02=Arrays.asList(10,20,30,40,50);
		//100+10+20+30+40+50
		int sum01= numbers02.stream().reduce(100,Integer::sum);
		System.out.println(sum01);
		
		System.out.println("3. 매개인자 3개로 받아 U로 리턴하는 메소드를 활용");
		List<Integer> numbers03=Arrays.asList(10,10,20,20,30,40,50);
		double sum02=numbers03.parallelStream()
				.reduce(0.0, (partialSum, a)
						-> partialSum +a, Double::sum);
		//세 개인 경우, 맨 앞은 초기 값, 
		System.out.println(sum02);
		
	}
}

 

Java Collectors(Class) and Collector(Interface)

public class CollectorTest {

	public static void main(String[] args) {

		Stream<String> s =Stream.of("a","b","c");
		Collection<String> res=s.collect(Collectors.toCollection(TreeSet::new));
		System.out.println(res);
		
		s =Stream.of("a","b","c","d","c");
		res=s.collect(Collectors.toSet());  //셋이니 중복 데이터 제거 되어서 나온다.
		System.out.println(res);
		
		s =Stream.of("a","b","c","f","c");
		res=s.collect(Collectors.toList());
		System.out.println(res);
	}

}
  • collect()
    • stream에서 수행한 작업 결과를 수집하고 싶을 때 사용
    • 수집된 데이터가 Collector로 변환한다. (List, Set, Map 으로 상태 변경 가능)
  • Collectors
    • final 클래스로 모든 메소드가 static으로 되어 있고 리턴 값이 Colletor 인터페이스로 리턴되나.
  • R collect(Collector collector)
    •  Collector 인터페이스에 의해 stream 요소를 사용하여 생성된 결과를 리턴.
      java.util.stream.Collector의 메소드를 사용할 때 적합
  • collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R,R> combiner):
    • supplier 생성된 R에 스트림 요소에서 리턴 받은 값을 accumulator에서 R에 저장하고모든 요소의 처리가 끝날 때 R로 리턴한다.

Comparator를 사용한 Lamda

 

public class ComparatorTest {

	public static void main(String[] args) {
		List<String> str=Arrays.asList("딸기","바나나","수박","멜론");
		Optional<String> o_min=str.stream().min(Comparator.naturalOrder());
		Optional<String> o_max=str.stream().max(Comparator.naturalOrder());
		Optional<String> find_one=str.stream().findFirst();
		Prn(o_min);
		Prn(o_max);
		Prn(find_one);//첫번째 잡히는 데이터 find Any
	}
	private static void Prn(Optional<String> o) {
		if(o.isPresent()) {
			System.out.println(o.get());
		}else
		{
			System.out.println("없음.");
		}
	}
}
//딸기
//수박
//딸기