簡述: 從這篇文章将繼續開始探索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 MutableList.remove(index: Int): T = removeAt(index)
@Deprecated注解的remove函數使用
//Deprecated注解的使用
fun main(args: Array) {
val list = mutableListOf("a", "b", "c", "d", "e")
list.remove(3)//這裡會報錯, 通過remove函數注解定義,這個remove函數在定義的level是ERROR級别的,是以編譯器直接抛錯
}

image

image
最後來看下@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 : ITeaching
fun main(args: Array) {
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) {
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
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 = 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
1、作用
該注解主要是為了生成多檔案的類
2、源碼定義
@Target(AnnotationTarget.FILE)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmMultifileClass()
3、注解使用
在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
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.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, "");
this.name = var1;
}
@JvmName(name = "getStudentScore")
//已經修改成傳入getStudentScore
public 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 {
@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
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包名下

image
@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 {
@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
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)//@JvmSuppressWildcardsd用于參數類型
fun getData(): List//@JvmWildcard用于傳回值類型
}
class CovertImpl implements ICovert {
@Override
public void covertData(List extends String> datas) {//參數類型生成通配符
}
@Override
public 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 {
@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
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)//這裡是異常類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邂逅設計模式系列:
資料結構與算法系列:
翻譯系列:
原創系列:
Effective Kotlin翻譯系列
實戰系列: