Java Lambda Expressions

Java 8 introduced several new language features designed for faster and clearer coding. With the most important feature, the so-called “Lambda Expressions”, it opened the door to functional programming. Lambda expressions allow for implementing and passing functions in a straightforward way without having to declare additional (anonymous) classes.

Attention Flink supports the usage of lambda expressions for all operators of the Java API, however, whenever a lambda expression uses Java generics you need to declare type information explicitly.

This document shows how to use lambda expressions and describes current limitations. For a general introduction to the Flink API, please refer to the DataSteam API overview

Examples and Limitations

The following example illustrates how to implement a simple, inline map() function that squares its input using a lambda expression. The types of input i and output parameters of the map() function need not to be declared as they are inferred by the Java compiler.

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

Flink can automatically extract the result type information from the implementation of the method signature OUT map(IN value) because OUT is not generic but Integer.

Unfortunately, functions such as flatMap() with a signature void flatMap(IN value, Collector<OUT> out) are compiled into void flatMap(IN value, Collector out) by the Java compiler. This makes it impossible for Flink to infer the type information for the output type automatically.

Flink will most likely throw an exception similar to the following:

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

In this case, the type information needs to be specified explicitly, otherwise the output will be treated as type Object which leads to unefficient serialization.

  1. import org.apache.flink.api.common.typeinfo.Types;
  2. import org.apache.flink.api.java.DataSet;
  3. import org.apache.flink.util.Collector;
  4. DataSet<Integer> input = env.fromElements(1, 2, 3);
  5. // collector type must be declared
  6. input.flatMap((Integer number, Collector<String> out) -> {
  7. StringBuilder builder = new StringBuilder();
  8. for(int i = 0; i < number; i++) {
  9. builder.append("a");
  10. out.collect(builder.toString());
  11. }
  12. })
  13. // provide type information explicitly
  14. .returns(Types.STRING)
  15. // prints "a", "a", "aa", "a", "aa", "aaa"
  16. .print();

Similar problems occur when using a map() function with a generic return type. A method signature Tuple2<Integer, Integer> map(Integer value) is erasured to Tuple2 map(Integer value) in the example below.

  1. import org.apache.flink.api.common.functions.MapFunction;
  2. import org.apache.flink.api.java.tuple.Tuple2;
  3. env.fromElements(1, 2, 3)
  4. .map(i -> Tuple2.of(i, i)) // no information about fields of Tuple2
  5. .print();

In general, those problems can be solved in multiple ways:

  1. import org.apache.flink.api.common.typeinfo.Types;
  2. import org.apache.flink.api.java.tuple.Tuple2;
  3. // use the explicit ".returns(...)"
  4. env.fromElements(1, 2, 3)
  5. .map(i -> Tuple2.of(i, i))
  6. .returns(Types.TUPLE(Types.INT, Types.INT))
  7. .print();
  8. // use a class instead
  9. env.fromElements(1, 2, 3)
  10. .map(new MyTuple2Mapper())
  11. .print();
  12. public static class MyTuple2Mapper extends MapFunction<Integer, Tuple2<Integer, Integer>> {
  13. @Override
  14. public Tuple2<Integer, Integer> map(Integer i) {
  15. return Tuple2.of(i, i);
  16. }
  17. }
  18. // use an anonymous class instead
  19. env.fromElements(1, 2, 3)
  20. .map(new MapFunction<Integer, Tuple2<Integer, Integer>> {
  21. @Override
  22. public Tuple2<Integer, Integer> map(Integer i) {
  23. return Tuple2.of(i, i);
  24. }
  25. })
  26. .print();
  27. // or in this example use a tuple subclass instead
  28. env.fromElements(1, 2, 3)
  29. .map(i -> new DoubleTuple(i, i))
  30. .print();
  31. public static class DoubleTuple extends Tuple2<Integer, Integer> {
  32. public DoubleTuple(int f0, int f1) {
  33. this.f0 = f0;
  34. this.f1 = f1;
  35. }
  36. }