Kotlin 1.5 来了,Inline classes 了解一下?| 开发者说·DTalk
The following article is from AndroidPub Author fundroid
本文原作者: fundroid,原文发布于: AndroidPub
https://mp.weixin.qq.com/s/WIFTh3KjU6MUoF3tbUW8Ww
Kotlin 1.5 中的 Inline classes
我在 CSDN 写的第一篇技术文章就是关于 Kotlin 1.4 新特性的介绍,如今 CSDN 上都写了 200 多篇原创了,终于迎来了 Kotlin 1.5。
如果您正在使用 Android Studio 4.2.0、IntelliJ IDEA 2020.3 或更高的版本,近期就会收到 Kotlin 1.5 的 Plugin 推送了。作为一个大版本,1.5 带来了不少新特性,其中最主要的要数 inline class 了。
早在 Kotlin 1.3 就已经有了 inline class 的 alpha 版本。到 1.4.30 进入 beta,如今在 1.5.0 中终于迎来了 Stable 版本。早期的实验版本的 inline 关键字在 1.5 中被废弃,转而变为 value 关键字。
//before 1.5
inline class Password(private val s: String)
//after 1.5 (For JVM backends)
@JvmInline
value class Password(private val s: String)
inline class 主要用途就是更好地 "包装" value
// For JVM backends
@JvmInline
value class Password(private val s: String)
val securePassword = Password("Don't try this in production")
如何安装 Kotlin 1.5
1. 首先更新 IDE 的 Kotlin Plugin,如果没收到推送,可以手动方式升级:
Tools > Kotlin > Configure Kotlin Plugin Updates
2. 配置 languageVersion & apiVersion
compileKotlin {
kotlinOptions {
languageVersion = "1.5"
apiVersion = "1.5"
}
}
经 inline 处理后代码
fun check(password: Password) {
//...
}
fun main() {
val securePassword = Password("Don't try this in production")
check(securePassword)
}
public static final void check_XYhEtbk/* $FF was: check-XYhEtbk*/(@NotNull String password) {
Intrinsics.checkNotNullParameter(password, "password");
}
public static final void main() {
String securePassword = Password.constructor-impl("Don't try this in production");
check-XYhEtbk(securePassword);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
securePassword 的类型由 Password 替换为 String check 方法改名为 check_XYhEtbk,签名类型也有 Password 替换 String
名字被混淆处理 (check_XYhEtbk) 主要有两个目的:
防止重载函数的参数经过 inline 后出现相同签名的情况 防止从 Java 侧调用到参数经过 inline 后的方法
Inline class 的成员
@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() // `greet()`作为static方法被调用
println(name.length) // property getter 也是一个static方法
}
Inline class 的继承
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()) // prettyPrint()也是一个 static方法调用
}
自动拆装箱
@JvmInline
value class WrappedInt(val value: Int)
fun take(w: WrappedInt?) {
if (w != null) println(w.value)
}
fun main() {
take(WrappedInt(5))
}
public static final void take_G1XIRLQ(@Nullable WrappedInt w) {
if (Intrinsics.areEqual(w, (Object)null) ^ true) {
int var1 = w.unbox_impl();
System.out.println(var1);
}
}
public static final void main() {
take_G1XIRLQ(WrappedInt.box_impl(WrappedInt.constructor_impl(5)));
}
genericFunc(color) // boxed
val list = listOf(color) // boxed
val first = list.first() // unboxed back to primitive
关于自动拆装箱在开发中无需太在意,只要知道有这个特性存在即可。
对比其他类型
与 type aliases 的区别 ?
inline class 本身是实际存在的 Class 只是在字节码中被消除了并被替换为被包装类型;
type aliases 仅仅是个别名,它的类型就是被代理类的类型。
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) // OK: NameTypeAlias等同String,可以传递
acceptString(nameInlineClass) // Not OK: NameInlineClass 与 String是两个类,不能等同
// 反之亦然:
acceptNameTypeAlias(string) // OK: 传入String也是可以的
acceptNameInlineClass(string) // Not OK: String不等同于NameInlineClass
}
与 data class 的区别 ?
inline class 只能有一个成员属性,其主要目的是通过一个额外类型的包装让代码更易用; data clas 可以有多个成员属性,其主要目的是更高效地处理一组相关数据的集合。
使用场景
场景 1: 提高可读性
fun auth(userName: String, password: String) { println("authenticating $userName.") }
auth("12345", "user1") //Error
@JvmInline value class Password(val value: String)
@JvmInline value class UserName(val value: String)
fun auth(userName: UserName, password: Password) { println("authenticating $userName.")}
fun main() {
auth(UserName("user1"), Password("12345"))
//does not compile due to type mismatch
auth(Password("12345"), UserName("user1"))
}
场景 2: 类型安全 (缩小扩展函数作用域)
inline fun <reified T> String.asJson() = jacksonObjectMapper().readValue<T>(this)
val jsonString = """{ "x":200, "y":300 }"""
val data: JsonData = jsonString.asJson()
"whatever".asJson<JsonData> //will fail
@JvmInline value class JsonString(val value: String)
inline fun <reified T> JsonString.asJson() = jacksonObjectMapper().readValue<T>(this.value)
场景 3: 携带额外信息
/**
* parses string number into BigDecimal with a scale of 2
*/
fun parseNumber(number: String): BigDecimal {
return number.toBigDecimal().setScale(2, RoundingMode.HALF_UP)
}
fun main() {
println(parseNumber("100.12212"))
}
@JvmInine value class ParsableNumber(val original: String) {
val parsed: BigDecimal
get() = original.toBigDecimal().setScale(2, RoundingMode.HALF_UP)
}
fun getParsableNumber(number: String): ParsableNumber {
return ParsableNumber(number)
}
fun main() {
val parsableNumber = getParsableNumber("100.12212")
println(parsableNumber.parsed)
println(parsableNumber.original)
}
ParsableNumber 的包装类型是 String,同时通过 parsed 携带了解析后的值。如前文提到的那样,字节码中,parsed getter 会以 static 方法的形式存在,因此虽然携带了更多信息,但实际上并不存在这样一个包装类实例:
@NotNull
public static final String getParsableNumber(@NotNull String number) {
Intrinsics.checkParameterIsNotNull(number, "number");
return ParsableNumber.constructor_impl(number);
}
public static final void main() {
String parsableNumber = getParsableNumber("100.12212");
BigDecimal var1 = ParsableNumber.getParsed_impl(parsableNumber);
System.out.println(var1);
System.out.println(parsableNumber);
}
最后
长按右侧二维码
查看更多开发者精彩分享
"开发者说·DTalk" 面向