🦁멋쟁이 사자처럼 15기/3월달 수업 내용 정리

멋쟁이 사자처럼 13회차 ( 03 / 17 )

코딩하는 하마 2025. 3. 17. 16:55

[학습 목표]

1. 스트림 (Stream API)를 활용하여 데이터를 효율적으로 변환, 필터링, 집계 및 병렬 처리를 할 수 있으며, 성능을 최적화할 수 있다. ( java.util -> stream -> 변환 , 필터링 집계 및 병렬 처리)

2. 스트림 중간 연산 (Intermediate Operation)과 최종 연산(Terminal Operations)의 개념을 이해하고, 다양한 연산 ( map , filter, sorted , reduce ,collect )을 활용하여 데이터를 효과적으로 처리할 수 있다.

3. 여러 연산 단계를 연결하 스트림 파이프라인의 특성을 이해하고 활용할 수 있다.

4. 컬렉션이나 배열 데이터를 스트림으로 전환하여 선언적 프로그래밍으로 변환 후 프로세싱 할 수 있다.

 

 


 

Stream API

>> 데이터 집합을 효과적으로 처리할 수 있는 API 필터링, 그룹핑 등 가능

 

1) Stream 연산 순서 

a. 생성 : Collection.stream() , Stream.of() , Arrays.stream()  -> 내부적으로 데이터 처리를 쉽게 하겠다는 의미 

- java.util의 메소드에도 stream() 이 있다 -> 요소를 기준으로 생성 

- java.lang.stream에 Stream에서 of(T...values) 메소드가 있다 ->  값을 기준으로 스트림 생성

- java.util의 Arrays 클래스에 stream(T[] array) 메소드를 볼 수 있다. -> 배열 객체 기준으로 스트림 생성

 

b. 중간 연산 (Intermediate Operation) -> 중간에 조건 연산

filter(), map(), sorted(), distinct(), limit(), flatMap()

중간 연산은 스트림을 변환하는 작업을 수행하며, 최종 연산이 호출될 때 실행된다

 

c. 최종 연산 (Terminal Operation) -> 집계 결과

forEach(), collect(), reduce(), count(), sum(), min(), max()

최종 연산이 수행되면 스트림이 소비되며, 이후에는 다시 사용할 수 없다

 

2) Stream 주요 연산

 (map()  reduce()를 합해서 mr 작업이라고 하는데 이는 자주 사용함) {집계 연산할 때 합이 좋음}

 

>> map() : 요소 변환 -> Stream<R> map(Function<T, R> mapper) -> R apply(T t)

형변환 map(String :: valueOf)

문자열 소대문자 변환 map(String::toUpperCase),

객체에서 String을 추출해서 가져오는 것 map(person -> person.name)

-> Stream<String> stream = IntStream.rangeClosed(1, 10).mapToObj(String::valueOf);

mapToObj -> 객체를 스트림으로 변환

map : 원하는 형으로 바꾸는 것

 

package com.sec12.mstream;

import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Stream;
import java.util.stream.IntStream;

public class StreamTest04 {
	public static void main(String[] args) {
    //Q1. 1~10까지 정수형 값을 스트림 생성 후 출력 map() : 요소 변환 -> 정수를 문자열로 변환 String.valueOf()
    //일반 Stream 이 아닌 특정 타입형 IntStream 같은 경우에는 map이 아닌 mapToObj로 객체로 변환하여 리턴해야한다
        Stream<String> stream2 = IntStream.rangeClosed(1,10).maptToObj(String::valueof);
        StreamTest.printStream("Q1. 1~10까지 정수형 값을 스트림 생성 후 출력 map()", stream);
		
		//Q2. 10 ~1 까지 정수형 값을 스트림 생성 후 출력 -> iterate 방법
		Stream <String> stream2 = IntStream.iterate(10,i->i-1).limit(10).mapToObj(String::valueOf);
		StreamTest.printStream("Q2. 10 ~1 까지 정수형 값을 스트림 생성 후 출력 ", stream2);
		
		//Q3. 10 ~ 1 까지 정수형 값을 스트림 생성 후 출력 -> rangeClosed 방법
		Stream<String> stream3 = IntStream.rangeClosed(1,10) //1 ~ 10까지 정수 스트림
				.map(i -> 11 -i) // 역순으로 리턴 하겠다
				.mapToObj(String::valueOf); // 문자열 변환 후 스트림 생성 후 리턴 
		StreamTest.printStream("Q3. 10 ~ 1 까지 정수형 값을 스트림 생성 후 출력 -> rangeClosed 방법", stream3);
		
		//Q4. Stream<Integer> boxed() + sorted(Comparator.reverseOrder()) 사용함
		Stream<String> stream4 = IntStream.rangeClosed(1,10)
				.boxed() //기본형 int -> Integer
				.sorted((a,b) -> b-a) //역순 내림차순 
				.map(String::valueOf);
		StreamTest.printStream("Q4. Stream<Integer> boxed() + sorted(Comparator.reverseOrder()) 사용함",stream4);
	}

}

 

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamTest02 {
	
	public static void main(String[] args) {
		//Q1 . 1~10 까지 정수형 값을 스트림 생성 후 출력 map() : 요소 변환 -> 정수를 문자열로 변환 String.valueof()
	//int arr[] = {1,2,3,4,5,6,7,8,9,10}; 일반 타입은 안됨 wrapper로 와야 함
		Integer arr[] = {1,2,3,4,5,6,7,8,9,10};
	
	//Stream<String> streamFroBalues = Arrays.asList("1","2","3",...."10");
	Stream<String> streamFroBalues = Arrays.stream(arr).map(String::valueOf);
	StreamTest.printStream("Q1 . 1~10 까지 정수형 값을 스트림 생성 후 출력: ",streamFroBalues);
		
		//Q2 "import java.util.stream.Stream"는 문자열을 잘라서 배열 후 만든 후 스트림 생성 후 룰력 
		
	String[] stringArray = "import java.util.stream.Stream".split(" ");
	Stream<String> stream2 = Arrays.stream(stringArray);
	StreamTest.printStream("Q2 \"import java.util.stream.Stream\"는 문자열을 잘라서 배열 후 만든 후 스트림 생성 후 룰력", stream2);

		
		//Q3. 10 ~1 까지 정수형 값을 스트림 생성 후 출력
	Integer[] arr1 = {10,9,8,7,6,5,4,3,2,1};
	Stream<String> stream3 = Arrays.stream(arr1).map(String::valueOf);
		StreamTest.printStream("Q3. 10 ~1 까지 정수형 값을 스트림 생성 후 출력", stream3);
	}
	
	
}

 

>> filter() :조건에 맞는 요소만 선택

List<String> result   = Arrays.stream(str.split("\s+"))
                .filter(word -> word.length() >= 5) 
                .map(String::toUpperCase).collect(Collectors.toCollection(ArrayList::new));
			System.out.println(result);

>> sorted() : 정렬 수행

>> reduce() : 누적 연산 수행 -> BiFunction<T,U,R> , reduce ((a,b) -> a+b)

package com.sec12.mstream;

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

public class ReduceTest {

    public static void main(String[] args) {
        // 1️. Optional<T> reduce(BinaryOperator<T> accumulator)
        // 리스트의 모든 값을 합산하여 Optional<Integer>로 반환
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
        Optional<Integer> sum = numbers.stream().reduce(Integer::sum);

        // Optional 값이 존재하면 출력, 없으면 "noValue" 출력
        System.out.println(sum.orElseGet(() -> {
        	
            System.out.println("noValue");
            return 0;
        }));
        System.out.println(sum.orElseGet(() -> 0));

        // 2. T reduce(T identity, BinaryOperator<T> accumulator)
        // 초기값(identity) 0을 지정하여 리스트의 모든 값을 합산
        int sum01 = numbers.stream().reduce(0, Integer::sum); 
        System.out.println("Sum with identity: " + sum01);
        

        // 3. <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
        // 병렬 스트림을 사용하여 초기값 0.0을 지정한 후, BiFunction과 BinaryOperator를 통해 병렬 연산 수행
        double sum03 = numbers.parallelStream().reduce(
                0.0, // 초기값(identity)
                (partialSum, a) -> partialSum + a, // BiFunction<U, ? super T, U>
                Double::sum // BinaryOperator<U>
        ); 
        System.out.println("Parallel Sum: " + sum03);
    }
}

>> collect(): 리스트, 맵 등으로 변환

>>peek() : 변환 작업을 할 때 테스트 용으로 사용

 

3) 병렬 처리 parallelStream()을 활용하면 데이터 병렬 처리가 가능하다.

package com.sec12.mstream;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
//직렬 처리(Sequential Processing)와 병렬 처리(Parallel Processing)를 비교해보자. 


public class ParallelTest01 {

    public static void main(String[] args) {
        // 리스트 초기화
        List<BigDecimal> list = createBigDecimalList();

        //  직렬 처리 실행
        long start = System.currentTimeMillis();
        BigDecimal sequentialResult = processSequential(list);
        long end = System.currentTimeMillis();
        System.out.println("=======> 직렬 처리 시간: " + (end - start) + "ms");

        //  병렬 처리 실행
        start = System.currentTimeMillis();
        BigDecimal parallelResult = processParallel(list);
        end = System.currentTimeMillis();
        System.out.println("==========> 병렬 처리 시간: " + (end - start) + "ms");
    }

    /**
     * BigDecimal 리스트 생성
     */
    private static List<BigDecimal> createBigDecimalList() {
        List<BigDecimal> list = new ArrayList<>();
        list.add(new BigDecimal("1"));
        list.add(new BigDecimal("2"));
        list.add(new BigDecimal("3"));
        list.add(new BigDecimal("4"));
        list.add(new BigDecimal("5"));
        return list;
    }

    /**
     * 직렬(Sequential) 스트림 처리
     */
    private static BigDecimal processSequential(List<BigDecimal> list) {
        System.out.println("--- 직렬 처리 (Sequential Processing) ---");
        return list.stream().reduce(new BigDecimal("100"), (value1, value2) -> {
            printValues(value1, value2);
            return value1.add(value2);
        });
    }

    /**
     * 병렬(Parallel) 스트림 처리
     */
    private static BigDecimal processParallel(List<BigDecimal> list) {
        System.out.println("\n--- 병렬 처리 (Parallel Processing) ---");
        return list.parallelStream().reduce(new BigDecimal("100"), (value1, value2) -> {
            printValues(value1, value2);
            return value1.add(value2);
        });
    }

    /**
     * 연산 과정 출력
     */
    private static void printValues(BigDecimal value1, BigDecimal value2) {
        System.out.println("value1 = " + value1);
        System.out.println("value2 = " + value2);
    }
}