람다활용

람다 활용 - 필터

V1 - 람다를 사용하지 않는 방식

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

public class FilterMainV1 {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 10);

        List<Integer> evens = filterEvenNumber(numbers);
        List<Integer> odds = filterOddNumber(numbers);
    }

    //짝수만 거르기
    private static List<Integer> filterEvenNumber(List<Integer> numbers) {
        List<Integer> filtered = new ArrayList<>();
        for (int num : numbers) {
            if (num % 2 == 0) {
                filtered.add(num);
            }
        }
        return filtered;
    }

    //홀수만 거르기
    private static List<Integer> filterOddNumber(List<Integer> numbers) {
        List<Integer> filtered = new ArrayList<>();
        for (int num : numbers) {
            if (num % 2 == 1) {
                filtered.add(num);
            }
        }
        return filtered;
    }
}

V2 - 람다를 사용해서 중복을 제거한 방식

V3 - 별도의 유틸리티 클래스 작성

V4 - 제네릭 사용


람다 활용 - 매핑

V1 - 람다를 사용하지 않는 방식

V2 - 람다를 사용해서 중복을 제거한 방식

V3 - 별도의 유틸리티 클래스 작성

V4 - 제네릭 사용


람다 활용 - 필터와 매핑

필터와 매핑 예제 1

direct()는 프로그램을 어떻게 수행해야 하는지 수행 절차를 명시한다.

  • 개발자가 로직 하나하나를 어떻게 실행해야 하는지 명시하며, 이러한 프로그래밍 방식을 명령형 프로그래밍이라고 한다.

  • 명령형 스타일은 익숙하고 직관적이지만, 로직이 복잡해질수록 반복 코드가 많아질 수 있다.

반면 lambda()무엇을 수행해야 하는지 원하는 결과에 초점을 맞춘다.

  • 특정 조건으로 필터하고 변환하라고 선언하면 구체적인 부분은 내부에서 수행된다.

  • 갭라자는 필터하고 변환하는 것과 같은 무엇을 해야 하는가에 초점을 맞춘다.

  • 이러한 프로그래밍 방식을 선언적 프로그래밍이라고 한다.

  • 선언형 스타일은 무엇을 하고자 하는지가 명확히 드러난다. 따라서 코드 가독성과 유지보수가 쉬워진다.

👆 명령형 vs 선언적 프로그래밍

  • 명령형 프로그래밍

    • 프로그램이 어떻게(How) 수행되어야 하는지, 즉 수행 절차를 명시하는 방식

    • 특징

      • 단계별 실행 : 프로그램의 각 단계를 명확하게 지정하고 순서대로 실행한다.

      • 상태 변화 : 프로그램의 상태(변수 값 등)가 각 단계별로 어떻게 변화하는지 명시한다.

      • 낮은 추상화 : 내부 구현을 직접 제어해야 하므로 추상화 수준이 낮다.

      • 예시 : 전통적인 for 루프, while 루프 등을 명시적으로 사용하는 방식

    • 장점 : 시스템의 상태와 흐름을 세밀하게 제어할 수 있다.

  • 선언적 프로그래밍

    • 프로그램이 무엇을(What) 수행해야 하는지, 즉 원하는 결과를 명시하는 방식

    • 특징

      • 문제 해결에 집중 : 어떻게 문제를 해결할지보다 무엇을 원하는지에 초점을 맞춘다.

      • 코드 간결성 : 간결하고 읽기 쉬운 코드를 작성할 수 있다.

      • 높은 추상화 : 내부 구현을 숨기고 원하는 결과에 집중할 수 있도록 추상화 수준을 높인다.

      • 예시 : filter, map 등 람다의 고차 함수를 활용, HTML, SQL 등

    • 장점 : 코드가 간결하고 의미가 명확하며 유지보수가 쉬운 경우가 많다.

필터와 매핑 예제 2

람다를 사용하면 구체적으로 어떻게 필터링하고 데이터를 추출하는지 보다는 요구사항에 맞춰서 무엇을 하고 싶은지에 초점을 맞춘다. 결과적으로 코드를 간결하게 작성하고 선언적 스타일로 해결할 수 있다.


람다 활용 - 스트림

필터와 매핑 기능을 별도의 유틸리티 클래스에서 각각 따로 제공하는 방식을 함께 편리하게 사용할 수 있도록 하나의 객체에 기능을 통합해본다.

V1 - 기본

MyStreamV1 클래스의 메서드는 자기 자신의 타입을 반환하기 때문에 메서드 체인 방식을 사용해 깔끔한 구조를 만들 수 있다.

V2 - 정적 팩토리 메서드 추가

👆 정적 팩토리 메서드

정적 팩토리 메서드는 객체 생성을 담당하는 static 메서드로, 생성자 대신 인스턴스를 생성하고 반환하는 역할을 한다. 즉 일반적인 생성자 대신에 클래스의 인스턴스를 생성하고 초기화하는 로직을 캡슐화하여 제공하는 정적 메서드이다.

주요 특징

  • 정적 메서드 : 클래스 레벨에서 호출되며, 인스턴스 생성 없이 접근할 수 있다.

  • 객체 반환 : 내부에서 생성한 객체(또는 이미 존재하는 객체)를 반환한다.

    • 예) Integer.valueOf()는 미리 생성된 객체를 반환한다.

  • 생성자 대체 : 생성자와 달리 메서드 이름을 명시할 수 있어 생성 과정의 목적이나 특징을 명확하게 표현할 수 있다.

  • 유연한 구현 : 객체 생성 과정에서 캐싱, 객체 재활용, 하위 타입 객체 반환 등 다양한 로직을 적용할 수 있다.

생성자는 이름을 부여할 수 없지만, 정적 팩토리 메서드는 의미있는 이름을 부여할 수 있기 때문에 가독성이 더 좋아지는 장점이 있다. 인자들을 받아 간단하게 객체를 생성할 때는 주로 of(...) 라는 이름을 사용한다.

하지만 반대로 보면 이름도 부여해야 하고 준비해야 하는 코드도 더 많다. 객체의 생성이 단순한 경우에는 생성자를 직접 사용하는 것이 더 나은 선택일 수 있다.

V3 - 제네릭 사용 + 기능 추가

👆 외부 반복 vs 내부 반복

  • 외부 반복

    • for문, while문과 같은 반복문을 직접 사용해서 데이터를 순회하는 방식

    • 개발자가 직접 각 요소를 반복하며 처리한다.

  • 내부 반복

    • 직접 반복 제어문을 작성하지 않고, 반복 처리를 스트림 내부에 위임하는 방식

    • 스트림 내부에서 요소들을 순회하고, 개발자는 처리 로직(람다)만 정의해주면 된다.

    • 코드가 훨씬 간결해지며, 선언형 프로그래밍 스타일을 적용할 수 있다.

내부 반복 방식은 반복의 제어를 스트림에게 위임하기 때문에 코드가 간결해진다. 즉 개발자는 어떤 작업을 할지를 집중적으로 작성하고, 어떻게 순회할지는 스트림이 담당하도록 하여 생산성과 가독성을 높일 수 있다.

많은 경우 내부 반복이 선언형 프로그래밍 스타일로 더욱 직관적이기 때문에 더 나은 선택이다. 다만 때때로 외부 반복을 선택하는 것이 더 나은 경우도 있다.

  • 단순히 한두 줄 수행만 필요한 경우

  • 반복 제어에 대한 복잡하고 세밀한 조정이 필요한 경우 (breakcontinue 등을 사용하는 경우)

Last updated