容易被忽视的几个 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



近期必读热门文章




目录
相关文章
|
11月前
|
安全 Java 编译器
3 亿美元的 bug,Kotlin 帮你避免 | 内联类 value class
3 亿美元的 bug,Kotlin 帮你避免 | 内联类 value class
92 0
|
IDE 编译器 开发工具
深入学习 Kotlin 特色之 Sealed Class 和 Interface
深入学习 Kotlin 特色之 Sealed Class 和 Interface
|
Java Apache 开发者
在 Kotlin 的 data class 中使用 MapStruct
在 Kotlin 的 data class 中使用 MapStruct
306 0
|
22天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
23天前
|
数据库 Android开发 开发者
构建高效Android应用:Kotlin协程的实践指南
【4月更文挑战第2天】随着移动应用开发的不断进步,开发者们寻求更流畅、高效的用户体验。在Android平台上,Kotlin语言凭借其简洁性和功能性赢得了开发社区的广泛支持。特别是Kotlin协程,作为一种轻量级的并发处理方案,使得异步编程变得更加简单和直观。本文将深入探讨Kotlin协程的核心概念、使用场景以及如何将其应用于Android开发中,以提高应用性能和响应能力。通过实际案例分析,我们将展示协程如何简化复杂任务,优化资源管理,并为最终用户提供更加流畅的体验。
|
1月前
|
调度 数据库 Android开发
构建高效Android应用:Kotlin协程的实践与优化
在Android开发领域,Kotlin以其简洁的语法和平台友好性成为了开发的首选语言。其中,Kotlin协程作为处理异步任务的强大工具,它通过提供轻量级的线程管理机制,使得开发者能够在不阻塞主线程的情况下执行后台任务,从而提升应用性能和用户体验。本文将深入探讨Kotlin协程的核心概念,并通过实例演示如何在实际的Android应用中有效地使用协程进行网络请求、数据库操作以及UI的流畅更新。同时,我们还将讨论协程的调试技巧和常见问题的解决方法,以帮助开发者避免常见的陷阱,构建更加健壮和高效的Android应用。
36 4
|
1月前
|
移动开发 Java Android开发
构建高效Android应用:Kotlin协程的实践之路
【2月更文挑战第31天】 在移动开发领域,性能优化和流畅的用户体验一直是开发者追求的目标。随着Kotlin语言的流行,其异步编程解决方案——协程(Coroutines),为Android应用带来了革命性的并发处理能力。本文将深入探讨Kotlin协程的核心概念、设计原理以及在Android应用中的实际应用案例,旨在帮助开发者掌握这一强大的工具,从而提升应用的性能和响应能力。
|
1月前
|
安全 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美结合
【2月更文挑战第30天】在移动开发领域,性能优化和流畅的用户体验是关键。本文深入探讨了如何通过结合Kotlin语言和协程技术来提升Android应用的性能和响应能力。我们将分析Kotlin的优势,介绍协程的基本概念,并通过实际案例展示如何在应用中实现协程以简化异步编程,从而提供更加高效的解决方案。
|
1月前
|
Java 编译器 Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【2月更文挑战第30天】 随着Kotlin成为开发Android应用的首选语言,开发者社区对于其性能表现持续关注。本文通过深入分析与基准测试,探讨Kotlin与Java在Android平台上的性能差异,揭示两种语言在编译效率、运行时性能和内存消耗方面的具体表现,并提供优化建议。我们的目标是为Android开发者提供科学依据,帮助他们在项目实践中做出明智的编程语言选择。
|
1月前
|
移动开发 调度 Android开发
构建高效Android应用:探究Kotlin协程的优势与实践
【2月更文挑战第30天】 在移动开发领域,尤其是针对Android平台,性能优化和应用流畅度始终是开发者关注的重点。近年来,Kotlin语言凭借其简洁性和功能性成为Android开发的热门选择。其中,Kotlin协程作为一种轻量级的线程管理解决方案,为异步编程提供了强大支持,使得编写非阻塞性代码变得更加容易。本文将深入分析Kotlin协程的核心优势,并通过实际案例展示如何有效利用协程提升Android应用的性能和响应速度。