跟郭神学Kotlin(第三行学习笔记03)

Lambda编程(主要是讲集合)

拉姆达
Java在JDK1.8之后才引入了Lambda语法支持。
Kotlin一上来就支持Lambda语法还是很爽滴。

这一部分主要还是借Lambda讲了一下集合

1.集合的创建与遍历

在Java中 我们说到集合 一般就是List和Set或者Map。
再拓展下就是ArrayList,LinkedList,HashMap,HashSet等。
Java中申明一个List集合

    List<String> fruits = new ArrayList<>();
    fruits.add("苹果");
    fruits.add("香蕉");

当然Kotlin也可以用这个种写法

    val fruits = ArrayList<String>()
    listk.add("苹果")
    listk.add("香蕉")

List集合

listOf()函数[不可变集合]

不过Kotlin提供了一种更简化的写法

    val list = listOf("苹果", "香蕉", "橘子", "梨", "葡萄")

有点类似于Java中申明数组的写法。
遍历集合

    for (fruts in list) {
        println(fruts)
    }

上边提到了不可变集合,所以**listOf()**创建的是不可变的。他具体指:**该集合只能用于读取,我们无法对集合进行添加,修改或者删除操作。**这个Kotlin设计初衷有关。

mutableListOf()函数[可变集合]

不要急,Kotlin也为我们提供了可变集合的写法

    val canList = mutableListOf("苹果", "香蕉", "橘子", "梨", "葡萄")

    //这个集合 我们是可以正常add或者remove的
    canList.add("芒果")
    
    //在遍历一下
        for (fruts in canList) {
        println(fruts)
    }

Set集合

同理Set集合也有它的可变和不可变
传统写法

    val setK = HashSet<String>()
    setK.add("工作")
    setK.add("学习")
    setK.add("娱乐")
    setK.add("睡觉")
setOf()函数[不可变集合]
    val set = setOf("工作","学习","娱乐","休息")
mutableSetOf()函数[可变集合]
    val canSet = mutableSetOf("工作","学习","娱乐","休息")

注意 和Java类似,Set集合底层是使用Hash映射机制来存放数据的,因此集合中的元素无法保证有序。

Map集合

Map是一种键值对形式的数据结,因此在用法上和List、Set有较大不同。
传统写法

    val mapK = HashMap<Int,String>()
    mapK.put(2,"上班")
    mapK.put(3,"摸鱼")
    mapK.put(4,"下班")

但是Kotlin中不建议用put()或get()方法操作集合,因为Kotlin提供了一套跟简便的方法

    val map = HashMap<Int,String>()
    map[1] = "上学"
    map[2] = "考试"
    map[2] = "拖堂"
    map[3] = "晚自习"

当然Kotlin也为Map提供了一套可变和不可变的写法

mapOf函数[不可变集合]
    val unMap = mapOf(1 to "你", 2 to "我", 3 to "他", 4 to "它")
mapOf函数[可变集合]
    val canMap = mutableMapOf(1 to "你", 2 to "我", 3 to "他", 4 to "它")
遍历Map集合
    for ((key, value) in canMap) {
        println("$key is $value")
    }

2.集合的函数API

有这么一个需求,从一个水果集合里找名字最长的水果。
我们可以这么写:

    val listBig = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
    var nowLongest = ""
    for (now in listBig) {
        if (now.length > nowLongest.length) {
            nowLongest = now
        }
    }
    println("名字最长的水果是:$nowLongest")

但是我们在学习Kotlin 来看看Kotlin有什么骚操作

list.maxBy{} - 找到条件的最大值

如果我们使用集合的函数式API 那么可以这样写

    val listBig2 = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
    val nowLongest2 = listBig2.maxBy { it.length }
    println("名字最长的水果是:${nowLongest2}")

可以看到 这是函数式API 只需要一行代码即可 我们Kotlin真是太好用了(伏拉夫附体)!

Lambda 表达式的定义

其中 maxBy 后边 { } 里的内容就可以称为 Lambda 表达式

Lambda表达式的定义:一小段可以作为参数传递的代码

不过不建议在 Lambda表达式中编写太长的代码。


Lambda的语法结构


    { 参数1: 参数类型, 参数2: 参数类型 -> 函数体 }

并且,Lambda 有很多中简化的写法 可以更加方便。

Lambda 表达式由繁到简的推导

用上面的 list.maxBy() 举例,它接收一个 Lambda类型的参数,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。

本质上 上面的list.maxBy() 最初应该是这个写的。

    val listBig3 = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
    val nowLongest3 = listBig3.maxBy( { furit : String -> furit.length })
    println("名字最长的水果是:${nowLongest3}")

list.maxBy()本质就是接收了一个 Lambda表达式 。然后对这个 Lambda表达式进行了多部简化。

  1. 当Lambda参数是函数的最后一个参数时,可以将 Lambda表达式移到函数括号的外面:
    val listBig5 = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
    //表达式外置
    val nowLongest5 = listBig5.maxBy() { furit : String -> furit.length }
    println("名字最长的水果是:${nowLongest5}")
  1. 如果Lambda参数是唯一的参数时,可以将函数的括号省略:
    val listBig6 = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
    //省略括号
    val nowLongest6 = listBig5.maxBy{ furit : String -> furit.length }
    println("名字最长的水果是:${nowLongest6}")
  1. 由于Kotlin的类推导机制,Lamdba表达式 中的参数列表其实在大多数情况下不用申明参数类型:
    val listBig7 = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
    //忽略参数类型
    val nowLongest7 = listBig7.maxBy{ furit -> furit.length }
    println("名字最长的水果是:${nowLongest7}")
  1. it关键字——当Lambda表达式 的参数列表中有一个参数时,也不必声明参数名,而可以用it关键字代替:
    val listBig8 = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
    //it关键字
    val nowLongest8 = listBig8.maxBy { it.length }
    println("名字最长的水果是:${nowLongest8}")

郭神讲完,豁然开朗。不得不说 牛逼!

集合中常用的函数时API

  • map() 函数 —— 用于将集合中的每个元素都映射成一个另外的值,映射的规则在Lambda表达式 中指定。
    //举例:将所有单词都大写
    //当然 这里 大写,小写,去首字母,等等都可以,只要符合逻辑,都可以。

    val listBig9 = listOf("apple", "house", "dog", "money", "bike", "rice")
    val lisBigName9 = listBig9.map { it.toUpperCase() }
    for (now in lisBigName9) {
        println("大写后:${now}")
    }

  • filter() 函数 —— 用于过滤集合中的数据。可以和map配合使用。
    //举例:获取长度在5之内的单词
    val filters = listOf("apple", "house", "dog", "money", "bike", "rice")
    val listFilters = filters.filter { it.length <= 5 }
    for (now in listFilters) {
        println("长度在5之内的单词:${now}")
    }
    
    //配合map一起使用,长度5之内 并且大写输出
    val filters = listOf("apple", "house", "dog", "money", "bike", "rice")
    val listFilters = filters.filter { it.length <= 5 }//长度5之内
        .map { it.toUpperCase() }//大写
    for (now in listFilters) {
        println("长度在5之内的单词大写后:${now}")
    }
  • any() 函数 —— 用于判断集合中至少存在一个元素满足指定条件。
    //是否 有单词的长度小于5
    val anys = listOf("apple", "house", "hot dog", "money", "bike", "rice")
    val isAny = anys.any { it.length <= 5 }
    println("至少有一个单词的长度小于5:${isAny}")
    
    //这里输出
    至少有一个单词的长度小于5true
  • all() 函数 —— 用于判断集合中是否所有元素都满足指定条件。
    //是否 所有单词的长度都小于5
    val alls = listOf("apple", "house", "hot dog", "money", "bike", "rice")
    val isAll = alls.all { it.length <= 5 }
    println("所有单词的长度都小于5:${isAll}")
    
    //这里输出
    所有单词的长度都小于5false
  • 执行效率

郭神还提了一下:先调用filter再调用map效率 比 先调用map再调用filter效率 要高。

如果先调用map就相当于要对集合中所有元素都进行一次映射转换后再进行顾虑,这是完全不必要的。

而先进行过滤操作,再对过滤后的元素进行映射转换,就会明显高效很多。

我的验证:效率好像差不多。

我做了个验证,对照组,一组先Map,一组先filter,遍历100W条数据,感觉耗时差不多。

你们可以自己试试。或者说数据量不够大。

下边是我的代码:

/**
 * 先map再filter
 */
fun round100Map() {

    Thread(Runnable {
        val startLong = System.currentTimeMillis()

        for (i in 0..10000000) {
            val filters = listOf("apple", "house", "dog", "money", "bike", "rice")
            val listFilters = filters.map { it.toUpperCase() }.filter { it.length <= 5 }
        }

        println("\n先Map用时:${System.currentTimeMillis() - startLong}")
    }).start()


}

/**
 * 先Filter再map
 */
fun round100Filter() {

    Thread(Runnable {

        val startLong = System.currentTimeMillis()

        for (i in 0..10000000) {
            val filters = listOf("apple", "house", "dog", "money", "bike", "rice")
            val listFilters = filters.filter { it.length <= 5 }.map { it.toUpperCase() }
        }

        println("\n先Filter用时:${System.currentTimeMillis() - startLong}")

    }).start()

}

Java的函数时API

在Kotlin中调用Java方法时也可以使用函数时API。

具体来讲:如果我们在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。

Java单抽象方法接口:指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。

Java原生API中由一个最常见的单抽象方法接口—— Runnable接口 。这个接口中只有一个待实现的 Run() 方法:

publick interface Runnable{
    void run();
}

根据前面的说法,对于任何一个Java方法,只要它接收Runnable参数,就可以使用函数式API。

Java中常见的Runnable写法:

    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Thread is running");
        }
    }).start();

这里,使用了匿名类写法,我们创建了一个Runnable接口的匿名类实例,并将它传给了Thread类的构造方法,最后调用Thread类的start()方法执行这个线程。

Kotlin应该这么写:

    Thread(object :Runnable {
        override fun run() {
            println("Thread is running")
        }
    }).start()

Kotiln中匿名类的写法和Java有一点区别,由于Kotlin完全舍弃了new关键字,因此创建匿名类实例的时候就不能再使用new了,而是改用Ojbect关键字。

目前Thread类的构造方法是符合Java函数式API的使用条件的,所以我们可以进行简化:

    Thread(Runnable {
        println("Thread is running")
    }).start()

这波啊,是大大的简化。Runnable类中只有一个待实现的方法,即使这里没有显式地重写run()方法,Kotlin也能自动明白Runnable后面的Lambda表达式就是要在run()方法中实现的内容。

不过还能简化,如果一个Java方法的参数列表中不存在一个以上Java单抽象方法接口参数,我们还可以将接口名进行省略:

    Thread({
        println("Thread is running")
    }).start()

终极版 如果Lamdba表达式是方法的最后一个参数时,可以将Lamdba表达式移到括号外。同时Lambda表达式还是方法的唯一一个参数,可以将括号省略:

    Thread{
        println("Thread is running")
    }.start()

最后还讲了 Android中OnClick事件 的举例。小伙伴们可以举一反三试试:

Java

    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            
        }
    });

Kotlin

    view.setOnClickListener {
        
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值