Kotlin 类未识别为 Flink 有效 POJO

dec*_*ctl 5 kotlin apache-flink

我正在用 Kotlin 编写 Flink 应用程序,并且数据类(以及其他 Kotlin 类)未被识别为有效的 POJO 类型。

Flink 文档指出,如果满足以下条件,则数据类型被识别为 POJO 类型(并允许“按名称”字段引用):

  • 该类是公共且独立的
  • 该类有一个公共无参构造函数
  • 类中的所有非静态、非瞬态字段要么是公共的(且非最终的),要么具有遵循 Java bean 命名约定的公共 getter 和 setter 方法。

我在实现 Kotlin 数据类时收到以下信息,该数据类应满足上述条件才能被识别为 POJO:

[main] INFO org.apache.flink.api.java.typeutils.TypeExtractor - 
Class class <Class> cannot be used as a POJO type because not all 
fields are valid POJO fields, and must be processed as GenericType. 
Please read the Flink documentation on "Data Types & Serialization" 
for details of the effect on performance.
Run Code Online (Sandbox Code Playgroud)

进一步调查,我回顾了Flink的TypeExtractor.isValidPojoField方法@ https://github.com/apache/flink/blob/master/flink-core/src/main/java/org/apache/flink/api/java/typeutils/TypeExtractor .java

在一个单独的项目中,我在一个简单的 Kotlin 数据类上应用了 java.lang.reflect.Modifier 的字段检查,试图缩小问题范围。

data class SomeDataClass(
    val topic: String = "",
    val message: String = ""
)

Run Code Online (Sandbox Code Playgroud)

虽然 Kotlin 类字段默认具有公共可见性,但 Modifier.isPublic 将这些字段识别为私有字段。此外,Modifier.isFinal 将这些字段识别为最终字段。

val clazz = SomeDataClass::class.java
val fields = clazz.declaredFields
fields.forEach { it ->
    println("field: $it")
    println(it.genericType)
    println("public? " + Modifier.isPublic(it.modifiers))
    println("final? " + Modifier.isFinal(it.modifiers))
    println("transient? " + Modifier.isTransient(it.modifiers))
    println("static? " + Modifier.isStatic(it.modifiers))
}

>
field: private final java.lang.String SomeDataClass.topic
class java.lang.String
public? false
final? true
transient? false
static? false
Run Code Online (Sandbox Code Playgroud)

但是,为这些字段创建了公共 getter 和 setter 方法,因此该对象仍应满足 POJO 标准。

println(clazz.declaredMethods.toList())

>
[public boolean SomeDataClass.equals(java.lang.Object), 
public java.lang.String SomeDataClass.toString(), 
public int SomeDataClass.hashCode(), 
**public final java.lang.String SomeDataClass.getMessage(),** 
public final SomeDataClass SomeDataClass.copy(java.lang.String,java.lang.String), 
**public final java.lang.String SomeDataClass.getTopic(),** 
public final java.lang.String SomeDataClass.component1(), 
public final java.lang.String SomeDataClass.component2(), 
public static SomeDataClass SomeDataClass.copy$default(SomeDataClass,java.lang.String,java.lang.String,int,java.lang.Object)]

Run Code Online (Sandbox Code Playgroud)

然而,getter 和 setter 方法是最终的,这让我相信这就是问题所在。

我对 JVM 开发比较陌生,因此我们将不胜感激。我查看了 Flink Jira、Stack Overflow 和 Flink 邮件列表,没有发现类似的问题报告。

Ale*_*nov 3

我发现所提供的数据类至少有两个违反 POJO 规则的情况。

1) 该类有一个公共的无参构造函数

默认情况下,Kotlin 不会为具有默认参数值的函数生成重载 ( https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#overloads- Generation )

所以你编译的类将只有一个带有两参数构造函数的构造函数,并且不会创建无参构造函数。要强制 Kotlin 编译器生成多个重载,应该使用@JvmOverloads注释。在您的情况下,它将在构造函数中使用,因此我们还需要添加 constructor 关键字:

data class SomeDataClass @JvmOverloads constructor
Run Code Online (Sandbox Code Playgroud)

2) 类中的所有非静态、非瞬态字段要么是公共的(且非最终的),要么具有遵循 Java bean 命名约定的公共 getter 和 setter 方法。

由于您使用的是val关键字,生成的字段将为final,并且不会为它们生成任何设置器。因此,您可以将vals 更改为vars,字段将不再存在final,并且也会生成正确的 getter 和 setter。(或者您可以使用另一个注释来防止生成 getter 和 setter 并公开一个字段,因为它是https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#instance-fields

所以最终的代码应该是这样的:

data class SomeDataClass @JvmOverloads constructor(
    var topic: String = "",
    var message: String = ""
)
Run Code Online (Sandbox Code Playgroud)