Kotlin (五)在集合里面感受Lambda
5.1 简化表达
举个Android里面最常用的例子,java总普遍的用法
view.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
...
}
})
翻译成kotlin并且简化
view.setOnClickListener(object:OnClickListener {
override fun void onClick(v:View){
...
}
})
按照Java 单一抽象方法OnClickListener 可以用Kotlin函数替代
fun setOnclickListener(listener:(View)->Unit)
->Lambda
view.setOnClickListener({
。。。
})
-> kotlin语法糖,listener是唯一的参数,所以可以省略括号
view.setOnClickListener{
。。。
}
带有接收者的Lambda
View 接收者类型扩展invisible方法
fun View.invisible(){
visibility = View.INVISIBLE
}
接收者函数类型
val sum:Int.(Int)->Int = { other ->plus(other)}
fun main() {
println(2.sum(1))
}
int型变量调用sum传入一个int型变量参数,进行plus操作
官网一个小例子
class HTML{
fun body(){
println("Test custom HTML")
}
}
fun html(init:HTML.()->Unit):HTML{
val html=HTML()
html.init()
return html
}
// 调用
html{
body()
}
with 和 apply
作用:再写Lambda时候,省略需要多次书写的对象名,默认用this指向它
Android中我们会给视图控件绑定属性。with例子
fun bindData(bean:ContentBean){
val titleTv = findViewById<TextView>(R.id.titleTv)
val contentTv = findViewById<TextView>(R.id.contentTv)
with(bean){
titleTv.text = this.title
contentTv.text = this.content
titleTv.textSize = this.titleFontSize
contentTv.textSize = this.contentFontSize
}
}
不用with会写很多重复的bean
with在kotlin中的定义
public inline fun <T, R> with(receiver: T, block: T.() -> R): R
第一个参数是一个接收者,第二个参数是一个创建这个类型的block。因此在接收者调用block的时候可以在Lambda直接使用this代替bean
看下apply 的写法
fun bindData(bean:ContentBean){
val titleTv = findViewById<TextView>(R.id.titleTv)
val contentTv = findViewById<TextView>(R.id.contentTv)
bean.apply {
titleTv.text = this.title
contentTv.text = this.content
titleTv.textSize = this.titleFontSize
contentTv.textSize = this.contentFontSize
}
}
apply 的定义
public inline fun <T> T.apply(block: T.() -> Unit): T
直接声明了一个T的扩展,block参数是一个返回Unit类型的函数
with的block返回自由的类型。with和apply很多情况可以互相替代
5.2 集合的高阶函数API
map 简化
java8 之前的遍历
int[] list = {1,2,3,4,5,6};
int newList[] = new int[list.length];
for (int i = 0; i < list.length; i++) {
newList[i] = list[i] * 2;
}
java8
int newList[] = Arrays.stream(list).map(x-> x*2).toArray();
kotlin
val list = listOf(1,2,3,4,5,6);
val newList = list.map{it*2}
map 后面的表达式就是一个匿名带参的函数, map接收函数
fun foo3(bar:Int) = bar * 2
val newList2 = list.map { foo(it) }
map 接收函数,然后将集合每个元素用这个函数操作,将操作结果返回,最后生成一个新的集合
map源代码
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
for (item in this)
destination.add(transform(item))
return destination
}
首先定义了map的扩展方法,他的实现主要依赖mapTo。两个参数,第一个MutableCollection 集合,第二个是一个方法 (transform: (T) -> R)。就是将transform方法产生的结果添加到新集合里面去,返回一个新集合,避免我们for并且产生临时变量
filter、count 集合筛选
data class Student(val name:String,val age:Int,val sex:String,val score:Int)
val jielun = Student("jielun",30,"m",85);
val david = Student("david",35,"f",80);
val lilei = Student("lilei",32,"m",90);
val lili = Student("",31,"m",97);
val jack = Student("jack",18,"m",92);
val pan = Student("pan",20,"m",82);
val students = listOf(jielun,david,lilei,lili,jack,pan)
val mstudents= students.filter { it.sex == "m" }
看下filter源码
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
}
看完map源码filter就比较好理解,依赖filterTo,接收两个参数,第一个MutableCollection 集合,第二个 predicate: (T) -> Boolean 返回boolean的函数(Lambda表达式)函数返回true保留否则丢弃
其他过率方法还有
filterNot,过率掉满足条件的
filterNotNull,过率掉null元素
count,统计满足条件的元素个数----显得到满足条件列表再统计,效率有点低
val stuCount= students.filter { it.sex == "m" }.size
sumBy、sum、fold、reduce 别样的求和方式
java中我们做法是for然后累加,用sumBy 一行
val scoleTotal = students.sumBy { it.score }
拿最开始的list的int数组求和,sum和sumBy差不多
val total = list.sum()
val total2 = list.sumBy { it }
fold 是一个比较强的API ,先看下实现
public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
var accumulator = initial
for (element in this) accumulator = operation(accumulator, element)
return accumulator
}
fold 接收两个参数,第一个,initial 初始值,第二个是一个operation函数。 实现的时候通过for遍历集合每个元素,每次都调用operation函数---也有两个参数,第一个是上一次调用这个函数的结果(第一次使用initial出事值),第二个参数就是当前遍历的元素。
如何使用
val foldTotal = students.fold(0){acumulator,student->acumulator+student.score}
fold很好的利用了递归思想
reduce 和 fold很相似,唯一区别没有初始值,看下源码
public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
val iterator = this.iterator()
if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
var accumulator: S = iterator.next()
while (iterator.hasNext()) {
accumulator = operation(accumulator, iterator.next())
}
return accumulator
}
如果我们不需要初始值,那么可以用reduce
val reduceTotal = students.reduce{acumulator,student->acumulator+student.score}
groupBy 分组
通常我们用java 写很多for和if进行循环和条件判断,kotlin groupBy提供了语法糖
students.groupBy { it.sex }
返回 Map<String,List<Student>>
{m=[Student(name=jielun, age=30, sex=m, score=85), Student(name=lilei, age=32, sex=m, score=90), Student(name=, age=31, sex=m, score=97), Student(name=jack, age=18, sex=m, score=92), Student(name=pan, age=20, sex=m, score=82)], f=[Student(name=david, age=35, sex=f, score=80)]}
扁平化--处理嵌套集合:flatMap,flatten
将list2 变成和前面list一样的普通集合
val list2 = listOf(listOf(jielun, david), listOf(lilei, lili, jack), listOf(pan))
println(list2.flatten())
>>>>>输出
[Student(name=jielun, age=30, sex=m, score=85), Student(name=david, age=35, sex=f, score=80), Student(name=lilei, age=32, sex=m, score=90), Student(name=, age=31, sex=m, score=97), Student(name=jack, age=18, sex=m, score=92), Student(name=pan, age=20, sex=m, score=82)]
看下源码
public fun <T> Iterable<Iterable<T>>.flatten(): List<T> {
val result = ArrayList<T>()
for (element in this) {
result.addAll(element)
}
return result
}
就是循环遍历多个集合合并成了一个集合
如果我们需要得到的是加工一下的集合 flatMap
list3.flatMap { it.map {it.name} }
>>>>>输出
[jielun, david, lilei, , jack, pan]
看下flatMap源码
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
return flatMapTo(ArrayList<R>(), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
for (element in this) {
val list = transform(element)
destination.addAll(list)
}
return destination
}
flatMapTo 接收两个参数,第一个为一个列表,改列表是一个空列表。另外一个是一个函数。改函数返回一个序列。
遍历集合的元素,然后将集合元素传入transform的到一个列表,将列表元素添加到空列表destination中,这样经过transform函数处理的扁平化列表。
如果需要扁平化处理集合flattern就好,需要对元素加工那么用flatMap
5.3 集合的相关设计
![](https://img.haomeiwen.com/i1717814/1d83a68625f55cd1.png)
1.list 是一个可以重复的列表,元素存储方式是线性存储
println(listOf(1,2,3,4,5,6))
>>>>
[1, 2, 3, 4, 4, 6]
- map 没有实现Iterator和Collection。Map用来表示键值对元素集合,键不能重复
println(mapOf(1 to 1 ,2 to 2,3 to 3,3 to 4))
>>>
{1=1, 2=2, 3=4}
- Set 表示一个补课重复的集合。实现有两种HashSet 是Hash散列存储无序和TreeSet 底层是二叉树,有序。我们一般说的是无序
println(setOf(1,2,3,3,4,4,5))
>>>
[1, 2, 3, 4, 5]
可变集合与只读集合
kotlin 虽然是基于java但是做了改动,分为可变集合和不可变集合
- 可变集合,都有一个前缀“Mutable”
val mutableList = mutableListOf(1,2,3,4)
mutableList[0] = 0
println(mutableList)
>>>>
[0, 2, 3, 4]
2 只读集合
val list = listOf(1,2,3,4) 如果尝试改变编译器报错
特殊情况:被改
val writeList = mutableListOf(1,2,3,4,5)
val readList:List<Int> = writeList
writeList[0] = 0
println(readList)
>>>
[0, 2, 3, 4, 5]
和java互相操作
-》java
public static List<Integer> fooJava(List<Integer> list){
for (int i = 0; i < list.size(); i++) {
list.set(i,list.get(i) * 2);
}
return list;
}
-》kotlin
fun bar(list:List<Int>){
println(fooJava(list))
}
->
val list = listOf(1,2,3,4)
bar(list)
println(list)
》》》
[2, 4, 6, 8]
[2, 4, 6, 8]
传入的list被改变了
5.4 惰性集合
如果集合元素数量比较大,使用上线的操作效率比较低
通过序列化提高效率
val list = listOf(1,2,3,4)
list.filter{it > 2}.map{it * 2}
这样会操作两个临时集合先filter,然后返回集合在用map处理产生新的集合
如果filter处理数据量大,开销就很大
使用序列
list.asSequence().filter{it > 2}.map{it * 2}
中间操作
list.asSequence().filter{
println("filter$it")
it > 2
}.map{
println("map$it")
it * 2
}
什么也没输出,知道末尾加上“.toList()”才输出了结果
末端操作
toList() 就是末端操作,就是链式调用最后需要输出结果而不是序列化的东西。
对比asSequence 和不加输出的结果
list.asSequence().filter{
println("filter($it)")
it > 2
}.map{
println("map($it)")
it * 2
}.toList()
>>>>>>>>>>>
filter(1)
filter(2)
filter(3)
map(3)
filter(4)
map(4)
所有的中间操作被执行
ilter{
println("filter($it)")
it > 2
}.map{
println("map($it)")
it * 2
}
>>>>>>>>
filter(1)
filter(2)
filter(3)
filter(4)
map(3)
map(4)
普通的链式调用先执行玩filter在执行map。所以建议能先用filter的尽量先用filter。
序列可以是无限的
惰性计算最大的好处就是构造出来一个无限的数据类型。
// 创造无限序列
val naturalNumList = generateSequence(0){it +1 }
println(naturalNumList.takeWhile { it<=9 }.toList())
>>>>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
虽然不能穷举状态,但是我们可以通过条件控制我们想要多少数据。给我们一个无限的感觉
和 java8 Stream对比
- java8 使用函数式API
上面students 学生按照性别筛选
students.stream().filter(it -> it.sex == "m").collect(toList());
类似kotlin的序列,java需要将集合转换成stream流,操作完成后,还要将stream转换为List,java8这种也是惰性计算的
- java8 的stream是一次性的
如果创建了一个stream,在这个stream上只能遍历一次,这个流就被消费掉。必须创建新的stream才能再次遍历
students.stream().filter(it -> it.sex == "m").collect(toList());
students.stream().filter(it -> it.sex == "f").collect(toList());
3.Stream 能够并行处理数据(kotlin 目前貌似还不行)
students.parallelStream().filter(it -> it.sex == "m").collect(toList());
多核架构可以并行处理
5.4 内联函数
Lambda会带来一定的开销。内联函数主要是针对Lambda的优化。
java却不需要,因为java7之后jvm引入了invokedynamic
的技术,他会自动做lambda优化
优化Lambda
Lambda虽然语法简单,但是在Kotlin每声明一个Lambda就会产生一个匿名函类,该匿名类包含一个invoke方法,作为Lambda的调用方法,每次调用还会创建一个对象。因为Kotlin主要是对Android的开发语言,Kotlin在Android中必须引入某有方法来优化,就是内联函数
1.java的Invokedynamic
与kotlin这种再编译器通过硬编码生成Lambda转换类机制不同,java在se7 之后通过invokedynamic实现了在运行期才产生相应翻译代码。在invokedynamic 被调用的时候会产生一个匿名类替换中间代码invokedynamic,后续会直接使用这个匿名类。
- 因为运行时候产生,字节码只能看到固定的invokedynamic,需要静态生成类的个数及字节码大小显著减小。
- 编译时候写死字节码不同,利用invokedynamic可以把实际的翻译策略隐藏在jdk库中,提高了灵活性,向后兼容,后期可以继续对翻译策略优化
- JMM天然支持针对这种方式Lambda的翻译和优化,开发者开发时候不需要关心这些优化。
2 内联函数
Kotlin 为了Android要兼容java se6 所以不能采用invokedynamic。
采用了另一种解决方案内联函数。c++和C#等语言也支持这种特性。简单说可以用inline关键字来修饰函数。这些函数体编译期间被嵌入到每一个被调用的地方,减少额外生成匿名类和减少时间开销。
我们看一个普通自定义高阶函数foo,接受一个类型()->Unit 的Lambda
fun foo(block:()->Unit){
println("before block")
block()
println("after block")
}
fun main() {
foo {
println("dive into kotlin...")
}
}
反编译查看
public final class KT内联函数Kt {
public static final void foo(@NotNull Function0 block) {
Intrinsics.checkParameterIsNotNull(block, "block");
String var1 = "before block";
boolean var2 = false;
System.out.println(var1);
block.invoke();
var1 = "after block";
var2 = false;
System.out.println(var1);
}
public static final void main() {
foo((Function0)null.INSTANCE);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
调用foo产生一个Function0的类型的block类,然后通过invoke方法执行,这样会增加额外的开销。我们给foo函数加上inline修饰符
public final class KT内联函数Kt {
public static final void foo(@NotNull Function0 block) {
int $i$f$foo = 0;
Intrinsics.checkParameterIsNotNull(block, "block");
String var2 = "before block";
boolean var3 = false;
System.out.println(var2);
block.invoke();
var2 = "after block";
var3 = false;
System.out.println(var2);
}
public static final void main() {
int $i$f$foo = false;
String var1 = "before block";
boolean var2 = false;
System.out.println(var1);
int var3 = false;
String var4 = "dive into kotlin...";
boolean var5 = false;
System.out.println(var4);
var1 = "after block";
var2 = false;
System.out.println(var1);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
上面集合高阶函数API也是用内联函数实现的,所以内联函数优化上很有必要。
内联函数不是万能,不要随便用
对于普通函数没必要用内联
避免有大量函数体的函数内联,会导致过多字节码量
一旦一个函数被内联,就不能获取闭包的私有成员。除非声明为internal
noinline 避免函数参数被内联
有时候函数需要接收多个参数,我们只想让Lambda参数内联。我们可以noinline加到不想内联参数的开头
做个测试
inline fun foo(block:()->Unit,noinline block2:()->Unit){
println("before block")
block()
block2()
println("after block")
}
fun main() {
foo ({
println("dive into kotlin inline...")
},{
println("no inline...")
})
}
对比查看字节码
public final class KT内联函数Kt {
public static final void foo(@NotNull Function0 block, @NotNull Function0 block2) {
int $i$f$foo = 0;
Intrinsics.checkParameterIsNotNull(block, "block");
Intrinsics.checkParameterIsNotNull(block2, "block2");
String var3 = "before block";
boolean var4 = false;
System.out.println(var3);
block.invoke();
block2.invoke();
var3 = "after block";
var4 = false;
System.out.println(var3);
}
public static final void main() {
Function0 block2$iv = (Function0)null.INSTANCE;
int $i$f$foo = false;
String var2 = "before block";
boolean var3 = false;
System.out.println(var2);
int var4 = false;
String var5 = "dive into kotlin inline...";
boolean var6 = false;
System.out.println(var5);
block2$iv.invoke();
var2 = "after block";
var3 = false;
System.out.println(var2);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
可以看到block2 没有被内联
非局部返回
fun foo(){
println("before local return")
localReturn()
println("after local return")
return
}
fun localReturn() {
return
}
fun main() {
foo()
}
>>>>>输出
before local return
after local return
localReturn 没有起作用
fun foo(returnning:()->Unit){
println("before local return")
returnning()
println("after local return")
return
}
fun main() {
foo{return}
------报错
}
报错了,Lambda表达式正常情况不能return,修改加inline
inline fun foo(returnning:()->Unit){
println("before local return")
returnning()
println("after local return")
return
}
fun main() {
foo{return}
}
》》》》》
before local return
中间返回了,因为inline lamba 被替代,所以return生效
crossinline
为了避免带有return的Lambda参数产生破坏,可以阻止此类问题,用crossinline关键字修饰
fun main() {
------这里报错
foo{return}
// testfoo { return@testfoo }
}
inline fun testfoo(crossinline returnning:()->Unit){
println("before local return")
returnning()
println("after local return")
return
}
具体化参数类型 reified
由于Kotlin也有泛型擦除机制,我们无法获得一个参数的类型。然而,由于内联函数直接在字节码生成相应函数体现,我们又可以获得具体类型。我们可以用reifield修饰符实现
fun main() {
getType<Int>()
}
inline fun <reified T>getType(){
println(T::class)
}
>>>>>
class java.lang.Integer (Kotlin reflection is not available)
这个在Android里比较有用
inline fun <reified T:Activity> Activity.startActivity(){
startActivity(this,T::class.java)
}
调用,简化代码
startActivity<MainActivity>()