教你如何彻底解析Kotlin中的注解

简述: 从这篇文章将继续开始探索Kotlin中的一些高级的内容,以前有着重探讨了Kotlin的泛型以及泛型型变等内容。如今咱们一块儿来看下Kotlin中的注解。Kotlin中的注解是100%与Java注解兼容的,有不少相同的地方,可是也有一些不一样的地方。一块儿来瞅瞅吧~java

1、注解的本质

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

2、注解的定义

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

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

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

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

4、注解的应用

  • 一、在上一步咱们知道了如何声明和定义标签了,那么接下来就是用这个标签,如何把咱们定义好的标签贴到指定的代码上。在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注解)
        //...
    }
}
复制代码
  • 二、在不少常见的Java或Kotlin框架中大量使用了注解,好比咱们最多见的JUnit单元测试框架
class ExampleUnitTest {
    @Test //@Test注解就是为了告诉JUnit框架,这是一个测试方法,当作测试调用。
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}
复制代码
  • 三、在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注解类)app

5、Kotlin中的元注解

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

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

@Target元注解

  • 一、介绍

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

  • 二、源码定义
@Target(AnnotationTarget.ANNOTATION_CLASS)//能够给标签本身贴标签
@MustBeDocumented
//注解类构造器参数是个vararg不定参数修饰符,因此能够同时指定多个做用的目标对象
public annotation class Target(vararg val allowedTargets: AnnotationTarget)
复制代码
  • 三、@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元注解

  • 一、介绍

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

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

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

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

@MustBeDocumented元注解

  • 一、介绍

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

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

@Repeatable元注解

  • 一、介绍

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

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

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

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

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

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}

@TestAnnotation
class Animal {
    //...
}

class Cat extends Animal{//也会拥有来自父类Animal的@TestAnnotation注解
    //...
}
复制代码
  • 二、Kotlin为啥不须要@Inherited元注解

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

6、注解的使用场景

  • 一、提供信息给编译器: 编译器能够利用注解来处理一些,好比一些警告信息,错误等
  • 二、编译阶段时处理: 利用注解信息来生成一些代码,在Kotlin生成代码很是常见,一些内置的注解为了与Java API的互操做性,每每借助注解在编译阶段生成一些额外的代码。
  • 三、运行时处理: 某些注解能够在程序运行时,经过反射机制获取注解信息来处理一些程序逻辑。

7、Kotlin中的预置注解

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

@JvmDefault

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

  • 一、做用

咱们都知道在Kotlin中的接口中能够增长非抽象成员,那么该注解就是为非抽象的接口成员生成默认的方法。

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

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

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

未使用@JvmDefault注解

interface ITeaching {
    fun speak() = println("open the book")
}

class ChineseTeacher : ITeaching

fun 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=enable
    fun speak() = println("open the book")
}

class ChineseTeacher : ITeaching

fun main(args: Array<String>) {
    ChineseTeacher().speak()
}
复制代码

反编译成Java代码

public interface ITeaching {
   @JvmDefault
   default 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

  • 一、做用

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

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

使用场景一:

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

//Person类中定义一个age属性,age属性默认是public公开的,可是反编译成Java代码,你就会看到它的幕后字段了。
class Person {
    var age = 18
        set(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 {
    @JvmField
    var age = 18
}
复制代码

反编译成的Java代码

public final class Person {
   @JvmField
   public 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 method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
复制代码

可是若是使用该注解就能直接经过Person类名.变量名访问

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

反编译成Java代码

public final class Person {
   @JvmField
   public 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 method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
复制代码

@JvmMultifileClass

  • 一、做用

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

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

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

//存在于IOUtilA文件中
@file:JvmName("IOUtils")
@file:JvmMultifileClass

package com.mikyou.annotation

import java.io.IOException
import java.io.Reader


fun closeReaderQuietly(input: Reader?) {
    try {
        input?.close()
    } catch (ioe: IOException) {
        // ignore
    }
}

//存在于IOUtilB文件中
@file:JvmName("IOUtils")
@file:JvmMultifileClass

package com.mikyou.annotation

import java.io.IOException
import java.io.InputStream


fun closeStreamQuietly(input: InputStream?) {
    try {
        input?.close()
    } catch (ioe: IOException) {
        // ignore
    }
}
//在Java中使用
public class Test {
    public static void main(String[] args) {
        //即便存在于不一样文件中,可是对于外部Java调用仍然是同一个类IOUtils
        IOUtils.closeReaderQuietly(null);
        IOUtils.closeStreamQuietly(null);
    }
}
复制代码

@JvmName

  • 一、做用

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

  • 二、源码定义
//做用的目标有: 函数、属性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的名称
复制代码
  • 三、注解使用
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.annotation

import java.io.IOException
import java.io.Reader


fun closeReaderQuietly(input: Reader?) {
    try {
        input?.close()
    } catch (ioe: IOException) {
        // ignore
    }
}
复制代码

反编译后的Java代码

public final class Student {
   @NotNull
   private String name = "Tim";

   @JvmName(name = "getStudentName")
   @NotNull
   //已经修改为传入getStudentName
   public final String getStudentName() {
      return this.name;
   }

   @JvmName(name = "setStudentName")
   //已经修改为传入setStudentName
   public final void setStudentName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   @JvmName(name = "getStudentScore")
   //已经修改为传入getStudentScore
   public final double getStudentScore() {
      return 110.5D;
   }
}
复制代码

@JvmOverloads

  • 一、做用

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

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

该注解使用最多就是用于带默认值函数的重载,在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 {
   @JvmOverloads
   public ScrollerView(@NotNull Context context, @Nullable AttributeSet attr, int defStyle) {
      Intrinsics.checkParameterIsNotNull(context, "context");
      super(context, attr, defStyle);
   }

   // $FF: synthetic method
   @JvmOverloads
   public 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);
   }

   @JvmOverloads
   public ScrollerView(@NotNull Context context, @Nullable AttributeSet attr) {
      this(context, attr, 0, 4, (DefaultConstructorMarker)null);
   }

   @JvmOverloads
   public ScrollerView(@NotNull Context context) {
      this(context, (AttributeSet)null, 0, 6, (DefaultConstructorMarker)null);
   }
   //...
}
复制代码

@JvmPackageName

  • 一、做用

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

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

package kotlin.collections
复制代码

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

@JvmStatic

  • 一、做用

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

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

@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 {
      @NotNull
      public final String getDefaultDataName() {
         return "default";
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
复制代码

使用@JvmStatic注解后

class Data {
    companion object {
        @JvmStatic
        fun 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
      @NotNull
      public final String getDefaultDataName() {
         return "default";
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
复制代码

@JvmSuppressWildcards和@JvmWildcard

  • 一、做用

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

  • 二、源码定义
//做用于类、函数、属性、类型
@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
复制代码
  • 三、注解使用
interface ICovert {
    fun covertData(datas: List<@JvmSuppressWildcards(suppress = false) String>)//@JvmSuppressWildcardsd用于参数类型

    fun getData(): List<@JvmWildcard String>//@JvmWildcard用于返回值类型
}
复制代码
class CovertImpl implements ICovert {
    @Override
    public void covertData(List<? extends String> datas) {//参数类型生成通配符

    }
    @Override
    public List<? extends String> getData() {//返回值类型生成通配符
        return null;
    }
}
复制代码

@JvmSynthetic

  • 一、做用

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

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

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

  • 三、合成属性能作什么?

它通常用于支持代码生成,容许编译器生成不该向其余开发人员公开但须要支持实际公开接口所需的字段和方法。咱们能够将其视为超越private或protected级别。

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

反编译后的Java代码

public final class Synthetic {
   // $FF: synthetic field
   @NotNull
   private final String name = "Tim";

   @NotNull
   public final String getName() {
      return this.name;
   }

   // $FF: synthetic method//咱们常常看到这些注释,就是经过@Synthetic注解生成的
   public final int getAge() {
      return 18;
   }

   // $FF: synthetic method
   public final void setAge(int value) {
   }
}
复制代码

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

public final int getAge();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_FINAL, ACC_SYNTHETIC//添加ACC_SYNTHETIC标识
    Code:
      stack=1, locals=1, args_size=1
         0: bipush        18
         2: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       3     0  this   Lcom/mikyou/annotation/Synthetic;
      LineNumberTable:
        line 12: 0

  public final void setAge(int);
    descriptor: (I)V
    flags: ACC_PUBLIC, ACC_FINAL, ACC_SYNTHETIC//添加ACC_SYNTHETIC标识
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/mikyou/annotation/Synthetic;
            0       1     1 value   I
      LineNumberTable:
        line 9: 0

复制代码

@Throws

  • 一、做用

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

  • 二、源码定义
//做用于函数、属性的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不定参数,能够同时指定一个或多个异常
复制代码
  • 三、注解使用
@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邂逅设计模式系列:

数据结构与算法系列:

翻译系列:

原创系列:

Effective Kotlin翻译系列

实战系列:

相关文章
相关标签/搜索