学习“Kotlin编程指南”笔记
第9章 标准库函数
1、apply 以this作为上下文对象,返回接收者
例如:var p = people.apply{ this.name } //p是people
2、let 以it作为上下文对象,返回lambda最后一行结果值
例如:var p = people.let{ this.name } //p是name的值
3、run 以this作为上下文对象,不返回接收者,而是跟let一样返回lambda最后一行结果值
例如:var p = people.run{ this.name } //p是name的值
4、with 以this作为上下文对象,返回lambda最后一行结果值,不过调用with时需要值参作为其第 一个参数传入
例如1:var p1 = with("abc"){ this.length >= 10 } //p1的结果是false
例如2:var p2 = with(100){ this >= 10 } //p2的结果是true
5、also 以it作为上下文对象,返回接收者
例如:var p = people.also{ it.name } //p是people
6、takeIf 以it作为上下文对象,需要判断 lambda中提供的条件表达式,给出 true 或 false 结果。
如果判断结果是 true,从 takeIf函数返回接收者对象;如果是 false,则返回 null。
例如:var p = people.takeIf{ it.name == "abc" } //name是abc的话,p是people,不是则p是null
第10章 List与Set
1、listOf 返回一个不可变的List集合(返回一个Collections.SingletonList)
2、mutableListOf() 返回一个可变的MutableList集合(返回一个ArrayList)
3、for(i in 0..10) { ... } 循环0-9,i是索引
4、for(i in 集合.size()) 循环集合,i是索引
5、for (元素 in 集合) { ... } 循环集合,每次循环出一个元素
6、setOf 返回一个不可变的Set集合(返回一个Collections.SingletonSet)
7、mutableSetOf() 返回一个可变的MutableSet集合(返回一个LinkedHashSet)
【List函数】
1、first() 返回第一个集合元素
2、last() 返回最后一个集合元素
3、getOrElse(Int) 返回指定索引元素,不存在返回lambda提供的默认值
4、getOrNull(Int) 返回知道索引元素,不存在就返回null
5、contains(E) 指定的元素是否存在
6、containsAll(E) 指定的多个元素是否存在
7、remove(E) 删除指定元素
8、add(E) 添加元素
9、addAll() 向列表中添加另一个同类型列表中的全部元素
10、toMutableList() 转成可变集合
11、toList() 转成不可变集合
12、+= 添加一个新元素或者新集合中的元素到列表中(必须是val才可以)
13、-= 删除列表中某个元素或者从列表中删除集合所列元素(必须是val才可以)
14、clear() 删除所有元素
15、removeIf { it.contains("o") } 基于lambda表达式指定的条件删除元素(循环集合把字符串元素
为o的删除)
16、filter{ it.contains("Eli") } 基于lambda表达式指定的条件找出元素(循环集合把字符串元素为Eli
的返回一个新集合)
17、forEach { 元素 -> ...} forEach循环
18、forEachIndexed { 索引, 元素 -> ... } forEach循环,多一个索引
19、shuffled() 返回一个打乱循序的集合
20、val(type,name,price) = list 解构,如果不想返回第二个元素可以把name换成_下划线
21、toSet() List集合转成Set集合
22、toMutableSet() MutableList集合转成MutableSet集合
23、distinct() 将List转成Set然后再转成List(转成Set集合剔除List集合重复元素,然后再转回到List)
【Set函数】
1、elementAt(Int) 返回指定索引元素,读取速度要慢于List集合,适合元素不多时用
(因为setOf和mutableSetOf返回的是一个有序的LinkedHashSet,所以才可以用下标查找)
2、toList() Set集合转成List集合
3、toMutableList() MutableSet集合转成MutableList集合
4、Set有很多函数跟List函数是一样,如add、clear函数等
【数组】
1、IntArray对应与Java的int[],其它都是对应与Java的基本类型数组
2、Array对应与Java的引用类型数组,如Kotlin的Array<Int>对应Java的Integer[]
第11章 Map
1、mapOf() 返回一个不可变的Map集合(返回一个Collections.SingletonMap)
2、mutableMapOf() 返回一个可变的MutableMap集合(返回一个LinkedHashMap)
3、to 函数将它左边和右边的值转化成一个对(Pair),一种表示两个元素一组(键和值)的特
殊类型;
例如:mapOf("Eli" to 10.75, "Mordoc" to 8.25)
4、Pair 类型定义 Map;
例如:mapOf(Pair("Eli", 10.75),Pair("Sophie", 5.50))
【Map函数】
1、[](取值运算符)读取键对应的值,如果键不存在就返回null;
例如:patronGold["Reginald"]
2、get() 读取键对应的值,如果键不存在就返回null, 不会抛出异常
3、getValue() 读取键对应的值,如果键不存在就抛出异常;
例如:getValue("Reggie")
4、getOrElse() 读取键对应的值,或者使用匿名函数返回默认值;
例如:getOrElse("Reggie") {
"No such patron"
}
5、getOrDefault() 读取键对应的值,或者返回默认值;
例如:getOrDefault("Reginald", 0.0)
6、+= 添加或更新键值对;必须是val
7、put() 添加键值对,或者更新现有键的值
8、putAll() 添加所有传入的键值对
9、getOrPut() 键值不存在,就添加并返回结果;否则就返回已有键对应的值;该函数是 MutableMap的函数,Map没有该函数
10、remove() 从 Map 集合里删除指定键值对
11、- 删除指定键值对,返回新的 Map 集合; 例如:mutableMapOf("Mordoc" to 6.0, "Jebediah" to 1.0) - "Mordoc"
12、-= 删除键值对; mutableMapOf("Mordoc" to 6.0, "Jebediah" to 1.0) -= "Mordoc"
13、clear() 清空 Map 集合
第12章 定义类
1、val 与 var 修饰符属性的区别:val表示属性只有getter方法,var表示属性有getter和setter方法
2、定义的每一个属性,Kotlin 会产生一个 field、一个 getter,以及一个 setter(如果需要的话)
3、重写(覆盖)属性的getter和setter方法时可以这样写
val name = "madrigal"
get() = field.capitalize()
set(value) {
field = value.trim()
}
4、属性不给外面的类修改,只能当前类修改,可以这样写
val name = "madrigal"
get() = field.capitalize()
private set(value) {
field = value.trim()
}
set 方法前加如private修饰符
5、Kotlin 可见修饰符
public(默认) 函数或属性类对于外部可见。默认情况下,不加可见性修饰符的函数或属性 都是公共可见的
private 函数或属性仅在类自己的内部可见
protected 函数或属性仅在类自己的内部或该类的子类内可见
internal 函数或属性仅在同一模块内可见
第13章 初始化
1、Kotlin分主/次构造函数
2、主/次构造函数的变量叫临时变量,临时变量不能直接用于任何表达式中,比如计算或者打印, 只能赋值给类中的相同类型的属性
3、可以在主构造函数中定义属性,次构造函数不行
4、次构造函数最终还是要直接调用或间接(调用其它已经调用主构造函数的次构造函数)调用主 构造函数
5、构造函数可以默认参数
6、构造函数可以像其它函数一样使用命名参数。命名参数不仅可以防止参数混淆,还能方便你以
任意循序给钩爪函数提供值参。
类:Player(var name:String,var sex:Int=1,var height:Double)
调用:Player(name="张三",height=170.5)或Player(height=170.5,name="张三")
6、初始化块,即init{}。初始化块代码会在构造类实例时执行
7、Kotlin 对属性有严格的初始化要求,函数外的非空属性必须给初始值,而函数内的属性可以后 面再赋值
8、初始化块相当于是主构造函数的函数体,类似于次构造函数的函数体(花括号)
9、Kotlin不可空的属性可以做延迟初始化,使用var声明再加上lateinit关键字,在被引用调用前完 成初始化一切都没有问题,否则就会报UninitializedPropertyAccessException异常
用法:lateinit var name: String
10、延迟属性可以使用::isIntit判断是否完成初始化
11、惰性初始化做预赋值处理,当属性被引用的时候lambda表达式中的代码才赋值给属性
例如:val hometown = by lazy{ selectHometown() } //by lazy是关键字
第14章 继承
1、类默认都是封闭的,也就是不允许其他类继承自己。要开放继承,必须使用open关键字修饰 它。
没有open的类通过查看字节码是这样的:public final class Test
有open的类通过查看字节码是这样的:public class Test
2、在Kotlin的类、属性(get/set)、函数都是不开放的(即都带有final关键字),就像上面第1点 说的那样, 类如果想要被其它类继承就要添加open关键字修饰。函数要被子类重写也要加上 open关键字修饰,才能被子类重写,如果子类重写了父类的函数,这个子类不想被他自己的子 类重写可以加final关键字。
类:public open class Test,没有加open前查看字节码是这样的 public final class Test
,加open后是这样的public class Test
属性:由于kotlin的属性自动生成get/set方法,没有加open关键字前,例如:public val name
,生成的是public final String getName(),加open后是这样public String getName()
函数:public open fun getTest(),没有加open前查看字节码是这样的 public final void getTest()
,加open后是这样的 public void getTest()
3、跟Java一样,可以使用super调用父类的函数。例如:当子类重写了父类的函数,可以使用 super调用父类的函数
override fun getA(){
super.getA() //调用父类的getA函数
}
4、Kotlin的类型检测是用is关键字,Java的则是instanceof关键字用来类型检测
5、Kotlin类层次结构的根。每个Kotlin类都有Any作为超类
6、Kotlin类型转换用as关键字,例如:var str = any as String ,将any转成String类型
7、智能类型转换则是在类型转换前用is关键字检测类型是否是相同类型,如果是则Kotlin编译器会 自动转换类型。
例如:if(any is String) any.indexOf() //当满足if条件时any自动转成String类型
8、Kotlin的超级基类是Any
9、Any公共超类,不局限于像JVM 这样的具体平台,Any类提供了超越类的一种抽象定义。
如果项目代码的目标平台是 JVM,Any 类的 toString 函数的实现版本就是
java.lang.Object.toString;如果编译目标是JavaScript,toString 函数的实现版本就会完全不一 样。
第15章 对象
1、object 关键字有三种方式
对象声明:就是Java版里单例模式中的饿汉方式,构造函数是private的,在static{}块中实例化 对象(static块只会在该类执行一次)
例子:object Game{
fun get():String = "内容"
}
调用:Game.get()
对象表达式:相当于Java里的匿名类和匿名接口,匿名接口不用写实现类
例子:val abandonedTownSquare = object : TownSquare() {
override fun load() = "加载内容"
}
伴生对象:属性和函数相当于Java里的静态属性和静态方法,查看字节码就可以看出了
例子:class Test{
companion object {
fun get():String = "内容"
}
}
调用:Test.get()
2、[嵌套类]跟Java的内部类是一样的,不能调用被嵌套类中的属性。Kotlin的嵌套类里面的函数返 回值如果是被嵌套类的类型时不能像Java一样直接使用 类名.this,即不能使用 this@类名,需 要通过构造函数或函数传递进来。
例如:public class Car {
private var build = Build(this@Car) //通过构造函数将Car传递到嵌套类中
public class Build(var car: Car) { //Build是嵌套类
fun get(): Car {
return car //Java可以Car.this,Kotlin不可以this@Car
}
}
}
3、[数据类]名顾思义就是用来存储数据的,跟Java的JavaBean一样。
重写了toString函数,可以看到类中构造函数属性的值。重写了equals函数,可以用来判断两个 数据类的构造函数属性值是否相同(判断是否引用相同则用==)。重写了copy函数,可以复制 一个浅复制对象,跟Java对比就是不用写实现Cloneable接口和重写close()方法。数据类也支 持解析声明,不用添加用operator关键字修饰的组件函数即可实现如var (a,b) = Data(1,0),
另外其它的operator修饰函数操作可以看第5点
4、[枚举类]是定义常量集合的一种特殊类,枚举常量有类似于构造函数的功能,也就是说可以给 枚举常量自定义属性,属性可以是数值类型或者引用类型,前提是枚举类的构造函数是带参 的。
在Kotlin中由于编译器会生成getting/setting函数,所以不用像Java一样还要自getting/seting。
Kotlin例子:
enum City(var value: Int){
BEIJIN(010),SHANGHAN(021) //比如BEIJIN(010)相当于调用构造函数,然后将010传递给value属性
}
可以City.BEIJIN.getValue() //Kotlin编译器会自动为属性生成getting/setting函数,调用
getValue函数获取到010
5、运算符重载是Kotlin的内置数据类型天生支持参与一系列运算。比如集合中通过get函数传递索引读取元素时,可以使用[]运算符代替get函数。使用operator修饰符定义对应的函数来实现。下面演示常见操作符中的+和in。
操作符:+, 函数名:plus, 作用:把一个对象添加到另一个对象里
data class Coordinate(val x: Int,val y: Int){
operator fun plus(other: Coordinate) = Coordinate(x + other.x, y + other.y)
}
调用:
var c = Coordinate(1,1) + Coordinate(1,2) //得到一个Coordinate(2,3)
操作符:in, 函数名:contains, 作用:如果对象包含在集合里,则返回true
data class Coordinate(val x: Int,val y: Int){
operator fun contains(list: MutableList<ObjData>): Boolean = list.contains(this)
}
调用:
var list= mutableListOf<ObjData>()
list.add(Coordinate(1,3))
println(list in Coordinate(1,3))
如果Coordinate是数据类则返回true,因为数据类重写了equals函数,会通过判断构造函数的属 性值来判断是否有相同的存在,如果是普通类则返回false,因为两个Coordinate(1,3)不是相同的 引用
6、如果重写equals函数,也应该一并重写hashCode函数,Java也是这样,具体为什么我也不知道
7、普通非数据类重写equals函数可以通过IntelliJ的Code -> Generate来生成equals和hashCode函数
8、[密封类](sealed关键字修饰)用来表示一组子类型的闭集(集合),跟枚举类似都属于代数数据类型(ADT),密封类可以有若干个子类,它的子类都必须继承它并且子类定义在同一个文件里。枚举类跟密封类的区别在于枚举只能一个实例,而密封类的子类可以多个实例,并且子类可以有属性操作比较灵活。密封类是抽象类,它无法被外部实例化,所有的实现都只能由其内部的子类去完成。判断是否是他的子类可以用is关键字。另外密封类和嵌套类的区别就是密封类是抽象的,
可以使用将子类变量传递给父类变量,而嵌套类不能。
例子:sealed class StudentStatus{
class Active(var courseId: String) : StudentStatus(){
//可以定义成员属性和函数
}
object Graduated : StudentStatus(){
//可以定义成员属性和函数
}
data class Test() : StudentStatus(){
//可以定义成员属性和函数
}
fun getValue(){
println("只要是StudentStatus的子类都可以调用getValue函数")
}
}
用法: StudentStatus.Active("001").courseId //打印001
StudentStatus.Graduated().getValue() //可以调用父类的公开函数
StudentStatus.Test().getValue() //可以调用父类的公开函数
第16章 接口与抽象类
1、Kotlin接口关键字跟Java一样也是interface
2、Kotlin接口成员和函数都是公开的,不用写public和open也可以
3、Kotlin实现接口和继承基类都是:符号,并且实现的接口后面不用跟继承类一样要写()括号,因为接口没有构造函数
4、Kotlin接口的属性和函数可以有默认实现,例如函数可以有函数体来实现默认的实现,在Java中是没有该特性的
5、Kotlin抽象类跟Java一样使用abstract关键字,加上abstract关键字的函数是抽象函数,由子类实现重写(不用加open关键字)。非抽象函数默认不能被子类重写,必须跟普通基类的函数一样要加上open关键字才能被子类重写。
第17章 泛型
1、泛型类一般使用<T>符号表示,例如class Test<T>(value: T)。T可以任意定义其它符号
2、泛型函数分两种,一种是当类定义了泛型符号时,可以在函数传参和返回值使用该泛型符号来表示传递和返回指定类型。另一种则是当类不是泛型类时,函数可以跟泛型类一样使用<T>符号表示。
例如:public <T> fun getText(value: T):T{}
3、可以多泛型参数,例如泛型类<T,R>,跟Java一样
4、泛型约束就是传入的类型只能是指定的基类、接口或者抽象类的子类、实现类
例如:Test<T: Car> //Java则是Test<T extends Car>
5、通常泛型参数类型会被[类型擦除],Kotlin跟Java一样,也就是说在运行无法获取类型信息,但Kotlin可以通过reified关键字获取
6、想知道泛型参数具体是什么类型可以使用reified(具体化)关键字,配合inline修饰函数使用。
例如:inline fun <reified T> getValue(t: T)
可以通过下面代码得到T的类
var cl: Class<T> = T::class.java
配合Gson
gson.fromJson("{\"color\":\"黄色1\"}",T::class.java)
第18章 扩展
1、扩展是在现有类基础上不用继承和修改的方式来给接收者类型(被扩展类)增加类功能。
2、定义扩展函数就是在定义函数名前加上接收者类型,例如给String类增加一个打印函数。
例如:创建一个类文件,在里面定义 fun String.easyPrint() = println(this)
调用 var str = String() str.easyPrint()
3、支持链式调用的函数就是返回它的接收者(比如当前类this),或返回另一个对象。
例如Android的Dialong类:var dialog = Dialog().setTitle("标题").setView(view).show()
其中setTitle和setView函数都是返回Dialog类型的,这样看起来像是链起来一样。
4、泛型也可以被扩展
比如: fun T.easyPrint(): T{ // 调用:"abs".easyPrint().easyPrint()
println(this) // 由于是String类型调用,easyPrint函数返回的T就是String类型,
return this // 可以链式再调用easyPrint函数打印两次"abs"
}
5、属性也可以被拓展,但Kotlin编译器不会自动为属性添加getter方法和setter方法和支持字段(field),需要自己定义get或set函数
例如:var String.value //【特别注意】不能 var String.value = "ddd"
get() = this
set(v){
//不能value = v,要不然会出现死循环
//这里好像设置不了value,因为没有field
}
6、【注意】扩展属性和函数不能写在class{}中而是直接写在一个kt文件中,否则外部无法调用
7、不能调用接收者类型的私有属性和函数
8、infix 关键字适用于有单个参数的扩展和类函数,可以让你以更简洁的语法调用函数。如果一个函数定义使用了 infix 关键字,那么调用它时,接收者和函数之间的点操作符以及参数的一对括号都可以不要
例子: 属性 函数名 参数str indexOf 'o'
9、可空类扩展 就是扩展的函数即使接收者是null时也可以调用扩展函数并不会报空指针。
例子: fun String?.printWithDefault(default: String) = println(this ?: default)
调用: var nullabeString: String? = null; nullabeString.printWithDefault("Default string")
输出: Default string 因为接收者为null,而this就会为null
10、Kotlin标准库中的扩展都是以类名加s后缀来命名的。例如:Strings.kt 、Maps.kt
第19章 函数式编程基础
1、一个函数式应用通常由三大类函数构成:变换(transform)、过滤(filter)和合并(combine)。
2、最常用的两个变换函数是 map 和 flatMap
3、map 变换函数会遍历接收者集合,让变换器函数作用于集合里的各个元素。返回结果是包含
已修改元素的集合,会作为链上下一个函数的输入。
例子:listOf("zebra","giraffe").map{ "A baby $it" }
map函数返回[A baby zebra,A baby giraffe] 新集合
4、flatMap 变换函数操作一个集合的集合,将其中多个集合中的元素合并后返回一个包含所有元素的单一集合
例子:listOf(listOf("ab","ac"),listOf("ba","bc")).flatMap{ it } //lambda表达式返回的是集合,it就是集合
还可以配合filter一起使用,例如找出两个集合相同元素并返回一个新的集合
var aList = listOf("a","e","b")
var bList = listOf("a","e")
var nList = aList.flatMap{
bList.filter{
bIt-> bIt == it
}
}
flatMap函数返回[a,e] 新集合,filter过滤找出相同的元素并返回集合给flatMap,flatMap将多个相同的集合返回一个新的集合
5、filter 过滤函数接受一个 predicate 函数,用它按给定条件检查接收者集合里的元素并给出 true 或 false 的判定。如果 predicate 函数返回 true,受检元素就会添加到过滤函数返回的新集合里。如果 predicate 函数返回 false,那么受检元素就被移出新集合。
例子:listOf("abs","bbs","xxs").filter{ it.contains("bs") }
filter函数返回["abs","bbs"]新集合,it.contains就是一个predicate函数,与filter相反的是filterNot
5、zip 合并函数来合并两个集合,返回的是一个包含键值对的新集合,然后,在这个新集合上调用 toMap 函数,返回一个可以按键取值的 map 集合
例如:val employees = listOf("Denny", "Claudette", "Peter")
val shirtSize = listOf("large", "x-large", "medium")
val employeeShirtSizes = employees.zip(shirtSize).toMap()
最后employeeShirtSizes输出{Denny=large,Claudette=x-large,Peter=medium}
6、fold 合并函数接受一个初始累加器值,随后会根据匿名函数(针对集合元素调用)的结果更新
例如:val foldedValue = listOf(1, 2, 3, 4).fold(0) { accumulator, number ->
accumulator + (number * 3)
}
最后foldedValue输出30,accumulator 就是初始累加值,number 就是集合元素值
7、LIst、Set、Map这几个集合类型统称为及早集合,Kotlin还有一种集合叫惰性集合,惰性集合类
型的性能表现优异——尤其是用于包含大量元素的集合时——因为集合元素是按需产生的。
Kotlin 有个内置惰性集合类型叫序列(Sequence)。序列不会索引排序它的内容,也不记录
元素数目。这样的函数叫迭代器函数(generateSequence)。
例子:var listOfNumbers = generateSequence(0) { it + 1 }.take(10) //也可以调用filter过滤函数
for(number in listOfNumbers){
println(number) //种子数值从0开始,打印从0 - 9数字
}
如果没有take函数将会不停的打印下去,take函数返回包含前[n]个元素的列表,上面是10次
下面将List转序列
val listOfNumbers = (0 until 10000000).toList()
val sequenceOfNumbers = listOfNumbers.asSequence()
8、在Kotlin中有两个评估代码性能(耗时)的函数,分别是measureNanoTime和measureTimeMillis。前者返回纳秒时间,后者返回毫秒时间,都支持lambda表达式值参,只要把运行的代码放入lambda表达式中即可。
例如:val listInNanos = measureNanoTime {
// List functional chain here
}
listInNanos就是耗时
第20章 Kotlin 与 Java 互操作
1、Java世界里的所有对象都可能是 null
2、Kotlin的对象、变量等默认都是不为空
3、Kotlin调用Java的方法时要注意为null问题,可以在Java的方法添加下面注解,Kotlin编译器认识这样的注解
4、@Nullable注解会警告API的使用者调用的方法有可能返回null值,Java方法声明该注解让Kotlin调用的时候Kotlin编译器知道该Java方法的返回值有可能为null而做出相应提示
5、@NotNull来表明某个值永远不会为 null,跟第四点一样告诉Kotlin编译器调用的Java方法不会返回null
6、Kotlin属性可自动生成getting/setting方法,在Java中调用Kotlin的属性时会发现调用的是Kotlin定义属性自动生成的get/set方法,在Kotlin中可以避开使用getting/setting语法,你可以使用看上去似乎是直接读写字段或属性的点语法,同时不影响封装性
7、Kotlin 几乎没什么限制。类、函数以及变量都可以一股脑地放入一个 Kotlin 文件里。而在 Java 里,一个文件就对应一个类
8、Kotlin 顶层函数在 Java 里都被当作静态方法看待和调用(也就是函数不写在class内),Kotlin编译器会创建一个类名+Kt的类
例如:创建一个Hero.kt类,并在class外写一个名为takeUpArms的public函数,Java那边可以通过HeroKt.takeUpArms()调用(注意类命多Kt)
9、@file:JvmName("类名"),如果让Java与Kotlin更自然流畅一些,可以在文件顶部加上该注解指定编译类的名字
例如:第8点的Hero类文件可以这样 @file:JvmName("Hero") 一定加在类文件的第一行,java那边就可以Hero.takeUpArms()调用
10、Java没有默认方法参数的概念,Kotlin的函数参数可以定义默认参数
11、@JvmOverloads注解, 当Java调用Kotlin有默认参数的函数时,可以在Kotlin有默认参数的函数上面加上该注解,编译器就会产生多个对应的Java方法,尽最大可能服务Java调用者
12、Kotlin 的命名函数参数可以免掉函数重载的麻烦,@JvmOverloads 注解来协助产生 Kotlin 函数的重载版本
13、设计一个可能会暴露给 Java 用户使用的 API 时,记得使用@JvmOverloads 注解。这样,无
论是你 Kotlin 开发者还是 Java 开发者,都会对这个 API 的可靠性感到满意
14、@JvmField 注解,暴露它的支持字段给 Java 调用者,从而避免使用 getter 方法
例如:kotlin:val content = "内容" Java调用: xx.getContent()可以变成xx.content
15、@JvmField 注解在伴生对象中也可以避免Java调用Kotlin伴生对象里的属性时必须引用伴生对象的访问器才能访问它们,例如:Java调用,没有使用前是 类名.Companion.getXXX() ; 使用后是 类名.XXX
16、@JvmStatic 注解,作用类似于@JvmField,允许你直接调用伴生对象里的函数(类名.函数名)
17、Kotlin 里没有【静态成员的概念】,但许多常用的模式还是会编译成静态变量和方法。使用@JvmStatic 注解,控制 Java 开发者如何使用 Kotlin 代码就有了更多可能。
18、Kotlin并不区分受检异常和未受检异常。不用指定函数抛出的异常,而且可以处理也可以不处理异常。这种设计是基于Java中使用异常的实践做出的决定。
19、Kotlin 中的所有异常都是未检查异常。但 Java 里的异常都是已检查异常,为防止应用崩溃,你必须处理它们
20、@Throws 注解给 Java版本的函数添加了一个throws 关键字(@Throws注解写在Kotlin的函数上面,这样变成Java字节码的方法才有看见有throws Exception)
例如:在Kotlin的函数这样写
@Throws(Exception::class)
fun getAccept(){
throw Exception()
}
在Java那边编译器才会提示该方法有可能会抛出异常需要做处理提示。
转成Java字节码是这样的:
public void getAccept() throws Exception
21、在编写供 Java 开发者调用的 Kotlin API 时,要考虑使用@Throws 注解。这样,用户就知道怎么正确处理任何异常了
22、Java调用Kotlin的匿名函数时,Java这边用Function类型表示,匿名函数比如有2个参数就是Function2,比如Function<参数1,参数2,返回类型>泛型。如果Kotlin为Java提供API使用,最好避免使用匿名函数,但要使用也无妨
第22章 协程
1、Kotlin并没有将协程纳入标准库的API当中,而是以依赖库的形式提供的。所以如果我们想要使用协程功能,需要先在app/build.gradle文件当中添加如下依赖库:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
2、GlobalScope.launch函数可以创建一个协程的作用域,这样传递给launch函数的代码块(Lambda表达式)就是在协程中运行了
例子:GlobalScope.launch{
//协程作用域
}
3、runBlocking函数同样会创建一个协程的作用域,但是它可以保证在协程作用域内的所有代码和子协程没有全部执行完之前一直阻塞当前线程。需要注意的是,runBlocking函数通常只应该在测试环境下使用,在正式环境中使用容易产生一些性能上的问题。
例子:runBlocking{
//协程作用域
}
4、delay()函数可以让当前协程延迟指定时间后再运行,但它和Thread.sleep()方法不同。delay()函数是一个非阻塞式的挂起函数,它只会挂起当前协程,并不会影响其他协程的运行。需要注意的是delay函数必须写在协程作用域里才行
5、suspend关键字,协程作用域外的函数需要调用delay函数时会报错,这时候给调用的函数加suspend关键字声明成挂起函数就可以了(因为delay函数也是一个挂起函数,必须在协程作用域或挂起函数中才能调用)
例子:suspend fun getStr(){
delay(1000)
}
但如果在getStr函数中调用launch函数时无法调用成功,因为launch函数要求必须在协程作用域
当中才能调用(可以看第6点例子)。
6、coroutineScope函数也是一个挂起函数,它的特点是会继承外部的协程的作用域并创建一个子协程,借助这个特性,我们就可以给任意挂起函数提供协程作用域了
例子:suspend fun getStr(){
delay(1000)
coroutineScope{
launch{ }
}
}
6、coroutineScope函数和runBlocking函数还有点类似,它可以保证其作用域内的所有代码和子协程在全部执行完之前,外部的协程会一直被挂起。虽然看上去的作用是有点类似的,但是coroutineScope函数只会阻塞当前协程,既不影响其他协程,也不影响任何线程,因此是不会造成任何性能上的问题的。而runBlocking函数由于会挂起外部线程,如果你恰好又在主线程中当中调用它的话,那么就有可能会导致界面卡死的情况,所以不太推荐在实际项目中使用。
5、repeat(times: Int, action: (Int) -> Unit)函数用来循环动作
6、不管是GlobalScope.launch函数还是launch函数,它们都会返回一个Job对象,只需要调用Job对象的cancel()方法就可以取消协程了,但最好维护、最常用的创建协程方式是下面这种
val job = Job()
val scope = CoroutineScope(job) //CoroutineScope并非一个类型对象而是一个函数
scope.launch {
//协程1处理具体逻辑
}
scope.launch {
//协程2处理具体逻辑
}
job.cancel() //在Android中当调用cancel函数后建议在协程处理逻辑中添加判断当前Activity是否被销毁,避免空指针
7、async函数可以返回(获取)它的执行结果,必须在协程作用域才能调用,它会创建一个新的子协程并返回一个Deferred对象,如果我们想要获取async函数代码块的执行结果,只需要调Deferred对象的await()方法即可协程阻塞住,直到可以获取async函数的执行结果,下面介绍async和await函数两种使用方式。
方式一(串行关系):
GlobalScope.launch {
val start = System.currentTimeMillis()
val result1 = async {
delay(2000)
5 + 5
}.await()
val result2 = async {
delay(1000)
4 + 6
}.await()
println("result is ${result1 + result2}")
val end = System.currentTimeMillis()
println("cost ${end - start} ms.")
}
这种是前一个执行完后一个才执行,打印结果:result is 20 和 cost 3000 ms.
方式二(并行关系):
GlobalScope.launch {
val start = System.currentTimeMillis()
val result1 = async {
delay(2000)
5 + 5
}
val result2 = async {
delay(1000)
4 + 6
}
println("result is ${result1.await() + result2.await()}")
val end = System.currentTimeMillis()
println("cost ${end - start} ms.")
}
这种是同时执行当前后都执行完成了才打印结果,打印结果:result is 20 和 cost 2000 ms.
8、在Android主线程中创建的协程执行网络请求还是会报错,因为Android要求网络请求必须在子线程进行,解决办法看第9点
9、withContext()函数,会立即执行代码块中的代码,同时将外部协程挂起。当代码块中的代码全部执行完之后,会将最后一行的执行结果作为withContext()函数的返回值返回,因此基本上相当于val result =async{ 5 + 5 }.await()的写法。唯一不同的是,withContext()函数强制要求我们指定一个线程参数。
线程参数主要有以下3种值可选:Dispatchers.Default、Dispatchers.IO和Dispatchers.Main。Dispatchers.Default表示会使用一种默认低并发的线程策略,当你要执行的代码属于计算密集型任务时,开启过高的并发反而可能会影响任务的运行效率,此时就可以使用Dispatchers.Default。Dispatchers.IO表示会使用一种较高并发的线程策略,当你要执行的代码大多数时间是在阻塞和等待中,比如说执行网络请求时,为了能够支持更高的并发数量,此时就可以使用Dispatchers.IO。Dispatchers.Main则表示不会开启子线程,而是在Android主线程中执行代码,但是这个值只能在Android项目中使用,纯Kotlin程序使用这种类型的线程参数会出现错误。
其中Dispatchers.Main参数相当于Android的Handler.post函数。
10、suspendCoroutine函数可以用作简化回调,必须在协程作用域或挂起函数中才能调用,它接收一个Lambda表达式参数,主要作用是将当前协程立即挂起,然后在一个普通的线程中执行Lambda表达式中的代码。Lambda表达式的参数列表上会传入一个Continuation参数,调用它的resume()方法或resumeWithException()可以让协程恢复执行。
平常网络请求的时候都要写回调接口,使用suspendCoroutine函数后可以简化操作并且看起来像是直接调用函数一样就可以。
实现异步回调操作,具体看例子:
suspend fun getBaiduRes(){
try{
val res = request("https://www.baidu.com/")
//当它调用的是resume()函数
}catch(e: Exception){
//当它调用的是resumeWithException()函数
}
}
suspend fun request(address: String): String{
//调用suspendCoroutine函数后当前协程立刻挂起阻塞,直到调用resume和resumeWithException函数后才返回String数据
return suspendCoroutine{ continuation ->
HttpUtil.sendHttpRequest(address,object : HttpCallbackListener{
override fun onFinish(response: String){
continuation.resume(response)
}
override fun onError(e: Exception){
continuation.resumeWithException(e)
}
})
}
}
对于网络请求返回不同的数据Bean,可以通过给request添加泛型实现,suspendCoroutine函数 几乎可以用于简化任何回调的写法。