Kotlin 中的属性注解

105 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第 1 天,点击查看活动详情

背景

最近阅读源码时,注意到这样一种注解(Annotation),

interface Component: ComponentIdentity {
    // ...
    
    @get:Incubating // <-- 这里
    val sources: Sources
    
    // ...
}

对这种新奇的用法比较陌生,遂有此文,以作记录。

分析过程

Google 搜索

根据经验,提取关键字 kotlin property annotation 进行搜索,得到结果如下: Google 搜索结果 一般通过 Google 搜索技术问题,返回的前几个结果通常是官方文档StackOverFlow,我一般会将它们都打开——这是我学习新知识的习惯做法:将官方文档和社区文章结合起来,来回阅读。

阅读官方文档

Kotlin Annotation 的官方文档链接:kotlinlang.org/docs/annota…

从官方文档页面,注意到这是 Annotation use-site targetskotlinlang.org/docs/annota…

阅读社区文档

StackOverFlow 上的热门回答:stackoverflow.com/a/59925322

官方文档可能会比较难阅读,尤其当涉及到一些较为新的概念时。所以结合社区高质量的文档辅助去理解,很有必要。

正文

Kotlin 自动生成 Java Bytecode 规则

有个很基础的知识点:当定义了一个属性(Property),Kotlin 编译器会为该属性,生成一个 getter 和一个 setter(当且仅当是 var 类型属性时)方法。

Kotlin 中的一个属性,它可能出现的位置至少有:

  • 构造函数参数列表
  • 属性自身在类中的定义
  • getter 方法
  • setter 方法(当且仅当是 var 类型属性)
  • setter 方法的参数列表

例如下面👇这块代码:

data class Person(
    var name: String,
)

则对应的 Java 代码为:

public final class Person {
    /**
     * position 2
     */
    private String name;
    
    /**
     * position 3
     */
    public String getName() { return this.name; }
    
    /**
     * position 4
     */
    public void setName(/* position 5 */String var1) { this.name = var1; }
    
    public Person(/* position 1 */String name) { this.name = name; }
    
    
}

注1:省略了与本文非关键代码,如空检查、。

注2:使用 Kotlin 1.7.20 版本。

use-site targets

考虑上面这种情况,当为 Kotlin 中的一个属性添加注解时,这个注解应用到哪里了呢?还是凡该属性出现的位置,都会应用这个注解?

这似乎让注解有点表意不清,Kotlin 为解决这个问题,为注解引入 use-site targets 的概念,即允许通过关键字,指定注解实际作用的位置。这些关键字包括不限于:

  • param——作用于 Java 代码示例中的 position 1
  • field——作用于 Java 代码示例中的 position 2
  • get——作用于 Java 代码示例中的 position 3
  • set——作用于 Java 代码示例中的 position 4
  • setparam——作用于 Java 代码示例中的 position 5

更多的关键字在官网查看:kotlinlang.org/docs/annota…

继续为类 Person 为例,

data class Person(
    @set:NameCheck // 注:这里 NameCheck 是一个注解
    var name: String
)

对应的 Java 代码为:

public final class Person {
    /**
     * position 2
     */
    private String name;
    
    /**
     * position 3
     */
    public String getName() { return this.name; }
    
    /**
     * position 4
     */
    @NameCheck // <-- 将会出现在这里
    public void setName(/* position 5 */String var1) { this.name = var1; }
    
    public Person(/* position 1 */String name) { this.name = name; }
    
}

缺省时的预期

一般不了解 use-site targets 的话,很多时候会这么写:

data class Person(
    @SerializedName("name")
    var name: String,
)

这时候 @SerializedName 注解会作用在哪里呢?看下对应的 Java 代码,

public final class Person {
    /**
     * position 2
     */
    @SerializedName <-- 在这里
    private String name;
    
    /**
     * position 3
     */
    public String getName() { return this.name; }
    
    /**
     * position 4
     */
    public void setName(/* position 5 */String var1) { this.name = var1; }
    
    public Person(/* position 1 */String name) { this.name = name; }
    
}

查找官方文档发现,当未指定注解的作用对象时,将会根据注解的 @Target 所指定的类型,按照如下的优先顺序,应用在第一个可应用的对象,

  1. param
  2. property
  3. field

总结

形如 @get:Incubating@field:SerializedName 的用法为 Kotlin 的 Annotation use-site targets。它让使用者可以指定注解实际作用的对象,以实现更精准的语义和某些特定的用法。