为啥这不会超载?
Posted
技术标签:
【中文标题】为啥这不会超载?【英文标题】:Why doesn't this overload?为什么这不会超载? 【发布时间】:2019-09-13 16:38:05 【问题描述】:写在 Jooq 助手类上。 (一旦我弄清楚这一点,就会添加更多特定于业务的方法......)
import org.jooq.*
import org.springframework.stereotype.Repository
import javax.inject.Inject
import javax.inject.Provider
/**A helper class facilitating database manipulations.
* Uses jOOQ for its manipulations.
* Relies on the application context being properly initialized s.t. Spring creates and injects the correct
* [DSLContext].*/
@Repository
class DatabaseManipulator
private val provider: Provider<DSLContext>
private val create:DSLContext
get() = provider.get()
@Inject
constructor(provider: Provider<DSLContext>)
this.provider = provider
/**Executes non-[Select]-type SQL queries on the database.
* Does not perform any kind of checks and trusts the client to know what they're doing.
* @param query the query to be executed.
*
* @return depending on the type of the [query]:
* <ul>
* <li> Delete : the number of deleted records</li>
* <li> Insert : the number of inserted records</li>
* <li> Merge : result may be meaningless</li>
* <li> Truncate : result may be meaningless</li>
* <li> Update : the number of updated records</li>
* </ul>
*/
fun execute(query:DSLContext.()-> Query):Int
return create.query().execute()
/**Executes [Select]-type [query] and returns its result*/
fun <T:Record> execute(query:DSLContext.()->Select<T>):Result<T>
return create.query().fetch()
到目前为止,一切都很好。现在让我们添加一些测试
@RunWith(SpringJUnit4ClassRunner::class)
@SpringBootTest
@Transactional
class DatabaseManipulatorIT
@Inject
private lateinit var manipulate:DatabaseManipulator
@Inject
private lateinit var em:EntityManager
@Test
fun executeSelectQuery()
//given
val expectedNumberOfOrganizations = em.createQuery("SELECT COUNT (o) FROM Organization o")
.singleResult as Long
//when
val reportedNumberOfOrganizations = manipulate.execute
selectCount().from(ORGANIZATION)
.first().value1().toLong()
//then
assertThat(reportedNumberOfOrganizations).isEqualTo(expectedNumberOfOrganizations)
@Test
fun executeNonSelectQuery()
//given
val expectedNumberOfDeletions = em.createQuery(
"SELECT COUNT (o) FROM Organization o WHERE o.usageCreditLimited = true"
).singleResult as Long
//when
val actualNumberOfDeletions = manipulate.execute (
deleteFrom(ORGANIZATION)
.where(ORGANIZATION.USAGECREDITLIMITED.eq(true))
) as Query
//then
assertThat(actualNumberOfDeletions).isEqualTo(expectedNumberOfDeletions)
编译失败是因为
Error:(50, 50) Kotlin: Type inference failed: fun <T : Record> execute(query: DSLContext.() -> Select<T>): Result<T>
cannot be applied to
(DSLContext.() -> DeleteConditionStep<OrganizationRecord!>!)
Error:(50, 58) Kotlin: Type mismatch: inferred type is DSLContext.() -> DeleteConditionStep<OrganizationRecord!>! but DSLContext.() -> Select<???> was expected
不,我不想想要你使用那个方法,我想要你使用其他。
让我们试着明确一点:
//when
val actualNumberOfDeletions = manipulate.execute (
deleteFrom(ORGANIZATION)
.where(ORGANIZATION.USAGECREDITLIMITED.eq(true))
) as Query
仍然无法编译第二个测试用例,因为
Error:(50, 50) Kotlin: Type inference failed: fun <T : Record> execute(query: DSLContext.() -> Select<T>): Result<T>
cannot be applied to
(DSLContext.() -> Query)
Error:(50, 58) Kotlin: Type mismatch: inferred type is DSLContext.() -> Query but DSLContext.() -> Select<???> was expected
如何让 Kotlin 调用正确的方法?
【问题讨论】:
Select
本质上是一个Query
,因此您要使用的方法无法正确解析。尝试更改方法签名,使其不依赖于传递的参数来解析方法。
@KeivanEsbati 您能否详细说明“因此无法正确解决您要使用的方法”?我可以看到情况就是这样,我更愿意了解为什么会这样。即使Select
是Query
,反过来也不成立。那么为什么非Select
查询不绑定到非Select
方法呢?
我添加了一个参考并将其作为答案发布,以便您可以阅读更多相关信息。
jOOQ 区分fetch
(产生结果)和execute
(产生更新计数)。为什么要对两者使用相同的术语呢?在某些时候,重载是比方便更大的痛苦来源......请注意,您可能希望引用 ResultQuery<T>
而不是 Select<T>
以获得更通用的解决方案。
如果您将代码提取到它自己的函数中,例如fun q() : DSLContext.() -> Query = ...
或 fun s() : DSLContext.() -> Select
并将这些函数传递给您的 execute
它将(可能)工作(尚未测试)...甚至可能以下工作:fun <T> dsl(dsl : DSLContext.() -> T) = dsl
并调用 manipulate.execute(dsl ... )
...工作? ;-) 也许你迟早会来到这里:How does type erasure work in Kotlin? 或这里:Kotlin type erasure - ... generics
【参考方案1】:
Select
本质上是Query
,因此无法正确推断您要使用的方法。
编译器会解析有关方法调用的所有内容,而不会关注所涉及的通用约束,直到最后一刻——它注意到所选方法无效,并因错误而失败。
泛型会出现此问题,编译器将这些方法识别为两种不同的方法,但在尝试解析该方法时,它可能无法根据可用数据找到正确的方法。如果您想知道为什么要查看这篇文章,请查看 Jon Skeet 的 Overloading and Generic constraints。
【讨论】:
以上是关于为啥这不会超载?的主要内容,如果未能解决你的问题,请参考以下文章