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

时间:2018-12-19 02:32:03

标签: reflection kotlin annotations jvm

我正在使用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方法

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

当我像这样更改注释的用法时(请注意目标说明符“立即获取”):

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

注释现在显示在Java类对象的生成的getter中。这至少在实践中是可行的,但是我很好奇为什么Kotlin让我将注释编译为以字段为目标的注释,但是却不允许我在运行时将其删除(除非我在运行时丢失了某些内容) Kotlin反射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())
    }
}

2 个答案:

答案 0 :(得分:1)

虽然我可以在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)

答案 1 :(得分:0)

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

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

如果要注释Kotlin类属性,并通过KClass反射找到它,则必须使用PROPERTY类型注释,这对于Kotlin是唯一的。这样,如果您在KClass的members列表中找到该属性,它将具有该注释(但没有底层的支持字段!)。

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

从更一般的意义上讲,仅由Java定义的注释类型仅适用于Java类反射,而仅由Kotlin扩展的注释类型仅适用于KClass反射。 。 Kotlin编译器将禁止您在Java元素上使用Kotlin特定的注释类型。这里的例外是,它将允许您将Java批注类型应用于“简化”为Java本机概念的Kotlin概念(具有后备字段的属性)。 (FWIW,如果您将Java本机注释代码复制到Kotlin中并进行自动转换,则在这种情况下转换可能没有意义。)

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

请有人更新文档。 :-)