8

Take the following example which uses safe call operator (?.):

class Sample {
    class A(
            val sampleB: B? = B()
    )

    class B(
            val sampleC: C = C()
    )

    class C(
            val sampleInt: Int = 1
    )

    fun test() {
        val intInC: Int? = A().sampleB?.sampleC?.sampleInt
    }
}

I understand that we need a safe call operator on sampleB. But why do we need the safe call operator on sampleC. If I remove that operator, it does not compile.

Based on my understanding of the operator, if sampleB were null, the line returns null. And if sampleB is not null, we can be sure that sampleC is not null, based on its type. But why does Kotlin force safe call operator on sampleC ?

Nishanth
  • 1,801
  • 21
  • 25
  • Because the function returns the result of the call or `null`, which is still a nullable value. It doesn't return the non-null type. You can see a difference by doing `A().sampleB?.sampleC?.` and `A().sampleB!!.sampleC`. `!!` returns the non-null type or throws a `NullPointerException`. – mkobit Nov 18 '17 at 03:23

3 Answers3

7
A().sampleB?.sampleC?.sampleInt

parses as

((A().sampleB)?.sampleC)?.sampleInt

The types are

A(): A
A().sampleB: B?
(A().sampleB)?.sampleC: C?
((A().sampleB)?.sampleC)?.sampleInt: Int?

Because the type before sampleC is a B?, the ?. is required.

ephemient
  • 198,619
  • 38
  • 280
  • 391
2

First of all, the safe call operator ?. will not break the chain of safe calls. When you write A().sampleB?.sampleC?.sampleInt, if A().sampleB is null, the chain will not stop at ?.sampleC but it will execute null?.sampleC. The type of A().sampleB?.sampleC will be C? but not C since it depends on the whole expression but not the type of the property. That's why ?. is needed if the previous expression is nullable.

If sampleB is the only nullable expression in the chain, you may consider using .run:

val intInC: Int? = A().sampleB?.run { sampleC.sampleInt }
BakaWaii
  • 6,732
  • 4
  • 29
  • 41
0

I am guessing you are not convinced because, as you said, if it reaches the point in the chain where .sampleC is called it's because sampleB was not null, and if it was null it would not have reached this point.

The point here is that you are thinking in terms of implementation and, in that sense, you may be right. But you need to interpret it semantically: even though, for optimization reasons let's say, it doesn't need to go to the end of line when it finds a null before, still, the whole line needs to have a coherent meaning even when there is a null. And for that to be true you need the ? symbol.

Ekeko
  • 1,879
  • 14
  • 15