Is there a way to use the default value on a non-optional parameter when null is passed?
Asked Answered
P

3

65

For example, if I have the following data class:

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

And functions that can return null:

fun newName(): String? {}

fun newNumber(): Long? {}

I know I can use the following to use the value of the functions if they are not null:

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

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

But is there a way to just use the default value specified in the constructor of the Data class when the values are null?

I could not find anything in the documentation, but I was hoping something like this would work:

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

But that does not compile.

Puff answered 23/6, 2017 at 19:15 Comment(2)
Instead of if (newName != null) newName else "" you can just use newName ?: "". It's called elvis operator.Squawk
@Squawk Oh, right, I forgot about that! Definitely more succinct, but it still doesn't use the default parameter defined in the class constructor.Puff
L
56

You can define a companion object for your data class and overload its invoke operator to use default values when null is passed:

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
        )
    }
}
Leandroleaning answered 23/6, 2017 at 20:9 Comment(8)
Oh, that is a nice work-around! Though, it makes me wish something like this was built into the language; if only to get rid of the boilerplate.Puff
Will it work if all the second property (number) is removed? I am getting compilation error for data class Data(val name: String = "") { constructor(name: String? = null) : this(name ?: "") }Zins
I think you'll have to pass in null or add another secondary constructor. e.g. constructor(): this("")Leandroleaning
@Zins I realized this overload resolution ambiguity can be avoided, I've updated my answer accordinglyLeandroleaning
@Leandroleaning It perfectly works when the number: Long parameter is there. But I guess If we keep only the name: String parameter, the problem will occur.Zins
@Zins you are right, I found that using a companion object with the invoke operator should work in both cases, I've updated my answer accordingly; i.e. now it will work without number: Long and only keep name: String 👍🏻Leandroleaning
Why do we need to override the invoke operator? Can't we just overload the constructor with another version that takes null and then call the primary constructor with the defaults?Bolster
That was how the original answer did it but eendroroy pointed out here that it doesn't work under certain circumstances (e.g. remove number: Long). If you only have name: String then you get Exception in thread "main" java.lang.ClassFormatError: Duplicate method name "<init>" with signature "(Ljava.lang.String;)V" in class file DataLeandroleaning
S
26

the secondary constructor only supports for the Nullable primitive properties. which means it will result in 2 same constructors if the property is not a primitive type, for example:

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

an alternative way is using invoke operator for that, for example:

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

IF the class is not a data class, then you can lazy initializing properties from parameters, rather than define properties on the primary constructor, for example:

class Data(name: String? = null, number: Long? = null) {
    val name = name ?: ""
    val number = number ?: 0
}
Serf answered 24/6, 2017 at 2:52 Comment(0)
R
1

If needed, I can offer another solution:

data class Data(
    val inputName: String?,
    val inputNumber: Long?
) {
    private val name = inputName ?: ""
    private val number = inputNumber ?: 0
}
Rhombohedral answered 28/4, 2022 at 13:57 Comment(2)
It can be done by setting up the default values inside the constructor of data class. Something like this: data class Data( val inputName: String? = "", val inputNumber: Long? = 0L )Charlacharlady
Yep way better.Rhombohedral

© 2022 - 2024 — McMap. All rights reserved.