# kotlin value关键字以及inline class初探


### inline Modifier

#### 介绍

>  inline class 最早在 kotlin 1.2.30 中出现, 在 1.4.30 中到达 beta 版, 在 1.5 中可能变为 stable, 本文代码基于 Kotlin-1.5.0-M1.

可能很多 Javaer 并不会去在意 kotlin 那么多繁杂的关键字, 但我更喜欢追求高效率, 高易用性, 简洁舒适的代码, 好了, 我们来看看我们为什么要使用 inline class 呢?

我们知道, 在 JVM 中, 内存的储存是下面这样的:

![JVM 内存模型](https://cdn.jsdelivr.net/gh/zsqw123/cdn@master/picCDN/20210323232512.png)

当我们创建一个基本类型的局部变量的时候, `int / float / boolean `等类型将会被储存在 jvm 的内存栈中, 这些基础类型储存在栈上, 访问/创建以及储存他们的开销并不会很大.

而当我们实例化一个对象的时候, 该对象实例就会储存在 JVM 堆上, 而堆内存的使用代价很高(详见下方 benchmark 部分), 虽然每个对象可能给我们的感觉不算很大, 但是大量的对象堆积起来对性能的影响不容轻视, 我们看下面一个例子:

我们定义了一个方法, 它需要传入一个时间参数:

```kotlin
fun fk(time: Int) {}
```

这时候就出现了一个问题, 这个 `time` 究竟是小时? 分钟? 秒? 我们仅仅知道这是一个 `Int`, 为了让编译器可以强制指定, 我们可以使用强类型语言的优势, 将函数这样封装, 我们就只能传入 `Minute`, 因为它不支持传入其他类型

```kotlin
class Minute(val v: Int)
fun fk(time: Minute) {
    println(time.v)
}
```

但是我们会发现在大多数开源库的源码中都不会选择这样的做法, 因为这样假如我们时间的类型是 `Hour`, 我们还需要自己去 `Hour` 的类里面定义一个 `toMinute` 的方法, 但这样就会创建了一个我们并不会用到的对象 `Hour`. 因此大多数开发者往往会定义一个 TimeUnit 作为额外的参数传入, 这样的做法我们已经习惯了, 但是我们能否做出改变呢?

在 kotlin 这里, 答案是肯定的: **`Inline Classes`**

```kotlin
inline class Hour(private val v: Int) {
    fun toMinutes() = Minute(v * 60)
}
```

那么这个关键字做了什么呢? 我们可以打开 kotlin 的字节码并将其反编译为 java, 我们就能发现其内部操作的实质:

```java
int hour = Hour.constructor-impl(1);
int var1 = Hour.toMinutes-impl(hour).getV();
```

我们来对比一下不使用 `inline` 关键字的时候字节码, 就知道发生了什么事情:

```java
Hour hour = new Hour(1);
int var1 = hour.toMinutes().getV();
```

我们发现, 不使用 `inline` 时, 额外创建了一个 `Hour` 对象, 这也就是性能开销的原因了, 我们示例这个对象还比较简单, 试想如果在 Android 开发中, 你创建了一个 `View` 对象, 可想而知性能开销有多么恐怖, 当然, 在少量的情况下你不会感觉到性能区别有多大.

那么我们来解释一些东西, 我们首先实例化一个类:

```kotlin
val hour = Hour(1)
```

事实上, 在使用了 `inline` 的情况下, 就 JVM 而言, 其内部会变成类似于这样的代码:

```java
int hour = 1;
```

而我们上面实例中的 `fk` 函数:

```kotlin
fun fk(time: Minute) {}
```

也会变成类似于如下的代码

```java
void fk(int time){}
```

那么这时候就会出现一个问题, `Hour` 中的 `toMinute()` 方法怎么办?? 我们继续反编译其字节码, 我们会发现:

```java
@NotNull
public static final Minute toMinutes_impl/* $FF was: toMinutes-impl*/(int $this) {
   return new Minute($this * 60);
}
```

我们会发现! 它直接返回了一个 `Minute` 对象, 如果 `Minute` 我们也将其变成 `inline`, 那么字节码会变成下面这样:

```java
public static final int toMinutes_SCGyLbI/* $FF was: toMinutes-SCGyLbI*/(int $this) {
   return Minute.constructor-impl($this * 60);
}
```

当然, 这样的 `inline` 到最后一层使用的时候还是会变成一个对象的(并不一定), 但是我们确实做到了减少了一个对象的创建!

不过其实如果我们最后需要使用的是原始类型的话, 我们倒也可以自己定义一个函数实现这种转换:

```kotlin
val hour = Hour(1)
val second = hour.toMinutes().toSecond()

inline class Hour(val v: Int) {
    inline fun toMinutes() = Minute(v * 60) // 这里的 inline 加不加性能影响微乎其微
}

inline class Minute(val v: Int){
    inline fun toSecond() = v * 60 // 这里的 inline 加不加性能影响微乎其微
}
```

对于 `JVM`, 就类似于这样:

```kotlin
val hour = 1
val second = 1 * 60 * 60
```

事实上反编译字节码我们发现是这样的:

```java
int hour = Hour.constructor-impl(1);
int $this$iv = Minute.constructor-impl(hour * 60);
int second = $this$iv * 60;
```

通过字节码, 我们还可以了解到这个 `constructor-impl` 实际上就是内部参数本身:

```java
public static int constructor_impl/* $FF was: constructor-impl*/(int v) {
   return v;
}
```

因此我们发现, 我们在并没有创建任何对象的情况下调用了两个对象的方法, 这样好!

#### 限制

那么既然这么好, 那大家都用 `inline` 吧! 但是事实上, `inline` 有很多的限制

```kotlin
inline class Seconds()             		 	// nope - 需要接收并只能接收一个参数!
inline class Minutes(value: Int)  		 	// nope - 参数必须要同时声明为成员!
inline class Hours(var value: Int) 		 	// nope - 成员必须为只读的!
inline class Days(val value: Int)   		// yes!
inline class Months(private val count: Int) // yes! - 可以为任意属性名 也可为私有成员!
inline class Years private constructor(val value: Int) // nope - 必须为共有构造函数
```

我们发现, 主构造器只能接受一个基础值作为成员属性, 但是它的内部是`可以拥有成员属性`的, 只要它们仅基于构造器中那个基础值计算, 或者从可以静态解析的某个值或对象计算(单例，顶级对象，常量等)

```kotlin
object Conversions {
    const val MINUTES_PER_HOUR = 60    
}

inline class Hours(val value: Int) {
    val valueAsMinutes get() = value * Conversions.MINUTES_PER_HOUR
}
```

还有更多的限制: 不允许继承, 但可以实现接口, 必须声明为顶层函数, 嵌套/内部类无法内连, 不支持枚举内联类, 具体可以看文章末尾的参考链接

#### Type Alias 不香吗

Type aliases 提供了我们另一种访问一个类的方式, 就像下面这样, 我们可以给 `String`用其他的名字, 但事实上这样编译后还是原来的类型, 只是换了一种名字罢了, 我们传入不是这个名字的同一类型的照样可以实现访问

```kotlin
typealias Username = String

fun validate(name: Username) {
    if(name.length < 5) {
        println("Username $name is too short.")
    }
}
```

就像下面这样, 如果我们在`username`传入了一个`Password`, 编译依旧会照常通过, :

```kotlin
typealias Username = String
typealias Password = String

fun authenticate(user: Username, pass: Password) { /* ... */ }

fun main(args: Array<String>) {
    val username: Username = "user's pass"
    val password: Password = "user"
    authenticate(password, username)
}
```

但如果我们换成了 `inline`, 则会直接在我们写代码的时候爆红, 虽然他们本质上都会变成 `String`, 但是这样提高了我们的可扩展性以及安全性!

```kotlin
inline class Username(val value: String)
inline class Password(val value: String)

fun authenticate(user: Username, pass: Password) { /* ... */ }

fun main(args: Array<String>) {
    val username: Username = Username("joe.user")
    val password: Password = Password("super-secret")
    authenticate(password, username) // <--- Compiler error here! =)
}
```

### value Modifier

#### 介绍

这个关键词从 `kotlin 1.4.30` 引入, 我发现它是因为我偶然写代码的时候发现敲完 va 之后除了 `val var` 两个修饰符以外还有一个  `value` 修饰符! 于是赶紧去翻了官方文档看这玩意怎么用, 终于在 kotlin 1.4.30 的更新日志中发现了这个新关键字, 同时注意到官方说这个修饰符还处于 `beta` 阶段, 于是我下载了 `kotlin 1.5-M1` 去提前体验一些新特性

#### 用法

```kotlin
@JvmInline
value class Hour(private val v: Int) {
    fun toMinutes() = Minute(v * 60)
}
```

这个`@JvmInline`其实只是限定了 `value` 修饰符只能用在 `JVM`平台, 具体有什么功能呢?

其实只是多了一个可以在 inline class 中写 `init` 罢了,需要注意的是 `value` 修饰符注解的类即是一个增强版的 `inline`, 您无需再写 `inline` 修饰符:

```kotlin
@JvmInline
value class Hour(private val v: Int) {
    init {
        println(v)
    }
    fun toMinutes() = Minute(v * 60)
}
```

这里插一句, 既然这玩意在 `JVM` 平台, 那么它理应能与 `Java` 进行互操作, 事实上也是这样的! 我们只需要使用 `@JvmName` 注解即可, 这个注解的主要用途就是告诉编译器生成的 Java 类或者方法的名称, 当然这只是可以这么干, 我觉得新写的代码你还用 Java 去调用 kotlin 的代码, 属实不优雅, 纯 kotlin 才是正道hhhhh

### Benchmark

class 与 inline class 的性能相差了较为明显的数量级, 因为前者创建了6亿个对象, 而后者采用了内联的方式减少了很多对象的创建

| class                                                        | inline class                                                 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| ![](https://cdn.jsdelivr.net/gh/zsqw123/cdn@master/picCDN/20210324010139.png) | ![](https://cdn.jsdelivr.net/gh/zsqw123/cdn@master/picCDN/20210324010055.png) |

---

ref:

- [KEEP/inline-classes.md at master · Kotlin/KEEP (github.com)](https://github.com/Kotlin/KEEP/blob/master/proposals/inline-classes.md)
- 如果上面链接不可用: [cdn/value-classes.md at master · zsqw123/cdn (github.com)](https://github.com/zsqw123/cdn/blob/master/blogCDN/others/value-classes.md)
-  [An Introduction to Inline Classes in Kotlin - Dave Leeds on Kotlin (typealias.com)](https://typealias.com/guides/introduction-to-inline-classes/#why-inline-classes-can-perform-better)

code: [learn-kt/210323.kt at master · zsqw123/learn-kt (github.com)](https://github.com/zsqw123/learn-kt/blob/master/src/main/kotlin/210323.kt)
