Java 람다 표현식

Java 람다 표현식 (Java Lambda Expressions)

Java 8은 더 빠르고 명확한 코딩을 위해 여러 새로운 언어 기능을 도입했습니다. 가장 중요한 기능인 "Lambda Expressions"로 함수형 프로그래밍의 문이 열렸고, 추가적인 (익명) 클래스를 선언하지 않고도 함수를 간단하게 구현하고 전달할 수 있게 되었습니다.

출처: 문서

본문

Java 8은 더 빠르고 명확한 코딩을 위해 여러 새로운 언어 기능을 도입했습니다. 가장 중요한 기능인 "Lambda Expressions"로 함수형 프로그래밍의 문이 열렸습니다. 람다 표현식은 추가적인 (익명) 클래스를 선언하지 않고도 함수를 간단하게 구현하고 전달할 수 있게 합니다.

정보: Flink는 Java API의 모든 연산자에 대해 람다 표현식 사용을 지원하지만, 람다 표현식이 Java 제네릭(generics)을 사용할 때마다 타입 정보를 명시적으로 선언해야 합니다.

이 문서는 람다 표현식 사용법과 현재의 제한 사항을 보여줍니다. Flink API에 대한 일반적인 소개는 DataStream API 개요를 참고하세요.

예시와 제한 사항 (Examples and Limitations)

다음 예시는 입력을 제곱하는 간단한 인라인 map() 함수를 람다 표현식으로 구현하는 방법을 보여줍니다. 입력 imap() 함수의 출력 파라미터 타입은 Java 컴파일러가 추론하므로 선언할 필요가 없습니다.

env.fromElements(1, 2, 3)
// returns the squared i
.map(i -> i*i)
.print();

OUT이 제네릭이 아니라 Integer이므로 Flink는 메서드 시그니처 OUT map(IN value)의 구현에서 결과 타입 정보를 자동으로 추출할 수 있습니다.

불행히도 시그니처가 void flatMap(IN value, Collector<OUT> out)flatMap() 같은 함수는 Java 컴파일러에 의해 void flatMap(IN value, Collector out)으로 컴파일됩니다. 이 때문에 Flink가 출력 타입의 타입 정보를 자동으로 추론하는 것이 불가능해집니다.

Flink는 대부분 다음과 유사한 예외를 던질 것입니다:

org.apache.flink.api.common.functions.InvalidTypesException: The generic type parameters of 'Collector' are missing.
    In many cases lambda methods don't provide enough information for automatic type extraction when Java generics are involved.
    An easy workaround is to use an (anonymous) class instead that implements the 'org.apache.flink.api.common.functions.FlatMapFunction' interface.
    Otherwise the type has to be specified explicitly using type information.

이 경우 타입 정보를 명시적으로 지정해야 하며, 그렇지 않으면 출력이 타입 Object로 처리되어 비효율적인 직렬화를 초래합니다.

DataStream<Integer> input = env.fromElements(1, 2, 3);

// collector type must be declared
input.flatMap((Integer number, Collector<String> out) -> {
    StringBuilder builder = new StringBuilder();
    for(int i = 0; i < number; i++) {
        builder.append("a");
        out.collect(builder.toString());
    }
})
// provide type information explicitly
.returns(Types.STRING)
// prints "a", "a", "aa", "a", "aa", "aaa"
.print();

제네릭 반환 타입을 가진 map() 함수를 사용할 때도 유사한 문제가 발생합니다. 아래 예시에서 메서드 시그니처 Tuple2<Integer, Integer> map(Integer value)Tuple2 map(Integer value)로 이레이저됩니다.

import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.tuple.Tuple2;

env.fromElements(1, 2, 3)
    .map(i -> Tuple2.of(i, i))    // no information about fields of Tuple2
    .print();

일반적으로 이러한 문제는 여러 방법으로 해결할 수 있습니다:

import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple2;

// use the explicit ".returns(...)"
env.fromElements(1, 2, 3)
    .map(i -> Tuple2.of(i, i))
    .returns(Types.TUPLE(Types.INT, Types.INT))
    .print();

// use a class instead
env.fromElements(1, 2, 3)
    .map(new MyTuple2Mapper())
    .print();

public static class MyTuple2Mapper extends MapFunction<Integer, Tuple2<Integer, Integer>> {
    @Override
    public Tuple2<Integer, Integer> map(Integer i) {
        return Tuple2.of(i, i);
    }
}

// use an anonymous class instead
env.fromElements(1, 2, 3)
    .map(new MapFunction<Integer, Tuple2<Integer, Integer>> {
        @Override
        public Tuple2<Integer, Integer> map(Integer i) {
            return Tuple2.of(i, i);
        }
    })
    .print();

// or in this example use a tuple subclass instead
env.fromElements(1, 2, 3)
    .map(i -> new DoubleTuple(i, i))
    .print();

public static class DoubleTuple extends Tuple2<Integer, Integer> {
    public DoubleTuple(int f0, int f1) {
        this.f0 = f0;
        this.f1 = f1;
    }
}

더 알아보기 (Learn more)