本文将会为大家介绍 Kotlin 的 “reified” 关键字,在介绍 “reified” 之前,咱们得先提一下泛型 (Generics)。泛型在编程畛域中是一个很重要的概念,它提供了类型平安,并帮忙开发者在编程时不须要进行显示的类型转换。泛型对编程语言的类型零碎进行了扩大,从而容许一个类型或办法在保障编译时类型平安的前提下,还能够对不同类型的对象进行操作。然而应用泛型也会有一些限度,比方当您在泛型函数中想要获取泛型所示意类型的具体信息时,编译器就会报错,提醒说相干的信息不存在。而 “reified” 关键字,正是为了解决此类问题诞生的。
无奈获取泛型所示意的类型
这些类型信息失落是因为 JVM 实现泛型的形式所导致的 (提醒: 类型擦除,咱们会在之后探讨这个问题)。解决这一问题的一个办法,是将泛型理论代表的类型信息作为一个参数传递给函数。
fun <T> printType(classType: Class<T>) {print(classType::class.java)
}
这样的代码看起来也没有那么不可承受,然而在 Kotlin Vocabulary 系列的文章 中咱们就始终在强调,Kotlin 中尽量不要呈现样板代码,这样能够让代码放弃简洁。为了达到这一指标,Kotlin 提供了一个特地的关键字 reified,应用它就能够在泛型函数中获取所需的类型信息。只有您对泛型的实现形式有所理解,就可能会不禁惊呼: 这怎么可能!上面就来看看这是如何在 Kotlin 中实现的。
泛型
在 Java 5.0 版本之前并未反对泛型,那时 Java 中的 collection 是没有类型信息的。也就是说一个 ArrayList 并不会申明它外部所蕴含的数据类型到底是 String、Integer 还是别的类型。
List list = new ArrayList();
list.add("First String");
// 失常解决,没有谬误
list.add(6);
在没有泛型反对时,任何时候想拜访 collection 中的对象,都要做一次显式的类型转换。另外也没有相应的谬误保障机制来防止出现非法的类型转换。
String str = (String)list.get(1);
// 须要显示地进行转换和抛出异样
为了解决这个问题,Java 从 Java 5 开始反对泛型。有了这一个性反对,您能够将 collection 关联一个指定的类型,当您向 collection 中增加非指定类型的数据时,编译器就会收回正告。同时,您也不须要进行显式的类型转换了,这也会缩小运行时异样的状况产生。
List<String> list = new ArrayList<>();
list.add("First String");
// 编译谬误
list.add(6);
// 无需进行类型转换
String str = list.get(0);
泛型是通过一种叫 类型擦除 (type erasure) 的技巧实现的。因为 Java 5 之前没有关联类型信息,编译器会先将所有类型替换为根本的 Object 类型,而后再进行必要的类型转换。通过将类型信息提供给编译器,类型擦除能够做到既保证编译时类型平安,又能够通过放弃字节码同之前的 Java 版本雷同来实现向后兼容。然而,当在泛型函数中须要获取类型信息时,类型擦除的实现形式就显得力不从心了。
Reified
Reified 关键字必须联合内联函数一起应用,它能让本该在编译阶段就被擦除的类型信息,可能在运行时被获取到。如果您还不相熟内联函数,能够浏览《Kotlin Vocabulary | 内联函数的原理与利用》。
简略地解释一下内联函数,如果一个函数被标记为 inline,那么 Kotlin 编译器会在所有应用该函数的中央将函数调用替换为函数体。这样做的益处是,编译器能够随便地在调用处对函数体进行批改,因为批改的函数体是被复制的,所以批改后不会影响到其余调用同样函数的中央。若是要在参数中应用 reified,那首先须要将函数标记为 inline,而后在泛型参数之前增加 reified 关键字即可。
inline fun <reified T> printType() {print(T::class.java)
}
fun printStringType(){
// 用 String 类型调用被 reified 润饰的泛型函数
printType<String>()}
让咱们反编译一下 Java 代码来摸索其中的神秘。从反编译后的代码中能够发现,当调用 reified 润饰的内联函数时,编译器会复制该函数体,并将泛型类型替换为理论应用的类型。这样,您就能够不必将类传递给函数也可能获取到相应类型信息了。
// 从字节码转换为 Java 的内联函数
public static final void printType() {
int $i$f$printType = 0;
Intrinsics.reifiedOperationMarker(4, "T");
Class var1 = Object.class;
boolean var2 = false;
System.out.print(var1);
}
// 从字节码转换为 Java 代码的调用方
public static final void printStringType() {
int $i$f$printType = false;
Class var1 = String.class;
boolean var2 = false;
System.out.print(var1);
}
Reified 关键字只能同内联函数一起应用,因而内联函数应用的规定也同样实用于被 reified 润饰的函数。另外请牢记,Java 代码中不能拜访被 reified 润饰的函数 。Java 不反对内联,也就意味着在 Java 中的泛型参数不能逃脱被编译器擦除类型的命运。
Reified 同样还反对重载函数返回泛型类型,例如,以下函数能够返回 Int 或者 Float:
inline fun <reified T> calculate(value: Float): T {return when (T::class) {
Float::class -> value as T
Int::class -> value.toInt() as T
else -> throw IllegalStateException("Only works with Float and Int")
}
}
val intCall: Int = calculate(123643)
val floatCall: Float = calculate(123643)
一般来说,具备雷同输出参数和不同返回类型的函数是不可能被重载的。应用内联函数,编译器能够在复制函数体时,同样将泛型返回类型替换为理论所示意的类型。如果您查看反编译后的 Java 代码,能够发现编译器在 intCall 变量中理论应用的是 Integer 类型,在 floatCall 变量中理论应用的是 Float 类型。
public final void call() {
float value = 123643.0F;
int $i$f$calculate = false;
KClass var5 = Reflection.getOrCreateKotlinClass(Integer.class);
Integer var10000;
if (Intrinsics.areEqual(var5, Reflection.getOrCreateKotlinClass(Float.TYPE))) {var10000 = (Integer)value;
} else {if (!Intrinsics.areEqual(var5, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {throw (Throwable)(new IllegalStateException("Only works with Float and Int"));
}
var10000 = (int)value;
}
// 这里用到了 Integer
int intCall = ((Number)var10000).intValue();
int $i$f$calculate = false;
KClass var6 = Reflection.getOrCreateKotlinClass(Float.class);
Float var8;
if (Intrinsics.areEqual(var6, Reflection.getOrCreateKotlinClass(Float.TYPE))) {var8 = value;} else {if (!Intrinsics.areEqual(var6, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {throw (Throwable)(new IllegalStateException("Only works with Float and Int"));
}
var8 = (Float)(int)value;
}
float floatCall = ((Number)var8).floatValue(); // 这里用到了 Float}
Reified 容许您在应用泛型来进行编程的同时,还可能在运行时获取到泛型所代表的类型信息,这在之前是无奈做到的。当您须要在内联函数中应用到类型信息,或者须要重载泛型返回值时,您能够应用 reified。应用 reified 不会带来任何性能上的损失,然而如果被内联的函数过于简单则,还是可能会导致性能问题。因为 reified 必须应用内联函数,所以要保障内联函数的简短,并且遵循应用内联函数的最佳实际,免得让性能受到损失。