本文介绍了协程内部的通用泛型参数不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在创建http json客户端.我将Volley与协程结合使用.我想创建通用的http客户端,以便可以在任何地方使用它.

I am creating http json client. I am using Volley in combination with coroutines. I wanted to create generic http client so I can use it everywhere.

我创建了通用扩展方法来将JSON字符串解析为对象.

I have created generic extension method to parse JSON string into object.

inline fun <reified T>String.jsonToObject(exclusionStrategy: ExclusionStrategy? = null) : T {
val builder = GsonBuilder()

if(exclusionStrategy != null){
    builder.setExclusionStrategies(exclusionStrategy)
}

return builder.create().fromJson(this, object: TypeToken<T>() {}.type)

}

问题是,当我调用此方法时,没有得到预期的结果.第一次通话会给出正确的结果.对象已初始化.但是第二次调用(我使用传递给方法的通用参数)以异常"LinkedTreeMap无法转换为令牌"结束.

Problem is that when I call this method I don't get expected result. First call gives proper result. Object is initialized. But second call, where I use generic parameter which is passed to method, ends with exception "LinkedTreeMap can not be cast into Token".

    protected inline fun <reified T>sendRequestAsync(endpoint: String, data: Any?, method: Int, token: Token?): Deferred<T> {
    return ioScope.async {
        suspendCoroutine<T> { continuation ->
            val jsonObjectRequest = HttpClient.createJsonObjectRequest(
                endpoint,
                data?.toJsonString(),
                method,
                Response.Listener {
                    //this call is successful and object is initialized
                    val parsedObject : HttpResponse<Token> = it.toString().jsonToObject()

                    //this call is not successful and object is not initialized properly
                    val brokenObject : HttpResponse<T> = it.toString().jsonToObject()
                    continuation.resume(brokenObject.response)
                },
                Response.ErrorListener {
                    continuation.resumeWithException(parseException(it))
                },
                token)
            HttpClient.getInstance(context).addToRequestQueue(jsonObjectRequest)
        }
    }
}

调用泛型方法.

fun loginAsync(loginData: LoginData): Deferred<Token> {
    return sendRequestAsync("/tokens/", loginData, Request.Method.POST, null)
}

这是httpresponse数据类的外观.

This is how httpresponse data class looks.

data class HttpResponse<T> (
val response: T
)

我在这里看到了使用Type :: class.java的解决方法,但是我不喜欢这种方法,我想使用经过修饰的关键字和内联关键字. Kotlin中的reified关键字如何工作?

I saw a workaround here using Type::class.java but I don't like this approach and I would like to use reified and inline keywords.How does the reified keyword in Kotlin work?

更新:根据我的进一步研究发现,这是不正确的. crossinline 不会阻止编译器内联lambda,而只是禁止它们影响函数的控制流.我可能将其与关键字 noinline 混合使用,顾名思义,该关键字实际上禁止内联.

Update: This is, as I've found after some further research, NOT true. crossinline doesn't stop the compiler from inlining lambdas, it just forbids them to influence the function's control flow. I probably mixed it up with the keyword noinline, which, as the name implies, actually forbids inlining.

但是,我很确定以下部分.但是,我仍然必须找出为什么Gson无法正确确定和/或反序列化类型.我会在了解更多信息后立即对其进行更新.

However, I'm pretty sure about the following part. However, I still have to find out why Gson is unable to determine and/or to deserialize the type correctly. I'll update this post as soon as I know more.

这使我们进入最后一部分,试图解释您收到的奇怪异常.为此,我们必须看一下Gsons的内部原理.

That brings us to the final part which tries to explain the weird exception you received. For that, we have to take a look at Gsons' internals.

内部,Gson具有负责反射序列化和反序列化的两种主要类型: TypeAdapterFactory TypeAdapter< T>.

Internally, Gson has two main types that are responsible for reflective serialization and deserialization: TypeAdapterFactory and TypeAdapter<T>.

TypeAdapter< T> 仅适应一种特定类型(为该类型提供(反序列化)逻辑).这意味着 Integer Double List< String> List< Float> 均由不同的 TypeAdapter< T> s.

A TypeAdapter<T> only adapts (= provides the (de-)serialization logic for) one specific type. This means that Integer, Double, List<String> and List<Float> are all handled by different TypeAdapter<T>s.

TypeAdapterFactory 负责提供匹配的 TypeAdapter< T> . TypeAdapter< T> s和 TypeAdapterFactory s之间的区别非常有用,因为一个工厂可能会创建所有适配器,例如像 List 这样的集合类型,因为它们的工作方式都相似.

TypeAdapterFactorys are responsible for, as their names already imply, providing matching TypeAdapter<T>s. The differentiation between TypeAdapter<T>s and TypeAdapterFactorys is extremely useful as one factory might create all adapters for e.g. a collection type like List as they all work in a similar way.

为了确定所需的适配器类型,Gson希望您在调用应处理通用类型的(反序列化)函数时传递 TypeToken< T> . TypeToken< T> 使用一个技巧"来访问传递给其type参数的类型信息.

In order to determine what kind of adapter you need, Gson expects you to pass a TypeToken<T> when calling a (de-)serialization function which should process a generic type. TypeToken<T> uses a "trick" to access the type information passed to its type parameter.

只要您调用 Gson#fromJson(此对象,TypeToken< T>(){} .type),Gson就会遍历所有可用的 TypeAdapterFactory s,直到它找到一个可以提供适当的 TypeAdapter< T> 的代码.Gson带有各种 TypeAdapterFactory ,包括用于原始数据类型,包装器类型,基本集合类型,日期等的工厂.除此之外,Gson还提供了两个特殊工厂:

As soon as you call Gson#fromJson(this, object: TypeToken<T>() {}.type), Gson iterates through all available TypeAdapterFactorys until it finds one that can provide an appropriate TypeAdapter<T>. Gson comes with a variety of TypeAdapterFactorys, including factories for primitive data types, wrapper types, basic collection types, date and many more. Besides that, Gson provides two special factories:

  • ReflectiveTypeAdapterFactory顾名思义,该工厂尝试以反射方式访问对象的数据.为了适当地适应每个字段的类型,它为每个字段请求一个匹配的TypeAdapter.这是将为(反序列化HttpRequest)选择的工厂.
  • ObjectTypeAdapter.Factory此工厂仅返回ObjectTypeAdapter.下面的代码片段显示了它在对象反序列化(分别在HttpRequest对象中的字段)上的作用:
@Override public Object read(JsonReader in) throws IOException {
    JsonToken token = in.peek();
    switch (token) {
    ...
    case BEGIN_OBJECT:
      Map<String, Object> map = new LinkedTreeMap<String, Object>(); // <-----
      in.beginObject();
      while (in.hasNext()) {
        map.put(in.nextName(), read(in));
      }
      in.endObject();
      return map;   // <-----
    ...
  }

这就是为什么您使用 com.google.gson.internal.LinkedTreeMap 来获得 ClassCastException 的原因.

That's why you get a ClassCastException with a com.google.gson.internal.LinkedTreeMap.

这篇关于协程内部的通用泛型参数不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-23 13:20