本周,该章名为“前进”。 样式的约束不是直接调用函数,而是将其作为参数传递给下一个函数,稍后再调用。
这是5在编程风格焦点series.Other职位练习日交包括:
- 以编程风格介绍练习
- 以编程风格进行练习,将内容堆叠起来
- 编程风格的练习,Kwisatz Haderach风格
- 编程风格的练习,递归
- 以高阶函数进行编程风格的练习 (本文)
- 以编程风格进行练习
- 以编程风格进行练习,回到面向对象的编程
- 编程风格的练习:地图也是对象
- 编程风格的练习:事件驱动的编程
- 编程风格的练习和事件总线
- 反思编程风格的练习
- 面向方面的编程风格的练习
- 编程风格的练习:FP&I / O
- 关系数据库风格的练习
- 编程风格的练习:电子表格
- 并发编程风格的练习
- 编程风格的练习:在线程之间共享数据
- 使用Hazelcast以编程风格进行练习
- MapReduce样式的练习
- 编程风格的练习总结
高阶函数
我不确定高阶函数的概念是否起源于函数式编程。 但是,这个概念本身很容易掌握:高阶函数可以用作另一个函数的函数参数或返回值。 从版本8开始,它在Java中可用,但是从其开始就在Scala和Kotlin中可用。
在Java中,它们主要与流结合使用:
Stream.of("A","B","C","D","E")
.map(newFunction<String,String>(){
@Override
publicStringapply(Strings){
returns.toLowerCase();
}
});
}
Stream.of("A","B","C","D","E")
.map(s->s.toLowerCase());
Stream.of("A","B","C","D","E")
.map(String::toLowerCase);
前面的三行都做的完全一样,因为lambda被映射到功能接口的匿名类。
在Kotlin中,这非常相似,但有2点:
- 没有Kotlin会写匿名类,正是因为自一开始就可以使用高阶函数
- 就像Groovy中一样,lambda的默认参数名称是
it
。
Stream.of("A","B","C","D","E")
.map{it.toLowerCase()}
Stream.of("A","B","C","D","E")
.map(String::toLowerCase)
调用函数
一旦将一个函数传递给另一个函数,下一个逻辑步骤就是调用它。 在Java中,需要知道高阶函数的类型。 例如,要执行一个Function
,需要调用apply()
并传递具有期望类型的参数。 要执行Predicate
,该函数称为test()
等。
Function<String,String>toLowerCase=String::toLowerCase;
toLowerCase.apply("A");
Predicate<String>isLowerCase=s->s.equals(s.toLowerCase());
isLowerCase.test("A");
Kotlin更一致,因为在KCallable
接口上定义了一个名为call()
函数。
funisLowerCase(string:String)=string==string.toLowerCase()
valcallable:Boolean=::isLowerCase.call("A")
但是, call()
接受任意数量的Any?
类型的参数Any?
。 调用者可以传递正确的数字和参数类型。 以下编译,但在运行时将失败:
valcallable:KCallable<String>=::isLowerCase.call("A",2,Any())
为了从编译器中受益,可以使用KFunctionX
类型,其中X
是函数接受的参数数量。 该类型为invoke()
函数提供了正确数量的参数及其类型。
valok:KFunction<String,Boolean>=::isLowerCase.invoke("A") (1)
valko:KFunction<String,Boolean>=::isLowerCase.invoke("A",2,Any()) (2)
- 编译
- 不编译
锦上添花, invoke()
是一个运算符函数,该运算符什么都不是,因此以下语法也是有效的:
(::isLowerCase)("A")
应用理论
在本练习中,入口点函数调用一个函数,该函数调用一个函数,依次类推到7个嵌套级别。 如果将KCallable
更改为KFunction
,则顶部函数具有以下非常“有趣的”签名:
funreadFile(
filename:String,
function:KFunction2<
List<String>,
KFunction2<
List<String>,
KFunction2<
List<String>,
KFunction2<
List<String>,
KFunction2<
List<String>,
KFunction2<
List<Pair<String,Int>>,
KFunction1<List<Pair<String,Int>>,Map<String,Int>>,
Map<String,Int>>,
Map<String,Int>>,
Map<String,Int>>,
Map<String,Int>>,
Map<String,Int>>,
Map<String,Int>>
):Map<String,Int>
尽管我相信静态类型化有很多好处,但是这当然不会使代码更易于阅读。 为了解决这个问题,Kotlin提供了类型别名 。 以下代码段使用它们来改善情况:
typealiasWordFrequency=Pair<String,Int>
typealiasWordFrequencies=List<WordFrequency>
typealiasLines=List<String>
typealiasWords=List<String>
typealiasMapFunction=KFunction1<WordFrequencies,Map<String,Int>>
typealiasSortFunction=KFunction2<WordFrequencies,MapFunction,Map<String,Int>>
typealiasFrequencyFunction=KFunction2<Words,SortFunction,Map<String,Int>>
typealiasRemoveFunction=KFunction2<Lines,FrequencyFunction,Map<String,Int>>
typealiasScanFunction=KFunction2<Lines,RemoveFunction,Map<String,Int>>
typealiasNormalizeFunction=KFunction2<Lines,ScanFunction,Map<String,Int>>
typealiasReadFunction=KFunction2<Lines,NormalizeFunction,Map<String,Int>>
funreadFile(filename:String,function:ReadFunction)=function(read(filename),::normalize)
这是最终代码的摘录:
funrun(filename:String)=(::readFile)(filename,::filterChars) (1)
funreadFile(filename:String,function:ReadFunction)=function(read(filename),::normalize) (2)
funfilterChars(lines:List<String>,function:NormalizeFunction):Map<String,Int>{ (3)
valpattern="\\W|_".toRegex()
valfiltered=lines
.map{it.replace(pattern," ")}
returnfunction(filtered,::scan) (3)
}
- 结合使用函数引用和
invoke()
的快捷方式运算符。 第二个参数是下一个要调用的函数上的函数引用 - 第二个参数的类型使用别名,以使代码更易于阅读。 它调用传递的
function
参数的invoke()
方法,并传递下一个函数作为参考进行调用 - 其余的遵循相同的逻辑:使用高阶函数作为最后一个参数,并调用
invoke()
的快捷方式版本以继续链
结论
至于介绍,虽然类型有助于降低某些错误的可能性,但它们也会降低可读性。 幸运的是,Kotlin类型别名对此很有帮助。
call()
和invoke()
是使用反射调用函数时要考虑的两个选项。 他们每个人都有优点和缺点。