Kotlin 宣布一个重磅特性

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Kotlin 1.5 宣布了一个重磅特性 value class,这是一个非常实用的特性,提高代码的可读性同时,还可以提高性能,因为编译器会对它进行更深层次的优化,减少对象的创建。

image.png


hi 大家好,我是 DHL。公众号:ByteCode ,专注分享最新技术原创文章,涉及 Kotlin、Jetpack、算法动画、数据结构 、系统源码、 LeetCode / 剑指 Offer / 多线程 / 国内外大厂算法题 等等。


Kotlin 1.5  宣布了一个重磅特性 value class,这是一个非常实用的特性,提高代码的可读性同时,还可以提高性能,因为编译器会对它进行更深层次的优化,减少对象的创建。


随着 Kotlin 不断的完善,出现了一系列的特性 inner classdata classsealed classsealed interfaceinline classvalue class 等等,之前写过几篇文章专门分析 sealed classsealed interface,可以点击下方链接前去查看。



而今天这篇文章主要分析 inline classvalue class


通过这篇文章你将学习到以下内容:


  • 什么是内联函数?
  • 什么情况下使用内联函数?
  • 什么是 inline class
  • 什么是 value class
  • value class 不能被继承
  • value class 可以实现接口
  • 当传递的对象为空时,会失去内联效果
  • value class 禁止使用 === 可以使用 ==
  • inline classvalue class 有什么区别?
  • Java 如何调用接受内联类的函数?


内联函数



在 Kotlin 中通过 inline-functions (内联函数) 实现函数内联,内联的作用:提升运行效率,调用被 inline 修饰符的函数,会把方法体内的代码放到调用的地方,其主要目的提高性能,减少对象的创建。内联函数代码如下所示。


fun testInline() {
    printByteCode()
}
inline fun printByteCode() {
    println("printByteCode")
}
// 编译之后
public static final void testInline() {
  String var1 = "printByteCode";
  System.out.println(var1);
}


inline 修饰的函数适用于以下情况


  • inline 修饰符适用于把函数作为另一个函数的参数,例如高阶函数 filtermapjoinToString 或者一些独立的函数 repeat
  • inline 操作符适合和 reified 操作符结合在一起使用
  • 如果函数体很短,使用 inline 操作符提高效率


如果使用 inline 修饰符标记普通函数,Android Studio 会给一个大大大的警告,如下图所示,这是为了防止 inline 操作符滥用而带来的性能损失。


image.png


但是如果函数体很短,想通过 inline 操作符提高效率,又想消除掉警告,可以前往查看 为数不多的人知道的 Kotlin 技巧及解析(三) 文章中的 「Kotlin 注解在项目中的使用」。


代码示例仓库地址:https://github.com/hi-dhl/KtKit


内联类



内联类是一个被忽略,非常有用的特性。有时必要的业务逻辑,需要将基本数据类型、String 等等参数封装在一个 Model 中,然后在 Model 中封装一些方法,对这个参数做检查、验证等等操作。


参数被封装之后,需要创建包装对象,对象的创建在堆中进行分配,数据量很大的情况,对性能的损耗也非常大,例如:内存的占用,运行时的效率,频繁创建对象,导致 GC 回收大量对象带来的卡顿问题等等。


基本数据类型、String 等等运行时 JVM 会对它进行优化,但是如果将这些参数封装在一个类中,包装类不会做任何处理,依然会在堆中创建对象。


所以为了减少性能的损耗,避免对象的创建,因此 Kotlin 推出了一个内联类 inline-classes。内联类只能在构造函数中传入一个参数,参数需要用 val 声明,编译之后,会替换为传进去的值,代码如下所示。


inline class User(val name: String)
fun testInline() {
    println(User("DHL"))
}
// 编译之后
public static final void testInline() {
  System.out.println("DHL");
}


正如你所见,编译后的 Java 代码并没有创建额外的对象,在 Kotlin 中创建的对象被替换为传进去的值。


Java 如何调用接受内联类的函数


在 Kotlin 中声明一个函数,并且将 inline class 作为参数传递,编译成 Java 代码之后,函数名称会被打乱,代码如下所示。


inline class User(val name: String) 
fun login(user: User) {
}
// 编译后的代码
public static final void login_FY_U7ig/* $FF was: login-FY_U7ig*/(@NotNull String user) {
}


编译后的函数名称会被打乱,例如 login-FY_U7ig。 但是这样存在一个问题, Java 无法调用接受内联类的函数,代码如下所示。


inline class User(val name: String) 
fun login(user: User) {
}
fun login(passwd: String) {
}
// Kotlin 编译正常
login(User("DHL"))
login("ByteCode")
// Java 编译失败
MainKt.login("DHL");


在 Kotlin 中调用,可以正常编译,因为内联的原因,导致 Java 调用就会失败,如下图所示。


image.png


为了能够在 Java 中正常调用,因此添加了注解 @JvmName 更改函数名称,来解决这个问题,代码如下所示。


inline class User(val name: String) 
@JvmName("loginWithName")
fun login(user: User) {
}
fun login(passwd: String) {
}
// Kotlin 编译正常
login(User("DHL"))
login("ByteCode")
// Java 编译正常
MainKt.loginWithName("DHL");


所以无论是 Inline classes 还是 Value classes 如果没有添加 @JvmName 注解,都会存在这个问题。将生成的函数名称打乱,是为了防止方法重载冲突,或者 Java 意外调用。


Inline classes 是在 Kotlin 1.3 引入的 ,在 Kotlin 1.5 时进入了稳定版本,废弃了 inline 修饰符,引入了 Value classes


什么是 Value classes



Inline classesValue classes 的子集, Value classesInline classes 会得到更多优化,现阶段 Value classes  和 Inline classes 一样,只能在构造函数中传入一个参数,参数需要用 val 声明,将来可以在构造函数中添加多个参数,但是每个参数都需要用  val 声明,官方说明如下图所示。


image.png


将来如果支持添加多个参数,那么它的使用范围会越来越广的。


升级到 Kotlin 1.5 之后,Inline classes 将被弃用,如下图所示,编译器将会给出警告。


image.png


根据提示目前唯一需要改变的是语法 inline 替换为 value, 然后在添加 @JvmInline 注解即可。


@JvmInline
value class User(val name: String)


编译后的效果和 Inline classes 是一样的,因此后面的案例将会使用 Value classes


Value classes 不能被继承


因为 value class 编译后将会添加 fianl 修饰符,因此不能被继承,同样也不能继承其他的类,如下图所示。


image.png


image.png


Value classes 可以实现接口


虽然 value class 不能继承其他的类,但是可以实现接口,代码如下所示。


interface LoginInterface
@JvmInline
value class User(val name: String) : LoginInterface
fun testInline() {
    println(User("DHL"))
}
// 编译后的代码
public static final void testInline() {
  User var0 = User.box-impl(User.constructor-impl("DHL"));
  System.out.println(var0);
}
public static final User box_impl/* $FF was: box-impl*/(String v) {
  return new User(v);
}
复制代码


正如代码所示,当 value class 实现接口时,失去了内联效果,依然会在堆中创建 User 对象,除了实现接口的时候,没有内联效果,当对象为空的时候,也会失去了内联效果。


当传递的对象为空的时候,会失去内联效果


当构造函数的参数为基本数据类型,且传递的参数 value class 的对象为空时,将失去内联效果,代码如下所示。


@JvmInline
value class User(val age: Int)
fun login(user: User?): Int = user?.age ?: 0
fun testInline() {
    println(login(User(10)))
}
// 编译后的代码
public static final int login_js0Jwf8/* $FF was: login-js0Jwf8*/(@Nullable User user) {
  return user != null ? user.unbox-impl() : 0;
}
public static final void testInline() {
  int var0 = login-js0Jwf8(User.box-impl(10));
  System.out.println(var0);
}
public static final User box_impl/* $FF was: box-impl*/(int v) {
  return new User(v);
}


正如你所见,依然会在堆中创建 User 对象,失去了内联效果,这是因为 Int 不能直接转换为 null


当构造函数的参数为 String,且传递的参数 value class 的对象为空时,内联效果不会受到影响,代码如下所示。


@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 代码并没有创建对象,传递给方法 login 的参数 User 被替换为传进去的值 String


Value class 禁止使用 === 可以使用 ==


Kotlin 中的操作符 ===== 的区别,以及它们分别在什么场景下使用,更多信息可以前往查看 揭秘 Kotlin 中的 == 和 ===,这里简单介绍一下。


Kotlin 提供了两种方式用于对象的比较。


  • 比较对象的结构是否相等( == 或者 equals
    Kotlin 中的操作符 == 等价于 equals 用于比较对象的结构是否相等, 很多情况下使用的是 ==,因为对于浮点类型 Float 和 Double,其实现方法 equals 不遵循 IEEE 754 浮点运算标准。
  • 比较对象的引用是否相等 ( === )
    Kotlin 中的操作符 === 用于比较对象的引用是否指向同一个对象,运行时如果是基本数据类型 === 等价于 ==


其中 value class 比较特殊,禁止使用操作符 ===,如下图所示,编译器将会给出警告。


image.png


因为操作符 === 用于比较对象的引用是否相等,因为内联的原因,最终会替换为传进去的值。


但是操作符 == 是不受影响的,操作符 == 用于比较对象的结构是否相等,代码如下所示。


fun testInline() {
    val u1 = User("DHL")
    val u2 = User("DHL")
    println(u1 == u2)
}
// 编译后的代码
public static final void testInline() {
  String u1 = "DHL";
  String u2 = "DHL";
  boolean var2 = User.equals-impl0(u1, u2);
  System.out.println(var2);
}



如果有帮助 点个赞 就是对我最大的鼓励


代码不止,文章不停


欢迎关注公众号:ByteCode,持续分享最新的技术


最后推荐长期更新和维护的项目:


  • 个人博客,将所有文章进行分类,欢迎前去查看 hi-dhl.com
  • KtKit 小巧而实用,用 Kotlin 语言编写的工具库,欢迎前去查看 KtKit
  • 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看 AndroidX-Jetpack-Practice
  • LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析


image.png



近期必读热门文章




目录
相关文章
|
3月前
|
编译器 Android开发 开发者
Android经典实战之Kotlin 2.0 迁移指南:全方位优化与新特性解析
本文首发于公众号“AntDream”。Kotlin 2.0 已经到来,带来了 K2 编译器、多平台项目支持、智能转换等重大改进。本文提供全面迁移指南,涵盖编译器升级、多平台配置、Jetpack Compose 整合、性能优化等多个方面,帮助开发者顺利过渡到 Kotlin 2.0,开启高效开发新时代。
161 0
|
6月前
|
安全 Java 编译器
Kotlin 1.6 正式发布,带来哪些新特性?
Kotlin 1.6 正式发布,带来哪些新特性?
50 0
|
IDE Java 开发工具
Kotlin 1.8.0 现已发布,有那些新特性?
Kotlin 1.8.0 现已发布,有那些新特性?
194 0
Kotlin 1.8.0 现已发布,有那些新特性?
|
网络协议 开发工具 Android开发
基于 Kotlin 特性开发的有限状态机
基于 Kotlin 特性开发的有限状态机
320 0
基于 Kotlin 特性开发的有限状态机
|
IDE Java 编译器
Kotlin 1.5 新特性:密封接口比密封类强在哪?
Kotlin 1.5 推出了密封接口(Sealed Interface),这与密封类(Sealed Class)有什么区别呢?
377 0
Kotlin 1.5 新特性:密封接口比密封类强在哪?
|
Cloud Native 架构师 安全
Kotlin 异步框架 Ktor 2.0 发布,提供新的插件特性
Ktor 是一个用于创建异步客户端和服务器应用程序的Kotlin框架。经过 1 年多的开发,2.0版本于近日发布,在带来新特性的同时,也带来了破坏性的变化。
442 0
Kotlin 异步框架 Ktor 2.0 发布,提供新的插件特性
|
数据安全/隐私保护 Kotlin
基于 Kotlin 特性实现的验证框架
基于 Kotlin 特性实现的验证框架
166 0
|
JavaScript 前端开发 Java
Kotlin 1.2 新特性
在Kotlin 1.1中,团队正式发布了JavaScript目标,允许开发者将Kotlin代码编译为JS并在浏览器中运行。在Kotlin 1.2中,团队增加了在JVM和JavaScript之间重用代码的可能性。
7566 0
|
Kotlin
Kotlin参数特性(具名、变长、默认)
一、这个问题其实很简单,就用以下几个例子讲解以下 package net.println.kotlin.chapters /** * @author:wangdong * @description:参数的讲解 ...
851 0
|
Kotlin
Kotlin新特性:区间
一、概念 一个数学上的概念、表示范围 ClosedRange 的子类,IntRange最常用 二、基本的写法 package net.
858 0