移动 前端 Python Android Java我的Kotlin之旅Android · Kotlin · 移动开发 · 性能测试 · 无线技术

Kotlin (五)在集合里面感受Lambda

2020-12-26  本文已影响0人  zcwfeng

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 集合的相关设计

2020-12-26 17.34.11.png

1.list 是一个可以重复的列表,元素存储方式是线性存储

    println(listOf(1,2,3,4,5,6))
>>>>
[1, 2, 3, 4, 4, 6]

  1. map 没有实现Iterator和Collection。Map用来表示键值对元素集合,键不能重复
    println(mapOf(1 to 1 ,2 to 2,3 to 3,3 to 4))
>>>
{1=1, 2=2, 3=4}

  1. Set 表示一个补课重复的集合。实现有两种HashSet 是Hash散列存储无序和TreeSet 底层是二叉树,有序。我们一般说的是无序
    println(setOf(1,2,3,3,4,4,5))
>>>
[1, 2, 3, 4, 5]

可变集合与只读集合

kotlin 虽然是基于java但是做了改动,分为可变集合和不可变集合

  1. 可变集合,都有一个前缀“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对比

  1. java8 使用函数式API
    上面students 学生按照性别筛选
students.stream().filter(it -> it.sex == "m").collect(toList());

类似kotlin的序列,java需要将集合转换成stream流,操作完成后,还要将stream转换为List,java8这种也是惰性计算的

  1. 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,后续会直接使用这个匿名类。

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>()

上一篇 下一篇

猜你喜欢

热点阅读