容易被忽视的几个 Kotlin 细节, value class 执行效率竟然这么高
hi
这是 dhl
的第 58 篇原创文章在之前的文章中,分析过 Kotlin 1.5 宣布了一个重磅特性 value class 这是一个非常实用的特性,提高代码的可读性同时,还可以提高性能,因为编译器会对它进行更深层次的优化。主要包含了以下内容,没有看过的小伙伴可以前去查看 。
inline class
和value class
有什么区别value class
不能被继承,但是可以实现接口当传递的对象为空时,
value class
将会失去内联效果value class
禁止使用===
可以使用==
而今天这篇文章主要介绍 value class
和 data class
的区别,这可能是平时在做业务开发的时候,容易被忽视的几个细节。通过这篇文章,你将学习到以下内容。
什么是
value class
?什么是
data class
?value class
和data class
的区别value class
占用更少的内存,执行效率更高value class
执行效率比data class
快多少value class
没有copy()
方法value class
构造函数只能传入一个参数value class
为什么不能重写equals()
、hashcode()
方法value class
和data class
都不能被继承
什么是 value class
value class
表示内联类,需要在主构造函数中传入一个参数,而且需要用 val
进行修饰, 编译成 Java 代码之后,会替换为传进去的值,代码如下所示。
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()
等等模板方法,将我们从重复的劳动力解放出来了,专注于核心业务的实现。
// 编译之后
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
中检查用户名是否为空。
init {
requireNotNull(name) { "name is not null" }
}
}
当我们每次创建 User
对象的时候,都会在堆中分配对象,需要占用更多的内存,同时也会使我们的代码执行效率更低。因为对象创建过程是非常的慢。会经历两个过程:类加载过程、对象创建过程。
类加载过程
会先判断这个类是否已经初始化,如果没有初始化,会执行类的加载过程
类的加载过程:加载、验证、准备、解析、初始化等等阶段,之后会执行
<clinit>()
方法,初始化静态变量,执行静态代码块等等对象创建过程
如果类已经初始化了,直接执行对象的创建过程
对象的创建过程:在堆内存中开辟一块空间,给开辟空间分配一个地址,之后执行初始化,会执行
<init>()
方法,初始化普通变量,调用普通代码块
value calss
的出现很好的帮助我们解决了这些问题,它使代码执行效率更高,占用更少的内存。这全都得益于 Kotlin 编译器对它进行大量的优化。
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 class
比 data class
快多少,代码如下所示。
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")
}
上述代码唯一的区别 User1
用 data class
来声明的,User2
用 value class
来声明的,最后的执行时间如下所示。
measure value class time 0.832866 ms
value class
执行效率远远高于 data class
。当数据量很大时,它们的差距也会越来越大。
Value class 没有 copy () 方法
value calss
同 data class
一样可以有 init function
,也可以有 internal function
,方便我们封装业务逻辑。
但是 value calss
不会生成 copy()
方法,而 data class
编译后会生成 copy()
方法,如下所示。
// 编译之后
public final class User {
......
@NotNull
public final User copy(@NotNull String name, @NotNull String pwd) {
return new User(name, pwd);
}
......
}
这也意味着通过 data calss
创建对象实例副本,我们不需要重写所有的参数,可以指定需要改变的参数。
而 value calss
只能通过构造函数去创建对象,需要显示指定所有的参数。
Value class 构造函数只能传入一个参数
现阶段 value class
只能在构造函数中传入一个参数,而且需要用 val
进行修饰,而 data calss
支持在构造函数中添加多个参数,参数可以用 val
或者 var
声明。不过在不久的将来 Kotlin 将会支持在 value class
构造函数中添加多个参数,如下图所示。
Value class 和 data class 都不能被继承
因为 value class
和 data class
编译后将会添加 fianl
修饰符,因此不能被继承,同样也不能继承其他的类,如下图所示。
Value class 不能重写 equals () 、hashcode () 方法
value class
相比于 data class
不能重写 equals()
和 hashcode()
方法, 如下图所示。
equals()
方法用于比较两个参数的内容是否相同,关于 Kotlin 中的 ==
和 ===
以及 eauals
方法的区别,可以查看我另外一篇文章 解密 Koltin 中的 == 和 === 以及 eauals。
因为 value class
构造函数只能传入一个参数,而且必须用 val
进行修饰,所以不存在需要比较两个相同的参数场景,因此 Kotlin 不让重写 equals()
和 hashcode()
方法。
推荐阅读:
往期视频
感谢大家帮忙 在看、点赞、分享 给身边的朋友
持续分享最新的技术
文末链接包含各类技术知识体系
👇🏻 真诚推荐你关注我👇🏻
因微信公众号更改了推送机制
可能无法及时看到最新文章
将公众号设为 星标
或常为文章点 在看
即可及时收到最新文章
欢迎前往 博客 查看更多 Kotlin、Jetpack 、动画算法图解、系统源码分析等等文章。以及开源项目、LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解。
https://www.hi-dhl.com