Kotlin進階學習筆記
從源碼分析學習Kotlin,知其然、知其所以然。
1. Why Kotlin 之官方說辭
簡潔Concise:
data class
、lambda
、快速單例類object
-
安全Safe:Nullable類型區分,自動推斷
//區分nullable var str:String//不同于String? str=null//編譯則報錯,因為String類型非空,任何可空類型需要?符號,比如String? //避免NPE val name:String?=null//name是可空的 println(name.length())//編譯報錯,原因name可能為null,會引起name.length()報空指針異常 //自動類型推斷 val str:Any//外部參數 if(str is String){ //此處在str is String作用域內,就不需要強轉類型了 str.length() }
-
互操作性Interoperable
官宣與Java的現有庫可以100%的兼容使用
2. 源碼分析簡潔/安全/互通性
-
語法簡潔
類型定義,函數聲明,POJO創建,labmda的隨處可見,快速單例類等都是kotlin語法簡潔的有力證明。
-
POJO的對比
public class Student{ private String name; private int age; private String desc; public void setName(String name){ this.name = name; } //省略... }
//kotlin中的data class專用于POJO的創建生成,并內部實現了getter/setter,toString,equals,hashCode等必要函數 data class Student(val name:String,var age:Int,val desc:String)
注意??:可通過AS的
Tools-->kotlin-->show kotlin Bytecode
在IDE中顯示當前kotlin文件對應的字節碼,點擊Decompile即可生成對應的java文件,從而對比kotlin于java的異同。 -
單例類的快速創建
object Tools{ fun abc(){ //... } }
//對應java文件大致如此,之所以有final,因為在kotlin中默認定義的類、函數都是final不可繼承的。 public final class Tools{ @NotNull public static final Tools INSTANCE; public final void abc(){ //.... } static{ Tools var0 = new Tools(); INSTANCE = var0; } }
通過AS操作對比生成的java文件,我們可以看得出,如上簡單的object單例類,對應于java中就是static京臺單例類的寫法。
-
-
安全性
- Kotlin中最大的一點在于很好的避免大多數的空指針異常(NPE)的發生,原因在于其從語法層級就區分了
nullable
與notnull
的類型,如字符串類型String
則在kotlin中就表示聲明該類型不能賦值null
,若要用null
的賦值,則必須是String?
方可。同理其他類型如是。 - 在與Java互通時候,就需要Java端的字段與函數標記nullable或notnull注解才能讓kotlin正確的接收類型。
- Kotlin中最大的一點在于很好的避免大多數的空指針異常(NPE)的發生,原因在于其從語法層級就區分了
操作示例圖
-
互通
雖然號稱和Java的100%兼容互通,在項目開發中仍然要有一些細節點注意
-
Kotlin調用Java
-
getter(無參)
與setter(單個單數)
的java
方法可映射為kotlin
中對象屬性。Boolean
的getter
理解為isXXX
public class AA{ private String name; private boolean adult; public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setAdult(boolean adult){ this.adult = adult; } public boolean isAdult(){ return adult; } }
fun test(){ val aa = AA() //kotlin中調用java,對應getter和setter可映射為kotlin中的屬性 aa.name = "Julia" aa.isAdult = true }
-
特殊字符,如
is
、object
、is
等在java
中無所謂,但是在kotlin
中是關鍵字,調用時候需要轉義//如java中定義一個函數名public void is(String name){} //kotlin調用的時候,is要轉義 aa.`is`("name")
-
空安全,
java
中任何引用都可能null
,所以kotlin
調用java
就比較尷尬。編譯器可能無法判斷類型可空與否。在接收
java
類型時,可根據情況最好注解為可null
的-
參照
java
中注解@Nullable
和@NotNull
,則在kotlin
中調用可明確類型空否。public void setName(@NotNull String name){ //.... } @Nullable public String getName(){ return name; } //在kotlin調用如上函數,就比較明晰,如果沒有注解的話,就要靈活判斷
-
Java
中泛型在Kotlin
中的使用,轉換-
Foo<? extends Bar>
——>Foo<out Bar!>!
-
Foo<? super Bar>
——>Foo<in Bar!>!
-
Java
中原始類型如List
轉化為List<*>!
,即List<out Any?>!
- 同樣
Kotlin
的泛型也存在類型擦除問題。泛型詳細講解,之后單獨文章分析
-
-
Java
中數組是可型變的,Kotlin
中數組不可型變。Kotlin
中基礎類型隱藏了拆裝箱的細節,對應于原生java
的數組提供了特殊類來映射。IntArray
、DoubleArray
等,但這些都不是數組,對應會編譯為Java
的原生數組。//Java數組方法 public class JavaArrayTest{ public void removeIndices(int[] indices){ //... } } //kotlin中調用java的數組參數的方法 fun testJavaArray(){ val javaObj = JavaArrayTest() val arr = intArrayOf(1,2,2,3)//生成kotlin語法的數組 javaObj.removeIndices(arr)//調用java的方法,傳入數組參數 //更新賦值,遍歷,均不會引入額外開銷 arr[2] = arr[1]*3//將2號位的值更新為1號值得3倍,這里也不會有get/set的調用 for(x in arr){//并不會創建迭代器 print(x) } for(i in arr.indices){//也不會有迭代器創建,不會有額外開銷,in的校驗也不會有額外開銷。 arr[i]+=3 } }
因為
kolint
的語法特定類,在編譯為JVM
的字節碼時候,會化為原生的形式。Java
的數組可型變與Kotlin
的數組不可型變,對比圖
-
-
-
Java
可變參數,Kotlin
中使用*
符號展開對應數組,不可傳null
//Java中可變參數使用...表示
public void removeIndices(int... indices){
//...
}
//kotlin中調用就需要是對應可展開的數組類型
val aar = intArrayOf(1,2,2,3)
removeIndices(*arr)
Kotlin
中無法使用中綴語法調用Java
方法Kotlin
中可能異常的函數,并不會要求你一定try...catch
,這就有點尷尬,你嗲用Java
或者kotlin
的其他可能異常的函數時候,就要自己注意catch
。Java
類的靜態成員會在Kotlin
中表示為伴生對象companion object
。Java
反射與Kotlin
反射類似,后續文章單獨分析SAM(simple abstract method conversions)
轉換。Kotlin
調用JNI
,類似于Java
,需要在Kotlin
中聲明一個函數,在C/C++
中有實現的,并使用external
修飾
external fun aaa(a:Int):Double//這個函數,在Kotlin中聲明,需要在C/C++中實現。
//也可以是屬性的形式,這樣就是生成兩個external的函數,setter/getter
var mName:String
external get
external set
-
Java中調用Kotlin
-
Kotlin
中頂級函數和字段,在Java
中調用//文件名叫做 test.kt package org.zhiwei class ABC(){} //top level 的函數和字段 fun getName(){ //... } const val bbbb = "bbbbb"http://靜態常量 var cccccc= "" val dddddd = ""
通過編譯,會生成
org.zhiwei.testKt
類,成為內部方法和屬性。//java中調用則 org.zhiwei.testKt.getName();//函數 org.zhiwei.testKt.bbbb;//靜態常量 org.zhiwei.testKt.setCccccc("");//可變量
默認生成
fileNameKt
,可通過@JvmName
指定生成的類名/函數名@file:JvmName("TestTools")//則在java中調用就用TestTools.來調用即可。
注:不同的
kt
文件中toplevel
可以指定生成相同的Java
類,需要添加@file:JvmMultifileClass
注解//oldUtils.kt @file:JvmName("Tools") @file:JvmMultifileClass package org.zhiwei fun getTime(){ //... } //newUtils.kt @file:JvmName("Tools") @file:JvmMultifileClass package org.zhiwei fun getDate(){ //... } //如上兩個kt文件中不同的top level的函數定義,都指定生成java文件Tools類,
//java中調用,就是如下 Tools.getTime(); Tools.getDate();
-
Kotlin
中非私有/非open/override/非const/無默認值/非被委托的屬性,通過@JvmField
修飾,可在Java
中調用。//kotlin class User(id:String){ @JvmField val ID = id } //java中 public String getId(User user){ return user.ID; }
-
Kotlin
中靜態字段通常會是私有的。可通過@JvmField
/lateinit
/const
來修飾,使得Java
中公開調用到。//kotlin class User(){ companion object{ const val SEX = '男' @JvmField val name = "張三" lateinit var mobile:String } } //java中,注意,不需要UserKt.的形式 User.SEX; User.name; User.mobile;
-
kotlin
中靜態方法對應于Java
中,@JvmStatic
注解適用于字段和函數(jdk1.8/kotlin 1.3)//top level中的靜態函數都是java對應的。而在具體的類中,可以寫在object或者伴生對象中 class CCC{ companion object{ //在kotlin中屬于靜態函數的調用方式,但是java調用就需要伴生對象形式 fun callInKotlinStatic(){} //支持如上方式,但同時在java中也是靜態調用方式 @JvmStatic fun callInJavaStatic(){} } } //java中 public void test(){ CCC.callInJavaStatic(); CCC.Companion.callInJavaStatic(); CCC.Companion.callInKotlinStatic();//只有這一個,沒有直接CCC.callInKotlinStatic() }
-
Kotlin
中接口的函數定義,可以有默認實現(jdk1.8+)interface IUser{ fun run(){ //...默認實現 } } //java實現接口的時候,可以不復寫run函數
-
權限符號
public
、private
、internal
、protected
與Java
中異同- private、public權限一致;private在kt文件級別,則等同于包內可見。
- Java中可以訪問包內的其他protected的類,kotlin不行
- internal的屬性字段,對應java的public,而internal 的類的成員,與java中作用關系無對應。
KClass調用可用
getKotlinClass(XXX.class)
-
Jvm
的簽名沖突處理,@JvmName
處理//kotlin中定義兩個同名函數 fun List<String>.filterN():List<String> fun List<Int>.filterN():List<Int> //屬性的setter和getter也可以單獨注解 val x:Int @JvmName("getX_prop") get() = 888 //或者 @get:JvmName("x") @set:JvmName("changeX") var x:Int = 33
-
@JvmOverloads
函數重載,用于將kotlin
中存在默認參數的函數,提供出多種重載函數給Java調用。可用于構造函數和靜態函數,不能修飾抽象函數和接口的方法。//這個函數存在java中就會有依次的集中重載組合函數 @JvmOverloads fun haha(name:String,age:Int=18,sex:Int=1,address:String="beijing")
-
異常,kotiln不檢查異常,所以想要讓調用方知道會異常就添加
@Throws
@Throws(IOException::class) fun write2File(){ //... throw IOException() }
Java中的類型參數可能接收null,并且kotlin不知道,比較尷尬。
Nothing類型,在Java中沒有對應。Nothing不能為null
-
以上為Kotlin進階學習中著意知識點;接下來會分析Kotlin的類型推斷原理、泛型、反射以及其他高階特性。