60

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.

Daniel Storm
  • 18,301
  • 9
  • 84
  • 152
Bryan
  • 14,756
  • 10
  • 70
  • 125
  • 4
    Instead of `if (newName != null) newName else ""` you can just use `newName ?: ""`. It's called elvis operator. – Mibac Jun 23 '17 at 19:27
  • @Mibac Oh, right, I forgot about that! Definitely more succinct, but it still doesn't use the default parameter defined in the class constructor. – Bryan Jun 23 '17 at 19:44

3 Answers3

52

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
        )
    }
}
mfulton26
  • 29,956
  • 6
  • 64
  • 88
  • 10
    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. – Bryan Jun 23 '17 at 20:14
  • 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 ?: "") }` – eendroroy Jun 17 '20 at 15:52
  • I think you'll have to pass in `null` or add another secondary constructor. e.g. `constructor(): this("")` – mfulton26 Jun 18 '20 at 01:15
  • @eendroroy I realized this overload resolution ambiguity can be avoided, I've updated my answer accordingly – mfulton26 Jun 18 '20 at 01:21
  • @mfulton26 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. – eendroroy Jun 18 '20 at 15:50
  • 1
    @eendroroy 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` – mfulton26 Jun 18 '20 at 18:59
  • 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? – findusl Sep 28 '21 at 07:14
  • That was how the original answer did it but [eendroroy](https://stackoverflow.com/users/2396067/eendroroy) pointed out [here](https://stackoverflow.com/questions/44728519/is-there-a-way-to-use-the-default-value-on-a-non-optional-parameter-when-null-is/44729234?noredirect=1#comment110452580_44729234) 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 Data` – mfulton26 Sep 28 '21 at 13:36
25

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
}
holi-java
  • 29,655
  • 7
  • 72
  • 83
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
}
  • 1
    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 )``` – A S M Sayem May 04 '22 at 19:41
  • Yep way better. –  May 17 '22 at 14:56