查看原文
其他

value class 完全代替 typealias?

DHL ByteCode 2022-12-14

hi 这是 dhl 的第 59 篇原创文章

个人微信: hi-dhl

Hi 大家好,我是 DHL。公众号:ByteCode ,专注分享最新技术原创文章,涉及 Kotlin、Jetpack、算法动画、数据结构、系统源码、 LeetCode / 剑指 Offer / 多线程 / 国内外大厂算法题等等。

这是 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


推荐阅读


感谢大家帮忙 在看点赞分享 给身边的朋友

持续分享最新的技术

 文末链接包含各类技术知识体系

👇🏻 真诚推荐你关注我👇🏻

因微信公众号更改了推送机制

可能无法及时看到最新文章

 将公众号设为 星标 

或常为文章点 在看

即可及时收到最新文章


欢迎前往 博客 查看更多 Kotlin、Jetpack 、动画算法图解、系统源码分析等等文章。以及开源项目、LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解。

https://www.hi-dhl.com

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存