Hi 大家好,我是 DHL。公众号:ByteCode ,专注分享最新技术原创文章,涉及 Kotlin、Jetpack、算法动画、数据结构、系统源码、 LeetCode / 剑指 Offer / 多线程 / 国内外大厂算法题等等。
这是 value class
第三篇文章,之前已经写了两篇文章,分别从不同的角度分析了 value class
。
- 在 Kotlin 1.5 宣布了一个重磅特性 value class 文章中介绍了
value class
是一个非常实用的特性,提高代码可读性同时还可以提高性能,详细分析了value class
和inline class
。 - 在 容易被忽视的几个 Kotlin 细节, value class 执行效率竟然这么高 文章中详细分析了
value class
和data class
的性能,以及使用value class
需要注意的几个细节
这篇文章将会从 类型安全 、 占用内存 、执行效率 、使用场景 这几个角度来分析 value class
,通过这篇文章,你将学习到以下内容。
- 什么是
value class
? - 什么是
typealias
? typealias
无法保证类型安全typealias
同value class
一样不会创建额外的对象value class
和typealias
的执行效率
typealias
和原始类型 String 对比value class
和typealias
对比
value class
和typealias
的优势以及使用场景
什么是 value class
value class
表示内联类,需要在主构造函数中传入一个参数,而且需要用 val
进行修饰, 编译成 Java 代码之后,会替换为传进去的值,代码如下所示。
@JvmInline value class User(val name: String) fun login(user: User?): String = user?.name ?: "" fun testInline() { println(login(User("DHL"))) } // 编译后的代码 public static final String login_js0Jwf8/* $FF was: login-js0Jwf8*/(@Nullable String user) { // ...... return var10000; } public static final void testInline() { String var0 = login-js0Jwf8("DHL"); System.out.println(var0); }
正如你所见,编译后的 Java 代码并没有创建额外的对象,而是将在 Kotlin 中创建的对象 User
替换为传进去的值 DHL
。
什么是 typealias
在 Kotlin 源码中遇到长签名的表达式多多少少都会使用 typealias
,它的作用就是给类取一个别名。
typealias Password = String fun inputPassword(password: Password) { } fun main() { val password: Password = "123456" inputPassword(password) }
通过 typealias
关键字,给 String
类型取了一个别名 Password
,接下来就可以像使用 String
来使用 Password
。
在上一篇文章 容易被忽视的几个 Kotlin 细节, value class 执行效率竟然这么高 对比了 value class
和 data class
, 接下来一起分析一下 value class
和 typealias
的区别,value class
是否可以完全代替 typealias
。
Typealias 无法保证类型安全
String
类型可以表示很多东西,比如 用户名 、密码 等等,同样我们也可以通过 typealias
关键字给 用户名 、密码 取一个别名。
typealias Username = String
这里有一个输入密码的函数 inputPassword(password: String)
参数是 String 类型,因此我们可以传入 typealias
别名 Username
,因为类型一样,赋值是兼容的,代码如下所示。
fun inputPassword(password: String) { } val userName: Username = "ByteCode" inputPassword(userName)
虽然这是一个输入密码的函数,但是如果调用者传入的参数是用户名,因为类型一样,赋值是兼容的,这种情况在编译的时候是无法检查出来,但是在运行的时候,可能会带来不可预知的后果。
而 value class
的出现,很好的帮助我们解决了这个问题,我们也可以通过 data class
或者其它的 class 来解决这个问题,但是会有额外的性能开销,详细分析请查看之前的文章 容易被忽视的几个 Kotlin 细节, value class 执行效率竟然这么高。
@JvmInline value class Password(val value: String) { } fun inputPassword(password: Password) { }
现在如果在往 inputPassword()
函数中,传入我们不想要的参数,编译的时候就会检查出来。
Typealias 同 value class 一样不会创建额外的对象
从内存的角度 value class
和 typealias
一样不会创建额外的对象,typealias
编译之后的代码如下所示。
typealias Password = String fun inputPassword(password: Password) { } // 编译之后的代码 public static final void inputPassword(@NotNull String password) { // ...... }
而 value class
编译之后的代码如下所示。
@JvmInline value class Password(val value: String) { } fun inputPassword(password: Password) { } 编译之后的代码 public static final void inputPassword_ZVkiumU(@NotNull String password) { // ...... }
正如你所看到的,无论是 value class
还是 typealias
都没有额外创建对象的开销。
Value class 和 typealias 的执行效率
接下来我们从以下几个角度来看一下 value class
和 typealias
执行效率。
typealias
和原始类型 String 对比value class
和typealias
对比value class
和data class
对比(之前的文章已经分析过了,这里就忽略了)
Typealias 和原始类型 String 对比
通过 typealias
关键字给 String
类型取了一个别名 Password
,那么 Password
和原始类型 String
执行效率如何,我们用一个例子验证一下。
fun inputPassword(password: String) { } fun inputPasswordTypealias(password: Password) { } typealias Password = String @ExperimentalTime fun main() { // 原始类型 val measureString = measureTime { repeat(1000) { inputPassword("123456") } } println("measure string time ${measureString.toDouble(TimeUnit.MILLISECONDS)} ms") // typealias val measuretypealias = measureTime { repeat(1000) { inputPasswordTypealias("123456") } } println("measure typealias time ${measuretypealias.toDouble(TimeUnit.MILLISECONDS)} ms") }
分别测试了 string
和 typealias
,他们的结果如下所示。
measure string time 5.475575 ms measure typealias time 5.853019 ms 复制代码
从结果来看基本上没有什么差别,原因在于编译之后的 Java 代码,会将 typealias
声明的别名,替换为原始类型 String
。
Value class 和 typealias 对比
接下来我们在来看一下 value class
和 typealias
的执行效率,代码很简单如下所示。
// typealias typealias Password = String fun inputPassword(password: Password) {} // value class @JvmInline value class Password(val value: String) {} fun inputPasswordValueClass(password: Password) {} @ExperimentalTime fun main() { // typealias val measureString = measureTime { repeat(1000) { inputPassword("123456") } } println("measure typealias time ${measureString.toDouble(TimeUnit.MILLISECONDS)} ms") // value class val measureValueClass = measureTime { repeat(1000) { inputPasswordValueClass(Password("123456")) } } println("measure value class time ${measureValueClass.toDouble(TimeUnit.MILLISECONDS)} ms") }
value class
和 typealias
的测试结果如下所示。
measure typealias time 6.437296 ms measure value class time 6.66023 ms
正如你所看到的,无论从内存、还是执行效率 value class
和 typealias
基本上是没有太大的差距,那么是不是可以使用 value class
完全代替 typealias
? 这显示是不可能的,虽然 value class
执行效率高,功能强大,但是它们的使用场景完全不同。
Value class 和 typealias 的优势以及使用场景
综合前面的内容和之前的两篇文章对 value class
的分析,value class
具有以下优势:
- 类型安全,防止调用者做出我们意想不到的事
- 占用更少的内存,执行效率更高
- 提高了代码的可读性
value class
是一个真实存在的类型,功能更强大,可以有构造函数、初始化函数、其他函数(getXXX()
、setXXX()
)等等,便于我们封装业务逻辑
而 data class
相比于 value class
最大的优势,支持多个参数,而 value class
只支持一个用 val
声明的参数,但是 value class
内存和执行效率远远高于 data class
。当数据量很大时,它们的差距也会越来越大。
measure data class time 6.790241 ms measure value class time 0.832866 ms
value class
具有这么多的优势,那么它的使用场景呢?其实没有固定的使用场景,我们可以在 Toast、单位之间的转换 (时间、距离)、定位、Json 序列化和反序列化等等场景中,都可以使用到 value class
, 当我们了解完它们的优缺点之后,可以从内存、执行效率等等更多维度考虑。
value class
虽然有很多优势,但是在某些场景下 typealias
比 value class
更具有优势,当我们使用高阶函数、Lambda 表达式、具有长签名的表达式的时候,使用 typealias
会更好,举个例子代码如下所示。
inline fun requestData(type: Int, call: (code: Int, type: Int) -> Unit) { call(200, type) }
方法参数中有一个 Lambda 表达式,未来也有可能随时改动 Lambda 表达式中的参数,如果通过 typealias
给 Lambda 表达式取一个别名,在使用的时候,使用别名除了提高可读性,也方便以后统一的修改,最后的代码如下所示。
typealias Callback = (code: Int, type: Int) -> Unit inline fun requestData(type: Int, call: Callback) { call(200, type) }
所以当我们使用高阶函数、长签名表达式的时候,可以考虑使用 typealias
。
如果有帮助点个赞就是对我最大的鼓励
代码不止,文章不停
欢迎关注公众号:ByteCode,持续分享最新的技术
最后推荐长期更新和维护的项目:
- 个人博客,将所有文章进行分类,欢迎前去查看 hi-dhl.com
- KtKit 小巧而实用,用 Kotlin 语言编写的工具库,欢迎前去查看 KtKit
- 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目以及相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看 AndroidX-Jetpack-Practice
- LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析
近期必读热门文章
- 容易被忽视的几个 Kotlin 细节, value class 执行效率竟然这么高
- 1分钟发布一个网站,不用域名、服务器,网站发布从未如此简单
- 这是最棒的效率工具集,打通 Notion x 云盘 x 其他笔记软件,写作、设计、开发都会用到工具
- Android 12 已来,你的 App 崩溃了吗?
- Kotlin 宣布一个重磅特性
- Google 宣布废弃 LiveData.observe 方法
- 使用 kotlin 需要注意的一个细节
- 影响性能的 Kotlin 代码(一)
- Jetpack Splashscreen 解析 | 助力新生代 IT 农民工 事半功倍
- 为数不多的人知道的 Kotlin 技巧及解析(三)
- 揭秘 Kotlin 中的 == 和 ===
- Kotlin 密封类进化了