在 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 来重载内联函数以返回不同类型。
相关文章
如何在 Java 中延迟几秒钟的时间
发布时间:2023/12/17 浏览次数:217 分类:Java
-
本篇文章主要介绍如何在 Java 中制造程序延迟。本教程介绍了如何在 Java 中制造程序延时,并列举了一些示例代码来了解它。
如何在 Java 中把 Hashmap 转换为 JSON 对象
发布时间:2023/12/17 浏览次数:187 分类:Java
-
它描述了允许我们将哈希图转换为简单的 JSON 对象的方法。本文介绍了在 Java 中把 Hashmap 转换为 JSON 对象的方法。我们将看到关于创建一个 hashmap,然后将其转换为 JSON 对象的详细例子。
如何在 Java 中按值排序 Map
发布时间:2023/12/17 浏览次数:171 分类:Java
-
本文介绍了如何在 Java 中按值对 Map 进行排序。本教程介绍了如何在 Java 中按值对 Map
进行排序,并列出了一些示例代码来理解它。
如何在 Java 中打印 HashMap
发布时间:2023/12/17 浏览次数:192 分类:Java
-
本帖介绍了如何在 Java 中打印 HashMap。本教程介绍了如何在 Java 中打印 HashMap 元素,还列举了一些示例代码来理解这个主题。
在 Java 中更新 Hashmap 的值
发布时间:2023/12/17 浏览次数:146 分类:Java
-
本文介绍了如何在 Java 中更新 HashMap 中的一个值。本文介绍了如何在 Java 中使用 HashMap 类中包含的两个方法-put() 和 replace() 更新 HashMap 中的值。
Java 中的 hashmap 和 map 之间的区别
发布时间:2023/12/17 浏览次数:79 分类:Java
-
本文介绍了 Java 中的 hashmap 和 map 接口之间的区别。本教程介绍了 Java 中 Map 和 HashMap 之间的主要区别。在 Java 中,Map 是用于以键值对存储数据的接口,
在 Java 中获取用户主目录
发布时间:2023/12/17 浏览次数:218 分类:Java
-
这篇文章向你展示了如何在 Java 中获取用户主目录。本教程介绍了如何在 Java 中获取用户主目录,并列出了一些示例代码以指导你完成该主题。
Java 中 size 和 length 的区别
发布时间:2023/12/17 浏览次数:179 分类:Java
-
这篇文章教你如何知道 Java 中大小和长度之间的区别。本教程介绍了 Java 中大小和长度之间的区别。我们还列出了一些示例代码以帮助你理解该主题。
Java 中的互斥锁
发布时间:2023/12/17 浏览次数:111 分类:Java
-
了解有关 Java 中互斥锁的一切,在计算机科学领域,互斥或互斥被称为并发控制的属性。每台计算机都使用称为线程的最小程序指令序列。有一次,计算机在一个线程上工作。为了更好地理解,