Kotlin关键字

符号

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

  • 定义变量

    • 不可变变量
      • val a :Int
      • val a :Int = 4
    • 可变变量
      • var b :String
  • 继承基类

    • calss 子类 : 父类() {}

      注意“”后是父类的构造方法【父类带括号“()”】

  • 实现接口

    • class 类 : 接口 {}
  • 定义函数返回值

    • 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

! 逻辑非

!! 非空断言运算符

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

->

+-*/% —— 数学操作符-/% 也用于将数组传递给 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

as?

用于安全类型转换

break

终止循环的执行

class

声明一个

continue

继续最近层循环的下一步

do

开始一个 do/while 循环(后置条件的循环)

else

定义一个 if 表达式条件为 false 时执行的分支

false

指定布尔类型的“假”值

for

开始一个 for 循环

fun

声明一个函数

if

开始一个 if 表达式

in

!in

interface

声明一个接口

is

!is

null

是表示不指向任何对象的对象引用的常量

object

同时声明一个类及其实例

package

指定当前文件的包

return

从最近层的函数或匿名函数返回

super

this

throw

抛出一个异常

true

指定布尔类型的“真”值

try

开始一个异常处理块

try、catch、finally

与java的一样(try、catch中都有return的情况)

  1. 如果try中报错那么走catch的return,再走finally(finally一定会走),finally后代码不执行
  2. 如果try中不报错,那么走try的return,再走finally(finally一定会走),finally后代码不执行

与java的一样(try、catch中没有return的情况)

  1. 如果try中报错:try–>catch–>finally,会走finally后的代码
  2. 如果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

catch

开始一个处理指定异常类型的块

constructor

声明一个主构造函数或次构造函数

delegate

用作注解使用处目标

dynamic

引用一个 Kotlin/JS 代码中的动态类型

field

用作注解使用处目标

file

用作注解使用处目标

finally

开始一个当 try 块退出时总会执行的块

get

import

将另一个包中的声明导入当前文件

init

开始一个初始化块

lateinit

  • 只能修饰var可读可写变量
  • 声明的变量为不可控类型
  • 声明的变量不能有初始值
  • 声明的变量不能是基本数据类型
  • 构造器中初始化的属性需要 lateinit 关键字

lateinit声明的var变量,使用的时候要用::xxx.isInitialized进行判断是否初始化过了(然后直接进行判空后的处理),否则会抛出UninitializedPropertyAccessException异常

lazy

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

param

用作注解使用处目标

property

用作注解使用处目标

receiver

用作注解使用处目标

set

setparam

用作注解使用处目标

where

指定泛型类型参数的约束

内置函数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

  1. 密封类和它的子类必须定义在一个文件中
  2. 密封类是不能被初始化的

理解:父类只是一个组织者(对于子类来说)(除了这个功能他什么都做不了)甚至初始化都做不到,具体可以出面做事情的是子类

泛型in、out、where