容易被忽视的几个 Kotlin 细节, value class 执行效率竟然这么高

简介: 今天这篇文章主要介绍 value class 和 data class 的区别,这可能是平时在做业务开发的时候,容易被忽视的几个细节。通过这篇文章,你将学习到以下内容。

image.png


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


在之前的文章中,分析过 Kotlin 1.5  宣布了一个重磅特性 value class 这是一个非常实用的特性,提高代码的可读性同时,还可以提高性能,因为编译器会对它进行更深层次的优化。主要包含了以下内容,没有看过的小伙伴可以前去查看


  • inline classvalue class 有什么区别
  • value class 不能被继承,但是可以实现接口
  • 当传递的对象为空时,value class 将会失去内联效果
  • value class 禁止使用 === 可以使用 ==


而今天这篇文章主要介绍 value classdata class 的区别,这可能是平时在做业务开发的时候,容易被忽视的几个细节。通过这篇文章,你将学习到以下内容。


  • 什么是 value class ?
  • 什么是 data class ?
  • value classdata class 的区别
  • value class 占用更少的内存,执行效率更高
  • value class 执行效率比 data class 快多少
  • value class 没有 copy() 方法
  • value class 构造函数只能传入一个参数
  • value class 为什么不能重写 equals()hashcode() 方法
  • value classdata class 都不能被继承


什么是 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


什么是 data class



data class 表示数据类,编译器会根据主构造函数声明的参数,自动生成 quals()hashCode()toString()componentN()copy()setXXX()getXXX() 等等模板方法,将我们从重复的劳动力解放出来了,专注于核心业务的实现。


data class User(val name: String)
// 编译之后
public final class User {
   private final String name;
   public final String getName() { return this.name; }
   ......
   public final String component1() { return this.name; }
   public final User copy(@NotNull String name) { return new User(name); }
   ......
}


value class 和 data class 的区别



Value Class 占用更少的内存,执行效率更高


为了保证相同的逻辑应用在各个地方,通常我们对于 model 中参数的验证都会封装在当前 model 中,比如在一个 data class User 中检查用户名是否为空。


data class User(var name: String? = null) {
    init {
        requireNotNull(name) { "name is not null" }
    }
}


当我们每次创建 User 对象的时候,都会在堆中分配对象,需要占用更多的内存,同时也会使我们的代码执行效率更低。因为对象创建过程是非常的慢。会经历两个过程:类加载过程、对象创建过程。


  • 类加载过程
  • 会先判断这个类是否已经初始化,如果没有初始化,会执行类的加载过程
  • 类的加载过程:加载、验证、准备、解析、初始化等等阶段,之后会执行 <clinit>() 方法,初始化静态变量,执行静态代码块等等
  • 对象创建过程
  • 如果类已经初始化了,直接执行对象的创建过程
  • 对象的创建过程:在堆内存中开辟一块空间,给开辟空间分配一个地址,之后执行初始化,会执行 <init>() 方法,初始化普通变量,调用普通代码块


value calss 的出现很好的帮助我们解决了这些问题,它使代码执行效率更高,占用更少的内存。这全都得益于 Kotlin 编译器对它进行大量的优化。


@JvmInline
value class User(val name: String)
fun login(user: User?): String = user?.name ?: ""
println(login(User("DHL")))
// 编译后的代码
String var0 = login-js0Jwf8("DHL");
System.out.println(var0);


正如你所见当我们在实例化 User 的时候,并没有在堆中分配对象,而是将传递给方法 login() 的参数 User 替换为传进去的值 DHL


Value class 执行时间比 data class 快多少


接下来我们用一个例子来感受一下 value classdata class 快多少,代码如下所示。


data class User1(val name: String)
fun printDataClass(user: User1) {}
@JvmInline
value class User2(val name: String)
fun printValueClass(user: User2) {}
@OptIn(ExperimentalTime::class)
fun main() {
    // data class
    val measureDataClass = measureTime {
        repeat(100) {
            User1("DHL")
        }
    }
    println("measure data class time  ${measureDataClass.toDouble(TimeUnit.MILLISECONDS)} ms")
    // value class
    val measureRunValueClass = measureTime {
        repeat(100) {
            User2("DHL")
        }
    }
    println("measure value class time ${measureRunValueClass.toDouble(TimeUnit.MILLISECONDS)} ms")
}


上述代码唯一的区别 User1data class 来声明的,User2value class 来声明的,最后的执行时间如下所示。


measure data class time  6.790241 ms
measure value class time 0.832866 ms


value class 执行效率远远高于 data class。当数据量很大时,它们的差距也会越来越大。


Value class 没有 copy () 方法


value calssdata class 一样可以有 init function,也可以有 internal function,方便我们封装业务逻辑。


但是 value calss 不会生成 copy() 方法,而 data class 编译后会生成 copy() 方法,如下所示。


data class User(val name: String, val pwd: String)
// 编译之后
public final class User {
    ......
   @NotNull
   public final User copy(@NotNull String name, @NotNull String pwd) {
      return new User(name, pwd);
   }
    ......
}


这也意味着通过 data calss 创建对象实例副本,我们不需要重写所有的参数,可以指定需要改变的参数。


user = user.copy(name = "hi-dhl")


value calss 只能通过构造函数去创建对象,需要显示指定所有的参数。


Value class 构造函数只能传入一个参数


现阶段 value class 只能在构造函数中传入一个参数,而且需要用 val 进行修饰,而 data calss 支持在构造函数中添加多个参数,参数可以用 val 或者 var 声明。不过在不久的将来 Kotlin 将会支持在 value class 构造函数中添加多个参数,如下图所示。


image.png


Value class 和 data class 都不能被继承


因为 value classdata class 编译后将会添加 fianl 修饰符,因此不能被继承,同样也不能继承其他的类,如下图所示。


image.png


image.png


image.png


Value class 不能重写 equals () 、hashcode () 方法


value class 相比于 data class 不能重写 equals()  和 hashcode() 方法, 如下图所示。


image.png


equals() 方法用于比较两个参数的内容是否相同,关于 Kotlin 中的 ===== 以及 eauals 方法的区别,可以查看我另外一篇文章 解密 Koltin 中的 == 和 === 以及 eauals


因为 value class 构造函数只能传入一个参数,而且必须用 val 进行修饰,所以不存在需要比较两个相同的参数场景,因此 Kotlin 不让重写 equals()  和 hashcode() 方法。


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


代码不止,文章不停


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


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


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


image.png



近期必读热门文章




目录
相关文章
|
安全 Java 编译器
3 亿美元的 bug,Kotlin 帮你避免 | 内联类 value class
3 亿美元的 bug,Kotlin 帮你避免 | 内联类 value class
115 0
|
IDE 编译器 开发工具
深入学习 Kotlin 特色之 Sealed Class 和 Interface
深入学习 Kotlin 特色之 Sealed Class 和 Interface
|
Android开发 Kotlin
【错误记录】Kotlin 编译报错 ( Not nullable value required to call an ‘iterator()‘ method on for-loop range )
【错误记录】Kotlin 编译报错 ( Not nullable value required to call an ‘iterator()‘ method on for-loop range )
275 0
【错误记录】Kotlin 编译报错 ( Not nullable value required to call an ‘iterator()‘ method on for-loop range )
|
Android开发 Kotlin
【错误记录】Kotlin 编译报错 ( Class ‘Xxx‘ is not abstract and does not implement abstract member )
【错误记录】Kotlin 编译报错 ( Class ‘Xxx‘ is not abstract and does not implement abstract member )
768 0
【错误记录】Kotlin 编译报错 ( Class ‘Xxx‘ is not abstract and does not implement abstract member )
|
Java Apache 开发者
在 Kotlin 的 data class 中使用 MapStruct
在 Kotlin 的 data class 中使用 MapStruct
399 0
|
2月前
|
JSON 调度 数据库
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
34 1
|
3月前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
96 1
|
4月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
65 4
|
5月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
177 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
|
5月前
|
安全 Android开发 Kotlin
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
62 8