Kotlin关键字

符号

: 变量类型、函数、接口、返回值

  • 定义变量
    • 不可变变量
      • val a :Int
      • val a :Int = 4
    • 可变变量
      • var b :String
  • 继承基类
    • calss 子类 : 父类() {}
      注意“”后是父类的构造方法【父类带括号“()”】
  • 实现接口
    • class 类 : 接口 {}
  • 定义函数返回值

? 将类型标记为可空

?. 判空不处理

1
val ages1 = age?.toInt()  //空的话不处理

?: 判空取后面的值

1
var data = lesson.data ?: "日期待定"

如果lesson.datanull则把后面的默认值赋值给data,否则把lesson.data赋值给data

例子:

1
2
3
if(user.username?.length ?: 0 < 4){}
//等价于
if(user.username == null || user.username.length < 4){}

user.usernamenull,那么user.username?.length这个整体就是null,就会得到0;否则,user.username有值则可以取到它的length,然后判断是否<4,就会忽略 ?:0

! 逻辑非

!! 非空断言运算符

将任何值转换为非空类型,若该值为空则抛出异常

->

  • 分隔 lambda 表达式的参数与主体
  • 分隔在函数类型中的参数类型与返回类型声明
  • 分隔 when 表达式分支的条件与代码体

    +-*/% —— 数学操作符-/% 也用于将数组传递给 vararg 参数

it

kotlinlambda更加简约

1
2
3
4
5
6
7
8
//正常情况
view.setOnClickListener({v -> v.setVisibility(View.INVISIBLE)})
//当lambda是函数的最后一个参数时,可以将其移到括号外面
view.setOnClickListener(){v -> v.setVisibility(View.INVISIBLE)}
//当函数只有一个lambda类型的参数,可以省去括号
view.setOnClickListener{v -> v.setVisibility(View.INVISIBLE)}
//当lambda只有一个参数,可省去参数列表,在表达式部分用it引用参数
view.setOnClickListener{it.setVisibility(View.INVISIBLE)}

reified

不再需要传参数clazz

比如定义实现一个扩展函数的启动Activity,一般都需要传Class<T>参数:

1
2
3
4
5
6
//Function
private fun<T: Activity> Activity.startActivity(context: Context, clazz: Class<T>){
startActivity(Intent(context, clazz))
}
//Caller
startActivity(context, NewActivity::class.java)

使用reified方式

通过添加类型传递简化泛型参数

1
2
3
4
5
6
//Function
inline fun<reified T: Activity> Activity.startActivity(context: Context){
startActivity(Intent(context, T::class.java))
}
//Caller
startActivity<NewActivity>(context)

不安全的转换

kotlin中,使用安全转换操作符as?,它可以在失败时返回null。如下,我们认为会安全的获取数据或返回null。然后如果获得的数据不是它期望的类型,这个函数会出现crash

1
2
3
4
5
6
7
8
9
10
//Function
fun <T> Bundle.getDataOrNull(): T? {
return getSerializable(DATA_KEY) as? T
}

//Caller
val bundle: Bundle? = Bundle()
bundle?.putSerializable(DATA_KEY, "Testing")
val strData: String? = bundle?.getDataOrNull()
val intData: Int? = bundle?.getDataOrNull() //crash

如果不想crash的,修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Function
fun <T> Bundle.getDataOrNull(clazz: Class<T>): T? {
val data = getSerializable(DATA_KEY)
return if (clazz.isInstance(data)){
data as T
} else {
null
}
}
//Caller
val bundle: Bundle? = Bundle()
bundle?.putSerializable(DATA_KEY, "Testing")
val strData: String? = bundle?.getDataOrNull(String::class.java)
val intData: Int? = bundle?.getDataOrNull(String::class.java) //Null

使用reified方式

简化泛型参数和保证 as? 类型转换安全性

1
2
3
4
5
6
7
8
9
10
//Function
private inline fun <reified T> Bundle.getDataOrNull(): T? {
return getSerializable(DATA_KEY) as? T
}

//Caller
val bundle: Bundle? = Bundle()
bundle?.putSerializable(DATA_KEY, "Testing")
val strData: String? = bundle?.getDataOrNull()
val intData: Int? = bundle?.getDataOrNull() //Null

不同的返回类型函数重载

实现一个函数计算 DP 到像素,并返回一个 Int 或 Float。这种情况就会想到函数重载,如下所示:

1
2
3
4
5
6
7
8
9
10
fun Resources.dpToPx(value: Int): Float {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
value.toFloat(), displayMetrics)
}

fun Resources.dpToPx(value: Int): Int {
val floatValue: Float = dpToPx(value)
return floatValue.toInt()
}

但是,这将导致编译时出错。原因是,函数重载方式只能根据参数计数和类型不同,而不能根据返回类型。
使用reified方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
inline fun <reified T> Resources.dpToPx(value: Int):  T {
val result = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
value.toFloat(), displayMetrics)

return when(T::class){
Float::class -> result as T
Int::class -> result.toInt() as T
else -> throw IllegalStateException("Type not supported")
}
}

//Caller
val intValue: Int = resource.dpToPx(64)

Any

相当于Java中的Object,如果在泛型中还可以用“*”表示,如<*>

硬关键字

as

  1. 如果try中报错那么走catch的return,再走finally(finally一定会走),finally后代码不执行
  2. 如果try中不报错,那么走try的return,再走finally(finally一定会走),finally后代码不执行
    与java的一样(try、catch中没有return的情况)
  3. 如果try中报错:try–>catch–>finally,会走finally后的代码
  4. 如果try不报错:try–>finally,会走finally后的代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    fun main() {
    println("tryIncludeRet:" + tryIncludeRet())
    println("catchIncludeRet:" + catchIncludeRet())
    }

    fun tryIncludeRet(): String{
    println("=====================tryIncludeRet=====================")
    try {
    return "tryIncludeRet try"
    } finally {
    println("tryIncludeRet finally")
    }

    return "tryIncludeRet finally 后的return"
    }

    fun catchIncludeRet():String{
    println("=====================catchIncludeRet=====================")
    try {
    1/0
    return "catchIncludeRet try"
    } catch (e: Exception) {
    println("catch error")
    return "catchIncludeRet catch"
    } finally {
    println("catchIncludeRet finally")
    }
    return "catchIncludeRet finally 后的renturn"
    }


    //结果:
    =====================tryIncludeRet=====================
    tryIncludeRet finally
    tryIncludeRet:tryIncludeRet try
    =====================catchIncludeRet=====================
    catch error
    catchIncludeRet finally
    catchIncludeRet:catchIncludeRet catch

    typealias

    声明一个类型别名

    typeof

    保留以供未来使用

    val

    声明一个只读属性局部变量

    var

    声明一个可变属性局部变量

    val 和 var

    val(value 的简写)用来声明一个不可变的变量,对应 Java 中的 final 变量。
    var(variable 的简写)用来声明一个可变的变量,对应 Java 中的非 final 变量。
    Kotlin 类型推导机制:val 关键字定义了个变量 a,给它赋值 10,a 就会自动推导成整型变量。若把字符串赋值给 a,a 就会自动推导成字符串变量。
    1
    2
    3
    val a = 10 //(直接赋值)类型自动推导

    val a = "hello" //(直接赋值)类型自动推导
    延迟赋值:需要显式声明变量的类型
    1
    2
    3
    4
    5
    6
    7
    var a: Int
    a = 10
    a = 20

    val b: Int
    b = 12
    b = 20//编译器报错 Val cannot be reassigned

    val 如果未在声明的时候初始化,之后可以初始化一次,之后再赋值编译不通过

    var 可以多次赋值。不过只能赋值同种类型的,赋值不同类型编译器会报错。

    when

    开始一个 when 表达式(执行其中一个给定分支)
    替代switch

    when可以与else配合使用

while

开始一个 while 循环(前置条件的循环)

循环while、for、do-while

in

  • in ‘A’..’F’
  • (a, b) in treeMap集合
  • !in

    downTo

    until

    step

软关键字

以下符号在适用的上下文中充当关键字,而在其他上下文中可用作标识符:

by

  • 只能修饰var可读可写变量
  • 声明的变量为不可控类型
  • 声明的变量不能有初始值
  • 声明的变量不能是基本数据类型
  • 构造器中初始化的属性需要 lateinit 关键字
    lateinit声明的var变量,使用的时候要用::xxx.isInitialized进行判断是否初始化过了(然后直接进行判空后的处理),否则会抛出UninitializedPropertyAccessException异常

    lazy

  • 只能修饰val常量。(懒加载:初始化方式已确定,只是在使用的时候执行。)
  • param

    用作注解使用处目标

    property

    用作注解使用处目标

    receiver

    用作注解使用处目标

    set

内置函数let、also、with、run、apply

前言
Kotlin中,有一些用于扩展 & 方便开发者编码的内置函数,能大大提高开发者的开发效率。今天,我将主要讲解的是:

  • let函数
  • also函数
  • with函数
  • run函数
  • apply函数

    基础知识:接口回调中Lambda使用

    在Kotlin中可使用Lambda函数简化一些不必要的嵌套接口回调方法

    注:仅支持单个抽象方法回调,多个回调方法不支持。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
     // Java接口回调
    mVar.setEventListener(new ExamEventListener(){

    public void onSuccess(Data data){
    // ...
    }

    });

    // 同等效果的Kotlin接口回调(无使用lambda表达式)
    mVar.setEventListener(object: ExamEventListener{

    public void onSuccess(Data data){
    // ...
    }
    });

    // Kotlin接口回调(使用lambda表达式,仅留下参数)
    mVar.setEventListener({
    data: Data ->
    // ...
    })

    // 继续简化
    // 简化1:借助kotlin的智能类型推导,忽略数据类型
    mVar.setEventListener({
    data ->
    // ...
    })

    // 简化2:若参数无使用,可忽略
    mVar.setEventListener({
    // ...
    })

    // 简化3:若setEventListener函数最后一个参数是一个函数,可把括号的实现提到圆括号外
    mVar.setEventListener(){
    // ...
    }

    // 简化3:若setEventListener函数只有一个参数 & 无使用到,可省略圆括号
    mVar.setEventListener{
    // ...
    }

    下面,我将讲解Kotlin里提供用于扩展 & 方便开发者编码的几个有用内置函数:let函数、also函数、with函数、 run函数、apply函数。

    let函数

    定义

  • 一个作用域函数
    作用
  • 定义一个变量在一个特定的作用域范围内
  • 避免写一些判断null的操作
    应用场景
  • 明确一个变量所处特定的作用域范围内可使用
  • 针对一个可null的对象统一做判空处理
    使用方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 作用1:使用it替代object对象去访问其公有的属性 & 方法
    object.let{
    it.todo()
    }

    // 作用2:判断object为null的操作
    object?.let{//表示object不为null的条件下,才会去执行let函数体
    it.todo()
    }

    // 注:返回值 = 最后一行 / return的表达式
    使用示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 使用Java
    if( mVar != null ){
    mVar.function1();
    mVar.function2();
    mVar.function3();
    }

    // 使用kotlin(无使用let函数)
    mVar?.function1()
    mVar?.function2()
    mVar?.function3()

    // 使用kotlin(使用let函数)
    // 方便了统一判空的处理 & 确定了mVar变量的作用域
    mVar?.let {
    it.function1()
    it.function2()
    it.function3()
    }

    also函数

    作用 & 应用场景
    类似let函数,但区别在于返回值:
  • let函数:返回值 = 最后一行 / return的表达式
  • also函数:返回值 = 传入的对象的本身
    使用示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // let函数
    var result = mVar.let {
    it.function1()
    it.function2()
    it.function3()
    999
    }
    // 最终结果 = 返回999给变量result

    // also函数
    var result = mVar.also {
    it.function1()
    it.function2()
    it.function3()
    999
    }
    // 最终结果 = 返回一个mVar对象给变量result

    with函数

    作用
    调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
    应用场景
    需要调用同一个对象的多个方法 / 属性
    使用方法
    1
    2
    3
    4
    5
    with(object){
    // ...
    }

    // 返回值 = 函数块的最后一行 / return表达式
    使用示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 此处要调用people的name 和 age属性
    // kotlin
    val people = People("carson", 25)
    with(people) {
    println("my name is $name, I am $age years old")
    }

    // Java
    User peole = new People("carson", 25);
    String var1 = "my name is " + peole.name + ", I am " + peole.age + " years old";
    System.out.println(var1);

    run函数

    作用 & 应用场景
    结合了let、with两个函数的作用,即:
  1. 调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
  2. 定义一个变量在特定作用域内
  3. 统一做判空处理
    使用方法
    1
    2
    3
    4
    object.run{
    // ...
    }
    // 返回值 = 函数块的最后一行 / return表达式
    使用示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 此处要调用people的name 和 age属性,且要判空
    // kotlin
    val people = People("carson", 25)
    people?.run{
    println("my name is $name, I am $age years old")
    }

    // Java
    User peole = new People("carson", 25);
    String var1 = "my name is " + peole.name + ", I am " + peole.age + " years old";
    System.out.println(var1);

    apply函数

    作用 & 应用场景
    与run函数类似,但区别在于返回值:
  • run函数返回最后一行的值 / 表达式
  • apply函数返回传入的对象的本身
    应用场景
    对象实例初始化时需要对对象中的属性进行赋值 & 返回该对象
    使用示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // run函数
    val people = People("carson", 25)
    val result = people?.run{
    println("my name is $name, I am $age years old")
    999
    }
    // 最终结果 = 返回999给变量result

    // applyh
    val people = People("carson", 25)
    val result = people?.apply{
    println("my name is $name, I am $age years old")
    999
    }
    // 最终结果 = 返回一个people对象给变量result
    至此,关于Kotlin里提供用于扩展 & 方便开发者编码的几个有用内置函数讲解完毕。

总结

可提前判空 可返回值 可重命名入参 定义inline的结构 返回值 是扩展函数 应用场景
let Y Y Y fun <T,R> T.let(block: (T)->R): R=block(this) 闭包形式返回 Y 1. 明确一个变量所处特定的作用域范围内可使用
2. 针对一个可null的对象统一做判空处理
also Y N(返回自身) Y fun <T> T.also(block: (T)->Unit): T{block(this);return this} 返回this Y 同上
run Y Y N fun <T,R> T.run(block: T.()->R): R=block() 闭包形式返回 Y 1. 调用一个对象的多个方法/属性时,可省去对象名重复,直接调用方法名/属性即可
2. 定义一个变量在特定作用域内
3. 统一做判空处理
apply Y N(返回自身) N fun <T> T.apply(block: T.()->Unit): T{block();return this} 返回this Y 对象实例初始化时需要对对象中的属性进行赋值&返回该对象
with N Y N fun <T,R> with(receiver: T,block: T.()->R): R=receiver.block() 闭包形式返回 N 需要调用同一个对象的多个方法/属性

with、run、let、apply、also

inline、noinline、crossinline的区别

其他

companion

Kotlin中的object 与companion object的区别

  1. object
    对象表达式;对象声明
    • 对象表达式
      1
      2
      3
      4
      5
      6
      val tv = findViewById<TextView>(R.id.tv)
      tv.setOnClickListener(object: OnClickListener{
      override fun onClick(p0: View?){
      Toast.makeText(this@MainActivity, "吐司内容", Toast.LENGTH_SHORT)
      }
      })
    • 对象声明
      object修饰的类为静态类,里面的方法和变量都是静态
      • 直接声明类
        1
        2
        3
        4
        5
        6
        object DemoManager{
        private val TAG = "DemoManager"
        fun a(){
        Log.e(TAG, "此时 object 表示 直接声明类")
        }
        }
      • 声明静态内部类
        类内部的对象声明,没有被inner修饰的内部类都是静态的
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        class DemoManager{
        object MyObject{
        fun a(){
        Log.e(TAG, "此时 object 表示 声明静态内部类")
        }
        }

        //kotlin中调用
        fun init(){
        MyObject.a()
        }
        }
        java中调用
        1
        MyObject.INSTANCE.a();
  2. companion object
    修饰为伴生对象。伴生对象在类中只能存在一个,类似于java中的静态方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class DemoManager{
    companion object{
    private val TAG = "DemoManager"
    fun b(){
    Log.e(TAG, "此时 companion object 表示伴生对象")
    }
    }

    //kotlin中调用
    fun init(){
    b()
    }
    }
    java中调用
    1
    DemoManager.Companion.b();

    internal

    internal修饰类的方法,表示这个类方法只适合当前module使用,其他module调用不到这个方法

    sealed

  3. 密封类和它的子类必须定义在一个文件中
  4. 密封类是不能被初始化的
    理解:父类只是一个组织者(对于子类来说)(除了这个功能他什么都做不了)甚至初始化都做不到,具体可以出面做事情的是子类

    泛型in、out、where