首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Jetbrains开发者日见闻(三)之Kotlin1.3新特性有哪些?

简述:

上接上篇文章,我们深入分析了Kotlin1.3版本中的Contract契约的内容,那么这篇文章将会继续把Kotlin1.3新特性研究完毕。这篇文章还有个非常重要的点就是inline class 内联类。关于内联类的知识除了这篇文章会有介绍,后面马上会翻译几篇有关Kotlin中的内联类相关内容。只有一个目的彻底搞定Kotlin中的内联类。那我们一起来看下本次提纲:

一、inline class内联类(Experimental)

1、复习inline内联函数的作用

关于inline内联相信大家都不陌生吧,在实际开发中我们经常会使用Kotlin中的inline内联函数。那大家还记得inline内联作用吗? 这里再次复习下inline内联的作用:

inline内联函数主要有两大作用:

用于lambda表达式调用,降低Function系列对象实例创建的内存开销,从而提高性能。声明成内联函数的话,而是在调用的时把调用的方法给替换掉,可以降低很大的性能开销。

另一个内联函数作用就是它能是泛型函数类型实参进行实化,在运行时能拿到类型实参的信息。

2、为何需要内联类

通过复习了inline函数的两大作用,实际上内联类存在意义和inline函数第一个作用有点像。有时候业务场景需要针对某种类型创建包装器类。 但是,使用包装器类避免不了去实例化这个包装器类,但是这样会带来额外的创建对象的堆分配,它会引入运行时开销。 此外,如果包装类型是基础类型的,性能损失是很糟糕的,因为基础类型通常在运行时大大优化,而它们的包装器没有得到任何特殊处理。

那到底是什么场景会需要内联类呢? 实际上,在开发中有时候就基本数据类型外加变量名是无法完全表达某个字段的含义,甚至还有可能造成歧义,这种场景就特别适合内联类包装器。是不是很抽象,那么一起来看个例子。

案例一: Iterable扩展函数joinToString源码分析

我们仔细分析下joinToString函数,它有很多个函数参数,其中让人感到歧义就是前面三个: .有的人就会说不会有歧义啊,定义得很清楚很明白了,separator,prefix,postfix形参名明显都有自己的含义啊。仅仅从joinToString这个函数的角度来看确实比较清晰。可是你有没有想过外层调用者困惑呢。对于外部调用者前面三个参数都是CharSequence类型,对于他们而言除非去看函数声明然后才知道每个参数代表什么意思,外面调用者很容易把三个参数调用顺序弄混了。就像下面这样。

上面那种问题,为什么我们平时感受不到呢? 这是因为IDE帮你做了很多工作,但是试想下如果你的代码离开IDE就突然感觉很丑陋. 就看上面那段代码假如没有给你joinToStr函数声明定义,是不是对传入三个参数一脸懵逼啊。针对上述实际上有三种解决办法:

第一种: IDE高亮提示

第二种: Kotlin中命名参数

关于Kotlin中命名参数解决问题方式和IDE提示解决思路是一样的。关于Kotlin中命名参数不了解的可以参考我之前这篇文章浅谈Kotlin语法篇之如何让函数更好地调用(三)

第三种: 使用包装器类解决方案

看到这里是不是很多人觉得这样实现有问题,虽然它能很好解决我们上述类型不明确的问题。但是却引入一个更大问题,需要额外创建Speparator、Prefix、Postfix实例对象,带来很多的内存开销。从投入产出比来看,估计没人这么玩吧。这是因为inline class没出来之前,但是如果inline class能把性能开销降低到和直接使用String一样的性能开销,你还认为它是很差的方案吗? 请接着往下看

第四种: Kotlin中inline class终极解决方案

针对上述问题,Kotlin提出inline class解决方案,就是从源头上解决问题。一起来看看:

通过使用inline class来改造这个案例,会发现刚刚上述那个问题就被彻底解决了,外部调用者不会再一脸懵逼了,一看就很明确,是该传入Speparator、Prefix、Postfix对象。性能开销问题的完全不用担心了,它和直接使用String的性能几乎是一样的,这样一来是不是觉得这种解决方案还不错呢。至于它是如何做到的,请接着往下看。这就是为什么需要inline class场景了。

3、内联类的基本介绍

基本定义

inline class 为了解决包装器类带来额外性能开销问题的一种特殊类。

基本结构

基本结构很简单就是在普通class前面加上inline关键字

4、如何尝鲜inline class

因为kotlin中的inline class还是处于Experimental中,所以你要使用它需要做一些额外的配置。首先你的Kotlin Plugin升级到1.3版以上,然后配置gradle,这里给出IntelliJ IDEA和AndroidStudio尝鲜gradle配置:

IntelliJ IDEA中gradle配置

AndroidStudio中gradle配置

使用maven的配置

5、内联类和typealias的区别

估计很多人都没使用过typealias吧,如果还不了解typealias的话请参考我之前的这篇文章:[译]有关Kotlin类型别名(typealias)你需要知道的一切. 其实上述那个问题还可以用typealias来改写,但是你会发现是有点缺陷的,并没有达到想要效果。可以给大家看下:

关于inline class和typealias有很大相同点不同点,相同点在于: 他们两者看起来貌似都引入一种新类型,并且两者都将在运行时表现为基础类型。不同点在于: typealias仅仅是给基础类型取了一个别名而已,而inline class是基础类型一个包装器类。换句话说inline class才是真正引入了一个新的类型,而typealias则没有。

是不是还是有点抽象啊,来个例子你就明白了

6、内联类的反编译源码分析

通过反编译分析,就能清楚明白为什么inline class不会存在创建对象性能开销。实际上inline class在运行时表现和直接使用基础类型的效果是一样的。

就拿上述例子反编译分析一下:

TokenWrapper类

可以看到TokenWrapper类中反编译后的源码重写了Any中toString、equal、hashCode三个方法。然后这三个方法又委托到给外部定义对应的静态方法来实现。unbox_impl和box_impl两个函数实际上就是拆箱和装箱的操作

main函数

分析如下: 可以先从main函数入手,重点看这行:

然后再跳到TokenWrapper中constructor-impl方法

所以main函数中的在运行时相当于. 所以性能问题就不用担心了。

7、内联类使用限制

1、内联类必须含有主构造器且构造器内参数个数有且仅有一个,形参只能是只读的(val修饰)。

2、内联类不能含有init block

3、内联类不能含有inner class

二、when表达式的使用优化

Kotlin1.3新特性对when表达式做一个写法上的优化,为什么这么说呢?仅仅就是写法上的优化,实际上什么都没做,一起来研究下。不知道大家在使用when表达式有没有这样感受(反正我是有过这样的感受):在when表达式作用域内,老天啊请赐我一个像lambda表达式中的一样it实例对象指代吧。---来自众多Kotlin开发者心声。一起看下这个例子:

1、Anko库源码中fillIntentArguments函数部分代码分析

可以看到上面的1.3版本之前源码案例实现,本就一个when表达式的实现由于在表达式内部需要使用传入值,但是呢表达式作用域内又不能像lambda表达式内部那样快乐使用it,所以被活生生拆成两行代码实现,是不是很郁闷。关于这个问题,官方已经注意到了,可以看到Kotlin团队的大佬们对开发者的问题处理还是蛮积极的,马上就优化这个问题。

2、1.3版本when表达式优化版本

官方到底是怎么优化的呢? 那么有的人就说了是不是像lambda表达式一样赐予我们一个it指代呢。官方的回答是: NO. 一起再来看1.3版本的实现:

3、优化之后反编译代码对比

kotlin 1.3版本之前when表达式实现

kotlin 1.3版本之前when表达式使用反编译代码

kotlin 1.3版本when表达式实现

kotlin 1.3版本when表达式使用反编译代码

通过对比两者实现方式反编译的代码你会发现没有任何变化,所以这就是我说为什么实际上没做什么操作。

三、无参的main函数

还记得开发者日大会上官方布道师Hali在讲Kotlin 1.3新特性的时候,第一个例子就是讲无参数main函数,在他认为这是一件很兴奋的事。下面给出官方一张动图一起兴奋一下:

不知道大家在开发中有没有被其他动态语言开发的人吐槽过。比如最简单的在程序中打印一行内容的时候,静态语言就比较繁琐用Java举例先得定义一个类,然后再定义main函数,函数中还得传入数组参数。人家python一行print代码就解决了。其实Kotlin之前版本相对Java还是比较简单至少不需要定义类了,但是Kotlin 1.3就直接把main函数中的参数干掉了(注意: 这里指的是带参数和不带参数共存,并不是完全把带参main函数给替换掉了)。

可以大家有没有思考过无参main函数是怎么实现的呢? 不妨我们一起来探索一波,来了你就懂了,很简单。

来个Hello Kotlin的例子哈。

将上述代码反编译成Java代码如下

看完反编译后的Java代码是不是一眼就清楚,所谓的无参main函数,实际上就是个障眼法。默认生成一个带参数的main函数继续作为执行的入口,只不过在这带参数的main函数中再去调用外部无参main函数。

注意:使用无参main函数有好处也有不妥的地方,好处显而易见的是使用非常简洁。但是也就间接丧失了main函数执行入口配置参数功能。所以官方并没有把带参数main函数去掉,而是共存。两种main函数都是有各自使用场景的。

四、接口的伴生对象支持@JvmStatic,@JvmField

我们自然而然知道在类的伴生对象是完全支持@JvmStatic,@JvmField注解。首先呢,关于@JvmStatic,@JvmField注解我想有必要说明下它们的作用。

@JvmStatic,@JvmField的作用(实际上以前文章中有提到过)

他们作用主要是为了在Kotlin伴生对象中定义的一个函数或属性,能够在Java中像调用静态函数和静态属性那样类名.函数名/属性名方式调用,让Java开发者完全无法感知这是一个来自Kotlin伴生对象中的函数或属性。如果不加注解那么在Java中调用方式就是类名.Companion.函数名/属性名。你让一个Java开发者知道Companion存在,只会让他一脸懵逼。

Kotlin 1.3版本接口(interface)中伴生对象支持@JvmStatic,@JvmField

这就意味着1.3接口中伴生对象中函数和属性可以向类中一样快乐地使用@JvmStatic,@JvmField注解了。

一起来看个使用例子:

五、支持可变参数的FunctionN接口

不知道大家是否还记得我之前几篇文章深入研究过Lambda表达式整个运行原理,其中就详细讲了关于Function系列的接口。因为我们知道Lambda表达式最后会编译成一个class类,这个类会去继承Kotlin中Lambda的抽象类(在kotlin.jvm.internal包中)并且实现一个Function0…FunctionN(在kotlin.jvm.functions包中)的接口(这个N是根据lambda表达式传入参数的个数决定的,目前接口N的取值为 0

由上面分析得到N取值范围是0

六、注解类的嵌套声明

在Kotlin 1.3中,注解类可以嵌套注解类、接口以及伴生对象.关于Kotlin中的注解和反射还没有详细深入研究过,这个暂且放一放,等到研究注解时候,会再次探讨有关注解类嵌套的问题。

七、结语

到这里Kotlin1.3新特性相关的内容就结束。下面将会继续深入研究下Kotlin 1.3中的inline class(主要是以翻译国外优秀文章为主)。然后就是去深入研究大家一直期待的协程和ktor框架,并把最终研究成果以文章的形式共享给大家。欢迎关注,会一直持续更新下去~~~

Kotlin系列文章,欢迎查看:

原创系列:

JetBrains开发者日见闻(二)之Kotlin1.3的新特性(Contract契约与协程篇)

JetBrains开发者日见闻(一)之Kotlin/Native 尝鲜篇

教你如何攻克Kotlin中泛型型变的难点(实践篇)

教你如何攻克Kotlin中泛型型变的难点(下篇)

教你如何攻克Kotlin中泛型型变的难点(上篇)

Kotlin的独门秘籍Reified实化类型参数(下篇)

有关Kotlin属性代理你需要知道的一切

浅谈Kotlin中的Sequences源码解析

浅谈Kotlin中集合和函数式API完全解析-上篇

浅谈Kotlin语法篇之lambda编译成字节码过程完全解析

浅谈Kotlin语法篇之Lambda表达式完全解析

浅谈Kotlin语法篇之扩展函数

浅谈Kotlin语法篇之顶层函数、中缀调用、解构声明

浅谈Kotlin语法篇之如何让函数更好地调用

浅谈Kotlin语法篇之变量和常量

浅谈Kotlin语法篇之基础语法

翻译系列:

[译]Kotlin的独门秘籍Reified实化类型参数(上篇)

[译]Kotlin泛型中何时该用类型形参约束?

[译] 一个简单方式教你记住Kotlin的形参和实参

[译]Kotlin中是应该定义函数还是定义属性?

[译]如何在你的Kotlin代码中移除所有的!!(非空断言)

[译]掌握Kotlin中的标准库函数: run、with、let、also和apply

[译]有关Kotlin类型别名(typealias)你需要知道的一切

[译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?

[译]Kotlin中的龟(List)兔(Sequence)赛跑

[译]Effective Kotlin系列之考虑使用静态工厂方法替代构造器

[译]Effective Kotlin系列之遇到多个构造器参数要考虑使用构建器

实战系列:

用Kotlin撸一个图片压缩插件ImageSlimming-导学篇(一)

用Kotlin撸一个图片压缩插件-插件基础篇(二)

用Kotlin撸一个图片压缩插件-实战篇(三)

浅谈Kotlin实战篇之自定义View图片圆角简单应用

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181205G0Y3CA00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券