value class 完全代替 typealias?

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

image.png


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


如果有帮助点个赞就是对我最大的鼓励


代码不止,文章不停


欢迎关注公众号:ByteCode,持续分享最新的技术


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


  • 个人博客,将所有文章进行分类,欢迎前去查看 hi-dhl.com
  • KtKit 小巧而实用,用 Kotlin 语言编写的工具库,欢迎前去查看 KtKit
  • 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目以及相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看 AndroidX-Jetpack-Practice
  • LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析


image.png



近期必读热门文章




目录
相关文章
|
6月前
with class of same name
with class of same name
24 0
|
6月前
Class 的讲解
Class 的讲解
136 1
Class下的getEnumConstants
Class下的getEnumConstants
88 0
class CSplitterWnd :public CSplitterWnd
class CSplitterWnd :public CSplitterWnd
71 0
|
JavaScript 程序员
Class-总结class的基本用法和两个注意点
一、注意点一:class关键字区间使用 二、注意点二:Class关键字内部函数
|
Apache
class7
搭建lamp
185 0
|
关系型数据库 MySQL Linux
class6
快速搭建LAMP坏境
328 0
class4
安装putty工具远程连接ECS服务器
193 0