본문 바로가기
JAVA

[JAVA] - Stream API 이해 2

by nam_ji 2024. 5. 27.

Stream API 람다 표현식

람다 표현식

  • 자바에서는 인터페이스를 선언하거나 매개변수로 주어야 할 때 1회용 구현체인 익명 클래스를 사용할 수 있습니다.
  • 예시를 보여드리겠습니다.
    더보기
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.List;
    import java.util.function.Consumer;
    import java.util.stream.Collectors;
    
    public class LambdaClass {
    
      public static void main(String[] args) {
    
        List<Integer> list = Arrays.asList(2, 1, 3, 5, 4);
    
        // 오름차순 정렬
        List<Integer> list2 = list.stream()
            .sorted(Comparator.naturalOrder())
            .collect(Collectors.toList());
    
        // 내림차순 정렬
        List<Integer> list3 = list.stream()
            .sorted(Comparator.reverseOrder())
            .collect(Collectors.toList());
    
        System.out.println("-----------------------printList-----------------------");
        printList(list);
        printList(list2);
        printList(list3);
    
        System.out.println("-----------------------printLambda-----------------------");
        printLambda(list);
        printLambda(list2);
        printLambda(list3);
      }
    
      private static void printList(List<Integer> list) {
    
        // 1회용 구현체 new Consumer를 매개변수로 활용
        list.forEach(new Consumer<Integer>() {
          @Override
          public void accept(Integer num) {
            System.out.print(num + " ");
          }
        });
        System.out.println("\n");
      }
    
      private static void printLambda(List<Integer> list) {
    
        list.forEach(num -> System.out.print(num + " "));
        System.out.println("\n");
      }
    
    }
    
    /*
    출력
    -----------------------printList-----------------------
    2 1 3 5 4 
    
    1 2 3 4 5 
    
    5 4 3 2 1 
    
    -----------------------printLambda-----------------------
    2 1 3 5 4 
    
    1 2 3 4 5 
    
    5 4 3 2 1 
    */
  • 람다를 활용하여 printList의 내용을 더 간결하게 변경했습니다.
  • 익명 클래스 new Consumer<Integer>(){}가 한 줄로 줄어들었으며 개발자의 의도가 드러나는 문장을 사용했습니다.
  • forEach의 매개변수에 사용되는 Consumer 타입과 Consumer::accept가 겉으로 드러나지 않습니다.

람다 표현식이란

  • 람다식은 익명 클래스를 간략하게 표현하는 방법입니다.
  • 식 전체가 인스턴스로 취급됩니다.

람다 표현식 구조

  • 람다 표현식은 기본적으로
    (parameter) -> expression
    (parameter) -> {statement;}
    이와 같은 구조를 가집니다.

  • 람다 표현식엔 인터페이스와 오버라이드할 메서드 이름이 표기되지 않습니다.
  • 매개변수 목록을 왼쪽에 표기합니다.
  • 매개변수는 타입을 생략할 수 있습니다.
  • 매개변수가 1개면 괄호를 생략할 수 있습니다.
  • 매개변수가 0개면 () ->으로 매개변수가 없음을 나타내어야 합니다.
  • 매개변수 목록 작성이 끝나면 화살표 ->를 작성합니다.
  • 화살표 우측에는 람다 바디를 작성합니다.
  • 한 줄 (세미콜론 한 번)으로 구현할 수 있는 경우 expression입니다.
  • expression은 중괄효, return, 세미콜론을 생략하여야 합니다.
  • 예제
    더보기
    package lambdaExample;
    
    import java.util.function.BiFunction;
    import java.util.function.Function;
    import javax.xml.transform.Source;
    
    public class LambdaExample {
    
      public static void main(String[] args) {
        Example example = new Example();
        example.example1();
        example.example2();
        example.example3();
      }
    }
    
    class Example {
      public void example1() {
        Function<String, Integer> test = String::length;
        int result = test.apply("가나다라마");
        System.out.println("\n-----------------------Expression 구현-----------------------");
        System.out.println(result);
      }
    
      public void example2() {
        Runnable runnable = () -> System.out.println("Hello Lambda");
        System.out.println("\n-----------------------매개변수가 없는 경우-----------------------");
        runnable.run();
      }
    
      public void example3() {
        BiFunction<Integer, Integer, Integer> print = (x, y) -> {
          return x * y;
        };
        int result = print.apply(10, 10);
        System.out.println("\n-----------------------statement 구현-----------------------");
        System.out.println(result);
      }
    }
    
    /*
    출력
    -----------------------Expression 구현-----------------------
    5
    
    -----------------------매개변수가 없는 경우-----------------------
    Hello Lambda
    
    -----------------------statement 구현-----------------------
    100
    */

람다 표현식 사용 범위

1. 함수형 인터페이스

  • 함수형 인터페이스는 추상 메서드가 단 1개만 있는 인터페이스를 의미합니다.
  • 특정 부모에게 상속받아 오버라이드한 클래스가 있는 경우는 함수형 인터페이스가 아닙니다.
  • @FunctionalInterface로 이 인터페이스가 함수형 인터페이스임을 표시할 수 있습니다.
    • @FunctionalInterface
      • 함수형 인터페이스를 표현하는 어노테이션입니다.
      • 이 어노테이션을 사용했으나 추상 메서드가 2개 이상이면 함수형 인터페이가 아니라는 경고를 표시합니다.

자주 사용하는 함수형 인터페이스

1. 조건 검증

인터페이스 설명
Predicate<T> T 타입을 매개변수로 받고 boolean을 반환합니다.
조건 검증에 사용합니다.
BiPredicate<L, R> L, R을 받고 boolean을 반환합니다.
주어진 두 매개변수를 사용하여 검증할 때 사용합니다.

2. 자원 생성 / 소모

인터페이스 설명
Consumer<T> T타입을 매개변수로 받고 반환 타입이 없습니다.
Supplies<T> 매개변수는 없으며 T 타입을 반환합니다.
BiCunsumer<T, U> T, U를 받고 반환은 하지 않습니다.
매개변수가 2개 필요하지만 반환 없이 자원을 소모하는 연산에 사용합니다.

3. 연산

인터페이스 설명
Function<T, R> T타입을 매개변수로 받고 R타입을 반환합니다.
unaryOperator<T> Function<T, R>을 상속받은 인터페이스이며 T, R을 같은 타입으로 사용합니다.
T를 받고 T를 반환합니다.
단항 연산 시 사용합니다.
BiFunction<T, U, R> T, U를 매개변수로 받고 R을 반환합니다.
T, U, R 모두 다른 타입을 유연하게 사용할 수 있습니다.
BinaryOperator<T> BiFunction<T, U, R>을 상속받은 인터페이스이며 T, U, R을 같은 타입으로 사용합니다.
T, T를 받고 T를 반환합니다.

매개변수와 반환형이 중복되는 함수형 인터페이스

  • 람다식은 인터페이스 이름과 오버라이드할 메서드 이름을 명시적으로 나타내지 않습니다.
  • 매개변수와 반환 타입이 완전 같은 함수형 인터페이스가 2개 이상 존재하는 경우 누구를 가리키는지 명확하지 않습니다.