为什么我被迫在 Kotlin 中使以下函数 属性 可为空?

Why am I forced to make the below functional property nullable in Kotlin?

package algorithms

import algorithms.util.IOUtils

object Calculator {
    /* static abstract class Operation {
     *   private Function logic; //functional interface
     *   Operation(Function f) { this.logic = f );
     * }
     */
    sealed class Operation(val logic : (Int, Int)-> Int)
    /* define singleton types 'ADD', 'SUB', 'MUL', 'DIV' - equivalent to inner class definitions in Java */
    /* static class ADD extends Operation {
     *   ADD(){
     *      super((int x, int y)-> x+y);
     *   }
     * }
     */
    object ADD: Operation({ x: Int , y: Int -> x+y } )
    object SUB: Operation({ x: Int , y: Int -> x-y })
    object MUL: Operation({ x: Int , y: Int -> x*y })
    object DIV: Operation({ x: Int , y: Int -> x/y })

    private fun getOperationFromChar(ch : Char): Operation? {
        return when(ch){
            '+' -> ADD
            '-' -> SUB
            '*' -> MUL
            '/' -> DIV
            else -> null
        }
    }

    fun eval(ch: Char, x: Int, y: Int): Int? {
        val op : Operation? = getOperationFromChar(ch)
        return op?.logic?.invoke(x,y)
    }
}


fun main(){
    println("Result : ${Calculator.eval(
        IOUtils.readChar("Enter desired operation (+,-,*,/) "),
        IOUtils.readInteger("Enter first number"),
        IOUtils.readInteger("Enter second number"))}")
}

上面的代码工作正常,但是,IntelliJ 强制我在中制作 logic return op?.logic?.invoke(x,y) 可为空

虽然Operation的定义sealed class Operation(val logic : (Int, Int)-> Int)没有任何地方提到它可以为null。

我想如果 Operation 对象的定义是 sealed class Operation(val logic : ((Int, Int)-> Int)?) 那么它就有意义了,但事实并非如此。 这是怎么回事?

因为op可以为空;如果将 op 的类型设置为 Operation,则不需要检查 logic.

的可空性

实际上,它检查整个op?.logic的可空性以调用invoke()方法;由于 op 可空性,可以抛出 NullPointerException

安全调用运算符(?.) returns null 如果左边的值为空,否则继续计算右边的表达式。 例如

val x:Int? = 4

x?.dec()?.inc()?.dec()
    
x?.let { 
 it.dec().inc().dec()
}

这是因为 getOperationFromChar() 的 return 值可以为空。

return不是您的操作函数可以为 null 的值。 op 本身已经可以为空。您自己用 val operation: Operation? 定义了它。当您使用 ?. 调用时,结果始终可以为 null,因为如果没有 object 来调用该函数,null 将是结果。

您的 getOperationFromChar() 函数的输入是一个字符。 Char 可以是数千个可能值中的任何一个,而不仅仅是 when 语句中的四个值。这就是编译器强制执行 else 分支的原因。如果你想避免 returning nullable,你可以选择在给出无效输入时抛出错误:

private fun getOperationFromChar(ch : Char): Operation {
    return when(ch){
        '+' -> ADD
        '-' -> SUB
        '*' -> MUL
        '/' -> DIV
        else -> error("Invalid input $ch")
    }
}

然后你可以定义 val op: Operation 并且它可以接受这个函数的结果作为 non-nullable.

当密封 class 是 when 的主题类型时,

Sealed classes 有助于避免需要 else 分支。然后编译器可以确定您对每个可能的输入都有一个分支。你的函数是相反的情况,你的 sealed class 类型是输出,而不是输入。

顺便说一下,在这种情况下,使用枚举而不是密封 class 更明智,因为密封 [=] 的 children 中的 none 36=] 具有独特的属性或功能。

如果你把链式计算分开,就会变得很清楚:

  fun eval(ch: Char, x: Int, y: Int): Int? {
    val op: Operation? = getOperationFromChar(ch)
    val logic: ((Int, Int) -> Int)? = op?.logic
    val retval: Int? = logic?.invoke(x, y)
    return retval
  }

logic 未键入 ((Int, Int) -> Int)((Int, Int ) -> Int)?,因为如果 op 为 null,则 op?.logic 的结果也将为 null。