如何在 Kotlin 数据类字段上列出(java)注释?

问题描述 投票:0回答:2

我正在使用 Firestore 的基于 Java 的注释来标记字段和将文档字段映射到 Java 类元素的方法:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface PropertyName {
  String value();
}

我在 Kotlin 数据类中的字段上使用它,编译得很好:

data class MyDataClass(
    @PropertyName("z") val x: Int
)

在 IntelliJ 和 Android Studio 中,我可以看到它出现在反编译的类转储中:

public final data class MyDataClass public constructor(x: kotlin.Int) {
    @field:com.google.cloud.firestore.annotation.PropertyName public final val x: kotlin.Int /* compiled code */

    public final operator fun component1(): kotlin.Int { /* compiled code */ }
}

我目前的印象是,这个注释应该可以通过 Kotlin 反射以某种方式发现。据我所知,事实并非如此。我尝试过迭代注释:

  1. 每个 Kotlin 数据类构造函数字段
  2. 每个 Kotlin 字段
  3. 每个 Kotlin 函数
  4. 每个 Java 构造函数
  5. 每个 Java 字段
  6. 每个Java方法

它只是没有出现在任何地方。

当我像这样更改注释的用法时(请注意现在目标说明符“get”):

data class MyDataClass(
    @get:PropertyName("z") val x: Int
)

注释现在显示在生成的 Java 类对象的 getter 中。这至少在实践中是可行的,但我很好奇为什么 Kotlin 允许我将注释编译为面向字段的注释,但不允许我在运行时将其返回(除非我在kotlin-reflect API?)。

如果我改用这个基于 Kotlin 的注释:

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class PropertyName(val value: String)

这样,注释就会在运行时显示在 Kotlin 字段上。这很奇怪,因为 Java 的 ElementType.FIELD 似乎并没有完美地映射到 Kotlin 的 AnnotationTarget.FIELD。

(顺便说一句,如果我将其更改为 AnnotationTarget.VALUE_PARAMETER,我也可以在数据类构造函数参数中发现此注释。)

这对我来说感觉像是一个错误,但我愿意看看我是否在这里做错了什么。或者这可能只是不受支持。我正在使用 Kotlin 1.3.11。 JVM 和 Android 上的行为相同。

查找注释的代码:

Log.d("@@@@@", "\n\nDump of $kclass")
val ctor = kclass.constructors.first()
Log.d("@@@@@", "Constructor parameters")
ctor.parameters.forEach { p ->
    Log.d("@@@@@", p.toString())
    Log.d("@@@@@", p.annotations.size.toString())
    p.annotations.forEach { a ->
        Log.d("@@@@@", "  " + a.annotationClass)
    }
}

Log.d("@@@@@", "kotlin functions")
kclass.functions.forEach { f ->
    Log.d("@@@@@", f.toString())
    if (f.annotations.isNotEmpty()) {
        Log.d("@@@@@", "*** " + f.annotations.toString())
    }
}

Log.d("@@@@@", "kotlin members")
kclass.members.forEach { f ->
    Log.d("@@@@@", f.toString())
    if (f.annotations.isNotEmpty()) {
        Log.d("@@@@@", "*** " + f.annotations.toString())
    }
}

Log.d("@@@@@", "kotlin declared functions")
kclass.declaredFunctions.forEach { f ->
    Log.d("@@@@@", f.toString())
    if (f.annotations.isNotEmpty()) {
        Log.d("@@@@@", "*** " + f.annotations.toString())
    }
}

val t = kclass.java
Log.d("@@@@@", "java constructors")
t.constructors.forEach { f ->
    Log.d("@@@@@", f.toString())
}

Log.d("@@@@@", "java methods")
t.methods.forEach { f ->
    Log.d("@@@@@", f.toString())
    if (f.annotations.isNotEmpty()) {
        Log.d("@@@@@", "*** " + f.annotations.toString())
    }
}

Log.d("@@@@@", "java fields")
t.fields.forEach { f ->
    Log.d("@@@@@", f.toString())
    if (f.annotations.isNotEmpty()) {
        Log.d("@@@@@", "*** " + f.annotations.toString())
    }
}
reflection kotlin annotations jvm
2个回答
10
投票

这里的问题是,我的期望(可能还有文档)并没有让我了解 Kotlin 编译器将如何处理各种类型的注释。我的假设是 Kotlin 数据类属性上的 FIELD 目标注释目标会将注释直接应用于 Kotlin 合成属性。这个假设不正确。

Kotlin 对合成属性上的 FIELD 注释所做的操作是将 FIELD 注释下推到生成的类文件中该属性的实际 backing field。这意味着对带注释的 Kotlin 属性的任何类型的反射都根本找不到该注释。 您必须深入 Java 类对象才能找到它。

如果您想注释 Kotlin 类属性,并通过 KClass 反射找到它,则必须使用 PROPERTY 类型注释,这是 Kotlin 独有的。这样,如果您在 KClass 的

members
列表中找到该属性,它将具有该注释(但不是底层支持字段!)。

更进一步,对于 Kotlin 数据类,构造函数是定义类属性的最重要的东西。因此,如果您想在运行时通过反射创建数据类实例,最好通过其构造函数注释其属性。这意味着将 VALUE_PARAMETER 类型的注释应用于数据类构造函数属性,可以通过构造函数参数本身的反射来发现它们。

从更一般的意义上来说,Java 定义的注解类型only适用于 Java Class 反射,而 Kotlin 扩展的注解类型only适用于 KClass 反射。 Kotlin 编译器将禁止您在 Java 元素上使用 Kotlin 特定的注释类型。这里的例外是,它允许您将 Java 注释类型应用于 Kotlin 概念(带有支持字段的属性),这些概念“归结为”Java 原生概念。 (FWIW,如果您将 Java 本机注释代码复制到 Kotlin 并让它自动转换,如果不考虑这一点,转换可能没有意义。)

如果您最喜欢的 Java 库仅公开适用于 Java 层概念的注释,请考虑要求他们提供 Kotlin 扩展,以帮助您在更纯粹的 Kotlin 级别使用他们的注释。尽管在 Java 代码中使用这可能会很棘手。

请有人更新文档。 :-)


4
投票

虽然我可以在 Kotlin 中找到它

KClass
,但我可以在 Java 中找到它。

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class PropertyName(val value: String)

data class MyDataClass(
    @PropertyName("z") val x: Int
)

我使用以下代码

val a = MyDataClass(1)
a::class.java.declaredFields.forEach {
    it.annotations.forEach { annotation ->
        Log.e(it.name, annotation.toString())
    }
}

打印

2018-12-19 11:33:07.663 25318-25318/com.example.application E/x: @com.example.PropertyName(value=z)

© www.soinside.com 2019 - 2024. All rights reserved.