内容简介
上一篇,我们算是把通配符和泛型弄明白了!这一篇我们讲解 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--
识别二维码,关注我们