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 方法中,就不能获取到引用变量的类型吗?(说通俗点就是能不能获取到 T 的 Class 字节码)
仔细想想 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 一个字符串消息。
消息内容主要有一个 action 与 data 字段, data 是 json 数据格式,但是 data 的 json 的结构是不同的哦(可以转换成不同的 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)