Можно ли сделать безопасный встроенный дополнительный в Котлине? - PullRequest
1 голос
/ 06 октября 2019

В Котлине иногда приходится работать с двойной обнуляемостью. Например, мне нужна двойная обнуляемость, когда я хочу использовать T?, где T может быть обнуляемым типом. Есть несколько подходов для этого:

  1. Holder<T>?, где Holder - data class Holder<out T>(val element: T) - пример 1
  2. boolean переменная флага - пример 1
  3. containsKey для Map<K, T?> - пример 1
  4. Специальный UNINITIALIZED_VALUE для представления второго типа null - пример 1

Последний подход имеет лучшую производительность,но это также наиболее подвержен ошибкам. Поэтому я решил инкапсулировать его в 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()
}

Первая проблема с этим Optional - public constructor, которая позволяет создавать экземпляры с аргументами несоответствующего типа.

Вторая проблема была замечена во время тестирования. Вот неудачный тест:

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

Исключение:

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

Если я удалю модификатор inline из Optional, тест пройдет.

Q : Есть ли способ исправить эти проблемы, не удаляя модификатор inline из Optional?


1 Примеры включают некоторый контекст. Пожалуйста, прочитайте их полностью, прежде чем писать, что я добавил неправильные ссылки.

...