Nullable generic parameter


#1

I searched across many similar questions, but still cannot get an answer for this simple example. The example is just one possible case among many others with the same root cause.

class C<T> {
    private var a: T? = null
    private var b: T? = null
    private var isCurrentA = true

    fun SetA(x: T)
    {
        a = x
        isCurrentA = true
    }

    fun SetB(x: T)
    {
        b = x
        isCurrentA = false
    }

    fun GetCurrent(): T
    {
        return if (isCurrentA) a!! else b!! //<<< NPE here
    }
}

@Test
fun F()
{
    val c = C<Int?>()
    c.SetA(null)
    assertNull(c.GetCurrent())
}

The code is synthetic and simplified to illustrate the problem. Null pointer exception is thrown if trying to call GetCurrent() method. What is the proper implementation of such class?


#2

I don’t really understand, what you are trying to do. It has nothing to do with generics. You explicitly are setting a to null twice. Once in variable declaration, and second one in c.SetA(null), and then calling a!! which is by default throwing exception on nulls.

Also your code style clearly showed, that you probably should spend some time reading documentation. Method names should start with lower case letter.


#3

This how I solved it:

fun main(args: Array<String>) {
    f()
}

class C<T> {
    private var a: T? = null
    private var b: T? = null
    private var isCurrentA = true

    fun setA(x: T?)
    {
        a = x
        isCurrentA = true
    }

    fun setB(x: T?)
    {
        b = x
        isCurrentA = false
    }

    fun getCurrent(): T?
    {
        return if (isCurrentA) a else b
    }
}

fun f()
{
    val c = C<Int>()
    c.setA(null)
    println(c.getCurrent())
}

This code prints null as expected and does not throw NPE


#4

In this case you do not need to specify generic type as <Int?> simple <Int> will suffice, because you declared type as nullable T? everywhere in class.


#5

Thanks, I updated the code.


#6

Setting null in initialization is just to set some default value here, actually value is not present at this moment, until Set() is called. The SetA() with null is just an example which illustrates possibility of setting null since my type parameter is nullable (Int?). After that the value is considered present and is null in this case, and somehow should be returned by GetCurrent() method.


#7

No, it does not solve the problem. I want return value to have the same nullability as the type parameter, that’s one of the main points, otherwise it seems that nullability in generic type parameters is not really a first-class-citizen, and cannot really be abstracted of.


#8

Then you should use T instead of T? everywhere, but then you can’t set the value to null in initializator, because compiler does not know whether T is nullable or not.


#9

Yes, that’s the problem (I cannot set null and I also cannot left it uninitialized too), and I am asking for solution.
One more example which may be more clear is implementing analogue of Java Optional class which has very similar principle. But it also should properly handle null value in contrast with Java 8 implementation (so that they are separate states - “None” and “Null set”). And again its get() method result type should have the same nullability as the passed type parameter.
Actually I have implemented such class but I forced to use interface and different implementation classes for present and not-present values. I think it’s quite big overhead for such simple thing which is needed just because of the language constraints, and I’m also looking for simpler solution for that.


#10

I would say that this should not compile:

val c = C<Int?>()

Because class C was defined like this:

class C<T> {
    // ...
}

and not like that:

class C<T?> {
    // ...
}

(note the ‘?’ in the generics parameter list)


#11

No, it compiles. If you want to prevent passing nullable types, it should be declared like this:

class C<T: Any> {
    //...
}

By default it is “class C<T: Any?>”. In my case I want both nullable and not-nullable, so default works.


#12

Another code example for mentioned Optional class, which might be better understandable. It also is mutable to be consistent with the first example.

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

#13

OK, in this case, I propose the following:

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

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

#14

Again, you changed the return type of get(). After that I will need every time in the code use !! operator where I am sure it is non-nullable (which is compiler job by design of all of this “null-safety” feature in Kotlin).


#15

You cannot return null from a method if the return parameter is not declared nullable.There is no way aorund this, nor should there be, otherwise the compiler would have to determine null safety on a case-by-case basis and not by simply inspecting the return type. I shudder to think how long the compilation with such an compiler would take.


#16

But T is nullable type itself (Int?) in this instantiation. I could return null if “value” member would be of type T. But in this case I am not able to provide default initialization for this member.


#17

Well, technically speaking you could do it like this:

class MutableOptional<T> {
    private var _value: T? = null
    private var value: T by Delegate()
    private var isSet: Boolean = false

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

    @Suppress("UNCHECKED_CAST")
    fun unset()
    {
        value = null as T
    }

    fun get(): T
    {
        return value
    }

    class Delegate<T> {
        @Suppress("UNCHECKED_CAST")
        operator fun getValue(thisRef: MutableOptional<T>, property: KProperty<*>): T {
            println("$thisRef, thank you for delegating '${property.name}' to me!")
            if (!thisRef.isSet) {
                throw Error("Value not set")
            }
            return thisRef._value as T
        }

        operator fun setValue(thisRef: MutableOptional<T>, property: KProperty<*>, value: T) {
            println("$value has been assigned to '${property.name}' in $thisRef.")
            thisRef.isSet = true
            thisRef._value = value
        }
    }
}

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

but it looks ugly to me.


#18

Oh, I did not think about cast possibility between T? and T. In this case it can be simpler:

    @Suppress("UNCHECKED_CAST")
    fun get(): T
    {
        if (!isSet) {
            throw Error("Value not set")
        }
        return value as T
    }

That looks like working solution.


#19

I think the main question to vagran was: what should happened BEFORE any value set?
Once the answer is Exception it was easy to come to final solution.

However I see that multiple people in this discussion (including vagran) are confused about nullable classes. The main idea is that and <T?> are two different classes (unlike T!!) and casting is the way to convert more generic to more specific.


#20

The problem is kotlin is explicitly made to avoid solutions like the one @vagran proposed. One should plan the architecture the way you either use nullable or not.
The problem with late initialization of variables could be solved without meddling with generics. First variant is lateinit var. It could be used when you are sure that value will be assigned. Another way is nun-nulable front to nulable field:

private val nullable: T? = null
val nonNulable: T
  get() = nullable ?: throw RuntimeException(...)