协程内的具体化泛型参数不起作用

2024-07-01

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

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

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 无法转换为令牌”结束。

    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 数据类的样子。

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

我在这里看到了使用 Type::class.java 的解决方法,但我不喜欢这种方法,我想使用具体化和内联关键字。Kotlin 中的 reified 关键字如何工作? https://stackoverflow.com/questions/45949584/how-does-the-reified-keyword-in-kotlin-work

UPDATE这是我得到的异常。

java.lang.ClassCastException:com.google.gson.internal.LinkedTreeMap 无法转换为 com.xbionicsphere.x_card.entities.Token

可能的解决方法我找到了可能的解决方法。如果我创建将从响应中解析令牌的方法,并在executeRequestAsync中使用此方法,一切都会开始工作,但我不喜欢这个解决方案,因为我必须为每个请求添加额外的参数。

新登录异步

fun loginAsync(loginData: LoginData): Deferred<Token> {
    val convertToResponse : (JSONObject) -> HttpResponse<Token> = {
        it.toString().jsonToObject()
    }

    return executeRequestAsync("/tokens/", loginData, Request.Method.POST, null, convertToResponse)
}

新的executeRequestAsync

    protected inline fun <reified T>executeRequestAsync(endpoint: String, data: Any?, method: Int, token: Token?, crossinline responseProvider: (JSONObject) -> HttpResponse<T>): Deferred<T> {
    return ioScope.async {
        suspendCoroutine<T> { continuation ->
            val jsonObjectRequest =
                HttpClient.createJsonObjectRequest(
                    endpoint,
                    data?.toJsonString(),
                    method,
                    Response.Listener {
                        val response: HttpResponse<T> = responseProvider(it)
                        continuation.resume(response.response)
                    },
                    Response.ErrorListener {
                        continuation.resumeWithException(parseException(it))
                    },
                    token
                )
            HttpClient.getInstance(
                context
            ).addToRequestQueue(jsonObjectRequest)
        }
    }
}

UPDATE我可能已经找到了可行的解决方案。 executeRequestAsync 需要通过泛型参数提供最终类型定义,因此我增强了方法的声明。现在方法声明如下所示:

    protected inline fun <reified HttpResponseOfType, Type>executeRequestAsync(endpoint: String, data: Any?, method: Int, token: Token?) : Deferred<Type> where HttpResponseOfType : HttpResponse<Type> {
    val scopedContext = context

    return ioScope.async {
        suspendCoroutine<Type> { continuation ->
            val jsonObjectRequest =
                HttpClient.createJsonObjectRequest(
                    endpoint,
                    data?.toJsonString(),
                    method,
                    Response.Listener {
                        val response: HttpResponseOfType = it.toString().jsonToObject()
                        continuation.resume(response.response)
                    },
                    Response.ErrorListener {
                        continuation.resumeWithException(parseException(it))
                    },
                    token
                )
            HttpClient.getInstance(
                scopedContext
            ).addToRequestQueue(jsonObjectRequest)
        }
    }
}

感谢这个复杂的函数声明,我可以通过此调用执行请求:

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

为了理解为什么第二个调用的行为有点奇怪,以及为什么按照 Leo Aso 的建议,删除关键字inline and reified(需要内联函数)也会破坏第一个调用,您必须了解类型擦除以及如何reified首先启用类型具体化。

注意:以下代码是用 Java 编写的,因为我对 Java 的语法比 Kotlin 的语法更熟悉。此外,这使得类型擦除更容易解释。

The 类型参数 of a 通用函数在运行时不可用;泛型只是一个“编译时技巧”。这适用于 Java 和 Kotlin(因为 Kotlin 能够在 JVM 上运行)。移除泛型类型信息的过程称为类型擦除并在编译期间发生。那么泛型函数是如何工作的在运行时?考虑以下函数,它返回任意集合中最有价值的元素。

<T> T findHighest(Comparator<T> comparator, Collection<? extends T> collection) {
    T highest = null;
    for (T element : collection) {
        if (highest == null || comparator.compare(element, highest) > 0)
            highest = element;
    }

    return highest;
}

由于可以使用许多不同类型的集合等来调用此函数,因此类型变量 T可能会随着时间的推移而变化。为了确保与所有这些的兼容性,该函数在类型擦除期间被重构。类型擦除完成后,该函数将类似于以下内容:

Object findHighest(Comparator comparator, Collection collection) {
    Object highest = null;
    for (Object element : collection) {
        if (highest == null || comparator.compare(element, highest) > 0)
            highest = element;
    }

    return highest;
}

在类型擦除期间,类型变量将替换为它们的绑定。在这种情况下,绑定类型是Object。参数化通常不保留其通用类型信息。

但是,如果您编译删除的代码,则会出现一些问题。考虑以下代码(未擦除),它调用已擦除的代码:

Comparator<CharSequence> comp = ...
List<String> list = ...
String max = findHighest(comp, list);

As #findHighest(Comparator, Collection)现在返回Object,第 3 行中的赋值将是非法的。因此,编译器在类型擦除期间在那里插入强制转换。

...
String max = (String) findHighest(comp, list);

由于编译器始终知道它必须插入哪个强制转换,因此类型擦除在大多数情况下不会导致任何问题。但是,它有一些限制:instanceof不起作用,catch (T exception)是非法的(而throws T是允许的,因为调用函数知道它必须期待什么样的异常)等。您必须克服的限制是缺乏可具体化的(=运行时可用的完整类型信息)泛型类型(有一些例外,但它们在这种情况下并不重要)。


但是等等,Kotlin 支持具体化类型,对吗?确实如此,但正如我之前提到的,这只适用于内联函数。但这是为什么呢?

当函数签名中包含关键字时inline被调用时,调用代码被替换为该函数的代码。由于“复制”的代码不再需要与所有类型兼容,因此可以针对其使用的上下文进行优化。

一种可能的优化是替换“复制代码”中的类型变量(幕后还有更多事情发生)before类型擦除完成。因此,类型信息被保留并且在运行时也可用;它与任何其他非通用代码没有区别。


Although both of your functions, #jsonToObject(ExclusionStrategy?) and #sendRequestAsync(String, Any?, Int, Token?), are marked as inlinable and have reifiable type parameters, there's still something you've missed: T is, at least in your call to #toJsonObject(ExclusionStrategy?), NOT reifiable.

原因之一是您致电#suspendCoroutine(...)。要理解为什么这是一个问题,我们必须首先查看它的声明:

suspend inline fun <T> suspendCoroutine(
    crossinline block: (Continuation<T>) -> Unit
): T

The crossinline-关键字是有问题的,因为它阻止编译器内联内部声明的代码block。您传递给的 lambda#suspendCoroutine因此将被转移到匿名内部类中。从技术上讲,这是在运行时发生的。

因此,通用类型信息不再可用,至少在运行时不可用。 在你调用的地方#jsonToObject(...), 类型变量T被擦除为Object. The TypeToken因此 Gson 生成如下所示:

TypeToken<HttpResponse<Object>>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

协程内的具体化泛型参数不起作用 的相关文章

随机推荐

  • 替换 pytest 中的测试用例继承?

    背景 在 Python 中unittest https docs python org 3 library unittest html在框架中 在一组基本测试上使用继承来将整套测试应用于新问题 并且偶尔添加其他测试是一种相当常见的习惯用法
  • AddKeysToAgent 是 ssh 配置在 Mac 上不起作用

    您好 我的 ssh 配置文件设置为自动将 ssh 密钥添加到 ssh 代理 然而 目前它不起作用 它之前正在工作 我更新了 bitbucket 的 ssh 密钥 但它不再起作用 当我重新启动时 我必须使用 ssh add K 选项手动添加
  • C++地址运算符的用途? [复制]

    这个问题在这里已经有答案了 可能的重复 为什么要使用指针 https stackoverflow com questions 162941 why use pointers 我知道 C 的作用 但它能用来做什么呢 当在调用站点使用时 用于将
  • Python 元组列表到 int 列表

    所以我有x 12 1 3 元组列表 我想要x 12 1 3 整数列表 以最好的方式可能 你能帮忙吗 你没有说 最好 是什么意思 但大概你的意思是 最Pythonic 或 最易读 或类似的东西 F3AR3DLEGEND 给出的列表理解可能是最
  • 你应该捕获所有异常吗? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何使用 PopUp 插件 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我尝试搜索插件 jquery 来创建评论弹出窗口 但我不知道如何使用它以及支持 Popup 的插件是什么 任何人都可以帮我展示简单的代码并
  • 如何将两个矩阵的列与所有组合相乘

    我喜欢将具有相同行的两个矩阵的列的所有可能组合相乘 这意味着两个矩阵 例如a 3x3 and b 3x4 将生成带有元素的 3x4 矩阵a i j a k j i and k表示从 1 到 3 的行 并且j代表从 1 到 4 的列 我创建了
  • 使用 writeBytes 的 Java 客户端套接字

    我正在从缓冲区读取字符串并将其写入服务器 我遇到的问题是 当我打开套接字并循环写入时 服务器永远不会收到该字符串 当我使用这个时 try Socket send new Socket localhost 1490 DataOutputStr
  • 在 Android 的视图中创建透明的圆形切口

    我正在尝试创建一个半透明的帮助叠加层 以便在用户首次打开应用程序时显示在我的活动的主屏幕上 我想通过 剪切 与按钮位置相对应的覆盖层部分来突出显示主布局中包含的按钮 并使用 setContentView 进行膨胀 并使剪切部分透明 覆盖层是
  • 错误:索引表达式中的函数必须在 Postgres 中标记为 IMMUTABLE

    我想创建多列表达式索引 但是当我创建索引时 输出以下消息 detail message wapgrowth gt create index CONCURRENTLY idx test on tmp table using btree sky
  • 避免使用react-redux重新渲染一个大的项目列表

    我正在为我的应用程序使用 redux 以及 React 和 TypeScript 我正在处理在我的应用程序的不同位置使用的许多项目 我的状态看起来像这样 items 42 53 A large dictionary of items ite
  • Firebase 令牌身份验证错误

    我正在使用 firebase 存储上传文件 但是当我上传时出现此错误 E StorageUtil error getting token java util concurrent ExecutionException com google
  • 在控制台应用程序中将命令行参数传递到 VB6 IDE

    我有一个 VB6 控制台应用程序 它使用命令行参数 为了进行调试 我希望能够从 IDE 启动它 并且最好能够向它传递这些参数以查看它的正常运行情况 我意识到我可以在适当的位置设置一个断点 并使用立即窗口在命令行之外设置值 并且我过去使用过一
  • 如何使用javascript从另一个页面(同一域)的内容中获取信息?

    假设我有一个网页 index html 包含以下内容 li div item1 div a href details item1 html details a li 我想要一些 javascript index html加载那个 detai
  • 连接到进程后 Xcode 控制台为空[重复]

    这个问题在这里已经有答案了 我在 iPhone 上构建了一个应用程序 退出它 再次启动它 然后成功附加到 Xcode 中的进程 现在我在日志导航器中运行了调试 但我的控制台是空的 我不应该在那里看到所有常用的控制台输出吗 您可以在设备日志中
  • SQL Server,误导性的 XLOCK 和优化

    从我最近所做的一些测试和阅读来看 XLOCK 的 X 独占 名称部分似乎具有误导性 事实上 它并不比 UPDLOCK 多加锁 如果它是独占的 它将阻止外部 SELECT 但事实并非如此 我无法从阅读或测试中看出两者之间的区别 XLOCK 唯
  • Mysql Slave 未更新

    我已经设置了复制 一切看起来都很好 我没有错误 但数据没有被移动到从站 mysql gt show slave status G 1 row Slave IO State Waiting for master to send event M
  • 在 lambda 中捕获完美转发的变量

    template
  • 如何在表格视图中对图像进行动画处理以同时扩展和打开另一个视图控制器?

    我正在制作一个消息应用程序 我希望用户单击我的表格视图中的图像 它应该扩展到全屏并在导航栏上显示不同的控件 我该怎么办 我想我可以拍摄相同的图像 将 UIImageView 放在原始单元格图像之上并将其动画化为全屏 但是我如何在没有闪烁 延
  • 协程内的具体化泛型参数不起作用

    我正在创建 http json 客户端 我将 Volley 与协程结合使用 我想创建通用的 http 客户端 这样我就可以在任何地方使用它 我创建了通用扩展方法来将 JSON 字符串解析为对象 inline fun