Kotlin第十四讲---真泛型

内容简介

上一篇,我们算是把通配符和泛型弄明白了!这一篇我们讲解 Kotlin 独有的泛型之真泛型。

真泛型的由来

我们知道在 Java 中使用泛型的时候,无法通过泛型来得到 Class,因此一般我们会将 Class 通过参数传过去。

我们最常用的 Gson 库,当我们要解析一个字符串,转换成对应的 JavaBean 对象的时候,我们会调用 fromJson 方法。

public class TestJava {
    public static void main(String[] args) {
        Gson gson = new Gson();
        // 需要显示的将 Data 的 class 字节码传入
        Data obj = gson.fromJson("对应的Json字符串", Data.class);
    }
}

我们知道调用 fromJson 方法,必须将对于的转换 Class 传入,那我们在看看 fromJson 的定义:

public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
   Object object = fromJson(json, (Type) classOfT);
   return Primitives.wrap(classOfT).cast(object);
 }

我们细细品味下上面的代码,在定义 obj 变量类型的时候,我们已经确定了 obj 的类型是 Data 类,难道我们的 fromJson 方法中,就不能获取到引用变量的类型吗?(说通俗点就是能不能获取到 TClass 字节码)

仔细想想 Java 中当然不能,为啥呢?因为 Java 中存在泛型擦除,在编译后的 fromJson 并不存在 T 的信息,编译器只是在调用 fromJson 地方,帮你强制转换罢了。

什么是真泛型

Kotlin 的真泛型,就是为了解决这样的问题,试想下 Java 解决不了真正原因是泛型擦除,那 Kotlin 作为 Java 的儿子,怎么可能实现获取到 T 的 class 字节码呢?

试想下若在编译期间,只要有调用 fromJson 方法的地方,都对应编译一个对应类型的 fromJson 方法,是不是就能解决这个问题呢?

还记得函数篇章讲解的 内联函数 吗?内联函数 不就会将代码平铺到调用点吗?不就代表这个泛型方法编译了 N 个吗?Kotlin 编译器通过调用点知道了传入的类型,然后通过 内联函数 将代码平铺到调用点,泛型方法中的代码不就知道了泛型的类型啦(我C吊炸天的想法)。

这就是 真泛型 的原理,因此真泛型一定要是内联函数,并且通过 reified 修饰泛型。

通过 reified 关键词,修饰泛型方法定义的泛型,修饰后的方法还必须用 inline (内联函数)修饰方法,这样定义的泛型就是真泛型了。

接下来,我们通过 真泛型 来重新定义 fromJson 方法,让我们的 fromJson 更加的优雅。

/**
 * 为 Gson 增加一个扩展方法
 * 由于是 真泛型,因此必须是内联函数
 */
inline fun <reified T> Gson.fromJson(json: String): T {
    /**
     * 注意这里,可以直接获取 对应 T 的字节码
     */
    return fromJson(json, T::class.java)
}
/**
 * 定义2个实体对象
 */
class Data1
class Data2
fun main() {
    val gson = Gson()
    /**
     * 这里可以注意到,我们无需显示的将对应类型的字节码传入了
     */
    val data1: Data1 = gson.fromJson("json字符串")
    val data2: Data2 = gson.fromJson("json字符串")
}

注意:虽然知道了泛型的类型,但是也不能直接使用 T() 构造对象,为啥呢?其实想想也很简单,鬼知道你传入的类型有没有无参构造函数呢?

接下来我们看看,反编译后的 Java 代码是什么样的。

来一场真正的实战

我们理解了真泛型了,以及真泛型是怎么实现的,接下来就给大家讲解一个我项目中的实例吧。

需求:我们有一个 Socket ,服务端会主动为我们 push 一个字符串消息。
消息内容主要有一个 actiondata 字段, datajson 数据格式,但是 datajson 的结构是不同的哦(可以转换成不同的 JavaBean 对象)。
那我们肯定设计肯有一个 Socket 管理类,可以通过 addAction 来增加类型与回调。
按照以前的想法我们会如何设计呢?

/**
 * 封装下类型
 */
data class ActionType(val dataClzz: Class<*>, val callBack: (Any) -> Unit)
object SocketManager {
    val actions = mutableMapOf<String, ActionType>()
    val gson = Gson()
    /**
     * 收到事件
     */
    fun onMessage(action: String, data: String) {
        actions[action]?.apply {
            // 解析数据
            val fromJson = gson.fromJson(data, dataClzz)
            callBack(fromJson)
        }
    }
    /**
     * 增加回调
     */
    fun <T> addHandler(action: String, dataClzz: Class<T>, callBack: (T) -> Unit) {
        /**
         * 记录时间
         */
        actions[action] = ActionType(dataClzz, callBack as (Any) -> Unit)
    }
}
/**
 * 测试数据
 */
data class LoginResult(val userName: String, val userID: Long)
fun main() {
    /**
     * 注册action回调
     * 注册还需要填写一个 clazz 的参数,原因就是泛型函数,中的泛型会被擦除,不能知道类型
     */
    SocketManager.addHandler<LoginResult>("login", LoginResult::class.java) {
        println("得到回调数据:$it")
    }
    /**
     * 模拟发送数据
     */
    SocketManager.onMessage("login", Gson().toJson(LoginResult("阿文", 18)))
}

注意实例代码中,我们调用 addHandler 还需要显示的传入一个 Class 的对象,通过前面学习的真泛型就可以省略掉这个参数。

只需要修改 addHandler 方法。

/**
 * 增加回调
 */
inline fun <reified T> addHandler(action: String, noinline callBack: (T) -> Unit) {
    /**
     * 记录时间
     */
    actions[action] = ActionType(T::class.java, callBack as (Any) -> Unit)
}

注意:这里有一个以前讲过的 内联函数 的知识点,若函数通过 inline 修饰,默认传入的 Lambda 的代码也会被平铺。由于我们的 Lambda 表达式,并不是在 addHandler 方法中调用,所以一定要用 noinline 修饰 Lambda 表达式。

接下来我们使用就简单的多啦!

fun main() {
    /**
     * 注册action回调
     * 我们无需传入 Class 啦
     */
    SocketManager.addHandler<LoginResult>("login") {
        println("得到回调数据:$it")
    }
    /**
     * 模拟发送数据
     */
    SocketManager.onMessage("login", Gson().toJson(LoginResult("阿文", 18)))
}

总结

这章我们主要讲解了真泛型,当然我讲的例子很简单,大家也可以去探索更多的用处。总结来说我们能在泛型方法中,获取泛型的类型的字节码。

还要注意一点就是真泛型,只能用在泛型方法中,不能用在类里。

推荐阅读

--END--

识别二维码,关注我们

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值