深入理解并学会运用Kotlin注解

前言

注解(Annotations)允许我们在代码中添加元数据(Meta data),提供代码以外的信息,这些元数据可以在编译时被编译器或其他工具读取和处理。 Kotlin作为一种功能强大且易于使用的多范式通用编程语言,注解(Annotations)是其核心特性之一。在Kotlin中,注解的使用非常广泛,可以用于框架设计、代码生成、测试、依赖注入等多个方面。今天就来学习一下Kotlin中注解的使用方法。

Kotlin是基于JVM的编程语言,并且可以与Java互通使用,因此事先了解一下Java的注解对于学习Kotlin的注解是非常有帮助的。可以阅读一下前面的文章来回顾Java语言的注解

什么是注解

注解是元编程的一种实现方式,它并不直接改变代码,而是为代码提供额外的数据。注解不能单独存在,必须与代码中的其他元素一起使用。在Kotlin中,注解要使用符号『@』后面加一个已定义的注解名字,如『@Deprecated』。注解在Kotlin中的使用非常广泛的,相信有过代码经验的同学都至少看过大量的注解。

注解的使用方法

注解的使用是非常的直观的,在需要的代码元素(类,变量,属性,函数,参数等等)加上想要使用的注解就可以了:

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
    }
}

Kotlin的注解也可以用在lambda上面,这实际上相当于应用于lambda函数生成的函数实例的invoke()上面:

annotation class Suspendable

val f = @Suspendable { Fiber.sleep(10) }

注解的使用点目标

由于Kotlin最终要编译成为字节码,运行在JVM上,所以它必须符合Java的规范。但语法上Kotlin与Java还是不一样的,比如一句Kotlin代码可能会相当于Java的好几句,换句话说一个Kotlin语句中的元素可能会对应着Java中的好几个。这可能会带来问题。

注解并不能单独出现,它必须作用到某一个语法上的元素,因为Kotlin语法元素可能会对应着几个Java语法元素,那么注解可能会被用在多个目标元素上面。为了能精确的指定注解的作用目标,可以使用『使用点目标』(use-site targets)来标记具体的目标元素:

class Example(@field:Ann val foo,    // annotate Java field
              @get:Ann val bar,      // annotate Java getter
              @param:Ann val quux)   // annotate Java constructor parameter

这里面『Ann』是一个注解,其前面的『field/get/param』就用以指定具体的注解目标元素。可用的使用点目标有这些:

  • file
  • property
  • field
  • get 属性的getter
  • set 属性的setter
  • receiver 扩展函数或者扩展属性的底层对象
  • param 构造函数的参数
  • setparam 属性setter的参数
  • delegate 指存储着受托对象实例的域成员

『receiver』指的是扩展函数发生作用的实例,比如说:

fun @receiver:Fancy String.myExtension() { ... }

那么,这个注解『Fancy』将作用于具体调用这个扩展方法myExtension的String实例上面。

这些具体的使用点目标可以精确的指定JVM认识的元素上面,可以发现,它们远比定义注解时的要丰富。如果不指定具体的使用点目标,那么就会按照@Target指定的目标,如果有多个目标,会按如下顺序选择:

  • param
  • property
  • field

兼容Java注解

Kotlin是完全兼容Java注解,也就是说Java中定义的注解,在Kotlin中都可以直接使用。

// Java
public @interface Ann {
    int intValue();
    String stringValue();
}

// Kotlin
@Ann(intValue = 1, stringValue = "abc") class C

虽然可以直接用,但毕竟Kotlin的语法要丰富得多,所以为了避免歧义,要使用前面介绍的使用点目标来精确指定注解的作用目标。

自定义注解

使用关键字『annotation』来声明自定义注解,如:

annotation class Fancy

之后就可以使用注解了:

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
    }
}

光这样声明还不够,还需要定义注解具体的内容,如可修饰的目标和行为特点,这就需要用到元注解(Meta annotations),也即定义注解时所需要的注解。

元注解(Meta annotations)

@MustBeDocumented

用于指定此注解是公开API的一部分,必须包含在文档中。

@Repeatable

允许在同一个地方多次使用注解。

@Retention

指定注解信息保存到代码生命周期的哪一阶段,编译前,编译时还是运行时。默认值是运行时,也即在运行时注解是可见的。

构造方法(Constructors)

与Java很不同的是Kotlin的注解更加的像常规的类(class),注解也可以有构造函数:

annotation class Special(val why: String)

@Special("example") class Foo {}

构造函数可以使用的参数包括:

  • 基础数据类型Int,Long,Float和String等
  • 类型原型(即class,如Foo::class)
  • 枚举类型
  • 其他注解类型
  • 由以上类型组成的数组

注意不能有可能为空(如String?)的类型,当然也不可以传递null给注解的构造函数。还有,如果用其他注解作为参数时,注解名字前就不用再加『@』了:

annotation class ReplaceWith(val expression: String)

annotation class Deprecated(
        val message: String,
        val replaceWith: ReplaceWith = ReplaceWith(""))

注解的实例化(Instantiation)

在Kotlin中可以通过调用注解的构造函数来实例化一个注解来使用。而不必非要像Java那样用反射接口去获取。

annotation class InfoMarker(val info: String)

fun processInfo(marker: InfoMarker): Unit = TODO()

fun main(args: Array<String>) {
    if (args.isNotEmpty())
        processInfo(getAnnotationReflective(args))
    else
        processInfo(InfoMarker("default"))
}

注解解析

Kotlin是基于JVM的编程语言,最终要编译成为字节码运行在JVM上面,所以注解的解析与Java语言注解解析是一样的,可以在运行时用反射API来解析注解。,因为运行时注解解析用处并不大,并且也不复杂,看一个简单就可以了:

class Item(
  @Positive val amount: Float, 
  @AllowedNames(["Alice", "Bob"]) val name: String)
  
val fields = item::class.java.declaredFields
for (field in fields) {
    for (annotation in field.annotations) {
        if (field.isAnnotationPresent(AllowedNames::class.java)) {
            val allowedNames = field.getAnnotation(AllowedNames::class.java)?.names
         }
    }
}

注解处理器

注解是元编程的一种方式,它最大的威力是在编译前进行代码处理和代码生成。除了注解的定义和使用外,更为关键的注解的处理需要用到注解处理器(Annotation Processor),并且要配合编译器插件kapt来使用。

需要注意,因为注解是JVM支持的特性,在编译时需要借助javac编译器,所以只有运行目标是JVM时注解才有效。因为Kotlin是支持编译为不同运行目标的,除了JVM外,还有JavaScript和Native。

实现注解处理器

与Java的注解处理器类似,在定义好注解后,还需要实现一个注解处理器,以对注解进行处理。一般情况下实现AbstractProcessor就可以了。在其process方法中过滤出来想要处理的注解进行处理,比如使用KotlinPoet生成代码。

另外,还要注意,注解处理器必须在一个单独的module中,然后添加为使用此注解module的依赖,这是因为注解的处理是在编译前,所以处理器需要在正式编译前就已经编译好。

package net.toughcoder

import javax.annotation.processing.*
import javax.lang.model.element.*
import javax.tools.Diagnostic

@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class MyAnnotationProcessor : AbstractProcessor() {

    override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment): Boolean {
        for (annotation : annotations) {
            for (element : roundEnv.getElementsAnnotatedWith(annotation)) {
                val myAnnotation = element.getAnnotation(MyAnnotation::class.java)
                val message = "Processing element with annotation MyAnnotation(value = ${myAnnotation.value})"
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message, element)
            }
        }
        return true
    }
}

从例子中可以看到,其实Kotlin中的注解处理器(Processor)直接就是用的Java的,所以在用的时候最好加上Java语言的版本。

注册注解处理器

为能正常使用注解处理器,需要把注解处理器放在一个单独的Module里,并作为其他module的依赖,这样能确保它在编译被依赖项时正常使用,被依赖项也即注解使用的地方。

需要在处理器module中与代码平级的文件夹创建resources文件夹,创建一个子文件夹META-INF,再在META-INF创建一个子文件services,在里面创建一个文件名为『javax.annotation.processing.Processor』,然后把实现的注解处理器的完整类名,写在这个文件的第一行:

// file: resources/META-INF/services/javax.annotation.processing.Processor
net.toughcoder.MyAnnotationProcessor

使用注解处理器

需要做两个事情,一个是把注解处理器添加为其他项目或者module的依赖。然后再用专门处理注解处理器的编译器插件使用注解处理器。

dependencies {
    implementation(kotlin('stdlib'))
    kapt 'net.toughcoder:my-annotation-processor:1.0.0'
}

kapt {
    useBuildCache = true
    annotationProcessors = ['net.toughcoder:my-annotation-processor:1.0.0']
}

总结

本文介绍了Kotlin中注解的基本语法、使用方法和处理过程。通过自定义注解处理器,我们可以在编译时处理注解并生成相应的代码或执行其他任务。注解是Kotlin编程中的核心特性,它可以帮助我们提高代码的可读性、可维护性和可扩展性。大部分的注解都在编译时,也不会对性能产生影响,所以可以放心大胆的用注解来提升开发效率。

如何学习Kotlin

Kotlin作为一种现代的、静态类型的编程语言,拥有诸多独特且强大的特性,虽然Kotlin语法简洁,但是想要深入理解他的新特性,熟练的使用在工作上面还是得要花费很大的时间成本来学习,因此我给大家准备了Kotlin从入门到精通高级Kotlin强化实战两份资料来帮助大家系统的学习Kotlin,需要的朋友扫描下方二维码,免费领取!!!

Kotlin从入门到精通

准备开始

  • 基本语法
  • 习惯用语
  • 编码风格在这里插入图片描述

基础

  • 基本类型
  • 控制流
  • 返回与跳转在这里插入图片描述

类和对象

  • 类和继承
  • 属性和字段
  • 接口
  • 可见性修饰词
  • 扩展
  • 数据对象
  • 在这里插入图片描述

函数和lambda表达式

  • 函数
  • 高级函数和lambda表达式
  • 内联函数在这里插入图片描述

其他

  • 多重申明
  • Ranges
  • 类型检查和自动转换
  • This表达式
  • 等式
  • 运算符重载
  • 在这里插入图片描述

互用性

  • 动态类型

工具

  • Kotlin代码文档
  • 使用Maven
  • 使用Ant
  • 使用Griffon
  • 使用Gradle在这里插入图片描述

FAQ

  • 与Java对比
  • 与Scala对比在这里插入图片描述

高级Kotlin强化实战

第一章 Kotlin入门教程

  • 1.Kotlin概述
  • 2.Kotlin与Java比较
  • 3.巧用Android Studio
  • 4.认识Kotlin基本类型
  • 5.走进Kotlin的数组
  • 6.走进Kotlin的集合
  • 7.集合问题
  • 8.完整代码
  • 9.基础语法在这里插入图片描述

第二章 Kotlin实战避坑指南

  • 2.1 方法入参是常量,不可修改
  • 2.2 不要 Companion 、INSTANCE ?
  • 2.3 Java 重载,在 Kotlin 中怎么巧妙过渡一下?
  • 2.4 Kotlin 中的判空姿势
  • 2.5 Kotlin 复写 Java 父类中的方法
  • 2.6 Kotlin “狠”起来,连TODO 都不放过!
  • 在这里插入图片描述

第三章 项目实战《Kotlin Jetpack实战》

  • 3.1 从一个膜拜大神的 Demo 开始
  • 3.2 Kotlin 写 Gradle 脚本是一种什么体验?
  • 3.3 Kotlin 编程的三重境界
  • 3.4 Kotlin 高阶函数
  • 3.5 Kotlin泛型
  • 3.6 Kotlin 扩展
  • 3.7 Kotlin 委托
  • 3.8 协程“不为人知”的调试技巧
  • 3.9 图解协程:suspend在这里插入图片描述
完整学习文档,可以扫描下方二维码免费领取!!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值