首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Kotlin内联类-它们如何工作以及何时应该使用它们

Kotlin引入了1.3版本的内联类作为实验性功能。您应该知道它们的实现在将来的版本中仍然可以更改,但现在已经是了解它们的好时机。内联类添加了一个简单的工具,我们可以使用它来包装其他类型,而无需通过额外的堆分配添加运行时开销。在本文中,我们希望了解Kotlin中的内联类如何工作以及何时使用它们是有意义的。

在项目中启用内联类

要在项目中启用内联类,只需使用Kotlin版本> 1.3,即将关键字添加到语言中。由于内联类仍处于试验阶段,因此当您使用它们时,IDE将显示相关警告。可以使用此处所述的显式编译器标志禁用警告。

第一眼内联课程

内联类非常简单。要使类内联,只需将关键字添加到您的类:

内联类有一些或多或少明显的限制:需要在主构造函数中精确指定一个属性,如图所示。您不能在一个类中包装多个值。它也被禁止在内联类中使用块,并且您不能拥有带有支持字段的属性。但是,内联类可以具有简单的可计算属性,我们将在本文后面看到。

在运行时,只要有可能,就会使用内联类的包装类型而不使用其包装器。这类似于Java的盒装类型,或者,只要编译器可以这样做,它就会表示为相应的基本类型。这正是Kotlin内联类的最大卖点:当你内联一个类时,除非绝对必要,否则类本身不会用在字节代码中。内联类大大减少了运行时的空间开销。

运行时表示

在运行时,内联类可以表示为包装类型和基础类型。如前一段所述,编译器更喜欢使用类的底层(包装)类型来尽可能地优化代码。这是类似之间拳击和。但是,在某些情况下,编译器需要使用包装器本身,因此它将在编译期间生成:

此代码段显示了表示为Java代码的简化字节代码,以显示类的外观。除了一些明显的东西,比如字段和它的getter,构造函数是私有的,而且新的对象将被创建,通过它们实际上不使用包装器类型但只返回传入的底层类型。最后,你可以看到和功能,如你所料,用于拳击目的。现在让我们看看在我们的代码中使用类时如何使用这个内联类包装器。

使用内联类

在此片段中,正在创建a并将其传递给打印其包装值的函数。相应的字节代码,再次作为Java代码,如下所示:

在已编译的代码中,不会创建任何实例。虽然使用了静态,但它只返回一个然后传递给函数的函数,该函数也不知道我们在源代码中最初具有的类的类型。请注意,接受内联类参数的函数名称使用字节代码中生成的哈希代码进行扩展。这样,它们可以与接受基础类型作为参数的重载函数保持区别:

要使两个方法在JVM字节代码中可用并避免签名冲突,编译器会将第一个方法重命名为类似的方法。请注意,上面的示例确实显示了“”而不是“”,因为Java不允许方法名称包含破折号,这也是接受内联类的方法无法从Java调用的原因。

拳击内联课程

我们看到早些时候和功能的内嵌类创建的,所以我们在需要它们?Kotlin文档引用了一条经验法则:

只要将内联类用作另一种类型,它们就会被加框。

例如,当您将内联类用作泛型类型或可空类型时,就会发生拳击:

在这段代码中,我们修改了函数以获取可为空的函数,并在参数不为null时打印基础类型。

在字节代码中,现在不再直接接受基础类型。它必须与包装类型一起使用。在打印其内容时,会调用它。在调用者站点上,我们可以看到用于创建盒装实例的。

很明显,我们希望尽可能避免拳击。请记住,内联类和原始类型的特定用法通常依赖于此技术,可能需要重新考虑。

用例内联类

我们看到内联类具有巨大的优势:在最好的情况下,它们大大减少了运行时开销,因为避免了额外的堆分配。但是我们什么时候想要使用包装类型呢?

使用内联类更好地打字

想象一下API中的身份验证方法,如下所示:

在一个美好的世界里,每个人都会用一个用户名和密码来称呼它。但是,某些用户以不同方式调用此方法并不是牵强附会:

由于这两个参数都是类型,你可能会搞砸他们的订单,当然,随着参数数量的增加,这种情况更有可能发生。围绕这些类型的包装可以帮助您降低风险,因此内联类是一个很棒的工具:

参数列表变得不那么混乱了,在调用者站点上,编译器不允许不匹配。前面描述的可能是使用内联类的最常见方案。它们为您提供简单的类型安全包装器,而不会引入额外的堆分配。对于这些情况,应尽可能优先使用内联类。然而,内联类可以更智能,下一个用例演示。

处理状态没有额外的空间

让我们考虑一个方法,它接受一个数字字符串并将其解析为一段时间,同时调整其比例:

代码非常简单,并且工作得很好,但要求可能是您需要以某种方式跟踪用于解析数字的原始字符串。要解决这个问题,您可能会创建一个包装器类型,或者可能使用现有的类从该函数返回一对值。这些方法虽然明显分配了额外的空间,但应该避免,特别是在某种情况下,这些方法是有效的。内联类可以帮助您。我们已经注意到内联类不能有多个带有支持字段的属性。但是,它们可以以属性和函数的形式具有简单的计算成员。我们可以为包装原始的用例创建一个内联类并提供一种方法或财产,按需要解析我们的价值。对于用户来说,这看起来像是两种类型的普通数据包装器,而在最好的情况下它不会增加任何运行时开销:

如您所见,该方法返回我们的类的实例,该实例提供两个属性(基础类型)和(计算的解析数)。这是一个有趣的用例,值得再次在字节代码级别上观察:

更多字节码

生成的包装类几乎看起来像前面显示的类。然而,一个重要的区别是函数,它代表了我们的可计算属性。如您所见,该函数实现为一个静态函数,它接受一个字符串并返回一个。那么如何在调用者代码中使用它?

正如所料,我们的包装类型没有任何参考。它只返回没有引入任何新类型。在中,我们看到静态用于将给定的解析成a。再次,没有使用。

缩小扩展功能的范围

扩展函数的一个常见问题是,如果在常规类型上定义,它们可能会污染您的命名空间。例如,您可能希望拥有一个将JSON字符串转换为相应类型的扩展函数:

要将给定字符串转换为某个数据持有者,您可以执行以下操作:

但是,扩展函数可用于表示其他数据的字符串,尽管它可能没有多大意义:

此代码将失败,因为它不包含有效的JSON数据。我们可以做些什么来使上面显示的扩展仅适用于某些字符串?是的,内联类可以帮助:

使用内联类缩小扩展范围

当我们为包含JSON数据的字符串引入包装器并相应地使用接收器更改扩展时,上述问题已得到解决。扩展名不再出现在任何任意内容上,只会出现在我们有意识地包含在内的那些内容中。

无符号类型

当查看使用1.3版添加到语言的无符号整数类型时,内联类的另一个很好的用例变得明显,这也是一个实验性功能:

如您所见,该类被定义为包装普通有符号整数的无符号类。您可以在相应的KEEP中了解有关此功能的更多信息。

结论

内联类是一个很好的工具,可用于减少包装类型的堆分配,并帮助我们解决各种问题。但是,请注意某些方案(如使用内联类作为可空类型)需要装箱。由于内联类仍然是实验性的,因此必须要求您的代码在将来的版本中因其行为的变化而中断。请记住这一点。不过,我认为立即开始使用它们是有效的。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20190721A0KRZO00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券