자바

자바 람다와 스트림

yoon4360 2025. 5. 12. 17:42

자바를 사용하다 보면 복잡하고 반복적인 코드 때문에 고민이 생기곤 한다.
특히 컬렉션을 다룰 때, 데이터를 변환하거나 필터링하는 과정이 길고 복잡해지기 쉬운것 같다.
이럴 때 람다와 스트림을 사용하면 코드가 간결해지고 가독성도 높아진다.


람다식은 자바 8부터 도입된 함수형 프로그래밍의 핵심 요소로
기존 메서드를 간단하게 표현하여 코드를 직관적으로 작성할 수 있다.


또한, 스트림을 사용하면 데이터를 효과적으로 필터링, 변환, 집계할 수 있어
데이터 처리 코드를 훨씬 간단하게 만들 수 있다.

 


 

람다식?

람다식은 한마디로 메서드를 하나의 식으로 표현한 것이다.


기존 메서드와 달리 이름과 반환 타입을 없애고 매개변수와 실행문을 -> 기호로 연결한다.
예를 들어, 두 수를 더하는 메서드를 람다식으로 표현하면 다음과 같다.

(int a, int b) -> a + b

 

람다식은 단순히 메서드를 줄여 쓰는 문법처럼 보이지만, 사실상 익명 클래스의 객체와 같다.
즉, 내부적으로는 익명 객체로 변환되어 실행되므로
람다식 자체가 객체라는 점을 이해하는 것이 중요하다.

 

람다식은 인터페이스를 구현한 객체로 대체할 수 있다.
이때 사용되는 인터페이스를 함수형 인터페이스라고 하며 하나의 추상 메서드만 선언할 수 있다는 제약이 있다.
이 제약 덕분에 람다식과 인터페이스 메서드가 1:1로 연결될 수 있다.

 


 

함수형 인터페이스와 자바 표준 함수형 인터페이스

자바는 람다식을 더 편리하게 사용하도록
java.util.function 패키지에 여러 함수형 인터페이스를 미리 정의해 두었다.
주요 함수형 인터페이스와 그 역할은 다음과 같다.

 

1. Runnable: 매개변수 없음, 반환값 없음

Runnable r = () -> System.out.println("Hello");
r.run();

 

2. Supplier: 매개변수 없음, 반환값 있음

Supplier<String> s = () -> "Hello";
System.out.println(s.get());

 

3. Consumer: 매개변수 있음, 반환값 없음

Consumer<String> c = str -> System.out.println(str);
c.accept("Hello");

 

4. Function: 매개변수 있음, 반환값 있음

Function<Integer, String> f = num -> "Number: " + num;
System.out.println(f.apply(5));

 

5. Predicate: 매개변수 있음, 반환 타입은 boolean

Predicate<Integer> p = num -> num > 0;
System.out.println(p.test(10)); // true

 

함수형 인터페이스를 활용하면, 메서드를 직접 정의하지 않고도
람다식을 통해 간결하게 함수형 프로그래밍을 할 수 있다.


특히 컬렉션을 다룰 때, 람다식과 함수형 인터페이스를 조합하여
직관적이고 간결한 코드 작성이 가능하다.

 


 

스트림?

스트림은 데이터 소스를 추상화하여 데이터를 일관된 방식으로 처리할 수 있는 데이터 처리 API이다.
컬렉션이나 배열 등 다양한 데이터 소스를 동일한 방식으로 다룰 수 있다는 것이 큰 장점이다.


한번 생성된 스트림은 데이터를 변경하지 않고 필터링, 매핑, 집계 등 다양한 중간 연산과 최종 연산을 통해 데이터를 가공할 수 있다.

스트림은 내부 반복을 통해 데이터를 처리한다.
기존 반복문처럼 직접 제어하는 대신 forEach, map, filter와 같은 메서드를 통해 데이터를 다룬다.
이는 코드의 가독성을 높이고, 내부적으로 병렬 처리를 지원하여 성능을 높일 수 있다.

 


 

스트림의 주요 특징

  • 데이터 소스를 변경하지 않는다: 원본 데이터는 그대로 유지되고, 새로운 스트림이 생성된다.
  • 일회용: 한 번 사용한 스트림은 재사용할 수 없으며, 필요 시 다시 생성해야 한다.
  • 지연 연산: 중간 연산은 최종 연산이 실행될 때까지 수행되지 않는다.
  • 병렬 처리 지원: 병렬 스트림으로 전환하여 멀티 코어를 활용할 수 있다.

 


 

스트림 연산

스트림은 두 가지 연산으로 나뉜다.

  1. 중간 연산: 연산 결과가 스트림인 연산, 여러 개 연결 가능
    • filter(), map(), flatMap(), sorted(), distinct()
  2. 최종 연산: 연산 결과가 스트림이 아닌 연산, 한 번만 실행 가능
    • forEach(), reduce(), collect(), count()

중간 연산 예시

List<String> list = Arrays.asList("apple", "banana", "cherry");
list.stream()
    .filter(s -> s.startsWith("a"))
    .map(String::toUpperCase)
    .forEach(System.out::println);
최종 연산 예시
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println("Sum: " + sum);

 


 

스트림의 그룹화와 분할

스트림의 데이터를 조건에 따라 그룹화하거나 분할할 수 있다.


그룹화는 여러 그룹으로 나누는 것이고
분할은 조건에 따라 두 그룹으로 나누는 것이다.

 

그룹화 예시

Map<Integer, List<Student>> stuBySex = stuStream
		.collect(groupingBy(Student::getBan));

 

 분할 예시

Map<Boolean, List<Student>> stuBySex = stuStream
		.collect(partitioningBy(Student::isMail));

 

두개의 그룹으로 나눈다면 partitiioningBy(), 그 외에는 groupingBy()를 사용하면 된다.
이처럼 스트림을 활용하면 복잡한 조건문 없이 데이터를 효과적으로 분류할 수 있다.

 


 

마무리

람다와 스트림을 사용하면 코드가 간결해지고 컬렉션을 다루는 반복 작업이 줄어들어 생산성이 높아진다.
특히 데이터 처리에서 함수형 프로그래밍의 이점을 극대화할 수 있으며
스트림을 이용한 병렬 처리는 대용량 데이터 처리에도 유리하다.


자바 8 이후로 함수형 프로그래밍이 필수가 된 만큼

람다와 스트림의 개념과 활용 방법을 확실히 익혀두면 실무에서도 효율적으로 코드를 작성할 수 있다.


람다와 스트림을 잘 활용하여 코드의 간결함과 성능을 모두 잡아보자.