抛砖引玉
做 C/C++ 开发的同学肯定熟悉域作用符 (::),这个符号在 kotlin 中可以获取函数的引用.
函数引用是 kotlin 引入的一个功能,对于 java 开发者来说,刚开始可能有点不适应.
对于程序员来说,最合适的抛砖引玉还是上代码:
val sum: (Int, Int) -> Int = { x, y -> x + y }
上面的 sum 是一个函数类型的变量,lambda 表达式执行了相加的操作.
fun applyOp(x: Int, y: Int, op: (Int, Int) - >Int) : Int = op(x, y)
applyOp 是一个接收三个参数的函数,第三个参数是 lambda 类型.可以使用下面的方式调用这个函数:
applyOp(2, 3, sum)
上面的三行代码是很基本的很普通的 kotlin 语法,但是也体现了相对于 java 的优势即高阶函数:函数可以作为一个类型赋值给变量,也可以作为另一个函数的参数.
函数引用:任何函数都可以是一个 lambda
前面例子中的 sum 的函数体是一个 lambda 表达式,其实可以用纯函数的形式:
fun sum(x: Int, y: Int) = x + y
这样写实现的效果是一样的,区别在于这样不需要使用变量来保持对函数的引用,但是这样的话调用 applOp 函数要做一些更改,原来的
applyOp(2, 3, sum) 写法会报类型不匹配的语法错误.我们需要做的更改是使用函数引用,代码如下:
applyOp(2, 3, ::sum)
通过上面两个例子,你应该大致了解了函数引用的概念.其实函数引用和 lambda 的行为是一样的,而且你也可以把函数引用赋值给变量:
val sumLambda: (Int, Int) - >Int = ::sum applyOp(2, 3, sumLambda)
惊不惊喜?意不意外?所以,尽量使用函数引用.
函数引用实战
下面的例子是函数引用实战.想象下面的情景:要接收一些 data 数据来更新 UI,但是只在 data 非空的时候来执行更新 UI 的操作.代码如下:
private fun updateUI(data: Data ? ) {
if (data != null) {
applyUiChanges(data)
}
}
private fun applyUiChanges(data: Data) {
// Do cool stuff in UI
}
如果你熟悉 let 操作符,可以使用 let 操作符避免烦人的 if-else 的非空检查.
data?.let { applyUiChanges(data) }
看下 let 操作符的源码:
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
let 接收一个 lambda 参数,有两个范型:T 和 R,在这里 T 是 Data,R 是 Unit
结合这里的具体情形,去掉范型:
public inline fun Data.let(block: (Data) - >Unit) = block(this)
你可以看到,里面的 lambda 和函数 applyUiChanges 的结构 (这里的结构是指参数和返回值) 一模一样,重点来了,这个 lambda 参数可以用函数引用代替:
data ? .let(: :applyUiChanges)
很酷吧?
请记住 The rule of thumb(经验法则):如果在一个 lambda 中使用函数,而且这个这个函数的参数全是 lambda 的输入,那么就完全可以使用函数引用.
那么,让我们走的更远一点.
多个参数的 lambda
如果 lambda 有多个参数,那么这个经验法则同样适用.加入你有下面的观察代理:
var items: List<MediaItem> by Delegates.observable(emptyList()) { property, oldValue, newValue ->
notifyChanges(oldValue, newValue)
}
这里的 notifyChanges 不能使用函数引用,原因你可能已经猜到了,notifyChanges 函数和 lambda 的结构不一样,lambda 接收三个参数,而 notifyChanges 函数只接收两个参数,为了能够使用函数引用,需要对 notifyChanges 函数做一些小的改动:
fun notifyChanges(property: KProperty<*>, oldValue: List<MediaItem>,
newValue: List<MediaItem>) {
...
}
经过这样的改动,我们就可以使用函数引用了:
fun notifyChanges(property: KProperty<*>, oldValue: List<MediaItem>,
newValue: List<MediaItem>) {
...
}
属性引用
引用不仅仅局限于函数方面,还可以对属性进行引用,返回一个值,类似 java 中的 getters 方法.
我们来看下 MediaItem 的结构:
data class MediaItem(val title: String, val url: String)
如果有一个 MediaItem 的列表,我们要根据每个 MediaItem 的 title 排序,排完序后打印出每个 MediaItem 的 url,一般的代码会这样写:
items
.sortedBy { it.title }
.map { it.url }
.forEach { print(it) }
然而,下面的引用风格对我来说更加简洁易懂:
items
.sortedBy(MediaItem::title)
.map(MediaItem::url)
.forEach(::println)
那么上面的两种写法的字节码有什么不同呢?对比来了:
Iterable $receiver$iv = (Iterable) items;
$receiver$iv = (Iterable) CollectionsKt.sortedWith($receiver$iv, (Comparator)(new HomeContentFragmentKt$getSortedUrls$inlined$sortedBy$1()));
Collection destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv, 10)));
Iterator var4 = $receiver$iv.iterator();
while (var4.hasNext()) {
Object item$iv$iv = var4.next();
MediaItem it = (MediaItem) item$iv$iv;
String var11 = it.getUrl();
destination$iv$iv.add(var11);
}
$receiver$iv = (Iterable)((List) destination$iv$iv);
Iterator var2 = $receiver$iv.iterator();
while (var2.hasNext()) {
Object element$iv = var2.next();
String it = (String) element$iv;
System.out.print(it);
}
引用风格的字节码:
Iterable $receiver$iv = (Iterable) items;
$receiver$iv = (Iterable) CollectionsKt.sortedWith($receiver$iv, (Comparator)(new HomeContentFragmentKt$getSortedUrls$inlined$sortedBy$1()));
Collection destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv, 10)));
Iterator var4 = $receiver$iv.iterator();
while (var4.hasNext()) {
Object item$iv$iv = var4.next();
String var10 = ((MediaItem) item$iv$iv).getUrl();
destination$iv$iv.add(var10);
}
$receiver$iv = (Iterable)((List) destination$iv$iv);
Iterator var2 = $receiver$iv.iterator();
while (var2.hasNext()) {
Object element$iv = var2.next();
System.out.println(element$iv);
}
可以看到,两者的字节码基本相同.但是引用风格的字节码节省了几个变量,因此是稍微高效一丢丢,当然你也可以不必在乎这些.
下面再展示一些 cool 的代码,体会下 "引用" 这个新的 feature:
//1,获取对应类型的,比如下面代码获取到 KProperty<Int>
var x = 1 fun main(args: Array < String > ) {
println(: :x.get()) // prints "1"
: :x.set(2) //KMutableProperty<Int>
println(x) // prints "2"
}
//2,获取 KClass
val c = MyClass: :class
项目中要使用函数引用吗?
当然要,赶快在项目中使用吧!程序员需要用代码装逼,用代码说话,当然学习成本还是要的.我认为这是 kotlin 的一个很好的特性.
参考文献
https://antonioleiva.com/function-references-kotlin/
来源: http://www.jianshu.com/p/10358883455c