当前位置:网站首页>Kotlin 值类 - value class
Kotlin 值类 - value class
2022-07-30 08:03:00 【0萌萌哒0】
说起存储模型(model)时,Kotlin 的数据类( data class) 是我们的第一选择。数据类加上一系列必要的方法,使得开发人员的编码效率得到了很大的提升。Kotlin 1.5 引入了 值类(value class )。这是什么类型的类,我们又该何时使用它呢?
当数据类用于保存模型时,值类将属性添加到值中并约束其使用。该类只是一个值的包装器,但是 Kotlin 编译器可以确保没有因包装而产生任何内存开销。
问题
持续时间(Duration)是一个经典问题,如果不阅读注释或者源代码,任何人都可能用错。 所以,我编写了一个在给定以毫秒为单位的持续时间内显示提示消息的函数,函数签名如下所示:
fun showTooltip(message: String, duration: Long) {
... }
为了确保调用方传入正确的持续时间,我可以在参数名中提示:
fun showTooltip(message: String, durationInMillis: Long) {
... }
甚至加上注释:
/** * Shows tooltip of message for given duration * @param message - message to display * @param durationInMillis - duration in milliseconds **/
fun showTooltip(message: String, durationInMillis: Long) {
... }
尽管有了清晰的命名和说明文档,仍可能有人调用函数时传入以秒为单位的持续时间。毕竟,美国宇航局就曾由于一个单位错误而损失了一艘航天器。
showTooltip("I'm going to pass duration in seconds", 2L)
一个简单的解决方案
为了确保调用方以毫秒为单位传递参数,可以将持续时间包装在一个类中,并提供帮助方法限制对象的创建。这个包装类可以确保向函数传递正确的值。
class Duration private constructor (
val millis: Long
) {
companion object {
fun millis(millis: Long) = Duration(millis)
fun seconds(seconds: Long) = Duration(seconds * 1000)
}
}
fun showTooltip(message: String, duration: Duration) {
println("Will show $message for ${
duration.millis} milliseconds")
}
...
showTooltip("Hello - Seconds", Duration.seconds(2L))
showTooltip("Hello - Millis", Duration.millis(1200))
...
现在 showTooltip 函数接收持续时间并以毫秒为单位进行处理。这确保调用者向函数传入正确的持续时间,showTooltip 可以信赖持续时间是以毫秒为单位。
然而,每个持续时间要用一个对象包装起来以避免歧义。这样每传入一个参数,都会创建一个新对象并为其额外分配内存。
使用 kotlin 值类
// Invoking the function that consumes value class param.
showTooltip_fxiZ0zM("",
Duration.Companion.millis_PZfE49U(2000L));
showTooltip_fxiZ0zM("",
Duration.Companion.seonds_PZfE49U(2L));
Kotlin 值类通过包装单个值来对该值进行限制或者转换。Kotlin 编译器可以在某些情况下取消装箱以保证性能。
@JvmInline
value class Duration private constructor (
val millis: Long
) {
companion object {
fun millis(millis: Long) = Duration(millis)
fun seconds(seconds: Long) = Duration(seconds * 1000)
}
}
可以看到,值类和普通类的区别只在于多了两个新的关键字。但此时正常持续时间类和值类是完全不同的。这是值类编译后的字节码:
// Duration -- value class bytecode
public final class Duration {
private final long millis;
private Duration(long millis) {
this.millis = millis;
}
public static final class Companion {
// Comapanion that outputs Duration is mangled to return the wrapped value
public final long millis_PZfE49U/* $FF was: millis-PZfE49U*/(long millis) {
return Duration.constructor-impl(millis);
}
// Comapanion that outputs Duration is mangled to return the wrapped value
public final long seconds_PZfE49U/* $FF was: seconds-PZfE49U*/(long seconds) {
return Duration.constructor-impl(seconds * (long)1000);
}
}
}
// Caller function name mangled
public static final void showTooltip_fxiZ0zM/* $FF was: showTooltip-fxiZ0zM*/(@NotNull String message, long duration) {
...
}
如上,在使用 Duration 对象的地方,函数名被打乱了,参数类型被改为 Long 类型。这里打乱函数名是为了防止方法重载冲突。
// Using a wrapped value arg
fun showTooltip(message: String, duration: Duration) {
...}
// Using a primitive value as arg
fun showTooltip(message: String, duration: Long) {
...}
如以上代码所示,开发者还定义了一个同名函数,且函数参数类型是基本类型 Long 如果。如果编译器只将参数类型替换为基本类型,这段代码就无法编译通过。所以有必要把函数名打乱。
普通包装类的字节码
public final class Duration {
private final long millis;
private Duration(long millis) {
this.millis = millis;
}
public static final class Companion {
// Companion function return the wrapped object
public final Duration millis(long millis) {
return new Duration(millis, (DefaultConstructorMarker)null);
}
// Companions function return the wrapped object
public final Duration seconds(long seconds) {
return new Duration(seconds * (long)1000, (DefaultConstructorMarker)null);
}
}
}
// Called function signature looks same
public static final void showTooltip(String message, Duration duration)
{
...}
// Caller is
showTooltip("", Duration.Companion.seconds(2L));
showTooltip("", Duration.Companion.millis(1200L));
这是普通包装类的字节码。肯定想问为什么使用普通类而不是数据类呢?请继续往下看。这是数据类定义:
data class Duration private constructor(
val millis: Long
) {
// Same companion as normal class
}
这是数据类编译后的字节码:
public final class Duration {
private Duration(long millis) {
this.millis = millis;
}
public final long component1() {
return this.millis;
}
@NotNull
public final Duration copy(long millis) {
return new Duration(millis);
}
// Same companion byte code from normal class
...
}
可以看到,数据类会生成一个 copy 方法,该方法允许在给定 long 类型参数时创建一个新的持续时间对象。而我们的目标是用伴生对象来限制对象的创建。此外,数据类还保留了一个多余的 componentN 函数。我们仅仅只想包装值而已。
尾注
Kotlin 1.2.xx 引入了内联类(inline clasas),内联类是值类的旧称。由于相比于内联函数,内联类实际上并没有内联,因此官方将其重命名为 value class,现在内联关键字已被废弃。
原文链接:Kotlin value class
边栏推荐
- typescript3-ts对比js的差别
- cmd命令
- 基于SSM实现个性化健康饮食推荐系统
- test3
- Alibaba Cloud Cloud Server Firewall Settings
- HashSet and LinkedHashSet
- Thinking about digital transformation of construction enterprises in 2022, the road to digital transformation of construction enterprises
- Network/Information Security Top Journal and Related Journals Conference
- typescript8-类型注解
- 智能存储柜——解决您的存储需求
猜你喜欢

七大排序之直接选择排序

【Flask框架①】——Flask介绍

RFID固定资产盘点系统给企业带来哪些便利?
![[Fun BLDC series with zero basics] Taking GD32F30x as an example, the timer related functions are explained in detail](/img/1d/700c79a766f115d5d0f3bd8263d67c.png)
[Fun BLDC series with zero basics] Taking GD32F30x as an example, the timer related functions are explained in detail
![[Mini Program Column] Summarize the development specifications of uniapp to develop small programs](/img/7b/110d324eba00652e4987bc623a5bc6.png)
[Mini Program Column] Summarize the development specifications of uniapp to develop small programs

瑞吉外卖项目(五) 菜品管理业务开发

It is said that FPGA is high-end, what can it do?

typescript4 - installs a toolkit for compiling ts

一文读懂二十种开关电源拓扑结构

积分简明笔记-第二类曲线积分的类型
随机推荐
Is R&D moving to FAE (Field Application Engineer), is it moving away from technology?Is there a future?
typescript6 - simplify the steps to run ts
Leetcode - 990: equations of satisfiability
R安装包出现error in rawtochar(block[seq_len(ns)]) :
蓝牙技术|了解蓝牙LE Audio的Auracast广播音频
[Unity]UI切换环形滚动效果
OA Project Pending Meeting & History Meeting & All Meetings
电脑文档误删除怎么恢复,恢复误删除电脑文档的方法
【无标题】
MagicDraw secondary development process
硬件工程师
瑞吉外卖项目(五) 菜品管理业务开发
MagicDraw二次开发过程
【蓝桥杯选拔赛真题45】Scratch猫鼠游戏 少儿编程scratch蓝桥杯选拔赛真题讲解
SwiftUI SQLite 教程之 构建App本地数据库实现创建、读取、更新和删除(教程含完成项目源码)
联想笔记本 如何更改Win10系统开机logo图标
【科普向】5G核心网架构和关键技术
How to implement Golang DES encryption and decryption?
tabindex attribute of input tag & tabindex attribute of a tag
TreeSet解析