코딩/sparta TIL

TIL 12 : Stream, Lambda

americanoallday 2025. 3. 5. 23:16

계산기 과제 레벨3 하려니, 스트림, 람다식에 대해 잘 몰라서 다시 복습 진행하였다.

 

스트림(Stream)이란?

✔ “데이터의 흐름”을 추상화한 개념으로, 데이터를 효율적으로 처리할 수 있도록 도와주는 API.

배열, 컬렉션(List, Set, Map 등)에서 데이터를 다룰 때 for문 없이 간결한 코드로 처리 가능.

데이터를 필터링, 변환, 정렬, 그룹화, 집계 등의 연산을 “함수형 프로그래밍 방식”으로 적용 가능.

 

스트림 적용 예

더보기

스트림 미적용시(for문 사용)

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        names.add("Andrew");

        List<String> filteredNames = new ArrayList<>();
        for (String name : names) {
            if (name.startsWith("A")) {
                filteredNames.add(name.toUpperCase());  // A로 시작하는 이름을 대문자로 변환
            }
        }

        for (String name : filteredNames) {
            System.out.println(name);
        }
    }
}

 

ALICE
ANDREW

 

 

스트림 방식

📌 names.stream()을 호출하면 **“names 리스트에 저장된 데이터를 순차적으로 처리할 준비가 된 흐름(Stream)”**을 반환함.

📌 이후 .filter(), .map(), .forEach() 등을 사용하여 데이터를 가공할 수 있음.

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

public class Main {
    public static void main(String[] args) {
    	// List<String> names : 데이터를 저장하는 컬렉션 (즉, 요소를 담고 있는 컨테이너).
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Andrew");
        // .asList 함수 
        // 일반 배열을 ArrayList로 변환합니다
        // 고정된 배열을 반환합니다
        // 읽기 전용 배열을 반환합니다

        names.stream()  // ✅  데이터 저장이 아니라, 데이터를 “흐름”으로 처리할 수 있는 스트림을 생성
             .filter(name -> name.startsWith("A"))  // ✅ A로 시작하는 이름 필터링
             .map(String::toUpperCase)  // ✅ 대문자로 변환
             .forEach(System.out::println);  // ✅ 최종 출력
    }
}
ALICE
ANDREW

 

스트림 주요 기능

스트림 메서드 설명 예제
.filter() 조건에 맞는 데이터만 걸러냄 stream.filter(n -> n > 10)
.map() 데이터를 변환 stream.map(String::toUpperCase)
sorted() 정렬 수행 stream.sorted()
.distinct() 중복 제거 stream.distinct()
.limit(n) 처음 n개 요소만 가져옴 stream.limit(3)
.skip(n) 처음 n개 요소를 건너뜀 stream.skip(2)
.count() 개수 반환 stream.count()
.forEach() 최종 출력 stream.forEach(System.out::println)
.collect() 최종 연산, 데이터를 List, Set, Map등의 자료구조로 변환 stream.collect(Collectors.toList())

 

 

스트림 동작 원리

스트림은 “중간 연산”과 “최종 연산”으로 구성됨

✔ 중간 연산(filter(), map(), sorted())은 데이터를 변환하지만 실제 실행되지 않음

✔ 최종 연산(forEach(), count(), collect())을 호출해야만 스트림이 실행됨!

예제 : 스트림 동작 과정

더보기
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry", "avocado");

        long count = words.stream()
                          .filter(word -> {
                              System.out.println("Filtering: " + word);
                              return word.startsWith("a");
                          })
                          .map(word -> {
                              System.out.println("Mapping: " + word); // System.out.println()은 변환 전 값
                              return word.toUpperCase();
                          })
                          .count();

        System.out.println("결과 개수: " + count);
    }
}
Filtering: apple
Mapping: apple
Filtering: banana
Filtering: cherry
Filtering: avocado
Mapping: avocado
결과 개수: 2

 

🔍 실행 과정

단계 실행 내용
Filtering: apple "apple".startWith("a") == true (통과)
Mapping: apple "apple"이 APPLE로 변환됨
Filtering: banana "banana".startWith("a") == false(제외)
Filtering: cherry "banana".startWith("a") == false(제외)
Filtering: avocado "apple".startWith("a") == true (통과)
Mapping: avocado "avocado"가 AVOCADO로 변환됨
count() 최종적으로 2개가 남아서 개수 출력

🔍  return word.startsWith("a"); 코드

입력 값 word.startsWith("a") 결과 filter()에서 남음?
"apple" true ✅ 남음
"banana" false ❌ 제외
"cherry" false ❌ 제외
"avocado"  true ✅ 남음

 

스트림과 컬렉션의 차이

구분 스트림(Stream) 컬렉션(Collection)
데이터 저장 여부 ❌ 데이터를 저장하지 않음 ✅ 데이터를 저장함
요소 처리 방식 데이터를 한번만 처리 (1회성) 여러 번 반복 가능
연산 방식 함수형 프로그래밍 스타일 (filter(), map()) for문을 사용한 명령형 프로그래밍
병렬 처리 ✅ 가능 (parallelStream()) ❌ 직접 for문으로 병렬화 필요

 


* 대량 데이터를 다룰 때 parallelStream()을 사용하면 자동으로 병렬 처리 가능

더보기

parallelStream()은 스트림을 병렬(Parallel)로 처리하는 기능을 제공하는 메서드로

여러 개의 CPU 코어를 활용해서 “여러 데이터 조각을 동시에 처리”할 수 있음

반면 stream()은 데이터를 “순차적(Sequential)“으로 처리함.

 

stream() vs parallelStream() 비교

구분 stream() parallelStream()
실행 방식 한 개의 스레드에서 차례로 실행 여러 개의 스레드에서 동시에 실행
성능 데이터가 적으면 빠름 데이터가 많을수록 성능 향상
순서 보장 순서 유지 O 순서 보장 X
CPU 활용 단일 CPU 코어만 사용 멀티 코어 CPU 활용 (병렬 연산)
사용 예시 작은 데이터 처리 대량의 데이터 처리, 계산량이 많은 작업

stream() 예제 (순차 처리)

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

public class StreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        names.stream()
             .map(name -> {
                 System.out.println("Mapping: " + name + " - " + Thread.currentThread().getName());
                 return name.toUpperCase();
             })
             .forEach(System.out::println);
    }
}
Mapping: Alice - main
Mapping: Bob - main
Mapping: Charlie - main
Mapping: David - main
ALICE
BOB
CHARLIE
DAVID

 

parallelStream() 사용 예

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

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        names.parallelStream()
             .map(name -> {
                 System.out.println("Mapping: " + name + " - " + Thread.currentThread().getName());
                 return name.toUpperCase();
             })
             .forEach(System.out::println);
    }
}
// 실행할 때마다 순서가 다를 수 있음
Mapping: Alice - ForkJoinPool.commonPool-worker-1
Mapping: Bob - main
Mapping: Charlie - ForkJoinPool.commonPool-worker-2
Mapping: David - ForkJoinPool.commonPool-worker-1
BOB
CHARLIE
DAVID
ALICE

 

성능 테스트 (연산 속도 측정)

1부터 1억까지의 숫자를 더하는 코드 비교 (stream() vs parallelStream())

import java.util.stream.LongStream;

public class StreamPerformanceTest {
    public static void main(String[] args) {
        long start, end;

        // ✅ 순차 처리 (stream)
        start = System.currentTimeMillis();
        long sum1 = LongStream.rangeClosed(1, 100_000_000).sum();
        end = System.currentTimeMillis();
        System.out.println("순차 처리 결과: " + sum1 + " (걸린 시간: " + (end - start) + "ms)");

        // ✅ 병렬 처리 (parallelStream)
        start = System.currentTimeMillis();
        long sum2 = LongStream.rangeClosed(1, 100_000_000).parallel().sum();
        end = System.currentTimeMillis();
        System.out.println("병렬 처리 결과: " + sum2 + " (걸린 시간: " + (end - start) + "ms)");
    }
}
// CPU 성능에 따라 다를 수 있음
순차 처리 결과: 5000000050000000 (걸린 시간: 500ms)
병렬 처리 결과: 5000000050000000 (걸린 시간: 200ms)

 

 

람다식(Lambda Expression) 

“메서드를 하나의 식(Expression)으로 간단히 표현하는 방법”

불필요한 class 선언 없이, 코드가 짧고 가독성이 좋아짐

함수형 프로그래밍을 자바에서 사용할 수 있도록 도입된 기능

 

람다식 적용 예

더보기

람다식 미적용 예

interface Calculator {
    int sum(int a, int b);
}

public class Main {
    public static void main(String[] args) {
        Calculator cal = new Calculator() {  // ✅ 익명 클래스 사용
            @Override
            public int sum(int a, int b) {
                return a + b;
            }
        };

        System.out.println(cal.sum(5, 3));  // 출력: 8
    }
}

 

람다식 적용 예

interface Calculator {
    int sum(int a, int b);
}

public class Main {
    public static void main(String[] args) {
        Calculator cal = (a, b) -> a + b;  // ✅ 람다식 사용!

        System.out.println(cal.sum(5, 3));  // 출력: 8
    }
}

 

🐧 람다식 기본 구조

(매개변수) -> { 실행문 }

 

예제 1: 매개변수가 하나인 경우

str -> System.out.println(str);

 

예제 2: 매개변수가 여러 개인 경우

(a, b) -> a + b;

 

예제 3: 실행문이 여러 개면 {} 사용

(a, b) -> {
    int sum = a + b;
    return sum;
};

 

예제 4: 매개변수가 없는 경우

interface Printer {
    void print();
}

public class Main {
    public static void main(String[] args) {
        Printer printer = () -> System.out.println("Hello, Lambda!");
        printer.print();
    }
}

 

예제 5: 람다식을 활용한 스트림(Stream) 예제

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

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        names.stream()
             .filter(name -> name.startsWith("A"))  // ✅ A로 시작하는 이름 필터링
             .map(String::toUpperCase)  // ✅ 대문자로 변환
             .forEach(System.out::println);  // ✅ 출력 //ALICE
    }
}