为啥这不会超载?

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 您能否详细说明“因此无法正确解决您要使用的方法”?我可以看到情况就是这样,我更愿意了解为什么会这样。即使SelectQuery,反过来也不成立。那么为什么非Select 查询不绑定到非Select 方法呢? 我添加了一个参考并将其作为答案发布,以便您可以阅读更多相关信息。 jOOQ 区分fetch(产生结果)和execute(产生更新计数)。为什么要对两者使用相同的术语呢?在某些时候,重载是比方便更大的痛苦来源......请注意,您可能希望引用 ResultQuery&lt;T&gt; 而不是 Select&lt;T&gt; 以获得更通用的解决方案。 如果您将代码提取到它自己的函数中,例如fun q() : DSLContext.() -&gt; Query = ... fun s() : DSLContext.() -&gt; Select 并将这些函数传递给您的 execute 它将(可能)工作(尚未测试)...甚至可能以下工作:fun &lt;T&gt; dsl(dsl : DSLContext.() -&gt; 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。

【讨论】:

以上是关于为啥这不会超载?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这不会崩溃?我不是在这里除以零吗?

为啥这不会一遍又一遍地返回一个新值?爪哇

为啥 Java 反射 API 允许我们访问私有和受保护的字段和方法?这不会破坏访问修饰符的目的吗? [复制]

无法超载点 '.' C++中的运算符

为啥php会出现500错误呢?急!

为啥这不是python中的语法错误?