Is it possible to make safe inline Optional in Kot

2020-07-30 03:27发布

问题:

In Kotlin sometimes I have to work with double nullability. For example, I need double nullability, when I want to use T? where T may be a nullable type. There are a few approaches for doing this:

  1. Holder<T>? where Holder is data class Holder<out T>(val element: T) - example1
  2. boolean flag variable - example1
  3. containsKey for Map<K, T?> - example1
  4. The special UNINITIALIZED_VALUE for representing the second kind of null - example1

The last approach has the best performance, but it's also the most error-prone. So I've decided to encapsulate it in inline class Optional<T>:

inline class Optional<out T> @Deprecated(
    message = "Not type-safe, use factory method",
    replaceWith = ReplaceWith("Optional.of(_value)")
) constructor(private val _value: Any?) {
    val value: T?
        get() =
            @Suppress("UNCHECKED_CAST")
            if (isPresent) _value as T
            else null

    val isPresent: Boolean
        get() = _value != NULL

    companion object {
        @Suppress("DEPRECATION")
        fun <T> of(value: T) = Optional<T>(value)

        fun <T : Any> ofNullable(value: T?): Optional<T> =
            if (value == null) EMPTY
            else of(value)

        @Suppress("DEPRECATION")
        val EMPTY = Optional<Nothing>(NULL)
    }

    private object NULL
}

inline fun <T> Optional<T>.ifPresent(code: (T) -> Unit) {
    @Suppress("UNCHECKED_CAST")
    if (isPresent) return code(value as T)
}

inline fun <T> Optional<T>.or(code: () -> T): T {
    ifPresent { return it }
    return code()
}

The first problem with this Optional is public constructor, which allows creating instances with arguments of not matching type.

The second problem was noticed at testing time. Here is the failed test:

emptyOr { Optional.EMPTY }.value assertEql null
fun <T> emptyOr(other: () -> T): T = Optional.EMPTY.or(other)

Exception:

Exception ClassCastException: Optional$NULL cannot be cast to Optional
    at (Optional.kt:42) // emptyOr { Optional.EMPTY }.value assertEql null

If I remove inline modifier from Optional, the test will pass.

Q: Is there any way to fix these problems without removing inline modifier from Optional?


1 Examples include some context. Please read them fully before writing that I added incorrect links.