大家好,本次的文章主要是聊聊Kotlin1.5和1.6版本提供的一些特性,希望能扩充大家的眼界,如果能对你日常的开发带来帮助,这将是我写这篇文章最大的价值。
一. 挂起函数类型可以作为父类型
在了解这个之前,先看下函数类型做父类型的用法吧:
class Block : () -> String {
override fun invoke(): String {
return ""
}
}
fun test(list: MutableList<() -> String>) {
list.add(Block())
}
Block类的父类型就是() -> String,并且需要重写方法invoke()方法。当需要调用该类时,可以通过Block().invoke()或者 Block()()实现。
我们将上面的反编译成java看下实现原理:
其实本质上就是实现了Function0这个接口,就那么朴实无华:
如果说我们充当父类型的函数类型为带一个参数的(Int) -> String,那么将会是实现Function1这个接口;以此类推,函数类型每新增一个参数,实现的接口FunctionX中X就要加一,当时这个不能无限新增,目前看到的FunctionX类型的接口最高为Function22。
我们可以看下LeakCanary的源码FragmentAndViewModelWatcher,就有用到这个特性:
以上都是普通函数类型作为父类型的讲解,接下来就看下挂起函数类型作为父类型吧,无非就是普通在函数类型前面增加了一个suspend关键字。
class Block : suspend () -> String {
override suspend fun invoke(): String {
return withContext(Dispatchers.IO) {
//模拟耗时
delay(2000)
"result"
}
}
}
这样我们就可以愉快的在invoke方法中调用协程官方api执行一些比如耗时操作了,非常的好用。
发编译下代码,其实可以看到本质上还是实现了Function1接口,为什么挂起来的函数类型明明没有参数,为啥不是实现Function0接口呢?我们看下反编译的代码:
invoke方法会默认新增一个Continuation类型的参数,这个是帮助我们执行挂起函数类型的关键,所以最终实现的接口为Function1。
至于挂起的原理,比如上图invoke方法中具体的代码逻辑太复杂了,想要了解协程挂起原理的给大家推荐一篇文章阅读:写给Android工程师的协程指南。
想要使用这个特性,要么你的kotlin插件版本升级到了1.6.0及以上,要么你的插件版本为1.5.30并且在build.gradle脚本中增加配置:
二. 稳定的挂起类型转换
在kotlin1.6.0的版本,实现了稳定的普通函数类型到挂起函数类型的隐式转换,说了这么多,接下来我们通过一个例子进行了解:
fun execute(block: suspend () -> Unit) {
}
fun test(block: () -> Unit) {
execute(block)
}
首先我们声明了一个execute()方法,其中方法的参数为挂起函数类型;然后test()方法参数声明了一个普通函数类型;最后我们在test()方法中成功调用了execute()方法并将普通函数类型的参数传递给了execute()方法。
上面这就完成了一次普通函数类型到挂起函数类型的转换,请注意,这个操作可不能反着来,也就是由挂起函数类型转化成普通函数类型是不可能的,毕竟普通函数类型可没有协程代码的执行环境。
官方提供这个特性的目的在笔者看来主要是增加兼容性吧,而且大家不要认为这样转换会增加挂起的开销,毕竟:
因为普通函数类型根本不存在所谓的挂起点,即使转换成挂起函数类型且被执行,也不会执行任何挂起操作的,只是作为一个普通的函数执行并返回而已。
三. 注解类可以被实例化
我们看下java中一个例子:
定义一个注解HuiHui:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HuiHui {
String content();
}
这个注解本质上就是一个接口,自然在java中是可以被实现的:
public class HuiImpl implements HuiHui {
@Override
public String content() {
return null;
}
@Override
public Class<? extends Annotation> annotationType() {
return null;
}
}
上面的场景在java中是可以的,那我们看下kotlin中的注解可不可以这样搞:
报错了看到了不,提示注解类HuiPlus需要增加open,但是给其增加open就会提示:
除了上面这种写法,这样也会报错的:
总之一句话,kotlin中的注解不支持实例化。Kotlin官方就注意到了这点,这样以后还能和java之间愉快的玩耍嘛,所以在1.5.30插件版本出手了。
简单一句话,注解类支持被实例化,接下来我们通过代码进行实战。
可以看到上面的代码没有丝毫报错。其实我们是可以通过kotlin的反射库是可以调用注解类的构造方法的,但是这样显得繁琐复杂,直接提供一个实例化注解类的特新岂不是更加美哉?
一般如果我们想了解kotlin是如何实现这种能力的,都会尝试反编译成java代码看下。但遗憾的是,对于kotlin注解实例化的细节被隐藏掉了,反编译成java也没有帮助,隐藏掉的好处是允许编译器做一些隐式的优化工作。
如果想要使用这个特性,和参考上面第一节内容末尾的操作。
四. 支持对类泛型声明注解
解释起来很麻烦,直接给大家上代码:
@Target(AnnotationTarget.TYPE_PARAMETER)
annotation class FLAG
class Annotation2<@FLAG T> {
val content: String = ""
}
可以看到我声明了一个FLAG注解,并且通过@Target(AnnotationTarget.TYPE_PARAMETER)限制只能给泛型类型修饰。
这个特性相当于把我们注解的范围更加颗粒化了,允许我们以在更加细致的维度中去处理问题。而且,请注意,这个注解最终是会被编译到jvm字节码的,所以通过Android自定义注解处理器是可以拿到并处理该注解的。 这样就更加扩展了该特性的使用范围。
如果想要使用这个特性,和参考上面第一节内容末尾的操作。
五. 长期支持对kotlin插件旧版本的维护
从kotlin1.6.0开始,kotlin官方将长期支持维护对当前稳定版本的过去三个旧版本的维护,换句话说,如果当前稳定版本为1.6.0,那么对于kotlin1.3、kotlin1.4和kotlin1.5也是继续进行维护的。
六. 总结
本篇文章主要是对kotlin1.5和1.6版本新增的特性做了一些讲解,小而美的那种。我这里并没有列举全所有新增的特性,那样对于大家和我都是一个负担,只是拿出一些比较典型的特性分享给大家。
请注意,如果由某个特性非常适合展开讲讲,以及对于我们开发很有帮助的话,比如@Jvm-default=all、@BuilderInference等等这些特性,我一般都是专门出一篇文章针对这些特性进行讲解的,大家感兴趣可以看下下面的历史文章列表。
七. 历史文章
@JvmDefaultWithCompatibility优化小技巧,了解一下~
优化@BuilderInference注解,Kotlin高版本下了这些“毒手”!
kotlin密封sealed class/interface的迭代之旅
八. 参考列表
youtrack.jetbrains.com/issue/KT-25…
本文正在参加「金石计划」