Как реализовать изменяемый необязательный в Kotlin? - PullRequest
0 голосов
/ 26 апреля 2018

Я хочу класс, который эквивалентен Java Optional, но также

  • Правильно обрабатывает нулевое значение (состояние «Не установлено» отличается от «Нулевого набора»)
  • Изменчиво
  • Использует встроенную в Kotlin нулевую безопасность, параметр типа может быть как обнуляемым, так и ненулевым, что влияет на все методы.

нерабочий код:

class MutableOptional<T> {
    private var value: T? = null
    private var isSet: Boolean = false

    fun set(value: T)
    {
        this.value = value
        isSet = true
    }

    fun unset()
    {
        isSet = false
        value = null
    }

    fun get(): T
    {
        if (!isSet) {
            throw Error("Value not set")
        }
        return value!! // <<< NPE here
    }
}

fun f()
{
    val opt = MutableOptional<Int?>()
    opt.set(null)
    assertNull(opt.get())
}

Проблема в том, что, если я пытаюсь установить значение null, вызов get () завершается с ошибкой нулевого указателя (вызвано оператором !!).

Некоторые неработающие предложения:

  • Не использовать члены типа "T?" в таком классе . Я бы не использовал его, если бы знал, как оставить их неинициализированными (не разрешенными компилятором) или как сделать так, чтобы они имели инициализацию по умолчанию.
  • Используйте "fun get (): T?" (с обнуляемым результатом) . Я хочу, чтобы тип результата имел такую ​​же обнуляемость, что и параметр типа класса. Иначе в такой нулевой безопасности нет смысла, если она потеряна в простом обобщенном классе, и мне нужно будет установить !! вручную, где я уверен, что он не обнуляем (что должен гарантировать компилятор), что делает мой код похожим на клиновую запись.

Примечание: этот пример является синтетическим, мне не нужен изменяемый необязательный параметр, это просто простой и понятный пример, иллюстрирующий проблему, с которой я иногда сталкиваюсь, с обобщениями Kotlin и нулевой безопасностью. Нахождение решения этого конкретного примера поможет со многими подобными проблемами. На самом деле у меня есть решение для неизменной версии этого класса, но оно включает создание интерфейса и двух классов реализации для существующих и отсутствующих значений. Такой неизменный необязательный параметр можно использовать как тип элемента «value», но я думаю, что это довольно большие издержки (учитывая также создание объекта-оболочки для каждого set ()) просто для преодоления языковых ограничений.

Ответы [ 2 ]

0 голосов
/ 25 апреля 2019

У меня похожая проблема. Мой вариант использования состоял в том, чтобы различать нулевое и неопределенное значение при десериализации объекта JSON. Поэтому я создаю неизменяемый необязательный параметр, который может обрабатывать нулевое значение. Здесь я поделюсь своим решением:

interface Optional<out T> {
    fun isDefined(): Boolean
    fun isUndefined(): Boolean
    fun get(): T
    fun ifDefined(consumer: (T) -> Unit)

    class Defined<out T>(private val value: T) : Optional<T> {
        override fun isDefined() = true
        override fun isUndefined() = false
        override fun get() = this.value
        override fun ifDefined(consumer: (T) -> Unit) = consumer(this.value)
    }

    object Undefined : Optional<Nothing> {
        override fun isDefined() = false
        override fun isUndefined() = true
        override fun get() = throw NoSuchElementException("No value defined")
        override fun ifDefined(consumer: (Nothing) -> Unit) {}
    }
}

fun <T> Optional<T>.orElse(other: T): T = if (this.isDefined()) this.get() else other

Хитрость: метод orElse должен быть определен как расширение, чтобы не нарушать ковариацию, потому что Котлин пока не поддерживает нижнюю границу .

Тогда мы можем определить MutableOptional без приведения следующим образом:

class MutableOptional<T> {

    private var value: Optional<T> = Optional.Undefined

    fun get() = value.get()
    fun set(value: T) { this.value = Optional.Defined(value) }
    fun unset() { this.value = Optional.Undefined }
}

Я доволен своей неизменной опциональной реализацией. Но я не очень доволен MutableOptional: мне не нравится предыдущее решение, основанное на приведении (я не люблю приведение). Но мое решение создает ненужный бокс, это может быть хуже ...

0 голосов
/ 26 апреля 2018

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

Если вы храните T? в свойстве, это тип, отличный от T в случае аргументов ненулевого типа, поэтому вы не можете использовать T и T? взаимозаменяемо.

Тем не менее, создание неконтролируемого приведения позволяет обойти ограничение и вернуть значение T? как T. В отличие от утверждения, не являющегося нулевым (!!), приведение не проверяется во время выполнения и не завершится ошибкой при обнаружении null.

Измените функцию get() следующим образом:

fun get(): T {
    if (!isSet) {
        throw Error("Value not set")
    }
    @Suppress("unchecked_cast")
    return value as T
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...