Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement mutable optional in Kotlin?

Tags:

kotlin

I want a class which is equivalent to Java Optional but also

  • Properly handles null value ("Not set" state is different from "Null set")
  • Is mutable
  • Uses Kotlin built-in null-safety, type parameter can be either nullable or non-nullable which affects all methods.

Non-working code:

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())
}

The problem is that if I try to set null, get() call fails with null pointer exception (caused by !! operator).

Some not-working proposals:

  • Do not use members of type "T?" in such class. I would not use it if I knew how to leave them uninitialized (not allowed by the compiler) or how to make them to have default initialization.
  • Use "fun get(): T?" (with nullable result). I want the result type to have the same nullability as the class type parameter. Otherwise there is no meaning in such null-safety if it is lost in a simple generic class, and I will need to set !! manually where I am sure it is non-nullable (the thing the compiler should ensure), making my code looking like wedge-writing.

Note: This example is synthetic, I do not really need the mutable optional, it is just a simple and understandable example, illustrating a problem I encounter occasionally with Kotlin generics and null-safety. Finding solution to this particular example will help with many similar problems. Actually I have a solution for immutable version of this class but it involves making interface and two implementation classes for present and non-present values. Such immutable optional can be used as type of "value" member but I think it's quite big overhead (accounting also wrapper object creation for each set()) just to overcome the language constraints.

like image 630
vagran Avatar asked Apr 26 '18 11:04

vagran


People also ask

How do I use Kotlin optional?

When mapping an Optional in Java, sometimes you have to unwrap another Optional . To do this, you use flatMap() instead of map() . With Kotlin's null system, the value is either present, or null , so there's nothing to unwrap. This means that Kotlin's equivalent for flatMap() and map() are similar.

What is Nullability in Kotlin?

Nullability and Nullable Types in Kotlin That means You have the ability to declare whether a variable can hold a null value or not. By supporting nullability in the type system, the compiler can detect possible NullPointerException errors at compile time and reduce the possibility of having them thrown at runtime.

What is mutable Kotlin?

Mutability is a core concept in Kotlin, but all is perhaps not what it seems. The fundamental concept here is if we declare variables using var then they are mutable and can be reassigned with another value, whereas if we declare variables using val then they are immutable and cannot be reassigned.

How do you ensure null safety in Kotlin?

Kotlin has a safe call operator (?.) to handle null references. This operator executes any action only when the reference has a non-null value. Otherwise, it returns a null value. The safe call operator combines a null check along with a method call in a single expression.


1 Answers

The compiler wants you to write code that will be type-safe for all possible T, both nullable and not-null (unless you specify a not-null upper bound for the type parameter, such as T : Any, but this is not what you need here).

If you store T? in a property, it is a different type from T in case of not-null type arguments, so you are not allowed to use T and T? interchangeably.

However, making an unchecked cast allows you to bypass the restriction and return the T? value as T. Unlike the not-null assertion (!!), the cast is not checked at runtime, and it won't fail when it encounters a null.

Change the get() function as follows:

fun get(): T {
    if (!isSet) {
        throw Error("Value not set")
    }
    @Suppress("unchecked_cast")
    return value as T
}
like image 90
hotkey Avatar answered Oct 05 '22 13:10

hotkey