Search code examples
genericskotlinlanguage-interoperability

Nullability when converting generic Java callback to Kotlin


I'm trying to write a Kotlin wrapper on Spring's TransactionTemplate. The crucial code looks like this:

import org.springframework.stereotype.Component
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionCallback
import org.springframework.transaction.support.TransactionTemplate

@Component
class MyTransactionHelper(
    platformTransactionManager: PlatformTransactionManager
) {
    private val transactionTemplate = TransactionTemplate(platformTransactionManager);

    fun <T> inTransaction(block: () -> T): T {
        return transactionTemplate.execute(TransactionCallback<T> { block() })
    }
}

The code doesn't compile. This is because Java class TransactionCallback, defined in Java as:

@FunctionalInterface
public interface TransactionCallback<T> {
    @Nullable
    T doInTransaction(TransactionStatus status);
}

is interpreted in Kotlin as returning nullable T - T?, but my inTransaction method returns T.

How can I change this code to make it compile, while at the same time allowing my callers to use a nullable type as the generic type?

I could do something like this:

fun <T: Any> inTransaction(block: () -> T): T = transactionTemplate.execute { block() }!!

but then my callers wouldn't be able to pass blocks of type ()->Int?.

Here's an example code, that I want to compile and run:

    val helper: MyTransactionHelper = TODO()

    helper.inTransaction {
        if (TODO("some irrelevant condition")) 42 else null
    }

Solution

  • fun <T : Any> inTransaction(block: () -> T?): T? {
        return transactionTemplate.execute(TransactionCallback<T> { block() })
    }
    

    (: Any bound optional). Note that thanks to variance, () -> T is a subtype of () -> T?, so users can pass a () -> Int (and still get Int? back).

    However,

    The code doesn't compile. This is because Java class TransactionCallback, defined in Java as...

    is interpreted in Kotlin as returning nullable T - T?, but my inTransaction method returns T.

    is not correct; the problem is TransactionTemplate.execute returning T?, not TransactionCallback.doInTransaction. And if execute is actually guaranteed not to return null when block doesn't (which I think is true from the documentation, but am not certain), then just add a cast to your original code:

    @Suppress("UNCHECKED_CAST")
    fun <T> inTransaction(block: () -> T): T {
        return transactionTemplate.execute(TransactionCallback<T> { block() }) as T
    }