23:kotlin类和对象 -- 内联值类(Inline value classes)

这篇具有很好参考价值的文章主要介绍了23:kotlin类和对象 -- 内联值类(Inline value classes)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

有时,将一个值包装在一个类中可以创建一个更具领域特定类型的类。然而,由于额外的堆分配,这会引入运行时开销。此外,如果包装的类型是原始类型,性能损失是显著的,因为原始类型通常由运行时进行了大量优化,而它们的包装类没有得到任何特殊处理。

为了解决这些问题,kotlin引入了一种特殊类型的类,称为内联类(inline class)。内联类是值类(value-based classes)的一个子集。它们没有身份,只能持有值。

要声明一个内联类,请在类名之前使用value修饰符

value class Password(private val s: String)

要在JVM后端声明一个内联类,可以在类声明之前使用value修饰符以及@JvmInline注解

// For JVM backends
@JvmInline
value class Password(private val s: String)

内联类的主构造函数必须具有一个初始化的单个属性。在运行时,内联类的实例将使用该单个属性来表示

// 实际上不会对Password类进行实例化
// 在运行时,securePassword只包含String
val securePassword = Password("Don't try this in production")

这就是内联类的主要特性,它启发了名称"inline":类的数据被内联到其使用的地方(类似于内联函数的内容被内联到调用站点)

成员

内联类支持常规类的某些功能。特别是,它们可以声明属性和函数,具有初始化块和辅助构造函数

@JvmInline
value class Person(private val fullName: String) {
    init {
        require(fullName.isNotEmpty()) {
            "Full name shouldn't be empty"
        }
    }

    constructor(firstName: String, lastName: String) : this("$firstName $lastName") {
        require(lastName.isNotBlank()) {
            "Last name shouldn't be empty"
        }
    }

    val length: Int
        get() = fullName.length

    fun greet() {
        println("Hello, $fullName")
    }
}

fun main() {
    val name1 = Person("Kotlin", "Mascot")
    val name2 = Person("Kodee")
    name1.greet() // the `greet()` function is called as a static method
    println(name2.length) // property getter is called as a static method
}

内联类的属性不能有backing fields。它们只能具有简单的可计算属性(不支持lateinit或委托属性)

继承

内联类可以实现接口

interface Printable {
    fun prettyPrint(): String
}

@JvmInline
value class Name(val s: String) : Printable {
    override fun prettyPrint(): String = "Let's $s!"
}

fun main() {
    val name = Name("Kotlin")
    println(name.prettyPrint())
}

内联类不能继承其它类,也不能被其它类继承

表示方式(Representation)

在生成的代码中,kotlin编译器会为每个内联类保留一个包装器。在运行时,内联类实例可以表示为包装器或基础类型。这类似于Int可以表示为原始的int类型或包装器Integer

编译器倾向于使用基础类型而不是包装器来生成性能最佳和经过优化的代码。然而,有时候需要保留包装器。一般而言,当内联类用作另一种类型时,它们会被装箱(boxed)

interface I

@JvmInline
value class Foo(val i: Int) : I

fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}

fun <T> id(x: T): T = x

fun main() {
    val f = Foo(42)

    asInline(f)    // 不装箱: 使用Foo自己
    asGeneric(f)   // 装箱: 使用泛型 T
    asInterface(f) // 装箱: 使用 I
    asNullable(f)  // 装箱:使用 Foo?, 这和Foo不同

    // 调用id()方法时。首先装箱成T类型,返回时拆箱成Foo
    // 最后,c是未装箱的状态,和f一样
    val c = id(f)
}

由于内联类可以同时表示为基础值和包装器,因此,引用相等性(==)是无意义的,是被禁止的。

内联类还可以具有泛型类型参数作为基础类型。在这种情况下,编译器将其映射为Any?或为类型参数的上界。

@JvmInline
value class UserId<T>(val value: T)

fun compute(s: UserId<String>) {} // compiler generates fun compute-<hashcode>(s: Any?)

编译器会优化 compute 函数的签名,将其转换为 fun compute-<hashcode>(s: Any?),其中 <hashcode> 是一个哈希码,用于确保函数名称的唯一性。这意味着编译器将 UserId<String> 类型的参数转换为 Any? 类型的参数。

名称修饰(Mangling)

由于内联类被编译为其基础类型,这可能导致各种晦涩的错误,例如意外的平台签名冲突

@JvmInline
value class UInt(val x: Int)

// 在jvm上会表示为`public final void compute(int x)`
fun compute(x: Int) { }

// 在jvm上也会表示为`public final void compute(int x)`
fun compute(x: UInt) { }

为了避免这些问题,使用内联类的函数会通过添加的哈希码来进行名称修饰。因此,fun compute(x: UInt) 将被表示为 public final void compute-<hashcode>(int x)

从Java代码调用

接受内联类参数的函数时,可以手动禁用名称修饰。为此,需要在函数声明之前添加@JvmName注解

@JvmInline
value class UInt(val x: Int)

fun compute(x: Int) { }

@JvmName("computeUInt")
fun compute(x: UInt) { }

内联类VS类行别名

两者似乎都引入了一个新的类型,并且在运行时都会表示为其基础类型

然而,关键的区别在于,类型别名可以与其基础类型(以及具有相同基础类型的其他类型别名)进行赋值兼容,而内联类则不行。

换句话说,内联类引入了一个真正的新类型,而类型别名只是为现有类型引入了一个替代名称(别名)

typealias NameTypeAlias = String

@JvmInline
value class NameInlineClass(val s: String)

fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}

fun main() {
    val nameAlias: NameTypeAlias = ""
    val nameInlineClass: NameInlineClass = NameInlineClass("")
    val string: String = ""

    acceptString(nameAlias) // 传递别名而不是底层类型
    acceptString(nameInlineClass) // 报错 -- 参数类型不匹配

    // And vice versa:
    acceptNameTypeAlias(string) // 传递基础类型而不是别名
    acceptNameInlineClass(string) // 报错 -- 参数类型不匹配
}

内联类和委托

委托后边讲解

使用内联类的内联值进行委托实现是允许的,可以通过接口实现

interface MyInterface {
    fun bar()
    fun foo() = "foo"
}

@JvmInline
value class MyInterfaceWrapper(val myInterface: MyInterface) : MyInterface by myInterface

fun main() {
    val my = MyInterfaceWrapper(object : MyInterface {
        override fun bar() {
            // body
        }
    })
    println(my.foo()) // prints "foo"
}

MyInterfaceWrapper 类型实例可以使用委托的 MyInterface 实现的方法,包括默认实现的 foo() 方法。在这种方式下,通过委托实现可以将内联类的功能扩展到具体的接口实现上文章来源地址https://www.toymoban.com/news/detail-770182.html

到了这里,关于23:kotlin类和对象 -- 内联值类(Inline value classes)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】初阶 --- 内联函数(inline)

    🥰 用C语言先来实现普通的Add函数看一下 👇 转到反汇编来看一下: 可以看到,编译器为了实现一个简单的相加函数,Add函数体内需要执行的汇编指令要很多,而且为了调用函数还要执行指令跳转 (并且要在栈区上为函数开辟栈帧空间) ,如果 Add函数被重复大量地使用,则会

    2024年02月14日
    浏览(26)
  • 【C++】内联函数----inline函数的详细使用教程

    🌹作者:云小逸 📝个人主页:云小逸的主页 📝Github:云小逸的Github 🤟motto:要敢于一个人默默的面对自己, 强大自己才是核心 。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。==希望春天

    2024年02月04日
    浏览(40)
  • inline内联函数为什么不能是虚函数?

    1. inline内联函数为什么不能是虚函数? 虚函数可以是内联函数 ,内联是可以修饰虚函数的, 但是当虚函数表现多态性的时候不能内联 。 理由如下:内联是在发生在编译期间,编译器会自主选择内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此

    2024年02月21日
    浏览(42)
  • 【跟小嘉学 Rust 编程】二十四、内联汇编(inline assembly)

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月10日
    浏览(38)
  • 【C++】类和对象 - 封装 - 属性和行为,访问权限,class 和 struct区别,成员属性私有化

    No. Contents 1 【C++】基础知识 - HelloWorld,注释,变量,常量,,标识符 2 【C++】数据类型 - 整型,sizeof,实型,字符型,转义字符,字符串类型,布尔类型,数据的输入 3 【C++】运算符 - 算术运算符,赋值运算符,比较运算符,逻辑运算符 4 【C++】程序流程结构 - 循序结

    2024年02月07日
    浏览(34)
  • [C++] C++入门第二篇 -- 引用& -- 内联函数inline -- auto+for

      目录 1、引用 -- 1.1 引用的概念 1.2 引用特性 1.3 常引用 -- 权限问题 1.4 引用的使用场景 1.4.1 做参数 1.4.2 做返回值 注意 1.5 传值、传引用的效率比较 1.6 引用和指针的区别 2、内联函数 2.1 概念 转存失败重新上传取消​编辑转存失败重新上传取消​编辑2.2 特性 3、auto 3.1 auto简

    2024年02月15日
    浏览(39)
  • 【C++初阶】三、类和对象(面向过程、class类、类的访问限定符和封装、类的实例化、类对象模型、this指针)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】二、入门知识讲解 (引用、内联函数、auto、基于范围的for循环、指针空值

    2024年02月04日
    浏览(31)
  • 《重构》:Extract Class and Inline Class

    i fork a rep, this the link GitHub - TIMPICKLE/refator-code: 重构 - 改善既有代码的设计 all right, lets see the genel description. 提取类 对立: 内联类 目的:将大类分成小类 场景:维护一个大量函数和数据的类 内联类 对立: 提炼类 目的:减少不必要的类 场景: 如果一个类不再承担足够的责任

    2024年02月11日
    浏览(35)
  • Kotlin withContext详解与suspend和inline

    在这段代码中, inline  用于修饰  test10  函数,这意味着编译器会将函数体内的代码直接复制到调用  test10  函数的地方,而不会创建一个函数调用的堆栈。这样的优化可以减少函数调用的时间开销,以提高程序的性能。 inline  通常用于当函数作为参数传递时

    2024年01月17日
    浏览(32)
  • Kotlin 内联函数

    在JVM中每次函数调用,都会进行 操作栈 操作(栈帧),会增加内存使用和开销。 另外传入的 lambda 函数 参数,也会 内存分配(创建类和对象)。 inline 使用 内联 (inline) 可以避免上面的开销,通过把 函数的代码 直接插入 调用处, 而不是 调用函数 和 创建 lambda 函数类和

    2024年01月19日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包