Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 363
Bryan Avatar asked Jun 23 '17 19:06

Bryan


People also ask

Can null be a default value?

If no default value is declared explicitly, the default value is the null value. This usually makes sense because a null value can be considered to represent unknown data. In a table definition, default values are listed after the column data type.

Is it mandatory to specify a default value to optional parameter?

Every optional parameter in the procedure definition must specify a default value. The default value for an optional parameter must be a constant expression. Every parameter following an optional parameter in the procedure definition must also be optional.

Why are optional parameters bad?

The thing with optional parameters is, they are BAD because they are unintuitive - meaning they do NOT behave the way you would expect it. Here's why: They break ABI compatibility ! so you can change the default-arguments at one place.


2 Answers

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
        )
    }
}
like image 177
mfulton26 Avatar answered Oct 14 '22 00:10

mfulton26


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
}
like image 25
holi-java Avatar answered Oct 13 '22 23:10

holi-java