一.扩展函数
1.定义扩展函数
扩展函数可以在不直接修改类定义的情况下增加类功能,扩展函数可以用于自定义类,也可以用于比如List、String,以及Kotlin标准库里的其他类。和继承相似,扩展函数也能共享类行为,在你无法接触某个类定义或者某个类没有使用open修饰符,导致你无法继承它时,扩展函数就是增加类功能的最好选择。
定义扩展函数和定义一般函数差不多,但有一点不大一样,除了函数定义,你还需要指定接收功能扩展的的接收者类型。
运行结果:
abc!!!!!!!!!!
test
15
在Kotlin文件中定义的扩展函数全局都可以用,如果不想暴露给全局使用,可以使用private修饰
2.泛型扩展函数
如果想在调用addExt扩展函数之前和之后分别打印字符串怎么办?
直接将easyPrint扩展函数新增Any返回值,在main方法中调用会报错,因为addExt是String的扩展函数,Any是String的父类,所以easyPrint方法返回必须是String才能调用,如下:
执行结果:
abc
abc!!!!!!!!!!
除了上边的方法外,还有别的方法,那就是泛型扩展函数
新的泛型扩展函数不仅可以支持任何类型的接收者,还保留了接收者的类型信息,使用泛型类型后,扩展函数能够支持更多类型的接收者,适用范围更广了。
泛型扩展函数在Kotlin标准库里随处可见,例如let函数,let函数被定义成了泛型扩展函数,所以能支持任何类型,它接收了一个lambda表达式,这个lambda表达式的接收者T作为值参,返回的R-lambda表达式返回的任何新类型。
二.扩展属性
1.扩展属性
除了给类添加功能扩展函数外,你还可以给类定义扩展属性,给String类添加一个扩展属性,这个扩展属性可以统计字符串里有多少个元音字母。
2.可空类扩展
你也可以定义扩展函数用于可空类型,在可空类型上定义扩展函数,你就可以直接在扩展函数内部解决可能出现的空值问题。
运行结果:abc
3.infix关键字
上个例子中用到了infix关键字,该关键字适用于有单个参数的扩展函数和类函数,可以让你以更简洁的语法调用函数,如果一个函数定义使用了infix关键字,那么调用它时,调用者和函数之间的点以及参数的一对括号都可以不要。
4.定义扩展文件
扩展函数需要在多个文件里面使用,可以将它定义在单独的文件中,然后import。
定义扩展函数:
使用扩展函数:
5.重命名扩展
有时候,你想使用一个扩展或一个类,但他的名字不合你意。
6.Kotlin标准库中的扩展
Kotlin标准库提供的很多功能都是通过扩展函数和扩展属性来实现的,包含类扩展的标准库文件通常都是以类名加S后缀来命名的,例如:Sequences.kt,Ranges.kt,Maps.kt
三.DSL
1.带接收者(接收者就是函数的调用者)的函数字面量
apply函数是如何做到支持接收者对象的隐式调用的。
首先,扩展函数里自带类调用者的this的隐式调用,apply函数的入参是block: T.() -> Unit,这是一个匿名函数,返回值类型为Unit(无返回值类型),而T.()是什么鬼?我们上边有介绍泛型扩展函数,比如fun
2.DSL
使用这样的编程范式,就可以写出业界知名的“领域特定语言”(DSL),一种API编程范式,暴露调用者的函数和特性,以便于使用你定义的lambda表达式来读取和配置它们。
四.函数式编程
一个函数式应用通常由三大类函数构成:变换transform、过滤filter、合并combine。每类函数都针对集合数据类型设计,目标是产生一个最终结果。函数式编程用到的函数生来都是可组合的,也就是说,你可以组合多个简单函数来构建复杂的计算行为。
1.变换
变换是函数式编程的第一大类函数,变换函数会遍历集合内容,用一个以值参形式传入的变换器函数,变换每一个元素,然后返回包含已修改的集合给链上的其他函数。
最常用的两个变换函数是map和flatMap。
2.map
map变换函数会遍历接收者集合,让变换器函数作用于集合里的各个元素,返回结果是包含已修改元素的集合,会作为链上下一个函数的输入。
map函数定义如下:
public inline fun
运行结果:
[zebra, giraffe, elephant, rat]
[A baby zebra, with the cutest little tail ever!, A baby giraffe, with the cutest little tail ever!, A baby elephant, with the cutest little tail ever!, A baby rat, with the cutest little tail ever!]
可以看到,原始集合没有被修改,map变换函数和你定义的变换器函数做完事情后,返回的是一个新集合,这样,变量就不用变来变去了。
事实上,函数式编程范式支持的设计理念就是不可变数据的副本在链上的函数间传递。
map返回的集合中的元素个数和输入集合必须一样,不过,返回的新集合里的元素可以是不同类型的。
运行结果:
[zebra, giraffe, elephant, rat]
[5, 7, 8, 3]
3.flatMap
flatMap函数操作一个集合的集合,将其中多个集合中的元素合并后返回一个包含所有元素的单一集合。
运行结果:
[1, 2, 3, 4, 5, 6]
4.过滤
过滤是函数式编程的第二大类函数,过滤函数接受一个predicate函数,用它按给定条件检查接收者集合里的元素并给出true或false的判定。如果predicate函数返回true,受检元素就会添加到过滤函数返回的新集合里。如果predicate函数返回false,那么受检元素就被移出新集合。
5.filter
filter函数定义如下:
public inline fun
例1:过滤集合中元素含有“J”字母的元素。
运行结果:
[Jack, Jimmy]
例2:filter过滤函数接收一个predicate函数,在flatMap遍历他的输入集合中的所有元素时,filter函数会让predicate函数按过滤条件,将符合条件的元素都放入它返回的新集合里。最后,flatMap会把变换器函数返回的子集合合并在一个新集合里。
运行结果:
[red apple, red fish]
例3:找素数,除了1和它本身,不能被任何数整除的数。仅使用了几个简单函数,我们就解决了找素数这个比较复杂的问题,这就是函数式编程的独特魅力:每个函数做一点,组合起来就能干大事。
6.合并
合并是函数式编程的第三大类函数,合并函数能将不同的集合合并成一个新集合,这和调用者是包含集合的集合的flatMap函数不同。
7.zip
zip合并函数来合并两个集合,返回一个包含键值对的新集合。
运行结果:
{Jack=large, Jason=x-large, Tommy=medium}
8.fold
另一个可以用来合并值的合并类函数是fold,这个合并函数接收一个初始累加器值,随后会根据匿名函数的结果更新。
运行结果:30
9.序列
List、Set、Map集合类型,这几个集合类型统称为及早集合(eager collection),这些集合的任何一个实例在创建后,他要包含的元素都会被加入并允许你访问。对应及早集合,Kotlin还有另外一类集合:惰性集合(lazy collection),类似于类的惰性初始化,惰性集合类型的性能表现优异,尤其是用于包含大量元素的集合时,因为集合元素是按需产生的。
Kotlin有个内置惰性集合类型叫序列(Sequence),序列不会索引排序它的内容,也不记录元素数目,事实上,在使用一个序列时,序列里的值可能有无限多,因为某个数据源能产生无限多个元素。
10.generateSequence
针对某个序列,你可能会定义一个只要序列有新值产生就被调用一下的函数,这样的函数叫迭代器函数,要定义一个序列和它的迭代器,你可以使用Kotlin的序列构造函数generateSequence,generateSequence函数接收一个初始值作为序列的起步值,在用generateSequence定义的序列上调用一个函数时,generateSequence函数会调用你置顶的迭代器函数,决定下一个要产生的值。
惰性集合究竟有什么用呢?为什么要用它而不是List集合呢?假设你想产生头1000个素数。
运行结果:669
这样的代码实现表明,你不知道该检查多少个数才能得到整1000个素数,所以用5000这个预估数。但事实上5000个数远远不够,只能找出669个素数。
使用如上这种方式(generateSequence)就可以得到1000个素数了,运行结果:1000