在 Kotlin 中使用 reified 关键字
reified 关键字是在 Kotlin 中使用泛型时最常使用的编程概念。
我们知道泛型是在 Java 5 中引入的,目的是为应用程序增加类型安全并防止显式类型转换。
泛型的一个限制是您在使用泛型函数时无法访问类型信息。 编译器抱怨它不能使用 T 作为具体化的类型参数,我们应该使用类来代替。
此问题是由编译期间的类型擦除引起的。 在本教程中,我们将学习如何使用两种方法解决此问题,包括将类型的类作为泛型函数的参数传递,以及将 reified 关键字与内联函数一起使用。
Java 5 之前的泛型
在 Java 5 之前,泛型是不存在的,所以我们无法判断一个列表实现是 String、Integer、Objects 还是其他类型的列表。
由于这个原因,我们总是不得不明确地转换为我们想要的类型。 这些应用程序很容易出现 RuntimeException,因为没有针对无效转换的验证。
以下示例显示了 Java 5 之前的集合是如何实现的。
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("John");
list.add(3);
list.add(5);
String john = (String) list.get(2);
}
}
Java 5 中的泛型
Java 5 中引入了泛型来解决这个问题。 泛型允许我们定义某种类型的集合,如果我们尝试输入无效类型,编译器会显示警告。
泛型还解决了 RuntimeException 问题,因为不需要显式类型转换就可以从集合中检索元素。 以下示例显示了如何从 Java 5 和更早版本中实现集合。
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("John");
// list.add(3); // Required type String
// list.add(true); // Required type String
String john = list.get(0);
System.out.println(john);
}
}
什么是类型擦除
类型擦除是 Java 5 引入的特性,用来解决我们刚才提到的问题。
转到 IntelliJ 并选择文件 > 新建 > 项目。 在项目名称部分输入项目名称 reified-in-kotlin。
在语言部分选择 Kotlin,在构建系统部分选择 Intellij。 最后,按创建按钮生成一个新项目。
在 kotlin 文件夹下创建 Main.kt 文件,将以下代码复制粘贴到该文件中。
fun <T> showType(t: T){
println(t);
}
fun main(){
showType("This is a generic function");
}
在 Java 5 之前,没有类型信息,因此 Java 编译器将类型信息替换为基本对象类型及其必要的类型转换。
要查看 Java 幕后发生的事情,请运行代码并按工具 > Kotlin > 显示 Kotlin 字节码。 在打开的窗口中,按 Decompile 以查看编译后的 Java 代码,如下所示。
public final class MainKt {
public static final void showType(Object t) {
System.out.println(t);
}
public static final void main() {
showType("This is a generic function");
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
请注意,我们传递给 showType()
方法的类型参数被转换为对象,这就是我们无法访问类型信息的原因。 这就是在使用泛型时发生类型擦除的方式。
将类传递给泛型函数
这是我们可以用来避免类型擦除的第一种方法,尽管它不如使用 reified 关键字有效,我们将在下一节中介绍。
要访问已删除的泛型类型,我们可以将泛型类型类作为泛型函数的参数传递,如下所示。
注释前面的示例并将以下代码复制并粘贴到 Main.kt 文件中。
fun <T> showType(clazz: Class<T>){
println(clazz);
}
fun main(){
showType(String::class.java)
}
使用这种方法,每次创建泛型函数时都需要为泛型类型传递一个类,这不是我们想要的。
运行此代码并确保代码输出以下内容。
class java.lang.String
在 Kotlin 中将 reified 关键字与内联函数一起使用
这是我们可以利用的第二种方法,也是使用泛型访问类型信息时最首选的方法。 请注意,reified 只能与内联函数一起使用,内联函数在 Kotlin 中可用,但在 Java 中不可用。
注释前面的示例并将以下代码复制并粘贴到 Main.kt 文件中。
inline fun < reified T> showType(){
println(T::class.java);
}
fun main(){
showType<String>()
}
内联函数帮助具体化的关键字访问类型信息,将内联函数体复制到它被使用过的每个地方。
运行上面的代码并使用我们上面介绍的相同步骤反编译它。 反编译后的代码如下所示。
public final class MainKt {
// $FF: synthetic method
public static final void showType() {
int $i$f$showType = 0;
Intrinsics.reifiedOperationMarker(4, "T");
Class var1 = Object.class;
System.out.println(var1);
}
public static final void main() {
int $i$f$showType = false;
Class var1 = String.class;
System.out.println(var1);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
由于我们在泛型参数中使用 reified 关键字定义了内联函数 showType(),因此编译器复制了函数的主体并将其替换为实际声明的类型,如名为 main() 的最终方法所示。
访问声明的类型允许我们从泛型函数中检索类型信息,而无需将类作为参数传递。
运行上面的代码来验证它是否可以访问我们作为参数传递的类型参数String的类型信息。
class java.lang.String
在 Kotlin 中使用 reified 重载具有相同输入的函数
在使用普通泛型函数时,我们不能重载具有相同输入并返回不同类型的函数,但我们可以使用 reified 关键字来实现。
对前面的示例进行注释,将以下代码复制并粘贴到 Main.kt 文件之后的文件中。
inline fun <reified T>computeResult(theNumber: Float): T{
return when(T::class){
Float::class -> theNumber as T
Int::class -> theNumber.toInt() as T
else -> throw IllegalStateException("")
}
}
fun main(){
val intResult: Int = computeResult(123643F);
println(intResult);
val floatResult: Float = computeResult(123643F);
println(floatResult);
}
在幕后,当我们调用 computeResult()
方法时,编译器期望第一次调用返回 Int 类型,第二次调用返回 Float 类型。
编译器在复制函数体时使用此信息将泛型类型替换为预期文件。
在使用 reified 的同时使用大型函数不是一个好习惯,因为它会在我们的应用程序中引入性能问题。 确保您的内联函数很小。
总结
所以,我们已经学会了使用带有内联函数的 reified 关键字来访问 Kotlin 中的类型信息。
我们还了解了类型擦除,它是类型信息在运行时删除的原因。 最后,我们介绍了如何使用具有相同输入的 reified 来重载内联函数以返回不同类型。
相关文章
Kotlin 中 Java String[] 的等价物
发布时间:2023/05/13 浏览次数:59 分类:Java
-
本文介绍了 Kotlin 中 Java String[] 的等价物。 我们将看到所有可能的方法来为 Kotlin 实现与 Java 中的 String[] 相同的结果。
将 Java 文件代码转换为 Kotlin
发布时间:2023/05/13 浏览次数:142 分类:Java
-
Kotlin 现在是一种官方的 Android 语言。 因此,您可能希望将 Java 文件更改为 Kotlin。 本文教您如何将 Java 转换为 Kotlin。
在 Kotlin 中使用 forEach
发布时间:2023/05/13 浏览次数:122 分类:Java
-
本文介绍 Kotlin 中 forEach 关键字的概念和使用。 我们将看到一些使用 Kotlin forEach 循环的示例来理解它。
Kotlin 中 ! 和 ? 运算符之间的区别
发布时间:2023/05/13 浏览次数:180 分类:Java
-
在 Kotlin 中,断言运算符 !! 和安全调用符 ? 使用 Null 安全。本文介绍空安全的概念。 我们也将通过如何! 和 ? 在 Kotlin 中有助于空安全。
在 Kotlin 中继承具有多个构造函数的类
发布时间:2023/05/12 浏览次数:200 分类:Java
-
本文讨论如何扩展具有多个构造函数的类。 构造函数是在创建对象时调用的函数,用于初始化类变量。在 Kotlin 中扩展具有多个构造函数的类
Kotlin中栈数据结构的使用
发布时间:2023/05/12 浏览次数:130 分类:Java
-
本篇文章我们就来学习一下Kotlin中栈的使用。 我们还将看到用于更新和更改 Kotlin 堆栈的标准函数,包括 push、pop、peek、search 和 isEmpty。我们还将研究比 Kotlin 中的堆栈更好的替代方案。
Kotlin中findViewById函数的使用
发布时间:2023/05/12 浏览次数:183 分类:Java
-
Kotlin 中的 findViewById 允许在 ID 的帮助下查看和更新视图。 本文介绍了 Kotlin 中 findViewById 函数的使用。
Kotlin runBlocking:用协程代码桥接非协程世界
发布时间:2023/05/12 浏览次数:194 分类:Java
-
Kotlin runBlocking 函数允许阻塞整个线程。 我们将在本文中看到更多关于 Kotlin Coroutine runBlocking 的信息。