符号
:
变量类型、函数、接口、返回值
- 定义变量
- 不可变变量
val a :Int
val a :Int = 4
- 可变变量
var b :String
- 不可变变量
- 继承基类
calss 子类 : 父类() {}
注意“:
”后是父类的构造方法【父类带括号“()”】
- 实现接口
class 类 : 接口 {}
- 定义函数返回值
?
将类型标记为可空
?.
判空不处理
1 | val ages1 = age?.toInt() //空的话不处理 |
?:
判空取后面的值
1 | var data = lesson.data ?: "日期待定" |
如果
lesson.data
是null
则把后面的默认值赋值给data
,否则把lesson.data
赋值给data
例子:
1 | if(user.username?.length ?: 0 < 4){} |
user.username
是null
,那么user.username?.length
这个整体就是null
,就会得到0
;否则,user.username
有值则可以取到它的length
,然后判断是否<4
,就会忽略?:0
!
逻辑非
!!
非空断言运算符
将任何值转换为非空类型,若该值为空则抛出异常
->
- 分隔 lambda 表达式的参数与主体
- 分隔在函数类型中的参数类型与返回类型声明
- 分隔 when 表达式分支的条件与代码体
+
、-
、*
、/
、%
—— 数学操作符-/% 也用于将数组传递给 vararg 参数
it
kotlin
的lambda
更加简约
1 | //正常情况 |
reified
不再需要传参数clazz
比如定义实现一个扩展函数的启动Activity
,一般都需要传Class<T>
参数:
1 | //Function |
使用reified
方式
通过添加类型传递简化泛型参数
1 | //Function |
不安全的转换
kotlin中,使用安全转换操作符as?
,它可以在失败时返回null
。如下,我们认为会安全的获取数据或返回null。然后如果获得的数据不是它期望的类型,这个函数会出现crash
1 | //Function |
如果不想crash的,修改如下
1 | //Function |
使用reified
方式
简化泛型参数和保证
as?
类型转换安全性
1 | //Function |
不同的返回类型函数重载
实现一个函数计算 DP 到像素,并返回一个 Int 或 Float。这种情况就会想到函数重载,如下所示:
1 | fun Resources.dpToPx(value: Int): Float { |
但是,这将导致编译时出错。原因是,函数重载方式只能根据参数计数和类型不同,而不能根据返回类型。
使用reified
方式
1 | inline fun <reified T> Resources.dpToPx(value: Int): T { |
Any
相当于Java中的Object,如果在泛型中还可以用“*”表示,如<*>
硬关键字
as
- 用于类型转换
- 为导入指定一个别名
用于安全类型转换as?
终止循环的执行break
声明一个类class
继续最近层循环的下一步continue
开始一个 do/while 循环(后置条件的循环)do
定义一个 if 表达式条件为 false 时执行的分支else
指定布尔类型的“假”值false
开始一个 for 循环for
声明一个函数fun
开始一个 if 表达式if
in
- 指定在 for 循环中迭代的对象
- 用作中缀操作符以检测一个值属于一个区间、 一个集合或者其他定义“contains”方法的实体
- 在 when 表达式中用于上述目的
- 将一个类型参数标记为逆变
!in
- 用作中缀操作符以检测一个值不属于一个区间、 一个集合或者其他定义“contains”方法的实体
- 在 when 表达式中用于上述目的
声明一个接口interface
is
- 检测一个值具有指定类型
- 在 when 表达式中用于上述目的
!is
- 检测一个值不具有指定类型
- 在 when 表达式中用于上述目的
是表示不指向任何对象的对象引用的常量null
同时声明一个类及其实例object
指定当前文件的包package
从最近层的函数或匿名函数返回return
super
- 引用一个方法或属性的超类实现
- 在次构造函数中调用超类构造函数
this
- 引用当前接收者
- 在次构造函数中调用同一个类的另一个构造函数
抛出一个异常throw
指定布尔类型的“真”值true
开始一个异常处理块try
try、catch、finally
与java的一样(try、catch中都有return的情况)
- 如果try中报错那么走catch的return,再走finally(finally一定会走),finally后代码不执行
- 如果try中不报错,那么走try的return,再走finally(finally一定会走),finally后代码不执行
与java的一样(try、catch中没有return的情况) - 如果try中报错:try–>catch–>finally,会走finally后的代码
- 如果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
39fun 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
3val a = 10 //(直接赋值)类型自动推导
或
val a = "hello" //(直接赋值)类型自动推导1
2
3
4
5
6
7var a: Int
a = 10
a = 20
val b: Int
b = 12
b = 20//编译器报错 Val cannot be reassignedval 如果未在声明的时候初始化,之后可以初始化一次,之后再赋值编译不通过
var 可以多次赋值。不过只能赋值同种类型的,赋值不同类型编译器会报错。
when
开始一个 when 表达式(执行其中一个给定分支)
替代switchwhen可以与else配合使用
while
开始一个 while 循环(前置条件的循环)
循环while、for、do-while
in
软关键字
以下符号在适用的上下文中充当关键字,而在其他上下文中可用作标识符:
by
- 将接口的实现委托给另一个对象
- 将属性访问器的实现委托给另一个对象
开始一个处理指定异常类型的块catch
声明一个主构造函数或次构造函数constructor
用作注解使用处目标delegate
引用一个 Kotlin/JS 代码中的动态类型dynamic
用作注解使用处目标field
用作注解使用处目标file
开始一个当 try 块退出时总会执行的块finally
get
- 声明属性的 getter
- 用作注解使用处目标
将另一个包中的声明导入当前文件import
开始一个初始化块init
lateinit
- 只能修饰
var
可读可写变量 - 声明的变量为不可控类型
- 声明的变量不能有初始值
- 声明的变量不能是基本数据类型
- 构造器中初始化的属性需要 lateinit 关键字
lateinit声明的var变量,使用的时候要用::xxx.isInitialized
进行判断是否初始化过了(然后直接进行判空后的处理),否则会抛出UninitializedPropertyAccessException
异常lazy
- 只能修饰
val
常量。(懒加载:初始化方式已确定,只是在使用的时候执行。)
用作注解使用处目标param
用作注解使用处目标property
用作注解使用处目标receiver
set
- 声明属性的 setter
- 用作注解使用处目标
用作注解使用处目标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对象给变量resultwith函数
作用
调用同一个对象的多个方法 / 属性时,可以省去对象名重复,直接调用方法名 / 属性即可
应用场景
需要调用同一个对象的多个方法 / 属性
使用方法使用示例1
2
3
4
5with(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
4object.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对象给变量resultKotlin
里提供用于扩展 & 方便开发者编码的几个有用内置函数讲解完毕。
总结
可提前判空 | 可返回值 | 可重命名入参 | 定义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 | 需要调用同一个对象的多个方法/属性 |
inline、noinline、crossinline的区别
其他
companion
Kotlin中的object 与companion object的区别
- object
对象表达式;对象声明- 对象表达式
1
2
3
4
5
6val 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
6object DemoManager{
private val TAG = "DemoManager"
fun a(){
Log.e(TAG, "此时 object 表示 直接声明类")
}
} - 声明静态内部类
类内部的对象声明,没有被inner
修饰的内部类都是静态的java中调用1
2
3
4
5
6
7
8
9
10
11
12class DemoManager{
object MyObject{
fun a(){
Log.e(TAG, "此时 object 表示 声明静态内部类")
}
}
//kotlin中调用
fun init(){
MyObject.a()
}
}1
MyObject.INSTANCE.a();
- 直接声明类
- 对象表达式
- companion object
修饰为伴生对象。伴生对象在类中只能存在一个,类似于java中的静态方法
java中调用1
2
3
4
5
6
7
8
9
10
11
12
13class DemoManager{
companion object{
private val TAG = "DemoManager"
fun b(){
Log.e(TAG, "此时 companion object 表示伴生对象")
}
}
//kotlin中调用
fun init(){
b()
}
}1
DemoManager.Companion.b();
internal
internal修饰类的方法,表示这个类方法只适合当前module使用,其他module调用不到这个方法sealed
- 密封类和它的子类必须定义在一个文件中
- 密封类是不能被初始化的
理解:父类只是一个组织者(对于子类来说)(除了这个功能他什么都做不了)甚至初始化都做不到,具体可以出面做事情的是子类泛型in、out、where