ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
### filter函数 接下来我们再来学习另外一个比较常用的函数式API——**filter函数。顾名思义,filter函数是用来过滤集合中的数据的,它可以单独使用,也可以配合刚才的map函数一起使用**。 **filter 函数可以从集合中移除你不想要的元素,但是它并不会改变这些元素。元素的变换是map 的用武之地。** 比如我们只想保留5个字母以内的水果,就可以借助filter函数来实现,代码如下所示: ~~~ fun main() { val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon") val newList = list.filter { it.length <= 5 } .map { it.toUpperCase() } for (fruit in newList) { println(fruit) } } ~~~ 可以看到,**这里同时使用了filter和map函数,并通过Lambda表示式将水果单词长度限制在5个字母以内**。重新运行一下代码,结果如图2.27所示。 ![图像说明文字](http://epub.ituring.com.cn/api/storage/getbykey/screenshow?key=200301e60b1c9fc32914) 另外值得一提的是,上述代码中**我们是先调用了filter函数再调用map函数。如果你改成先调用map函数再调用filter函数,也能实现同样的效果,但是效率就会差很多,因为这样相当于要对集合中所有的元素都进行一次映射转换后再进行过滤,这是完全不必要的。而先进行过滤操作,再对过滤后的元素进行映射转换,就会明显高效得多**。 下面我们看另一个示例 ~~~ data class PersonData(val name: String, val age: Int, val sex: String, val score: Int) fun main(args: Array<String>) { val jilen = PersonData("Jilen", 30, "m", 85) val shaw = PersonData("Shaw", 18, "m", 90) val yison = PersonData("Yison", 40, "f", 59) val jack = PersonData("Jack", 30, "m", 70) val lisa = PersonData("Lisa", 25, "f", 88) val pan = PersonData("Pan", 36, "f", 55) val persons = listOf(jilen, shaw, yison, jack, lisa, pan) val mPersons = persons.filter { it.sex == "m" } for (p in mPersons) { println(p) } } ~~~ 输出结果如下 ``` PersonData(name=Jilen, age=30, sex=m, score=85) PersonData(name=Shaw, age=18, sex=m, score=90) PersonData(name=Jack, age=30, sex=m, score=70) ``` 上面的代码定义了一个人组成的列表,接下来我们会围绕这个人列表来介绍一下Kotlin中丰富的集合API。 假设现在我们要获取一份由所有男人组成的列表。按照以前的思路,我们可以先定义一个新的列表,然后遍历students,将满足条件的项添加到新列表中去。但是,在Kotlin中我们不用这么去做,Kotlin为我们提供了一个filter方法,我们来使用一下: ``` val mPersons = persons.filter { it.sex == "m" } ``` **通过使用filter方法,我们就筛选出了男人**。该方法**与map类似,也是接收一个函数,只是该函数的返回值类型必须是Boolean。该函数的作用就是判断集合中的每一项是否满足某个条件,如果满足,filter方法就会将该项插入新的列表中,最终就得到了一个满足给定条件的新列表**。 #### 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 } ~~~ 可以看到,filter方法的实现主要是依赖于filterTo,filterTo接收两个参数,第1个参数destination为一个列表(该方法最终要返回的列表,初始时为空的列表),第2个参数predicate: (T) -> Boolean是一个返回值类型为Boolean的函数,该函数就是我们在使用filter的时候传入的Lambda表达式。可以看到,filterTo的实现非常简单,就是通过遍历给定的列表,将每一个元素传入predicate函数中,如果返回值为true就保留,反之则丢弃,最终返回一个满足条件的新列表。 通过查看filter的结果我们可以知道,**调用filter之后产生的新列表是原来列表的子集**。 具有过滤功能的方法还有以下这些: * filterNot,用来过滤掉满足条件的元素。**filterNot方法与filter方法的作用相反,当传入的条件一样时,会得到相反的结果**。 ``` val fStudents = students.filterNot {it.sex == "m"} ``` * filterNotNull,用来过滤掉值为null元素。 * count,统计满足条件的元素的个数。 比如,我们要统计男女学生各自的人数,那么就可以使用count方法: ``` val countMStudent = students.count {it.sex == "m"} val countFStudent = students.count {it.sex == "f"} ``` 这里,我们还有另外一种方式可以得到上面的结果: ``` val countMStudent = students.filter {it.sex == "m"}.size val countFStudent = students.filter {it.sex == "f"}.size ``` 但是**这种方式会有一个问题,就是我们需要先通过filter得到一个满足条件的新列表,然后再去统计该新列表的数量,这样就增加了额外的开销**。