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表达式进行了多部简化。
- 当Lambda参数是函数的最后一个参数时,可以将 Lambda表达式移到函数括号的外面:
val listBig5 = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
//表达式外置
val nowLongest5 = listBig5.maxBy() { furit : String -> furit.length }
println("名字最长的水果是:${nowLongest5}")
- 如果Lambda参数是唯一的参数时,可以将函数的括号省略:
val listBig6 = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
//省略括号
val nowLongest6 = listBig5.maxBy{ furit : String -> furit.length }
println("名字最长的水果是:${nowLongest6}")
- 由于Kotlin的类推导机制,Lamdba表达式 中的参数列表其实在大多数情况下不用申明参数类型:
val listBig7 = listOf("苹果", "香蕉", "橘子", "梨", "葡萄", "火龙果")
//忽略参数类型
val nowLongest7 = listBig7.maxBy{ furit -> furit.length }
println("名字最长的水果是:${nowLongest7}")
- 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}")
//这里输出
至少有一个单词的长度小于5:true
- all() 函数 —— 用于判断集合中是否所有元素都满足指定条件。
//是否 所有单词的长度都小于5
val alls = listOf("apple", "house", "hot dog", "money", "bike", "rice")
val isAll = alls.all { it.length <= 5 }
println("所有单词的长度都小于5:${isAll}")
//这里输出
所有单词的长度都小于5:false
- 执行效率
郭神还提了一下:先调用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 {
}