[Java] 람다와 스트림

hyunoi·2024년 11월 21일
0

Java

목록 보기
9/20
post-thumbnail

람다(Lambda)


자바 8 버전부터 '람다식(Lambda Expression)이 지원되었다.
이때부터 자바는 기존의 객체지향 언어의 특징와 함수형 언어의 특징을 함께 가지게 되어, 간단한 작업에서 코드의 가독성과 효율성을 크게 향상 시켜주었다.

람다식 표현법

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

이렇게 표현되고, 접근자와 반환형이 모두 생략이 된다.
한 줄로 함수가 표현이 되는 것

// 람다식 X
void print() {
	System.out.println("HELLO JAVA");
}

// 람다식 O
() -> System.out.println("HELLO JAVA");

위 두개의 메소드가 동일한 출력값을 가져온다.

// 람다식 X
void sum(int x, int y) {
	return x+ y;
}

// 람다식 O
(x, y) -> x + y

이렇게 매개변수가 있는 경우에는 예상이 가능하다면 생략할 수 있다.
return문과 세미콜론도 생략이 가능하다.
하지만 쓰는게 더 가독성 면에서 좋아보임!

익명 클래스

익명 클래스가 뭐냐 하면,,,
객체의 선언과 생성을 동시에 하면서 오직 하나의 객체를 생성하고 한번만 사용되고 마는 일회용 클래스이다.

람다식은 일회용으로 이름도 안 붙여지고 사용하고 끝이니 익명 클래스가 맞다.

하지만 기존의 익명 클래스는 람다식처럼 간결한 상황, 간단한 로직에서 사용되지는 않는다.
더 복잡한 로직과 메소드가 많은 경우에 익명 클래스가 적합하다.

람다식은 익명 클래스를 대체할 수는 있지만, 모든 익명 클래스를 사용하는 상황에서는 적합하지 않다!

스트림(Stream)


람다와 같이 자바 8 버전에서 도입된 기능
배열과 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있게 해주는 반복자이다.
기존의 Iterator와 기능이 비슷하지만, 람다식으로 처리한다는 점에서 코드가 간결해지고 병렬 처리도 가능하다.

스트림의 특징

1. 일회용

Iterator와 같이 한 번 사용한 스트림은 재사용이 불가능하다.

2. 중간 연산과 최종 연산이 나뉜다.

스트림은 다양한 연산은 제공하는데, 이를 사용해서 복잡한 작업들을 간단하게 처리할 수 있다.
이 연산들을 중간 연산과 최종 연산으로 분류할 수 있는데

  • 중간 연산
    스트림을 변환하여 새로운 스트림을 생성
    중간 연산을 계속해서 연결지어 사용할 수 있다.
    ex. filter, map, ...
  • 최종 연산
    스트림이 아닌 결과를 도출하고 스트림을 종료
    스트림의 요소를 소모하면서 연산을 수행하기 때문에 단 한번만 사용 가능하다.
    ex. collect, forEach, ...

3. 지연된 연산이 존재한다.

스트림 연산에서 중요한 점은 최종 연산이 수행되기 전까지 중간 연산이 수행되지 않는다.
스트림에 대해 filter, map을 해도 바로 연산이 수행되지 않는다는 것

이 수행되지 않는 중간 연산이 지연된 연산이 된다.

스트림 생성

// 컬렉션에서 생성
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream = list.stream();

// 배열에서 생성
String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array);

이런 식으로 컬렉션과 배열을 만들어 준 뒤, 스트림을 만든다.

스트림 주요 연산

중간 연산

  • filter
    조건에 맞는 요소만 걸러낸다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// 이름이 'A'로 시작하는 요소만 필터링
names.stream()
	.filter(name -> name.startsWith("A"))
	.forEach(System.out::println); // 출력: Alice
  • map
    각 요소를 변환하여 새로운 스트림으로 생성한다.
List<String> names = Arrays.asList("alice", "bob", "charlie");

// 모든 이름을 대문자로 변환
names.stream()
	.map(String::toUpperCase)
	.forEach(System.out::println); // 출력: ALICE, BOB, CHARLIE
  • sorted
    스트림의 요소들을 정렬한다.
List<Integer> numbers = Arrays.asList(5, 3, 8, 1);

// 정렬된 요소 출력
numbers.stream()
	.sorted()
	.forEach(System.out::println); // 출력: 1, 3, 5, 8
  • distinct
    스트림 내 요소들의 중복을 제거한다.
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);

// 중복 제거 후 출력
numbers.stream()
	.distinct()
	.forEach(System.out::println); // 출력: 1, 2, 3, 4, 5

최종 연산

  • forEach
    스트림의 각 요소에 대해 작업을 수행한다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 각 요소 출력
names.stream()
	.forEach(System.out::println); // 출력: Alice, Bob, Charlie
  • collect
    스트림을 컬렉션으로 변경한다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 이름을 대문자로 변환한 후 리스트로 수집
List<String> upperCaseNames = names.stream()
                                   .map(String::toUpperCase)
                                   .collect(Collectors.toList());

System.out.println(upperCaseNames); // 출력: [ALICE, BOB, CHARLIE]
  • reduce
    요소들을 누적하여 하나의 결과를 만든다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);

// 모든 숫자의 합 계산
int sum = numbers.stream()
                 .reduce(0, Integer::sum);

System.out.println(sum); // 출력: 10
  • count
    스트림 내 요소들의 개수를 센다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 이름의 개수 계산
long count = names.stream()
                  .count();

System.out.println(count); // 출력: 3 

병렬 스트림

스트림 작업을 여러 개의 CPU 코어에서 병렬 처리가 가능하다.
이를 통해서 대량의 데이터 처리 시 성능을 향상시킬 수 있다.

public class Main {
    public static void main(String[] args) {
        // 큰 데이터 세트
        int max = 100_000_000;

        // 순차 스트림
        long start = System.currentTimeMillis();
        long sumSequential = IntStream.rangeClosed(1, max)
                .sum();
        long end = System.currentTimeMillis();
        System.out.println("Sequential Stream Time: " + (end - start) + " ms");

        // 병렬 스트림
        start = System.currentTimeMillis();
        long sumParallel = IntStream.rangeClosed(1, max)
                .parallel()
                .sum();
        end = System.currentTimeMillis();
        System.out.println("Parallel Stream Time: " + (end - start) + " ms");
    }
}

> Output
Sequential Stream Time: 34 ms
Parallel Stream Time: 14 ms

순차 스트림과 병렬 스트림의 실행 속도에서 차이가 나는 것을 확인할 수 있다.

0개의 댓글