我正在使用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反射以某种方式发现此批注。据我所知,事实并非如此。我试过迭代以下位置的注释:
它只是没有出现在任何地方。
当我像这样更改注释的用法时(请注意目标说明符“立即获取”):
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())
}
}
答案 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代码中使用它可能很棘手。
请有人更新文档。 :-)