kotlin 中的函数引用详解

抛砖引玉

做 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

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值