当传入null时,是否有一种方法可以在非可选参数上使用默认值?

62

例如,如果我有以下数据类:

data class Data(
    val name: String = "",
    val number: Long = 0
)

可以返回 null 的函数:

fun newName(): String? {}

fun newNumber(): Long? {}

如果函数的值不是 null,我知道我可以使用以下代码来使用它们的值:

val newName = newName()
val newNumber = newNumber()

val data = Data(
        if (newName != null) newName else "",
        if (newNumber != null) newNumber else 0
)

但是,如果值为null,是否有一种方法可以只使用Data类构造函数中指定的默认值呢?

我在文档中找不到任何内容,但我希望像这样的东西可以起作用:

val data = Data(newName()?, newNumber()?)

但是那样不会编译。


4
你可以使用 Elvis 操作符 newName ?: "" 来代替 if (newName != null) newName else ""。这个操作符可以使代码更简洁易懂。 - Mibac
@Mibac 哦,对了,我忘记了!这样做确实更简洁,但它仍然没有使用类构造函数中定义的默认参数。 - Bryan
3个回答

52
你可以为你的数据类定义一个伴生对象,并重载它的invoke操作符,以在传递null时使用默认值:
data class Data private constructor(
    val name: String,
    val number: Long
) {
    companion object {
        operator fun invoke(
            name: String? = null,
            number: Long? = null
        ) = Data(
            name ?: "",
            number ?: 0
        )
    }
}

11
哦,这是一个很好的解决方法!尽管如此,它让我希望像这样的东西被集成到语言中;只是为了摆脱样板代码。 - Bryan
我认为你需要传入 null 或者添加另一个次要构造函数。例如:constructor(): this("") - mfulton26
1
@eendroroy,你是对的,我发现使用伴生对象和调用运算符应该在两种情况下都可以工作,我已经相应地更新了我的答案;即现在它将在没有 number: Long 的情况下工作,只保留 name: String - mfulton26
为什么我们需要重载调用操作符?我们不能只是重载构造函数,并提供另一个带有null参数的版本,然后使用默认值调用主构造函数吗? - findusl
这就是原始答案的做法,但eendroroy这里指出,在某些情况下它不起作用(例如删除number: Long)。如果你只有name: String,那么你会得到Exception in thread "main" java.lang.ClassFormatError: Duplicate method name "<init>" with signature "(Ljava.lang.String;)V" in class file Data - mfulton26
显示剩余3条评论

25

次要构造函数仅支持Nullable原始属性。这意味着如果属性不是原始类型,将导致2个相同的构造函数,例如:

data class Data(val name: String) {
    constructor(name: String? = null) : this(name ?: "foo");
    // ^--- report constructor signature error                
}

data class Data(val number: Long = 0) {
     constructor(number: Long? = null) : this(number ?: 0)
     //                  ^--- No problem since there are 2 constructors generated:
     //                       Data(long number) and Data(java.lang.Long number)
}

另一种方法是使用 invoke 运算符,例如:

data class Data(val name: String) {
    companion object {
        operator fun invoke(name: String? = null) = Data(name ?: "")
    }
}

如果这个类不是数据类,那么你可以从参数中进行懒加载属性的初始化,而不是在主构造函数中定义属性,例如:

class Data(name: String? = null, number: Long? = null) {
    val name = name ?: ""
    val number = number ?: 0
}

1
如果需要,我可以提供另一个解决方案:
data class Data(
    val inputName: String?,
    val inputNumber: Long?
) {
    private val name = inputName ?: ""
    private val number = inputNumber ?: 0
}

1
可以通过在data class的构造函数中设置默认值来实现。就像这样: val inputName: String? = "", val inputNumber: Long? = 0L )``` - A S M Sayem
是的,好多了。 - user12148074

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接