Java/Kotlin: Gson() 泛型转换,解决泛型参数的类型擦除

有这么一个带类型参数的类,就叫S

你的目的是获取S里面的data这很关键


class S<T>(
    val data: T
    //.....省略其他成员变量
)

可以看成网络包,不解释了

Gson().fromJson()

函数声明如下

public <T> T fromJson(String json, Type typeOfT)
public <T> T fromJson(Reader json, Class<T> classOfT)

这里只谈第一个,假如现在要将一个SJson转成对应的S(这里为了方便就用的String),那么正常来说需要如下的代码

val type = object: TypeToken<S<String>>(){}.type
val s = Gson().fromJson(sJson, type)

这就有一个问题,也不算是问题。就是你使用的S具有泛型参数,但是你jsonobj的时候还得去写TypeToken....一堆代码,如果要转的次数多就很烦,很多样板代码。

所以似乎可以这样

fun <T> jsonToT(sJson: String){
	val type = Object: TypeToken<S<T>>(){}.type
	val s = Gson().fromJson(sJson,type)
}

很可惜,不可以这样,因为在编译期Java对T进行了类型擦除,于是乎当你的代码执行到这里时,现在这个typename就是object。然后Gson将抛出异常 linkTreeMap cannot be cast to String(因为我这用的String)

解决类型参数T传入函数的编译期类型擦除

在kotlin中,使用如下代码可以解决;Java中是传入Class,不说Java了

inline fun <reified T> jsonToT(sJson:String){
	
}

下面用上面这个函数框架解决异常 linkTreeMap cannot be cast to String

再明确一下,目的是为了获取data

首先
这时,T的类型会被传入这个函数

inline fun <reified T> jsonToT(sJson:String): T{
	val typeS = object : TypeToken<S<T>>(){}.type
	val s = Gson().fromJson(sJson, typeS)
	return s.data
}

这时你以为稳了,错!当S传入TypeToken这个工具类时,由于TypeToken里面的函数也是带有类型参数的函数,又进行了类型擦除,完蛋,T又成了Object,又是同样的异常。

所以,那我把TypeToken里面的函数改成inline fun不就行了,事实上可以,但是我,看不懂TypeToken…所以我选择了改Gson().fromJson()

思路是这样,先看代码

inline fun <reified T> jsonToT(sJson:String): T{
	val typeS = object : TypeToken<S<T>>(){}.type
	val typeT = object : TypeToken<T>(){}.type
	val s = Gson().fromJson(sJson, typeS)
	val dataJson = Gson().toJson(s.data)
	
	val data = Gson().fromJson(dataJson, typeT)
	
	return data
}

由于T的类型在这里没有被擦除,所以typeT现在是String的类型。先将s.dataJson,然后再使用typeT,将dataJson转成String,大功告成。哈哈错!注意了,Gson().fromJson()也是一个带泛型的函数,上面我提到了的,意味着在Gson().fronJson()里面的你的typeT,又变成了Object,于是异常又来了 linkTreeMap cannot be cast to String

离成功只差一步之遥了,那就是自己写一个fromJson(),我给他命名fromJson2()使他使用我们的模板,让传入的T类型不再被擦除:

inline fun <reified T> fromJson2(json: String?, typeOfT: Type) : T?{
    
}

当然,不可能自己完全从头写。这里使用拓展函数,似乎是kotlin才有,我没去查了,反正这里用。

//拓展函数 Kotlin
@Throws(JsonSyntaxException::class)
inline fun <reified T> Gson.fromJson2(json: String?, typeOfT: Type) : T?{
    if (json == null) {
        return null
    }
    val reader = StringReader(json)
    return fromJson(reader, typeOfT)
}
//原来的 Java
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
  if (json == null) {
    return null;
  }
  StringReader reader = new StringReader(json);
  T target = (T) fromJson(reader, typeOfT);
  return target;
 }

拓展函数可以和原来的Gson完美契合,而且不用更改Gson的任何代码。那个异常也要抛出,Gson内部需要处理,和Java相比只是换了个写法。好的,完成了,用Gson().fromJson2(),去代替最后一个Gson().fromJson()

inline fun <reified T> jsonToT(sJson:String): T{
	val typeS = object : TypeToken<S<T>>(){}.type
	val typeT = object : TypeToken<T>(){}.type
	val s = Gson().fromJson(sJson, typeS)
	val dataJson = Gson().toJson(s.data)
	
	val data = Gson().fromJson2(dataJson, typeT)
	
	return data
}

这样,随便扔一个SJson数据进去,只要在扔的时候指定想获取的data的类型就可以了。说这么多,其实就是为了少些两行模板代码…还不是懒嘛

你可能感兴趣的:(笔记)