注解是什么?简单说注解就是一种标注(标记、标识),没有具体的功能逻辑代码。经过注解开发人员能够在不改变原有代码和逻辑的状况下在源代码中嵌入补充信息。Kotlin注解的使用和Java彻底同样,声明注解类的语法略有不一样。Java 注解与 Kotlin 100% 兼容。html
注解能够把额外的元数据关联到一个声明上,而后元数据能够被反射机制或相关的源代码工具访问。java
Kotlin的声明注解的语法和常规类的声明很是类似,但须要在class
关键字以前加上annotation
修饰符。但Kotlin编译器禁止为注解类指定类主体,由于注解类只是用来定义关联到 声明 和 表达式 的元数据的结构。android
#daqiKotlin.kt
annotation class daqiAnnotation
复制代码
Java注解声明:数组
#daqiJava.java
public @interface daqiAnnotation {
}
复制代码
注解能够有接受参数的构造函数。bash
其中注解的构造函数容许的参数类型有:jvm
当注解做为另外一个注解的参数,则其名称不用以 @
字符为前缀:函数
annotation class daqiAnnotation(val str: String)
annotation class daqiAnnotation2(
val message: String,
val annotation: daqiAnnotation = daqiAnnotation(""))
复制代码
当须要将一个类指定为注解的参数,请使用 Kotlin 类 (KClass)。Kotlin 编译器会自动将其转换为 Java 类,以便 Java 代码可以正常看到该注解及参数 。工具
annotation class daqiAnnotation(val arg1: KClass<*>, val arg2: KClass<out Any>)
@daqiAnnotation(String::class, Int::class) class MyClass
复制代码
将其反编译后,能够看到转换为相应的Java类:post
@daqiAnnotation(
arg1 = String.class,
arg2 = int.class
)
public final class MyClass {
}
复制代码
注意:注解参数不能有可空类型,由于 JVM 不支持将 null 做为注解属性的值存储。gradle
和Java同样,Kotlin的注解类也使用元注解进行注解。用于其余注解的注解称为元注解,能够理解为最基本的标注。
Kotlin标准库中定义了4个元注解,分别是:MustBeDocumented、Repeatable、Retention、Target。
@Target用于指定能够应用该注解的元素类型(类、函数、属性、表达式等)。
查看Target的源码:
#Annotation.kt
@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets: AnnotationTarget)
复制代码
@Target注解中能够同时接收一个或多个AnnotationTarget
枚举值:
public enum class AnnotationTarget {
//做用于类(包括枚举类)、接口、object对象和注解类
CLASS,
//仅做用于注解类
ANNOTATION_CLASS,
//做用于泛型类型参数(暂时不支持)(JDK8)
TYPE_PARAMETER,
//做用于属性
PROPERTY,
//做用于字段(包括枚举常量和支持字段)。
FIELD,
//做用于局部变量
LOCAL_VARIABLE,
//做用于函数或构造函数的参数
VALUE_PARAMETER,
//做用于构造函数(包括主构造函数和次构造函数)
CONSTRUCTOR,
//做用于方法(不包括构造函数)
FUNCTION,
//仅做用于属性的getter函数
PROPERTY_GETTER,
//仅做用于属性的setter函数
PROPERTY_SETTER,
//做用于类型(如方法内参数的类型)
TYPE,
//做用于表达式
EXPRESSION,
//做用于文件,可配合 file点目标 使用: (例如: @file:JvmName("daqiKotlin"))
FILE,
//做用于类型别名
@SinceKotlin("1.1")
TYPEALIAS
}
复制代码
注意:Java代码中没法使用Target
为AnnotationTarget.PROPERTY
的注解。若是想让这样的注解在Java中使用,能够添加多一条AnnotationTarget.FIELD
的注解。
@Retention 声明注解的保留策略。
查看Retention的源码:
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)
复制代码
@Retention注解中能够接收一个AnnotationRetention
枚举值:
public enum class AnnotationRetention {
//表示注解仅保留在源代码中,编译器将丢弃该注解。
SOURCE,
//注解将由编译器记录在class文件中 但在运行时不须要由JVM保留。
BINARY,
//注解将由编译器记录在class文件中,并在运行时由JVM保留,所以能够反射性地读取它们。(默认行为)
RUNTIME
}
复制代码
注意:Java的元注解默认会在.class文件中保留注解,但不会让它们在运行时被访问到。大多数注解须要在运行时存在,以致于Kotlin将RUNTIME
做为@Retention
注解的默认值。
容许在单个元素上屡次使用相同的该注解;
查看Repeatable的源码:
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Repeatable
复制代码
注意:在"尝试使用"@Repeatable
时发现,该注解必需要在Retention
元注解指定为AnnotationRetention.SOURCE
时才能重复使用,但Java的@Repeatable
元注解并无该限制。(具体的Java @Repeatable
元注解的使用示例能够看这篇文章)。由于@Repeatable
是Java 8引入的新的元注解,而兼容Java 6的Kotlin对此有点不兼容?
指定该注解是公有 API 的一部分,而且应该包含在生成的 API 文档中显示的类或方法的签名中。
查看MustBeDocumented的源码:
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class MustBeDocumented
复制代码
相对Java的5个元注解,Kotlin只提供了与其对应的4个元注解,Kotlin暂时不须要支持@Inherited
元注解。
@Inherited
注解代表注解类型能够从超类继承。具体意思是:存在一个带@Inherited
元注解的注解类型,当用户在某个类中查询该注解类型而且没有此类型的注解时,将尝试从该类的超类以获取注解类型。重复此过程,直到找到此类型的注解,或者到达类层次结构(对象)的顶部为止。若是没有超类具备此类型的注解,则查询将指示相关类没有此类注解。此注解仅适用于类声明。
Kotlin为了与Java具备良好的互通性,定义了一系列注解用于携带一些额外信息,以便编译器作兼容转换。
将Kotlin接口的默认方法生成Java 8的默认方法的字节码
查看源码:
@SinceKotlin("1.2")
@RequireKotlin("1.2.40", versionKind = RequireKotlinVersionKind.COMPILER_VERSION)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
annotation class JvmDefault
复制代码
前面接口和类中提到,当在Kotlin中声明一个带默认方法的接口时,每每会将这些“默认方法”声明为抽象方法,同时也会在该接口中生成一个DefaultImpls
静态内部类,并在其中定义同名的静态方法来提供默认实现。
但这样会存在一个问题,当对旧的Kotlin接口添加新的默认方法时,实现该接口的Java类须要从新实现新添的接口方法,不然会编译不经过。同是默认方法,但与Java 8引入默认方法的初衷相违背。为此,Kotlin提供了@JvmDefault
注解。对标有@JvmDefault
注解的默认方法,编译器会将其编译为Java 8的默认接口。
#daqiKotlin.kt
public interface daqiInterface{
@JvmDefault//刚添加会报错
fun daqiFunc() = println("带@JvmDefault的默认方法")
fun daqiFunc2() = println("默认方法")
}
复制代码
#java文件
public interface daqiInterface {
@JvmDefault
default void daqiFunc() {
String var1 = "带@JvmDefault的默认方法";
System.out.println(var1);
}
void daqiFunc2();
public static final class DefaultImpls {
public static void daqiFunc2(daqiInterface $this) {
String var1 = "默认方法";
System.out.println(var1);
}
}
}
复制代码
当你直接添加 @JvmDefault
时,编译器会报错。这时你须要在Gradle中配置如下参数:(具体Kotlin使用Gradle看官网)
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = ['-Xjvm-default = compatibility']
//freeCompilerArgs = ['-Xjvm-default = enable']
}
}
复制代码
经过@JvmDefault
的注释得知,配置时能够选择-Xjvm-default = enable
或-Xjvm-default = compatibility
。这两个的区别是:
-Xjvm-default = enable
会从DefaultImpls
静态内部类中删除对应的方法。-Xjvm-default = compatibility
仍会在DefaultImpls
静态内部类中保留对应的方法,提升兼容性。注意:只有JVM目标字节码版本1.8(-jvm-target 1.8
)或更高版本才能生成默认方法。
指示Kotlin编译器不为此属性生成getter / setter并将其修饰为public。
查看源码:
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmField
复制代码
Kotlin声明的属性都默认使用private
修饰,并提供setter
/ getter
访问器对其进行访问。而@JvmField
就是告诉编译器不要为该属性自动建立setter
/ getter
访问器,并将对其使用public
修饰。(用在伴生对象的属性上,可生成public
修饰的static
属性)
#daqiKotlin.kt
class Person{
@JvmField
val name:String = ""
}
复制代码
反编译后查看源码中只声明了一个public对象:
#java文件
public final class Person {
@JvmField
@NotNull
public final String name = "";
}
复制代码
注意该注解只能用在有幕后字段的属性上,对于没有幕后字段的属性(例如:扩展属性、委托属性等)不能使用。由于只有拥有幕后字段的属性转换成Java代码时,才有对应的Java变量。
Kotlin属性拥有幕后字段须要知足如下条件之一:
指定生成Java类的类名或方法名。
查看源码:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FILE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmName(actual val name: String)
复制代码
根据注解的声明,属性的访问器getter / setter也可使用该注解,但属性不能使用~。
在daqiKotlin.kt文件中声明的全部函数和属性(包括扩展函数)都被编译为名为在DaqiKotlinKt的Java类的静态方法。其中文件名首字母会被改成大写,后置Kt。当须要修改该Kotlin文件生成的Java类名称时,可使用@JvmName
名指定生成特定的文件名:
@file:JvmName("daqiKotlin")
package com.daqi.test
@JvmName("daqiStateFunc")
public fun daqiFunc(){
}
复制代码
反编译能够看到生成的Java类名称已经修改成daqiKotlin
,而非DaqiKotlinKt
,同时顶层函数daqiFunc
的方法名被修改成daqiStateFunc
:
public final class DaqiKotlinKt {
@JvmName(name = "daqiStateFunc")
public static final void daqiStateFunc() {
}
}
复制代码
指示Kotlin编译器生成一个多文件的类。该文件具备在此文件中声明的顶级函数和属性。
查看源码:
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class JvmMultifileClass
复制代码
当须要将多个Kotlin文件中的方法和属性归到一个Java类时,能够在多个文件中声明同样的@JvmName
,并在其下面添加@JvmMultifileClass
注解。(多个文件中声明同样的@JvmName
,但不添加@JvmMultifileClass
注解会编译不经过)
#daqiKotlin.kt
@file:JvmName("daqiKotlin")
@file:JvmMultifileClass
package com.daqi.test
fun daqi(){
}
复制代码
#daqiKotlin2.kt
@file:JvmName("daqiKotlin")
@file:JvmMultifileClass
package com.daqi.test
fun daqi2(){
}
复制代码
Kotlin编译器会将该两个文件中的方法和属性合并到@JvmName
注解生成的指定名称的Java类中:
指示Kotlin编译器为此函数生成替换默认参数值的重载函数(从最后一个开始省略每一个参数)。
查看源码:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmOverloads
复制代码
Java并无参数默认值的概念,当你从Java中调用Kotlin的默认参数函数时,必须显示地指定全部参数值。使用@JvmOverloads注解该方法,Kotlin编译器会生成相应的Java重载函数,从最后一个参数开始省略每一个函数。
#daqiKotlin.kt
@JvmOverloads
fun daqi(name :String = "daqi",age :Int = 2019){
println("name = $name,age = $age ")
}
复制代码
将对象声明或伴生对象的方法或属性的访问器暴露成一个同名的Java静态方法。
查看源码:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
public actual annotation class JvmStatic
复制代码
对于Kotlin的对象声明和伴生对象,在Kotlin中能够像静态函数那样调用类名.方法名进行调用。但在Java中,须要在这其中添加多一个Companion
或INSTANCE
,使调用很不天然。使用@JvmStatic
注解标记伴生对象或对象声明中的方法和属性,使其在Java中能够像Kotlin同样调用这些方法和属性。
在Kotlin中定义一个伴生对象,并用标记@JvmStatic
注解:
class daqi{
companion object {
@JvmStatic
val name:String = ""
@JvmStatic
fun daqiFunc(){
}
}
}
复制代码
反编译能够观察到 伴生对象类 或 对象声明类 中声明了属于它们本身的方法和属性,但同时在对象声明类自己或伴生对象类的外部类中也声明了同样的静态的方法和属性访问器供外部直接访问。
public final class daqi {
@NotNull
private static final String name = "";
public static final daqi.Companion Companion = new daqi.Companion((DefaultConstructorMarker)null);
@NotNull
public static final String getName() {
daqi.Companion var10000 = Companion;
return name;
}
@JvmStatic
public static final void daqiFunc() {
Companion.daqiFunc();
}
public static final class Companion {
@JvmStatic
public static void name$annotations() {
}
@NotNull
public final String getName() {
return daqi.name;
}
@JvmStatic
public final void daqiFunc() {
}
}
}
复制代码
因此,若是对象声明和伴生对象须要和Java层进行比较频繁的交互时,建议仍是加上@JvmStatic
@JvmSuppressWildcards
指示编译器为泛型参数生成或省略通配符。(默认是省略)@JvmWildcard
指示编译器为为泛型参数生成通配符。
查看源码:
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmSuppressWildcards(actual val suppress: Boolean = true)
--------------------------------------------------------------------------
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmWildcard
复制代码
指示Kotlin编译器将带该注释的Java类视为给定Kotlin接口的纯实现。“Pure”在这里表示类的每一个类型参数都成为该接口的非平台类型参数。
查看源码:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
public annotation class PurelyImplements(val value: String)
复制代码
Kotlin对来自Java的变量会看成平台类型来处理,由开发者以为其是可空仍是非空。但即使将其声明为非空,但其实他仍是能接收空值或者返回空值。
#java文件
class MyList<T> extends AbstractList<T> { ... }
复制代码
#kotlin文件
MyList<Int>().add(null) // 编译经过
复制代码
但能够借助@PurelyImplements
注解,并携带对应的Kotlin接口。使其与Kotlin接口对应的类型参数不被看成平台类型来处理。
#java文件
@PurelyImplements("kotlin.collections.MutableList")
class MyPureList<T> extends AbstractList<T> { ... }
复制代码
MyPureList<Int>().add(null) // 编译不经过
MyPureList<Int?>().add(null) // 编译经过
复制代码
等价于Java的throws关键字
查看源码:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
public annotation class Throws(vararg val exceptionClasses: KClass<out Throwable>)
复制代码
例子
@Throws(IOException::class)
fun daqi() {
}
复制代码
等价于Java的strictfp关键字
查看源码:
@Target(FUNCTION, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER, CLASS)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Strictfp
复制代码
等价于Java的transient关键字
查看源码:
@Target(FIELD)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Transient
复制代码
等价于Java的synchronized关键字
查看源码:
@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Synchronized
复制代码
等价于Java的volatile关键字
查看源码:
@Target(FIELD)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Volatile
复制代码
许多状况下, Kotlin代码中的单个声明会对应成多个 Java 声明 ,并且它们每 个都能携带注解。例如, Kotlin 属性就对应了 Java 宇段、 getter ,以 及一个潜在的 setter。这时须要使用点目标指定说明注解用在什么地方。
点目标声明被用来讲明要注解的元素。使用点目标被放在@符号和注解名 称之间,并用冒号和注解名称隔开。
点目标的完整列表以下:
//注解的是get方法,而不是属性
@get:daqiAnnotation
val daqi:String = ""
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class daqiAnnotation()
复制代码