作者:Yomon-00 | 来源:互联网 | 2023-07-23 12:49
在使用Java编写apache-flink程序的时候相信很多新手都遇到下面这样的异常;org.apache.flink.api.common.functions.Invalid
在使用Java编写apache-flink程序的时候相信很多新手都遇到下面这样的异常;
org.apache.flink.api.common.functions.InvalidTypesException: The return type of function 'main(DemoApp.java:29)' could not be determined automatically, due to type erasure. You can give type information hints by using the returns(...) method on the result of the transformation call, or by letting your function implement the 'ResultTypeQueryable' interface.
at org.apache.flink.api.dag.Transformation.getOutputType(Transformation.java:45
函数返回类型由于类型删除,无法自动确定类型;可使用returns方法或使用函数实现ResultTypeQueryable接口;
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.
Collector泛型类型参数丢失。在使用Java泛型时lambda方法无法提供足够的信息来进行自动进行类型提取。一个简单的解决方案是使用匿名内部类代替来实现FlatMapFunction接口,不然只能使用类型信息显式指定类型;
抛出的上面这两个异常描述的内容其实很明确了,简单来说就是:在实现FlatMapFunction时使用lambda表达式导致了Collector变量的泛型类型参数丢(由于类型删除),简单的解决方案是把lambda表达式换成匿名内部类或者显式指定类型(使用returns方法或实现ResultTypeQueryable接口);
下面简单谈谈Java的类型擦除与flink的显式指定类型;
Java类型擦除
Java的泛型被很多人诟病称为“伪泛型”,也是因为类型擦除这个原因,泛型在Java中就是属于语法糖;
在Java中JVM虚拟机层面并不存在泛型的概念,Java在编译阶段把泛型的类型参数给擦除掉了,在运行阶段并没有泛型的概念;
public class Data {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
如上类,在经过Java编译成为class文件后其中的类型参数T将被擦除,字段obj变成了Object类型,两个get、set方法中的T也都换成了Object类型;
泛型实现主要有两种:
Code sharing:一个原始类的泛型类型只有一份目标代码。
Code specialization:对每个泛型类型都生成不同的代码。
Java属于第一种,C#与C++属于第二种,两种实现各有春秋吧,这里不讨论;
为了保证Java的多态特性编译器在进行类型擦除时还可能会生成桥接方法用于保证类型擦除所导致子类与父类方法实现不一致问题;
Flink中的泛型与lambda
stream.flatMap(new FlatMapFunction() {
@Override
public void flatMap(Integer value, Collector out) throws Exception {
System.out.println(value);
}
});
在Flink中使用各种算子的时候可能会有类似上面面这种用法,上面这种方式使用并没有什么问题,这里的FlatMapFunction就是一个泛型接口,使用了匿名内部类实现了该接口并传递给了flatMap算子;
stream.flatMap((FlatMapFunction) (value, out) -> {
System.out.println(value);
})
也有的人直接使用lambda表达式实现FlatMapFunction接口传递给flatMap算子,但这时候很多新手估计会发现程序运行的时候报错了,抛出了本文最开始的那两个异常;
为什么使用匿名内部类就没问题,而使用lambda表达式就不行报错了,其实异常信息已经描述很清楚了。这里简单看看为什么匿名内部类可以,lambda表达式不可以,使用returns方法或实现ResultTypeQueryable接口也可以;
上面介绍了在Java中会对泛型信息进行类型参数擦除,但在这里为啥使用匿名内部类实现FlatMapFunction时却还是可以获取得到泛型参数?
其实Java中编译时的泛型类型擦除并不是把所以泛型相关的信息全部擦干干净净,Javac编译时擦除的只是结构化之外(程序执行流)的信息这部分信息存储在字节码的Code属性中,类、字段、方法的泛型类型参数元数据都会被保留下来,这些存储在Signature属性中;可通过反射得到相关的泛型参数信息;
s.flatMap(new FlatMapFunction() {
@Override
public void flatMap(String value, List out) {
System.out.println("stu");
}
});
而lambda表达式实现FlatMapFunction却获取不到泛型参数,是的。
匿名内部类会编译成相关的类字节码存储在class文件中,而lambda表达式却也只是Java的语法糖并不会存在相关的类字节码,只会在lambda表达式运行时调用invokedynamic指令执行逻辑。lambda表达式丢失了更多的类型信息,也就导致了使用lambda表达式获取不到泛型类型参数;
s.flatMap((FlatMapFunction) (value, out) ->
System.out.println("stu"));
Flink中使用lambda后的写法
其实上面异常信息已经说得非常清楚了,调用returns方法或实现ResultTypeQueryable接口,这里就简单说这两种用法;
returns方法
调用该方法的用法也比较简单,就是返回的Collector需要哪个泛型类型参数你就调用returns方法注册哪种类型,调用returns方法一定是要在某个算子之后紧接着第一个调用,简单理解就是未某个算子注册返回类型;
stream.flatMap((FlatMapFunction) (value, out) -> {
System.out.println(value);
})
.returns(String.class)
ResultTypeQueryable接口
实现此接口就可以告诉系统此算子的返回值类型,实现了此接口的优先级最高,不会再通过反射去获取返回值类型。还可以根据类型参数的不同使用不同的返回值类型;实现此接口可定制化程度很高、灵活。Flink kafka相关的连接器中就是用了这种模式。
public class FlatFun implements ResultTypeQueryable, FlatMapFunction {
@Override
public TypeInformation getProducedType() {
return TypeInformation.of(String.class);
}
@Override
public void flatMap(Integer value, Collector out) {
out.collect(String.valueOf(value));
System.out.println("flatFun");
}
}
stream.flatMap(new FlatFun())
.print();