简述: 从这篇文章将继续开始探索Kotlin中的一些高级的内容,之前有着重探讨了Kotlin的泛型以及泛型型变等内容。现在我们一起来看下Kotlin中的注解。Kotlin中的注解是100%与Java注解兼容的,有很多相同的地方,但是也有一些不同的地方。一起来瞅瞅吧~

一、注解的本质

注解实际上就是一种代码标签,它作用的对象是代码。它可以给特定的注解代码标注一些额外的信息。然而这些信息可以选择不同保留时期,比如源码期、编译期、运行期。然后在不同时期,可以通过某种方式获取标签的信息来处理实际的代码逻辑,这种方式常常就是我们所说的反射

二、注解的定义

在Kotlin中注解核心概念和Java一样,注解就是为了给代码提供元数据。并且注解是不直接影响代码的执行。一个注解允许你把额外的元数据关联到一个声明上,然后元数据就可以被某种方式(比如运行时反射方式以及一些源代码工具)访问

三、注解的声明(标签的声明)

在Kotlin中的声明注解的方式和Java稍微不一样,在Java中主要是通过 @interface关键字来声明,而在Kotlin中只需要通过 annotation class 来声明, 需要注意的是在Kotlin中编译器禁止为注解类指定类主体,因为在Kotlin中注解只是用来定义关联的声明和表达式的元数据的结构。

  • 1、Kotlin注解声明
package com.mikyou.annotation
//和一般的声明很类似,只是在class前面加上了annotation修饰符
annotation class TestAnnotation(val value: String)
复制代码
  • 2、Java注解声明
package com.mikyou.annotation;
//java中的注解通过@interface关键字进行定义,它和接口声明类似,只不过在前面多加@
public @interface TestAnnotation {String value();
}
复制代码

四、注解的应用

  • 1、在上一步我们知道了如何声明和定义标签了,那么接下来就是用这个标签,如何把我们定义好的标签贴到指定的代码上。在Kotlin中使用注解和Java一样。要应用一个注解都是 @注解类名
@Target(AnnotationTarget.FUNCTION)
@Retention(value = AnnotationRetention.RUNTIME)
annotation class TestAnnotation(val value: Int)//和一般的声明很类似,只是在class前面加上了annotation修饰符class Test {@TestAnnotation(value = 1000)fun test() {//给test函数贴上TestAnnotation标签(添加TestAnnotation注解)//...}
}
复制代码
  • 2、在很多常见的Java或Kotlin框架中大量使用了注解,比如我们最常见的JUnit单元测试框架
class ExampleUnitTest {@Test //@Test注解就是为了告诉JUnit框架,这是一个测试方法,当做测试调用。fun addition_isCorrect() {assertEquals(4, 2 + 2)}
}
复制代码
  • 3、在Kotlin中注解类中还可以拥有注解类作为参数,不妨来下Kotlin中对 @Deprecated这个注解源码定义,以及它的使用。@Deprecated注解在原来的Java基础增强了一个ReplaceWith功能. 可以直接在使用了老的API时,编译器可以根据ReplaceWith中的新API,自动替换成新的API。这一点在Java中是做不到的,你只能点击进入这个API查看源码来正确使用新的API。
//@Deprecated注解比Java多了ReplaceWith功能, 这样当你在调用remove方法,编译器会报错。使用代码提示会自动IntelliJ IDEA不仅会提示使用哪个函数提示替代它,而且会快速自动修正。
@Deprecated("Use removeAt(index) instead.", ReplaceWith("removeAt(index)"), level = DeprecationLevel.ERROR)//定义的级别是ERROR级别的,这样当你在调用remove方法,编译器会报错。
@kotlin.internal.InlineOnly
public inline fun <T> MutableList<T>.remove(index: Int): T = removeAt(index)
复制代码

@Deprecated注解的remove函数使用

//Deprecated注解的使用
fun main(args: Array<String>) {val list = mutableListOf("a", "b", "c", "d", "e")list.remove(3)//这里会报错, 通过remove函数注解定义,这个remove函数在定义的level是ERROR级别的,所以编译器直接抛错
}
复制代码

最后来看下@Deprecated注解的定义

@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)
@MustBeDocumented
public annotation class Deprecated(val message: String,val replaceWith: ReplaceWith = ReplaceWith(""),//注解类中构造器可以使用注解类作为函数参数val level: DeprecationLevel = DeprecationLevel.WARNING
)
@Target()
@Retention(BINARY)
@MustBeDocumented
public annotation class ReplaceWith(val expression: String, vararg val imports: String)
复制代码

注意: 注解类中只能拥有如下类型的参数: 基本数据类型、字符串、枚举、类引用类型、其他的注解类(例如Deprecated注解类中的ReplaceWith注解类)

五、Kotlin中的元注解

和Java一样在Kotlin中,一个Kotlin注解类自己本身也可以被注解,可以给注解类加注解。我们把这种注解称为元注解,可以把它理解为一种基本的注解,可以把它理解为一种特殊的标签,用于标注标签的标签。

Kotlin中的元注解类定义于kotlin.annotation包中,主要有: @Target@Retention@Repeatable@MustBeDocumented 4种元注解相比Java中5种元注解: @Target@Retention@Repeatable@Documented@Inherited少了 @Inherited元注解。

@Target元注解

  • 1、介绍

Target顾名思义就是目标对象,也就是这个标签作用于哪些代码中目标对象,可以同时指定多个作用的目标对象。

  • 2、源码定义
@Target(AnnotationTarget.ANNOTATION_CLASS)//可以给标签自己贴标签
@MustBeDocumented
//注解类构造器参数是个vararg不定参数修饰符,所以可以同时指定多个作用的目标对象
public annotation class Target(vararg val allowedTargets: AnnotationTarget)
复制代码
  • 3、@Target元注解作用的目标对象

在@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//表示作用对象是一个类型别名
}
复制代码

@Retention元注解

  • 1、介绍

Retention对应的英文意思是保留期,当它应用于一个注解上表示该注解保留存活时间,不管是Java还是Kotlin一般都有三种时期: 源代码时期(SOURCE)编译时期(BINARY)运行时期(RUNTIME)

  • 2、源码定义
@Target(AnnotationTarget.ANNOTATION_CLASS)//目标对象是注解类
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)//接收一个参数,该参数有个默认值,默认是保留在运行时期
复制代码
  • 3、@Retention元注解的取值

@Retention元注解取值主要来源于AnnotationRetention枚举类

public enum class AnnotationRetention {SOURCE,//源代码时期(SOURCE): 注解不会存储在输出class字节码中BINARY,//编译时期(BINARY): 注解会存储出class字节码中,但是对反射不可见RUNTIME//运行时期(RUNTIME): 注解会存储出class字节码中,也会对反射可见, 默认是RUNTIME
}
复制代码

@MustBeDocumented元注解

  • 1、介绍

该注解比较简单主要是为了标注一个注解类作为公共API的一部分,并且可以保证该注解在生成的API文档中存在。

  • 2、源码定义
@Target(AnnotationTarget.ANNOTATION_CLASS)//目标对象只能是注解类
public annotation class MustBeDocumented
复制代码

@Repeatable元注解

  • 1、介绍

这个注解决定标注的注解在一个注解在一个代码元素上可以应用两次或两次以上。

  • 2、源码定义
@Target(AnnotationTarget.ANNOTATION_CLASS)//目标对象只能是注解类
public annotation class Repeatable
复制代码

为啥Kotlin去掉了Java中的@Inherited元注解

  • 1、Java中的@Inherited元注解介绍

Inheried顾名思义就是继承的意思,但是这里需要注意并不是表示注解类可以继承,而是如果一个父类被贴上@Inherited元注解标签,那么它的子类没有任何注解标签的话,这个子类就会继承来自父类的注解。类似下面的例子:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}@TestAnnotation
class Animal {//...
}class Cat extends Animal{//也会拥有来自父类Animal的@TestAnnotation注解//...
}
复制代码
  • 2、Kotlin为啥不需要@Inherited元注解

关于这个问题实际上在Kotlin官网的discuss中就有人提出了这个问题,具体感兴趣的可以去看看:Inherited annotations and other reflections enchancements. 这里大概说下原因,我们都知道在Java中,无法找到子类方法是否重写了父类的方法。因此不能继承父类方法的注解。然而Kotlin目前不需要支持这个@Inherited元注解,因为Kotlin可以做到,如果反射提供了override标记而且很容易做到。

六、注解的使用场景

  • 1、提供信息给编译器: 编译器可以利用注解来处理一些,比如一些警告信息,错误等
  • 2、编译阶段时处理: 利用注解信息来生成一些代码,在Kotlin生成代码非常常见,一些内置的注解为了与Java API的互操作性,往往借助注解在编译阶段生成一些额外的代码。
  • 3、运行时处理: 某些注解可以在程序运行时,通过反射机制获取注解信息来处理一些程序逻辑。

七、Kotlin中的预置注解

在Kotlin中最大的一个特点就是可以和Java做到极高的互操作性,我们知道Kotlin的语法和Java语法还是有很大的不同,要想做到与Java做到很大兼容性可能需要携带一些额外信息,供编译器或者运行时做类似兼容转换。其中注解就起到了很大的作用,在Kotlin内置很多个注解为了解决Java中的调用Kotlin API的一些调用习惯和控制API的调用。它们就是Kotlin中的@Jvm系列的注解,咱们一一来看下它们都有哪些。

@JvmDefault

@JvmDefault注解是在Kotlin 1.2.40版本加入的,并且在之后的Kotlin 1.2.50版本增强一些实验性特性。

  • 1、作用

我们都知道在Kotlin中的接口中可以增加非抽象成员,那么该注解就是为非抽象的接口成员生成默认的方法。

使用-Xjvm-default = enable,会为每个@JvmDefault注解标注的方法生成接口中的默认方法。在此模式下,使用@JvmDefault注解现有方法可能会破坏二进制兼容性,因为它将有效地从DefaultImpls类中删除该方法。

使用-Xjvm-default = compatibility,除了默认接口方法之外,还会生成兼容性访问器。在DefaultImpls类中,它通过合成访问器调用默认接口方法。在此模式下,使用@JvmDefault注解现有方法是二进制兼容的,但在字节码中会产生更多方法。从接口成员中移除此注解会使在两种模式中的二进制不兼容性发生变化。

  • 2、源码定义
@SinceKotlin("1.2")//从Kotlin的1.2版本第一次出现该注解
@RequireKotlin("1.2.40", versionKind = RequireKotlinVersionKind.COMPILER_VERSION)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)//目标对象是函数和属性
annotation class JvmDefault
复制代码
  • 3、使用注解前后反编译java代码对比

未使用@JvmDefault注解

interface ITeaching {fun speak() = println("open the book")
}class ChineseTeacher : ITeachingfun main(args: Array<String>) {ChineseTeacher().speak()
}
复制代码

反编译成Java代码

public interface ITeaching {void speak();public static final class DefaultImpls {//可以看到在接口为speak函数生成一个DefaultImpls静态内部类public static void speak(ITeaching $this) {String var1 = "open the book";System.out.println(var1);}}
}public final class ChineseTeacher implements ITeaching {public void speak() {ITeaching.DefaultImpls.speak(this);//注意:这里却是直接调用ITeaching中静态内部类DefaultImpls的speak方法。}
}public final class JvmDefaultTestKt {public static final void main(@NotNull String[] args) {Intrinsics.checkParameterIsNotNull(args, "args");(new ChineseTeacher()).speak();//这里会调用ChineseTeacher中的speak方法}
}
复制代码

使用@JvmDefault注解

interface ITeaching {@JvmDefault//注意: 可能一开始使用该注解会报错,需要在gradle中配置jvm参数:-jvm-target=1.8 -Xjvm-default=enablefun speak() = println("open the book")
}class ChineseTeacher : ITeachingfun main(args: Array<String>) {ChineseTeacher().speak()
}
复制代码

反编译成Java代码

public interface ITeaching {@JvmDefaultdefault void speak() {//添加注解后外层的静态内部类被消除String var1 = "open the book";System.out.println(var1);}
}public final class ChineseTeacher implements ITeaching {//内部并没有类似上面的speak方法调用的桥接委托
}public final class JvmDefaultTestKt {public static final void main(@NotNull String[] args) {Intrinsics.checkParameterIsNotNull(args, "args");(new ChineseTeacher()).speak();}
}
复制代码

总而言之,在没有添加 @JvmDefault注解,Kotlin会自动生成一个叫做 DefaultImpl静态内部类,用于保存静态方法的默认实现,并使用自身接收器类型来模拟属于对象的方法。然后,对于扩展该接口的每种类型,如果类型没有实现方法本身,则在编译时,Kotlin将通过调用将方法连接到默认实现。

这样一来确实带来一个很大好处就是在JDK1.8之前的版本JVM上提供了在接口上也能定义具体的实现方法功能。但是这样也存在一些问题:

第一问题: 比如它和现在的Java的处理方式不兼容,这样会导致互操作性极度下降。我们甚至可以在Java中直接去调用自动生成的DefaultImpls,类似这样的调用ITeaching.DefaultImpls.speak(new ChineseTeacher());,这样内部的细节居然也能暴露给外部,这样更会调用者一脸懵逼。

第二问题: Java 8中存在默认方法的主要原因之一是能够向接口添加方法而无需侵入每个子类。 然而Kotlin实现不支持这个原因是必须在每个具体类型上生成默认调用。 向接口添加新方法导致必须重新编译每个实现者。

基于上述问题,Kotlin推出了 @JvmDefault注解

@JvmField

  • 1、作用

可以应用于一个字段,把这个属性暴露成一个没有访问器的公有Java字段;以及Companion Object对象中。

  • 2、源码定义
@Target(AnnotationTarget.FIELD)//作用对象是字段,包括属性的幕后字段
@Retention(AnnotationRetention.BINARY)//注解保留期是源码阶段
@MustBeDocumented
public actual annotation class JvmField
复制代码
  • 3、注解使用

使用场景一:

我们知道在Kotlin中默认情况下,Kotlin类不会公开字段而是会公开属性.Kotlin会为属性的提供幕后字段,这些属性将会以字段形式存储它的值。一起来看个例子

//Person类中定义一个age属性,age属性默认是public公开的,但是反编译成Java代码,你就会看到它的幕后字段了。
class Person {var age = 18set(value) {if (value > 0) field = value}
}
复制代码

反编译成Java代码

public final class Person {private int age = 18;//这个就是Person类中的幕后字段,可以看到age字段是private私有的。//外部访问通过setter和getter访问器来操作。由于Kotlin自动生成setter、getter访问器,所以外部可以直接类似公开属性操作,//实际上内部还是通过setter、getter访问器来实现public final int getAge() {return this.age;}public final void setAge(int value) {if (value > 0) {this.age = value;}}
}
复制代码

但是如果在Kotlin需要生成一个公开的字段怎么实现呢?那就要借助@JvmField注解了,它会自动将该字段的setter、getter访问器消除掉,并且把这个字段修改为public

class Person {@JvmFieldvar age = 18
}
复制代码

反编译成的Java代码

public final class Person {@JvmFieldpublic int age = 18;//消除了setter、getter访问器,并且age字段为public公开
}
复制代码

使用场景二:

@JvmField另一个经常使用的场景就是用于Companion Object伴生对象中。

未使用@JvmField注解

class Person {companion object {val MAX_AGE = 120}
}
复制代码

反编译成Java代码

public final class Person {private static final int MAX_AGE = 120;//注意: 这里默认是private私有的MAX_AGE,所以在Java中调用无法直接通过Person类名.变量名访问public static final Person.Companion Companion = new Person.Companion((DefaultConstructorMarker)null);public static final class Companion {//在Java中调用无法直接通过Person类名.变量名访问, //而是通过静态内部类Companion的getMAX_AGE间接访问,类似这样Person.Companion.getMAX_AGE();public final int getMAX_AGE() {return Person.MAX_AGE;}private Companion() {}// $FF: synthetic methodpublic Companion(DefaultConstructorMarker $constructor_marker) {this();}}
}
复制代码

但是如果使用该注解就能直接通过Person类名.变量名访问

class Person {companion object {@JvmFieldval MAX_AGE = 120}
}
//在Java中调用
public static void main(String[] args) {System.out.println(Person.MAX_AGE);//可以直接调用,因为它已经变成了public了
}
复制代码

反编译成Java代码

public final class Person {@JvmFieldpublic static final int MAX_AGE = 120;//公有的MAX_AGE的,外部可以直接调用public static final Person.Companion Companion = new Person.Companion((DefaultConstructorMarker)null);public static final class Companion {private Companion() {}// $FF: synthetic methodpublic Companion(DefaultConstructorMarker $constructor_marker) {this();}}
}
复制代码

@JvmMultifileClass

  • 1、作用

该注解主要是为了生成多文件的类

  • 2、源码定义
@Target(AnnotationTarget.FILE)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmMultifileClass()
复制代码
  • 3、注解使用

在Kotlin分别定义两个顶层函数在两个不同文件中,可通过该注解将多个文件中的类方法合并到一个类中。

//存在于IOUtilA文件中
@file:JvmName("IOUtils")
@file:JvmMultifileClasspackage com.mikyou.annotationimport java.io.IOException
import java.io.Readerfun closeReaderQuietly(input: Reader?) {try {input?.close()} catch (ioe: IOException) {// ignore}
}//存在于IOUtilB文件中
@file:JvmName("IOUtils")
@file:JvmMultifileClasspackage com.mikyou.annotationimport java.io.IOException
import java.io.InputStreamfun closeStreamQuietly(input: InputStream?) {try {input?.close()} catch (ioe: IOException) {// ignore}
}
//在Java中使用
public class Test {public static void main(String[] args) {//即使存在于不同文件中,但是对于外部Java调用仍然是同一个类IOUtilsIOUtils.closeReaderQuietly(null);IOUtils.closeStreamQuietly(null);}
}
复制代码

@JvmName

  • 1、作用

将改变由Kotlin默认生成的Java方法、字段或类名

  • 2、源码定义
//作用的目标有: 函数、属性getter方法、属性setter方法、文件
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FILE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmName(actual val name: String)//有个name参数,将生成传入指定name的名称
复制代码
  • 3、注解使用
class Student {@get:JvmName(name = "getStudentName")//修改属性的getter函数名称@set:JvmName(name = "setStudentName")//修改属性的setter函数名称var name: String = "Tim"@JvmName("getStudentScore")//修改函数名称fun getScore(): Double {return 110.5}
}
//修改生成的类名,默认Kotlin会生成以文件名+Kt后缀组合而成的类名
@file:JvmName("IOUtils")//注意:该注解一定要在第一行,package顶部
package com.mikyou.annotationimport java.io.IOException
import java.io.Readerfun closeReaderQuietly(input: Reader?) {try {input?.close()} catch (ioe: IOException) {// ignore}
}
复制代码

反编译后的Java代码

public final class Student {@NotNullprivate String name = "Tim";@JvmName(name = "getStudentName")@NotNull//已经修改成传入getStudentNamepublic final String getStudentName() {return this.name;}@JvmName(name = "setStudentName")//已经修改成传入setStudentNamepublic final void setStudentName(@NotNull String var1) {Intrinsics.checkParameterIsNotNull(var1, "<set-?>");this.name = var1;}@JvmName(name = "getStudentScore")//已经修改成传入getStudentScorepublic final double getStudentScore() {return 110.5D;}
}
复制代码

@JvmOverloads

  • 1、作用

指导Kotlin编译器为带默认参数值的函数(包括构造函数)生成多个重载函数。

  • 2、源码定义
//作用对象是函数和构造函数
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmOverloads()
复制代码
  • 3、注解使用

该注解使用最多就是用于带默认值函数的重载,在Android中我们在自定义View的时候一般会重载多个构造器,需要加入该注解,如果不加默认只定义一个构造器,那么当你直接在XML使用这个自定义View的时候会抛出异常。

class ScrollerView @JvmOverloads constructor(context: Context,attr: AttributeSet? = null,defStyle: Int = 0
) : View(context, attr, defStyle) {//...
}
复制代码

反编译后的Java代码

public final class ScrollerView extends View {@JvmOverloadspublic ScrollerView(@NotNull Context context, @Nullable AttributeSet attr, int defStyle) {Intrinsics.checkParameterIsNotNull(context, "context");super(context, attr, defStyle);}// $FF: synthetic method@JvmOverloadspublic ScrollerView(Context var1, AttributeSet var2, int var3, int var4, DefaultConstructorMarker var5) {if ((var4 & 2) != 0) {var2 = (AttributeSet)null;}if ((var4 & 4) != 0) {var3 = 0;}this(var1, var2, var3);}@JvmOverloadspublic ScrollerView(@NotNull Context context, @Nullable AttributeSet attr) {this(context, attr, 0, 4, (DefaultConstructorMarker)null);}@JvmOverloadspublic ScrollerView(@NotNull Context context) {this(context, (AttributeSet)null, 0, 6, (DefaultConstructorMarker)null);}//...
}
复制代码

@JvmPackageName

  • 1、作用

更改从使用该注解标注的文件生成的.class文件的JVM包的完全限定名称。 这不会影响Kotlin客户端在此文件中查看声明的方式,但Java客户端和其他JVM语言客户端将看到类文件,就好像它是在指定的包中声明的那样。 如果使用此批注对文件进行批注,则它只能包含函数,属性和类型声明,但不能包含。

  • 2、源码定义
@Target(AnnotationTarget.FILE)//作用于文件
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@SinceKotlin("1.2")//Kotlin1.2版本加入
internal annotation class JvmPackageName(val name: String)
复制代码
  • 3、注解使用
//以Collection源码为例
@file:kotlin.jvm.JvmPackageName("kotlin.collections.jdk8")package kotlin.collections
复制代码

可以看到该类会编译生成到kotlin.collections.jdk8包名下

@JvmStatic

  • 1、作用

能被用在对象声明或者Companion object伴生对象的方法上,把它们暴露成一个Java的静态方法

  • 2、源码定义
//作用于函数、属性、属性的setter和getter
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmStatic()
复制代码
  • 3、注解使用

@JvmStatic这个注解一般经常用于伴生对象的方法上,供给Java代码调用

class Data {companion object {fun getDefaultDataName(): String {return "default"}}
}
//在java中调用,只能是Data.Companion.getDefaultDataName()调用
public class Test {public static void main(String[] args) {System.out.println(Data.Companion.getDefaultDataName());}
}
复制代码

反编译后Java代码

public final class Data {public static final Data.Companion Companion = new Data.Companion((DefaultConstructorMarker)null);public static final class Companion {@NotNullpublic final String getDefaultDataName() {return "default";}private Companion() {}// $FF: synthetic methodpublic Companion(DefaultConstructorMarker $constructor_marker) {this();}}
}
复制代码

使用@JvmStatic注解后

class Data {companion object {@JvmStaticfun getDefaultDataName(): String {return "default"}}
}
//在java中调用,可以直接这样Data.getDefaultDataName()调用
public class Test {public static void main(String[] args) {System.out.println(Data.getDefaultDataName());}
}
复制代码

反编译后的Java代码

public final class Data {public static final Data.Companion Companion = new Data.Companion((DefaultConstructorMarker)null);@JvmStatic@NotNull//注意它会在Data类内部自动生成一个getDefaultDataName,然后内部还是通过Companion.getDefaultDataName()去调用。public static final String getDefaultDataName() {return Companion.getDefaultDataName();}public static final class Companion {@JvmStatic@NotNullpublic final String getDefaultDataName() {return "default";}private Companion() {}// $FF: synthetic methodpublic Companion(DefaultConstructorMarker $constructor_marker) {this();}}
}
复制代码

@JvmSuppressWildcards和@JvmWildcard

  • 1、作用

用于指示编译器生成或省略类型参数的通配符,JvmSuppressWildcards用于参数的泛型是否生成或省略通配符,而JvmWildcard用于返回值的类型是否生成或省略通配符

  • 2、源码定义
//作用于类、函数、属性、类型
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.TYPE)
@MustBeDocumented
@OptionalExpectation
//指定suppress为true表示不生成,false为生成通配符,默认是true不生成
public expect annotation class JvmSuppressWildcards(val suppress: Boolean = true)@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmWildcard
复制代码
  • 3、注解使用
interface ICovert {fun covertData(datas: List<@JvmSuppressWildcards(suppress = false) String>)//@JvmSuppressWildcardsd用于参数类型fun getData(): List<@JvmWildcard String>//@JvmWildcard用于返回值类型
}
复制代码
class CovertImpl implements ICovert {@Overridepublic void covertData(List<? extends String> datas) {//参数类型生成通配符}@Overridepublic List<? extends String> getData() {//返回值类型生成通配符return null;}
}
复制代码

@JvmSynthetic

  • 1、作用

它在生成的类文件中将适当的元素标记为合成,并且编译器标记为合成的任何元素都将无法从Java语言中访问。

  • 2、什么是合成属性(Synthetic属性)?

JVM字节码标识的ACC_SYNTHETIC属性用于标识该元素实际上不存在于原始源代码中,而是由编译器生成。

  • 3、合成属性能做什么?

它一般用于支持代码生成,允许编译器生成不应向其他开发人员公开但需要支持实际公开接口所需的字段和方法。我们可以将其视为超越private或protected级别。

  • 4、源码定义
//作用于函数、属性的setter,getter以及字段
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FIELD)
@OptionalExpectation
public expect annotation class JvmSynthetic()
复制代码
  • 5、注解使用
class Synthetic {@JvmSyntheticval name: String = "Tim"var age: Int@JvmSyntheticset(value) {}@JvmSyntheticget() {return 18}
}
复制代码

反编译后的Java代码

public final class Synthetic {// $FF: synthetic field@NotNullprivate final String name = "Tim";@NotNullpublic final String getName() {return this.name;}// $FF: synthetic method//我们经常看到这些注释,就是通过@Synthetic注解生成的public final int getAge() {return 18;}// $FF: synthetic methodpublic final void setAge(int value) {}
}
复制代码

通过反编译代码可能看不到什么,我们直接可以通过javap -v xxx.class查阅生成的字节码文件描述

  public final int getAge();descriptor: ()Iflags: ACC_PUBLIC, ACC_FINAL, ACC_SYNTHETIC//添加ACC_SYNTHETIC标识Code:stack=1, locals=1, args_size=10: bipush        182: ireturnLocalVariableTable:Start  Length  Slot  Name   Signature0       3     0  this   Lcom/mikyou/annotation/Synthetic;LineNumberTable:line 12: 0public final void setAge(int);descriptor: (I)Vflags: ACC_PUBLIC, ACC_FINAL, ACC_SYNTHETIC//添加ACC_SYNTHETIC标识Code:stack=0, locals=2, args_size=20: returnLocalVariableTable:Start  Length  Slot  Name   Signature0       1     0  this   Lcom/mikyou/annotation/Synthetic;0       1     1 value   ILineNumberTable:line 9: 0复制代码

@Throws

  • 1、作用

用于Kotlin中的函数,属性的setter或getter函数,构造器函数抛出异常

  • 2、源码定义
//作用于函数、属性的getter、setter函数、构造器函数
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
public annotation class Throws(vararg val exceptionClasses: KClass<out Throwable>)//这里是异常类KClass不定参数,可以同时指定一个或多个异常
复制代码
  • 3、注解使用
@Throws(IOException::class)
fun closeQuietly(output: Writer?) {output?.close()
}
复制代码

@Transient

该注解充当了Java中的transient关键字

@Strictfp

该注解充当了Java中的strictfp关键字

@Synchronized

该注解充当了Java中的synchronized关键字

@Volatile

该注解充当了Java中的volatile关键字

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

Kotlin系列文章,欢迎查看:

Kotlin邂逅设计模式系列:

  • 当Kotlin完美邂逅设计模式之单例模式(一)

数据结构与算法系列:

  • 每周一算法之二分查找(Kotlin描述)
  • 每周一数据结构之链表(Kotlin描述)

翻译系列:

  • [译] Kotlin中关于Companion Object的那些事
  • [译]记一次Kotlin官方文档翻译的PR(内联类)
  • [译]Kotlin中内联类的自动装箱和高性能探索(二)
  • [译]Kotlin中内联类(inline class)完全解析(一)
  • [译]Kotlin的独门秘籍Reified实化类型参数(上篇)
  • [译]Kotlin泛型中何时该用类型形参约束?
  • [译] 一个简单方式教你记住Kotlin的形参和实参
  • [译]Kotlin中是应该定义函数还是定义属性?
  • [译]如何在你的Kotlin代码中移除所有的!!(非空断言)
  • [译]掌握Kotlin中的标准库函数: run、with、let、also和apply
  • [译]有关Kotlin类型别名(typealias)你需要知道的一切
  • [译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?
  • [译]Kotlin中的龟(List)兔(Sequence)赛跑

原创系列:

  • 教你如何完全解析Kotlin中的类型系统
  • 如何让你的回调更具Kotlin风味
  • Jetbrains开发者日见闻(三)之Kotlin1.3新特性(inline class篇)
  • JetBrains开发者日见闻(二)之Kotlin1.3的新特性(Contract契约与协程篇)
  • JetBrains开发者日见闻(一)之Kotlin/Native 尝鲜篇
  • 教你如何攻克Kotlin中泛型型变的难点(实践篇)
  • 教你如何攻克Kotlin中泛型型变的难点(下篇)
  • 教你如何攻克Kotlin中泛型型变的难点(上篇)
  • Kotlin的独门秘籍Reified实化类型参数(下篇)
  • 有关Kotlin属性代理你需要知道的一切
  • 浅谈Kotlin中的Sequences源码解析
  • 浅谈Kotlin中集合和函数式API完全解析-上篇
  • 浅谈Kotlin语法篇之lambda编译成字节码过程完全解析
  • 浅谈Kotlin语法篇之Lambda表达式完全解析
  • 浅谈Kotlin语法篇之扩展函数
  • 浅谈Kotlin语法篇之顶层函数、中缀调用、解构声明
  • 浅谈Kotlin语法篇之如何让函数更好地调用
  • 浅谈Kotlin语法篇之变量和常量
  • 浅谈Kotlin语法篇之基础语法

Effective Kotlin翻译系列

  • [译]Effective Kotlin系列之考虑使用原始类型的数组优化性能(五)
  • [译]Effective Kotlin系列之使用Sequence来优化集合的操作(四)
  • [译]Effective Kotlin系列之探索高阶函数中inline修饰符(三)
  • [译]Effective Kotlin系列之遇到多个构造器参数要考虑使用构建器(二)
  • [译]Effective Kotlin系列之考虑使用静态工厂方法替代构造器(一)

实战系列:

  • 用Kotlin撸一个图片压缩插件ImageSlimming-导学篇(一)
  • 用Kotlin撸一个图片压缩插件-插件基础篇(二)
  • 用Kotlin撸一个图片压缩插件-实战篇(三)
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用

教你如何完全解析Kotlin中的注解相关推荐

  1. 教你如何完全解析Kotlin中的类型系统

    简述: 已经很久没有更新文章,这大概是2019年第二篇文章了,有很多小伙伴们都在公众号留言说是不是断更了.是不是跑路了.在这里统一回复下我还好,并没有跑路哈,只是在思考接下来文章主要方向在哪? 如何在 ...

  2. java中注解的解析_全面解析Java中的注解与注释

    注解一.什么是 Annotation? (注解 or 注释)Annotation, 准确的翻译应该是 -- 注解. 和注释的作用完全不一样. Annotation 是JDK5.0及以后版本引入的一个特 ...

  3. java里什么是注释,全面解析Java中的注解与注释

    注解 一.什么是 Annotation? (注解 or 注释) Annotation, 准确的翻译应该是 -- 注解. 和注释的作用完全不一样. Annotation 是JDK5.0及以后版本引入的一 ...

  4. java注释_全面解析Java中的注解与注释

    注解一.什么是 Annotation? (注解 or 注释)Annotation, 准确的翻译应该是 -- 注解. 和注释的作用完全不一样. Annotation 是JDK5.0及以后版本引入的一个特 ...

  5. [译]带你揭开Kotlin中属性代理和懒加载语法糖衣

    翻译说明: 原标题: How Kotlin's delegated properties and lazy-initialization work 原文地址: https://medium.com/t ...

  6. 教你如何攻克Kotlin中泛型型变的难点(下篇)

    简述: 前几天我们一起为Kotlin中的泛型型变做了一个很好的铺垫,深入分析下类型和类,子类型和子类之间的关系.什么是子类型化关系以及型变存在的意义.那么今天将会讲点更刺激的东西,也就是Kotlin泛 ...

  7. 教你如何攻克Kotlin中泛型型变的难点(应用篇)

    简述: 这是泛型型变最后一篇文章了,也是泛型介绍的最后一篇文章.顺便再扯点别的,上周去北京参加了JetBrains 2018开发者日,主要是参加Kotlin专场.个人感觉收获还是挺多的,bennyHu ...

  8. [译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?

    翻译说明: 原标题: Sequences - a Pragmatic Approach 原文地址: https://proandroiddev.com/sequences-a-pragmatic-ap ...

  9. 解析xml_Mybatis中mapper的xml解析详解

    上一篇文章分析了mapper注解关键类MapperAnnotationBuilder,今天来看mapper的项目了解析关键类XMLMapperBuilder. 基础介绍 回顾下之前是在分析config ...

最新文章

  1. mysql5.5.9_mysql5.5.9字符集设置
  2. 利用计算机解决问题实际依赖,行测答题技巧:准确把握加强、削弱的“相关性”...
  3. RFID采用率迟迟不涨,原因何在?
  4. Oracle数据库之对象视图、索引、序列、同义词
  5. Spring Cloud综合实战 - 基于TCC补偿模式的分布式事务
  6. 世界机器人冠军王宇航_★​身边的榜样,为你喝彩:我校学子摘冠第七届河南省VEX U机器人挑战赛...
  7. 注解的力量 -----Spring 2.5 JPA hibernate 使用方法的点滴整理(六): 一些常用的数据库 注解...
  8. IDR、CRA、BLA、RASL、RADL、closed-gop、open-gop
  9. android怎样封装,如何封装属于自己的博客网站安卓APP 源码家园
  10. Codeforces Zepto Code Rush 2014 -C - Dungeons and Candies
  11. (81)Vivado实现约束过程
  12. idea 配置maven一直停留在loading archetype list
  13. python正在处理中_协程和 asyncio
  14. 人到不惑,程序员是否该认命?
  15. Golang groupcache LRU 缓存简介与用法
  16. H5 新特性之 fileReader 实现本地图片视频资源的预览
  17. Dialog是逻辑字体,实际绘制时会选择不同字体
  18. mysql数据库封装类_基于mysqli封装的数据库类
  19. windows资源监视器中内存项(提交、工作集、可共享、专用)的含义及区别
  20. 【知识图谱】阿里巴巴电商知识图谱

热门文章

  1. macos安装盘第三方工具制作_简单制作OSXYosemite10.10正式版U盘USB启动安装盘方法教程(全新安装Mac系统)下载|异次元软件世界...
  2. Linux写出相应密码的用途,linux运维面试题中级
  3. 云服务器,Linux上安装xampp,搭建node服务,云服务器Firewalld、iptables,nginx反向代理
  4. vue项目,cli-3.0项目部署运行报错:Uncaught SyntaxError: Unexpected token
  5. 学法减分小程序可用可运营源码附带安装教程
  6. Wordpress简约昼夜切换主页导航
  7. 你们的2016年前端学习计划是什么?
  8. Node.js:模块查找,引用及缓存机制
  9. css 多栏文字流 css two columns text flow
  10. DNS攻击原理与防范