- kotlin值得系列1:内置类型。基本类型,数组,Range区间,集合框架,函数,Lambda
- kotlin值得系列2,关于类你所应该知道的一切
- kotlin值得系列3、函数加强之 高阶函数、内联函数、集合变换与序列、SAM转换、常用的高阶函数
- kotlin值得系列4、变量,常量,表达式,运算符
- kotlin值得系列5、关于kotlin泛型,也许值得看的文章
- kotlin值得系列6、kotlin的反射,走一个?
- kotlin值得系列7、也是该看看注解annotation了
本文是Kotlin系列第7篇。
注解实际上就是一种代码标签,它作用的对象是代码。它可以给特定的注解代码标注一些额外的信息。然而这些信息可以选择不同保留时期,比如源码期、编译期、运行期。然后在不同时期,比如运行期可以通过某种方式获取标签的信息来处理实际的代码逻辑,这种方式常常就是我们所说的反射。
一、注解
- 注解是对
程序附加信息的说明 - 注解可以对类,函数,函数参数,属性等做标注
- 注解的信息可用于源码级,编译期,运行期
直接的使用场景
- 1、提供信息给编译器:
编译器可以利用注解来处理一些,比如一些警告信息,错误等 - 2、编译阶段时处理:
利用注解信息来生成一些代码,在Kotlin生成代码非常常见,一些内置的注解为了与Java API的互操作性,往往借助注解在编译阶段生成一些额外的代码。 - 3、运行时处理: 某些注解可以在程序运行时,
通过反射机制获取注解信息来处理一些程序逻辑。
二、注解的定义
annotation class Api
- annotation 标记的为 注解类
三、元注解
一个Kotlin注解类自己本身也可以被注解,可以给注解类加注解。我们把这种注解称为元注解,可以把它理解为一种基本的注解,可以把它理解为一种特殊的标签,用于标注标签的标签。
Kotlin中的元注解类定义于kotlin.annotation包中,主要有:
- @Target
- @Retention
- @Repeatable
- @MustBeDocumented
注解相比Java中5种元注解: @Target、 @Retention、 @Repeatable、 @Documented、 @Inherited,少了 @Inherited元注解。
为什么kt没有 @Inherited 元注解?
Inheried顾名思义就是继承的意思,但是这里需要注意并不是表示注解类可以继承,而是Java中如果一个父类被贴上@Inherited元注解标签,那么它的子类没有任何注解标签的话,这个子类就会继承来自父类的注解。
我们都知道在Java中,无法找到子类方法是否重写了父类的方法。因此不能继承父类方法的注解。然而Kotlin目前不需要支持这个@Inherited元注解,因为Kotlin可以做到,如果反射提供了`override`标记而且很容易做到。
.
来个小例子你就知道了
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Api(val url:String)
@Retention和@Target就是两个元注解啦,给 注解annotation 的注解。 接下来,我们就这4种注解,分别说一说。
三.1、@Target
Target顾名思义就是目标对象,也就是这个标签作用于哪些代码中目标对象,可以同时指定多个作用的目标对象。
@Target注解目标和AnnotationTarget枚举类
在@Target注解中可以同时指定一个或多个目标对象,那么到底有哪些目标对象呢?这就引出另外一个AnnotationTarget枚举类
public enum class AnnotationTarget {
CLASS, //表示作用对象有类、接口、object对象表达式、注解类
ANNOTATION_CLASS,//表示作用对象只有注解类
TYPE_PARAMETER,//表示作用对象是泛型类型参数(暂时还不支持)
PROPERTY,//表示作用对象是属性
FIELD,//表示作用对象是字段,包括属性的幕后字段
LOCAL_VARIABLE,//表示作用对象是局部变量
VALUE_PARAMETER,//表示作用对象是函数或构造函数的参数
CONSTRUCTOR,//表示作用对象是构造函数,主构造函数或次构造函数
FUNCTION,//表示作用对象是函数,不包括构造函数
PROPERTY_GETTER,//表示作用对象是属性的getter函数
PROPERTY_SETTER,//表示作用对象是属性的setter函数
TYPE,//表示作用对象是一个类型,比如类、接口、枚举
EXPRESSION,//表示作用对象是一个表达式
FILE,//表示作用对象是一个File
@SinceKotlin("1.1")
TYPEALIAS//表示作用对象是一个类型别名
}
.
三.2、@Retention
Retention对应的英文意思是保留期,当它应用于一个注解上表示该注解保留存活时间,不管是Java还是Kotlin一般都有三种时期: 源代码时期(SOURCE) 、编译时期(BINARY) 、运行时期(RUNTIME) 。
@Retention元注解取值主要来源于AnnotationRetention枚举类
public enum class AnnotationRetention {
SOURCE,//源代码时期(SOURCE): 注解不会存储在输出class字节码中
BINARY,//编译时期(BINARY): 注解会存储出class字节码中,但是对反射不可见
RUNTIME//运行时期(RUNTIME): 注解会存储出class字节码中,也会对反射可见, 默认是RUNTIME
}
.
三.3、@Repeatable
可重复注解,它允许相同的程序元素中重复注解,可重复注解必须使用这个@Repeatable 来进行注解
.
三.4、@MustBeDocumented
该注解比较简单,主要是为了标注一个注解类作为公共API的一部分,并且可以保证该注解在生成的API文档中存在。
该注解可以修饰代码元素(类,接口,函数,属性等),文档生成工具可以提取这些注解信息
.
.
需要来个例子了
说了那么多,来个例子润润喉咙
例子:自定义注解实现API调用时的请求方法检查
public enum class Method {
GET,
POST
}
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class HttpMethod(val method: Method)
interface Api {
val name: String
val version: String
get() = "1.0"
}
@HttpMethod(Method.GET)
class ApiGetArticles : Api {
override val name: String
get() = "/api.articles"
}
fun fire(api: Api) {
val annotations = api.javaClass.annotations
val method = annotations.find { it is HttpMethod } as? HttpMethod
println("通过注解得知该接口需要通过:${method?.method} 方式请求")
}
.
四、给注解添加参数
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Api(val url:String)
- val url:String 这里就是给注解添加参数
注解的参数支持的类型
- 基本类型
- KClass
- 枚举
- 其他注解
五、Kotlin中的预置注解
kotlin的语法和java的语法还是有很大的不同,针对这个问题,kotlin中预置了一些注解,解决与Java兼容问题。
-
JvmDefault: kotlin中,给接口中的方法设置实体,反编译成java代码后,会在接口中生成一个静态内部类DefaultImpls,将方法的实体放入其中,对应的调用时都需要先调用这个静态内部类。使用JvmDefault可以去除这个静态内部类,将方法实体直接放入了接口之中(JAVA8)。 -
JvmFiled:kotlin中定义的公开属性,反编译成java中的属性是私有的且将setter和getter暴漏出来的,不能直接访问;加上JvmFiled,变量在java中就成了公开变量,可以直接访问。在伴生对象companion object中定义一个val变量,生成的会是一个私有变量但将getter方法生成到Companion对象中,在java中访问需要调用伴生对象中的getter方法,给这个变量加上const修饰词会让生成的变量是公开静态变量,加上JvmFiled注解效果相同,让变量可以在Java中直接访问,不用加上Companion。 -
JvmName:指定kotlin反编译的Java的方法名,字段名,类名等。 -
JvmMultifileClass:将不同的kotlin文件生成到一个java文件中,需要使用JvmName指定生成的文件名,之后就可以在java中通过这个文件名访问不同kotlin中代码。 -
JvmOverloads:kotlin中的方法可以给入参设置默认值,这样在kotlin中调用的时候就可以不传入所有的值,但是在java中却不能这样调用,因为生成的java代码只有一个传入了全部参数的方法。使用Jvmoverloads指定函数生成多个重载,就可以在Java中调用。一般需要在自定义view的时候使用,将构造函数重载成多个,否则在XML中使用这个view会抛出异常。 -
JvmPackageName:指定从该文件生成.class文件的包名称。 -
JvmStatic:想要在kotlin中定义静态方法是将这些方法放入伴生对象中,虽然在kotlin中可以通过调用静态方法那样通过类名调用,但反编译Java是通过定义一个静态内部类Companion,其中存放着伴生对象中的方法,类中持有一个这个内部静态类的实例对象来调用这些方法。给这些方法加上JvmStatic注解之后,会把这些方法暴露成java的静态方法。如果标注的是静态属性,则会生成静态的getter和setter。 -
JvmSuppressWildcards:在kotlin中,没有通配符(类如? extends Number),可以直接进行泛型转换val numberList : List<Number> = ArrayList<Int>(),这种写法在Java中会报错。默认情况下,kotlin反编译Java的时候会在必要的情况下(1.类型不是最终类。2.不是函数的返回类型。)生成通配符。- 类如下面生成的方法中,入参生成
Number的通配符List<? extends Number>,另一个入参List<String>因为是最终类没有生成,返回值也没有生成。 - 某些情况下,如果想要更改默认的生成行为,可以使用
JvmSUppressWildcards,此注释可传入一个布尔值(默认值是true),true表示生成类型不带通配符,false表示生成类型带通配符。
- 类如下面生成的方法中,入参生成
fun transformList(list : List<Number>, list : List<String>) : List<Number> // Kotlin
public List<Number> transformList(List<? extends Number> list, List<String> list) // Java
复制代码
JvmWildcard:与上一个一致,相当于JvmSuppressWildcards(false),不同的是可以做用的目标对象不同,JvmWildcard只能作用于类型参数,JvmSuppressWildcards可以作用于类、函数、属性、类型参数。JvmSynthetic:在生成的Java字节码中加上'ACC_SYNTHETIC'标志,标记为合成,禁止方法在java中访问。Throws:kotlin中没有受检的异常(不强制要求必须处理异常),可以有选择的决定是否对异常进行处理,所以在kotlin中没有声明异常的throws关键字。使用注解Thorws会让生成的Java代码中进行throws,这样在java中调用的时候会检查这个异常。Volatile:充当java中vilatile关键字(多线程变量可见性)。Transient:充当java中transient关键字(不被序列化)。Strictfp:充当java中strictfp关键字(浮点数精度)。Synchronized:充当java中synchronized关键字(锁)。
例子的话,后面可有空在写吧。
关于这些预置注解,可以查看这篇文章:zhuanlan.zhihu.com/p/63602769