2.flink泛型擦除《深入理解flink系列》

文章目录

      • 1 泛型擦除
      • 2 操作符上的return方法

Java 8的Lambda表达式允许以一种直接的方式实现和传递函数,而不需要声明额外的(匿名的)类。Flink对Java API的所有操作符都支持使用lambda表达式,但是当lambda表达式使用了Java泛型时,开发者需要显式声明类型信息。如果开发者没有显式声明类型信息,那么使用lambda表达式会导致程序出错,这是Java编译器的泛型擦除问题所导致的。

1 泛型擦除

Java编译器在编译后会抛弃大部分的泛型类型信息,这被称为Java的泛型擦除。这意味着Flink应用程序在运行时,对象的一个实例不会知道它的泛型类型,例如DataStream和DataStream的实例在JVM中看起来是相同。

下面的示例map函数的输入和输出参数的类型不需要声明,因为它们是由Java编译器推断的。

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream dataStream = env.generateSequence(1, 5);
// 在map操作上使用lambda表达式
DataStream resultStream=dataStream.map(i -> i*i);
resultStream.print();
env.execute();

Flink可以从方法签名OUT map(IN value)的实现中自动提取结果类型信息,但是对于具有泛型返回或输入类型的map函数Tuple2 map(Tuple2 value)会被Java编译器编译成Tuple2 map(Tuple2 value),这使得Flink不可能自动推断输入和输出类型的类型信息。

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream dataStream = env.generateSequence(1, 5);
//将Long类型的数据流转换为一个Tuple2类型的新数据流
DataStream> mapStream=dataStream.map(new MapFunction>() {
    @Override
    public Tuple2 map(Long value) throws Exception {
        return new Tuple2<>(value, value);
    }
});
// 在map操作上使用lambda表达式
DataStream> resultStream=mapStream.map(value -> value);
resultStream.print();
env.execute();

第二次map操作中数据流中的元素为Tuple2具有泛型类型,而map操作使用了Lambda表达式导致Flink无法推断出泛型的类型,应用程序会抛出一个类似于下面的异常:

Caused by: org.apache.flink.api.common.functions.InvalidTypesException: The generic type parameters of 'Tuple2' 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.MapFunction' interface.
Otherwise the type has to be specified explicitly using type information.
    at org.apache.flink.api.java.typeutils.TypeExtractionUtils.validateLambdaType(TypeExtractionUtils.java:350)
    at org.apache.flink.api.java.typeutils.TypeExtractor.getUnaryOperatorReturnType(TypeExtractor.java:579)
    at org.apache.flink.api.java.typeutils.TypeExtractor.getMapReturnTypes(TypeExtractor.java:175)
    at org.apache.flink.streaming.api.datastream.DataStream.map(DataStream.java:587)
    ... 1 more

2 操作符上的return方法

对于Flink应用程序中操作符上使用Lambda表达式导致Flink无法推断出泛型的类型的问题,需要开发者在使用lambda表达式传递函数的操作符后面调用returns(…)方法来添加有关此操作符的返回类型的类型信息提示,否则输出将被视为Object类型,从而导致无效的序列化。

对上面的程序我们在mapStream数据流上调用map操作符之后调用returns(…)方法添加此操作符的返回类型的类型信息提示。

import org.apache.flink.api.common.typeinfo.Types;

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream dataStream = env.generateSequence(1, 5);
//将Long类型的数据流转换为一个Tuple2类型的数据流
DataStream> mapStream=dataStream.map(new MapFunction>() {
    @Override
    public Tuple2 map(Long value) throws Exception {
        return new Tuple2<>(value, value);
    }
});
// 在map操作上使用lambda表达式
DataStream> resultStream=mapStream.map(value -> value)
                                                //提供明确的类型信息
                                                .returns(Types.TUPLE(Types.LONG, Types.LONG))
resultStream.print();
env.execute();

returns(…)方法提供了三个重载方法:

  • public SingleOutputStreamOperator returns(Class typeClass):类可以用作非泛型类型(没有泛型参数的类)的类型提示,但不能用于诸如Tuples之类的泛型类型。对于那些泛型类型,请使用returns(TypeHint typeHint)方法。

  • public SingleOutputStreamOperator returns(TypeHint typeHint):

    通过以下方式使用此方法:
    import org.apache.flink.api.common.typeinfo.TypeHint;
    DataStream> result =
              stream.flatMap(new FunctionWithNonInferrableReturnType())
                    .returns(new TypeHint>(){});
    
  • public SingleOutputStreamOperator returns(TypeInformation typeInfo):在大多数情况下,首选方法returns(Class)和returns(TypeHint)。

    通过以下方式使用此方法:
    import org.apache.flink.api.common.typeinfo.Types;
    DataStream> result =
              stream.flatMap(new FunctionWithNonInferrableReturnType())
                    .returns(Types.TUPLE(Types.String, Types.Double));
    

你可能感兴趣的:(深入理解flink,flink,java,大数据)