官方文档:https://kotlinlang.org/docs/inline-classes.html
注:
inline class
关键字已经被废弃,取而代之的是value class
。现在使用内联类需要定义类为value class
,并使用@JvmInline注解进行标注。
一、使用场景
有时候,根据业务需求,我们需要一些包装类。但是,包装类在运行时会造成一些不可避免的额外开支,比如堆上分配的额外空间。尤其是对于基本类型的包装类——因为基本类型在运行时会有很多其他优化,而包装类型没有。于是,内联类便应运而生了。
内联类在编码时作为一个其他类型的包装类使用,而在运行时会被拆开作为其内部值类型使用。
例如,当我们设计了一个动画:
class Animation(duration: Int) {
// ...
}
duration
参数可能会让人迷惑:它的单位是什么?秒或者毫秒?(虽然注释可以解决一切问题,但它不在讨论范围之内)这个时候,就可以用到内联类。
我们可以创建一系列的内联类,来表示不同的时间单位:
@JvmInline
value class Millis(val value: Int)
@JvmInline
value class Second(val value: Int)
// ...
假使 duration
参数单位是毫秒,那么将其类型修改为 Millis
类型即可:
class Animation(duration: Millis) {
// ...
}
这样,当创建 Animation
对象的时候,就需要强制传入一个 Millis
类型的对象;如果传入的是一个 Second
类型的对象,编译器就会报错。
二、内联类允许的成员
内联类允许函数、init
块、以及没有 backing field 的属性。
@JvmInline
value class Name(val s: String) {
init {
require(s.length > 0) { }
}
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // method `greet` is called as a static method
println(name.length) // property getter is called as a static method
}
三、内联类和普通包装类的区别
对于原生类型来说,在运行时会进行大量的优化,而包装类不会进行处理。
而内联类在运行时,会自动使用内联类型而不是包装类型进行处理。
例如以下例子中:
// 毫秒
inline class Millisecond(val value: Long)
private fun doSomething(millisecond: Millisecond) {
Log.i(TAG, "doSomething: $millisecond")
}
通过 IDE 的 Kotlin 字节码反编译功能,可以看到,生成的 Kotlin 字节码已经没有 Millisecond
类型了,而是直接使用的 Long
类型:
而普通的包装类则不会进行如此优化:
class LongWrapper(val millis: Long)
private fun doSomething2(millisecond: LongWrapper) {
Log.i(TAG, "doSomething: $millisecond")
}
四、其他
1. 继承
内联类只允许继承接口,而不允许继承类,也不允许被其他类继承。
2. 与 typealias 的相比
在读取值的时候,value class
和 typealias
起到了类似的作用;但是,当进行赋值的时候,情况就变得不一样了。
假设我们现在使用一个类型 Name
,表示一个字符串值。同时,有两个函数 setString
和 setName
:
fun setName(name: Name) {}
fun setString(string: String) {}
在使用 typealias
的情况下,不管是 setString
还是 setName
,均可以传入 Name
或是 String
类型的参数:
typealias Name = String
fun main() {
val name: Name = "Bob"
setName("Bob") // √
setString(name) // √
}
但是,如果使用的是内联类,则二者均是不被允许的:
@JvmInline
value class Name(value: String)
fun main() {
val name: Name = "Bob"
setName("Bob") // ×
setString(name) // ×
}
五、总结
- 内联类相当于一个包装类,但是在编译时会自动进行拆包使用内部数据类型。
- 在实际使用中,和普通包装类相同。
- 内联类最大的优点在于对于基本类型的包装不会消耗额外的性能。