Kotlin进阶学习笔记
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)的发生,原因在于其从语法层级就区分了
操作示例图
show kotlin bytecode-
互通
虽然号称和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 arr
-
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"//静态常量 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的类型推断原理、泛型、反射以及其他高阶特性。