阅读 1056

[译]Kotlin的独门秘籍Reified实化类型参数(上篇)

翻译说明:

原标题: Getting Real with Kotlin's Reified Type Parameters

原文地址: typealias.com/guides/gett…

原文作者: Dave Leeds

之前的Kotlin系列文章,欢迎查看:

翻译系列:

原创系列:

实战系列:

简述:(今天扯点不一样的东西)

本篇已经是Kotlin泛型系列第三篇了,先来回顾下前面两篇分别讲的是泛型中的类型形参和类型实参以及什么时候该使用类型形参约束。今天我们来说点Kotlin独有的泛型特性,但是Java中是没有的。那就是Kotlin中reified关键字修饰的泛型实化类型参数。再说这个之前我觉得有必要科普一下历史背景以及为什么Kotlin会出现实化类型参数。

历史背景:

我们都知道Java中的泛型是在JDK1.5的版本引入的,可是集合Collection在JDK1.2版本中就引入的,我们现在所看到的List<T>,是在泛型出来后加入的,那么JDK1.2之前就直接用List(java中俗称原生态类型)表示。问题来了为了兼容之前的版本Java采用所谓的伪泛型,伪泛型有个什么特征我想大家应该猜到那就是泛型擦除,就是泛型类型信息在编译期都会被抹掉,不管你是List<String>还是List<Float>在运行时他们都一样,那都是List类型,泛型类型信息已经被擦除了。当然泛型擦除也有它的好处,当然这不是这次讨论的重点。伪泛型对应的就是真泛型,如果熟悉C#就知道,它是真泛型,不会存在类型擦除情况,具体可以自己去了解下。

时势造英雄Kotlin登场:

显然我们知道,泛型擦除在一些开发场景下是有很大影响,使用起来非常不便,Kotlin这门新的语言不像Java一样有太多的历史负担,它就像是全局者一样,看着Java之前的坑走过来的,于是乎它想来填一填。有的人可能会问了那它是不是像C#那样采用真泛型,答案不是。我们都知道Kotlin力求做到与Java百分百的互操作性,所以Kotlin妥协了还是采用伪泛型,所以它和Java一样依然会存在泛型擦除问题。但是很幸运地是Kotlin偷偷给我们开了一个后门那就是今天的主角Reified实化类型参数,它可以保证运行时依然能拿到泛型具体实际类型。Reified实化类型参数已经大量运用在Kotlin的anko中,关于这块后面博客会细讲。

进入正题(开始翻译)

让我们来想一想在Kotlin中你可以类名来做些什么-想想你在源码中编写类名的所有场景。我想到了列举以下15种情景,没想全可能会有漏一些。让我们一起来看下吧...

  • 1、定义一个成员属性
private val thing: Thing
复制代码
  • 2、函数参数
fun doSomething(thing: Thing) {}
复制代码
  • 3、类型实参
val list = listOf<Thing>()
复制代码
  • 4、类型形参约束
class Item<T : Thing>
复制代码
  • 5、强制类型转换
something as Thing
复制代码
  • 6、定义一个类
open class Thing {}
复制代码
  • 7、继承一个类
class Other : Thing()
复制代码
  • 8、导入一个类
import com.example.Thing
复制代码
  • 9、给类名定义typealias别名
typealias Thingy = Thing
复制代码
  • 10、构建一个对象
val thing = Thing()
复制代码
  • 11、捕获一个错误类型
catch (e: ExceptionalThing)
复制代码
  • 12、调用静态方法
Thing.doStaticStuff()
复制代码
  • 13、定义函数引用
val function = Thing::doSomething
复制代码
  • 14、类型比较
something is Thing
复制代码
  • 15、获得类的一个Class对象:
val clazz = Thing::class.java
复制代码

大问题来了

现在,最大的问题是:

在上面哪些案例中我们应该使用泛型类型参数引用而不是真实的类名?

换句话说,上面15种案例中哪些我们可以用类型参数比如 T去替代Thing类呢?

你在哪些地方可以引用类型参数?

你想到了什么? 这是我能想到的 - 上面的案例1-5都可以采用类型参数。让我们一起展示所有五个:

class GenericThing<T>(constructorArg: T) {
    // 1. Define a member property 定义一个成员属性
    private val thing: T = constructorArg

    // 2. Define the type of a function argument 定义函数参数
    fun doSomething(thing: T) = println(thing)

    // 3. Use as a type argument 定义泛型类型实参
    fun emptyList() = listOf<T>()

    // 4. Use as a type parameter constraint, and... 使用作为类型形参约束
    // 5. Cast to the type (produces "unchecked cast" warning) 强制类型转换
    fun <U : T> castIt(): T = thing as U
}
复制代码

你在哪些地方可以不引用类型参数?

引用类型参数适用于案例1-5。但是对于案例6-15,如果我们要在(例如,Thing或ExceptionalThing)的地方替换类型参数(例如T),则最终会出现编译器错误。

  • 在某些情况下,使用类型参数是没有意义的。例如,在案例6中我们正在定义一个类 - 根据其类型参数之一定义一个类有什么意义呢?
  • 在其他情况下,Java和Kotlin通过反射提供一些变通的方法,例如构建对象(案例10) 但在某些情况下,能够使用类型参数肯定会很好。特别是,案例14和15--比较类型和分配类对象 - 在某些情况下会非常方便。

Reified类型实化参数介绍

Java限制了哪些类型是reifiable - reifiable也就意味着它们“在运行时完全可用”(具体可查阅:Java SE specs on reifiable types),泛型类型参数通常在编译期间被擦除,但是在Kotlin中的reified类型参数的情况下, 由于底层语法下一些巧妙的技巧,让运行时也能准确拿到泛型参数类型信息。

Reified类型参数仅适用于函数(或具有get()函数的扩展属性),并且仅适用于声明为inline内联的函数。这是一个例子:

inline fun <reified T> Any.isInstanceOf(): Boolean = this is T
复制代码

当您将函数标记为inline时,编译器会把实现内联函数的字节码插入到每次调用发生的地方。这就是reified类型的工作原理 - 具体实际类型在调用地方是已经知道的,因此在调用x.isInstanceOf<String>()有效地把x编译为String.

reified类型实化参数经常被使用到的几个地方

上面的案例15是许多Kotlin开发人员最喜欢的案例。假设我们有一个User类,以及我们想要读取的JSON字符串

data class User(val first: String, val last: String)

val json = """{
      "first": "Sherlock",
      "last": "Holmes"
    }""
复制代码

在Java序列化库(如Gson)中,当您想要反序列化该JSON字符串时,您最终必须将Class对象作为参数传递,以便Gson知道您想要的类型。

User user = new Gson().fromJson(getJson(), User.class);
复制代码

现在,让我们一起展示reified类型实化参数的魔法 我们将创建一个非常轻量级的扩展函数来包装Gson方法:

inline fun <reified T> Gson.fromJson(json: String) = 
        fromJson(json, T::class.java) 
复制代码

现在,在我们的Kotlin代码中,我们可以反序列化JSON字符串,甚至根本不需要传递类型信息!

val user: User = Gson().fromJson(json)
复制代码

Kotlin根据它的用法推断出类型 - 因为我们将它分配给User类型的变量,Kotlin使用它作为fromJson()的类型参数。或者,您可以使类型推断其他的类:

val user = Gson().fromJson<User>(json)
复制代码

在这种情况下,从传递给fromJson()的类型参数推断出user类型。

如果想了解更多关于Reified Type Parameters.请关注[译]Kotlin的独门秘籍Reified实化类型参数(下篇),未翻译,敬请期待。

译者有话说

这篇文章属于reified-type-parameter实化类型参数的上篇,算是一个简单开头介绍怎么使用reified,然后前面花了一些篇幅阐述了开发中什么情况下可以使用泛型,感觉这点应该更实用吧,下篇译文将会比较深入reified讲解实化类型参数,敬请期待。

欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

关注下面的标签,发现更多相似文章
评论