Search code examples
kotlin

Optional.of not working with generic parameter in kotlin


Why this doesn't compile?

fun <T> returnOptionalOf(t: T): Optional<T> {
    val optional: Optional<T> = Optional.of(t)
    return optional
}

The error is

Type mismatch: inferred type is T but T & Any was expected

How to make it work?

This compiles just fine:

fun returnOptionalOfString(t: String): Optional<String> {
    val optional: Optional<String> = Optional.of(t)
    return optional
}

Solution

  • My first advice would be to just avoid Optional types in Kotlin. Nullable types offer the same compile-time guarantees about whether a value can hold nulls, they force you to check for null in the right places, and they are more idiomatic (there is also plenty of syntax sugar that will help deal with nullable values, such as ?: or ?. operators).

    Now back to your specific question, what you see is the combination of several things working against you:

    • in your function signature, you didn't constrain T at all. This means that T could be a nullable type (for instance, String?), which in turn would mean that this function accepts nulls. Note that Optional.of is a Java function that expects a non-null value, and throws NPE if given null as argument. You might want to double check whether throwing NPE is what you intended for your function in this case.

    • the other (bigger) problem is that you might be assuming that Optional<T> is covariant in T, but it's actually invariant (probably because it's a Java class and nothing special was done about it, but there is an issue to change this). To put it more clearly, this means that Optional<A> is not a subtype of Optional<B> even if A is a subtype of B, and thus you cannot assign an Optional<String> value to a variable with type Optional<String?>.

    • the third thing is that Optional factory functions like of and ofNullable actually return the non-nullable projection of the type you pass, so always Optional<T & Any> (because a null value will respectively fail or result in an empty Optional). For instance, Optional.of<String?>("abc") still returns an Optional<String>, which doesn't play well with the fact that Optional is invariant in T.

    This explains why your explicit type (Optional<T>) of the optional variable is incorrect. Because Optional.of returns a Optional<T & Any> , which cannot be assigned to a variable of type Optional<T> (even though T & Any is a subtype of T).

    This might be a lot of jargon, so let me clarify by using your string example, but instead of using String as T let's use String?:

    fun returnOptionalOfString(t: String?): Optional<String?> {
        val optional: Optional<String?> = Optional.of(t) // error here
        return optional
    }
    

    Here you'll see the same error for the assignment, but in easier terms.

    As @marstran already mentioned, one way to solve all of this is to just use non-nullable types when dealing with Optionals. In your case, this means constraining T using : Any:

    fun <T : Any> returnOptionalOf(t: T): Optional<T> {
        val optional: Optional<T> = Optional.of(t)
        return optional
    }