Kotlin中的高阶函数
函数式编程
从Java的日常挖坑转到Kotlin中挖坑后一个显著的感受就是Kotlin相比于Java最大的一个不同点:函数式编程。它的定义如下:
函数式编程(functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。
函数式编程是一种编程范式,传统指令型语言Java也可以写出“函数式代码”,它最显著的特点的无程序状态,也就是说无中间可变的变量(如果有也是不可变变量、表达式或者函数),它解决的是计算问题,也就是说给它一个输入,它必定给出依赖于这个输入的唯一输出,函数式编程关注的是做什么而不是怎么做,基于它这样的特性所以它有这样的一些优点:无副作用(不修改与依赖于外界的变量),易于测试和调试错误,易于并发编程,易于热更新。
作为一个现代先进的语言Kotlin为了开发者更容易写出函数式代码相对于Java在语言层面上提供了一些新特性,如高阶函数,函数一等公民身份。
Kotlin中的高阶函数
高阶函数的定义
在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:接受一个或多个函数作为输入,输出一个函数
Kotlin中高阶函数的一个例子:集合中的map
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}
它接受一个函数作为输入,返回列表中每个子项基于这个输入函数处理过后的新的数据集,例如 需要对一个数字列表中的每个数字翻倍的后生成一个新的列表的调用如下:
val doubled = ints.map { value -> value * 2 }
Kotin集合中还提供了很多对集合操作的其他的函数,其本质都是通过高阶函数来完成的,可以看到这些函数不管在代码编写上还是维护性上都相对于Java都优秀不少,上一次我们见到这种代码还是在RxJava中。
另一个例子: 我们在日常开发中经常需要使用监听者模式,在Java中我们会写这样的代码:
public class JavaAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private OnItemClickListener onItemClickListener;
public JavaAdapter(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
...
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onClick(holder.itemView, position);
}
}
});
}
public interface OnItemClickListener {
void onClick(View view, int position);
}
}
我们监听Adapter内部的点击事件需要定义一个点击事件的接口来包装外部需要在点击事件后的操作回调,然后传入一个实现了该接口包装了回调操作的对象, 而如果在Kotlin中使用高阶函数则可以免去定义这个回调操作的接口的过程:
class KotlinAdapter(val itemClick: (view: View?, position: Int) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
...
override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
holder?.itemView?.setOnClickListener({
itemClick(holder.itemView, position)
})
}
}
可以看到Kotlin中高阶函数可以让我们免去不少代码,这其中还有其它因素 Kotlin中函数作为一等公民可以被定义和使用在任何地方,而Java中函数必须包含在类中
高阶函数对于Gof设计模式的取代
有种说法是“设计模式的产生是由于这个语言的一些缺陷造成的”,作为现代先进语言Kotlin在语言层级设计上就去掉了传统Java中需要用设计模式解决问题的操作。那么有哪些传统Java中的设计模式能被Kotlin新的函数式特性取代呢?
策略模式:
策略模式定义了算法簇,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
Java中的一个例子:
/**
* 定义策略接口
*/
public interface Strategy {
void doSth();
}
/**
* A策略
*/
public static class AStrategy implements Strategy {
@Override
public void doSth() {
System.out.println("Do A Strategy");
}
}
/**
* B策略
*/
public static class BStrategy implements Strategy {
@Override
public void doSth() {
System.out.println("Do B Strategy");
}
}
/**
* 策略实施者
*/
public static class Worker {
private Strategy strategy;
public Worker(Strategy strategy) {
this.strategy = strategy;
}
public void work() {
System.out.println("START");
if (strategy != null) {
strategy.doSth();
}
System.out.println("END");
}
}
如上面的例子所示,有A、B两种策略,Worker根据不同的策略做不同的工作,使用策略时:
Worker worker1 = new Worker(new AStrategy());
Worker worker2 = new Worker(new BStrategy());
worker1.work();
worker2.work();
在java中实现这种策略模式难免需要先定义好策略的接口,然后根据接口实现不同的策略, 在Kotlin中完全可以用用高阶函数来简化策略模式,上面的例子用Kotlin实现:
/**
* 策略实施者
* @param strategy lambda类型的策略
*/
class Worker(private val strategy: () -> Unit) {
fun work() {
println("START")
strategy.invoke()
println("END")
}
}
/*
* 测试
* */
fun testStrategy() {
val worker1 = Worker({
println("Do A Strategy")
})
val bStrategy = {
println("Do B Strategy")
}
val worker2 = Worker(bStrategy)
worker1.work()
worker2.work()
}
不需要先定义策略的接口,直接把策略以函数的形式传进来就行了。
模板方法模式:
在操作中定义算法(步骤)的骨架,将一些步骤委托给子类
这个设计模式同时用到了类的继承。定义一些 抽象方法 并且在基类调用这些方法。抽象方法由子类负责实现。
Java 中的例子
public abstract class Task {
protected abstract void work();
public void execute(){
beforeWork();
work();
afterWork();
}
}
现在从 Task 派生出一个在 work 方法中真正做了事情的具体类。
而Kotlin中可以通过高阶函数的形式来免去继承这个操作:
fun execute(task: () -> Unit) {
val startTime = System.currentTimeMillis() //"beforeWork()"
task()
println("Work took ${System.currentTimeMillis() - startTime} millis") //"afterWork()"
}
...
//usage:
execute {
println("I'm working here!")
}
命令模式:
命令模式将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其它对象,命令模式也支持可撤销的操作
Java中的例子:
public interface Command{
public void execute();
}
public class LightOnCommand implements Command{
Light light;
public LightOnCommand(Light light){
this.light=light;
}
publick void execute(){
light.on();
}
}
publick class SimpleRemoteControl{
Command slot;
public SimpleRemoteControl(){}
public void setCommand(Command command){
slot=command;
}
public void buttomWasPressed(){
slot.execute();
}
}
Java中实现命令模式需要屏蔽的具体命令操作需要包装成一个命令对象传入到控制器内,而Kotlin中高阶函数可以直接传入被需要操作的命令,而不需要定义接口并包装成一个对象传入
class SimpleRemoteControl{
public void buttomWasPressed(command:()-> Unit){
command()
}
}
迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露内部的表示
例如我们可以用高阶函数屏蔽集合类中具体迭代的实现,如Kotlin中的forEach()高阶函数,而在Java中封装一个类中顺序访问的实现细节必须定义一个接口然后来实现这个迭代器接口,外部调用的是迭代器接口实现的方法来屏蔽这个具体的实现细节
装饰者模式
动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替换方案
Kotlin中可以通过扩展函数+高阶函数的组合达到目的:
class Text(val text: String) {
fun draw() = print(text)
}
fun Text.underline(decorated: Text.() -> Unit) {
print("_")
this.decorated()
print("_")
}
// usage
Text("Hello").underline {
draw()
}
可以看出将函数作为一等公民可以在任意地方使用包括函数参数中,可以解决一些编码中之前需要用对象包装函数的一些局限所产生的模式,当然上述Kotlin中的高阶函数提供了替代这些设计模式的可能,但是有些情况并不适合真正地去替换掉这些设计模式,如我们在Android开发中经常使用到模板方法模式 虽然在Kotlin中可以用高阶函数替代,但是某些情况下使用模板方法模式可以使代码更职责分明,语义更加明确,便于理解和维护。
Kotlin中的一些好用的高阶函数:
let
inline fun
T.let(block: (T) -> R): R = block(this)
在需要if not null 执行代码的时候使用:
val value = ……
value?.let {
…… // 代码会执行到此处, 假如data不为null
}
apply
inline fun
T.apply(block: T.() -> Unit): T { block(); return this }
在函数范围内,可以任意调用该对象的任意方法,并返回该对象
/*
* 用apply语句简化类的初始化,在类实例化的时候,就可以通过apply把需要初始化的步骤全部实现,非常的简洁
* */
fun testApply(context: Context) {
var imgView = ImageView(context).apply {
setBackgroundColor(0)
setImageBitmap(null)
}
var textView = TextView(context).apply {
text = "content"
textSize = 20.0f
setPadding(10, 0, 0, 0)
}
var user = User().apply {
age = 15
name = "Jack"
val a = address
address = "bbb"
}
}
with
inline fun
with(receiver: T, block: T.() -> R): R = receiver.block()
可以用with语句来省略同一个变量的多次声明
/*
* 通过with语句,将result参数作为参数,在内部this也可以省略掉
* */
fun alphabet3(): String {
val result = StringBuilder()
return with(result) {
append("START\n")
for (letter in 'A'..'Z') {
append(letter)
}
append("\nEND")
toString()
}
}
最后
文中内容如有错误欢迎交流沟通,谢谢阅读!
Thanks:
Kotlin tips
Gang of Four Patterns in Kotlin
傻瓜函数式编程
什么是函数式编程思维?