value class 完全代替 typealias?

value class 完全代替 typealias?

  • 如果评论区没有及时回复,欢迎来公众号:ByteCode 咨询
  • 公众号:ByteCode。致力于分享最新技术原创文章,涉及 Kotlin、Jetpack、算法、译文、系统源码相关的文章

这是 value class 第三篇文章,之前已经写了两篇文章,分别从不同的角度分析了 value class

这篇文章将会从 类型安全占用内存执行效率使用场景 这几个角度来分析 value class ,通过这篇文章,你将学习到以下内容。

  • 什么是 value class
  • 什么是 typealias
  • typealias 无法保证类型安全
  • typealiasvalue class 一样不会创建额外的对象
  • value classtypealias 的执行效率
    • typealias 和原始类型 String 对比
    • value classtypealias 对比
  • value classtypealias 的优势以及使用场景

什么是 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 classdata class , 接下来一起分析一下 value classtypealias 的区别,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 classtypealias 一样不会创建额外的对象,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 classtypealias 执行效率。

  • typealias 和原始类型 String 对比
  • value classtypealias 对比
  • value classdata 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")
}

分别测试了 stringtypealias,他们的结果如下所示。

measure string time  5.475575 ms
measure typealias time 5.853019 ms

从结果来看基本上没有什么差别,原因在于编译之后的 Java 代码,会将 typealias 声明的别名,替换为原始类型 String

Value class 和 typealias 对比

接下来我们在来看一下 value classtypealias 的执行效率,代码很简单如下所示。

// 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 classtypealias 的测试结果如下所示。

measure typealias time    6.437296 ms 
measure value class time 6.66023 ms

正如你所看到的,无论从内存、还是执行效率 value classtypealias 基本上是没有太大的差距,那么是不是可以使用 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 虽然有很多优势,但是在某些场景下 typealiasvalue 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,持续分享最新的技术



最后推荐长期更新和维护的项目:

  • 个人博客,将所有文章进行分类,欢迎前去查看 https://hi-dhl.com

  • KtKit 小巧而实用,用 Kotlin 语言编写的工具库,欢迎前去查看 KtKit

  • 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目以及相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看 AndroidX-Jetpack-Practice

  • LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析

近期必读热门文章

致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,在技术的道路上一起前进

Android10 源码分析

正在写一系列的 Android 10 源码分析的文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,如果你同我一样喜欢研究 Android 源码,可以关注我 GitHub 上的 Android10-Source-Analysis

算法题库的归纳和总结

由于 LeetCode 的题库庞大,每个分类都能筛选出数百道题,由于每个人的精力有限,不可能刷完所有题目,因此我按照经典类型题目去分类、和题目的难易程度去排序。

  • 数据结构: 数组、栈、队列、字符串、链表、树……
  • 算法: 查找算法、搜索算法、位运算、排序、数学、……

每道题目都会用 Java 和 kotlin 去实现,并且每道题目都有解题思路,如果你同我一样喜欢算法、LeetCode,可以关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin

精选国外的技术文章

目前正在整理和翻译一系列精选国外的技术文章,不仅仅是翻译,很多优秀的英文技术文章提供了很好思路和方法,每篇文章都会有译者思考部分,对原文的更加深入的解读,可以关注我 GitHub 上的 Technical-Article-Translation

评论