简体   繁体   中英

Generalize method with nullable arguments and return type

I have a method that converts ByteArray? to base64 String? so that if argument was null output will be null as well. This is its implementation:

fun toBase64String(array: ByteArray?): String? = if(array == null) null else 
    Base64.getEncoder().encodeToString(array)

But when I pass in not nullable ByteArray method returns String? which is expected. Is there a way to make it generic so such use case will be possible:

val base64 = toBase64String(ByteArray(4))

where base64 will be of type String and not String? since argument was not nullable?

I just started to work with Kotlin and probably don't know language feature that can make this possible.

You can make two overloads, one for nullable ByteArray? and one for non-null ByteArray :

fun toBase64String(array: ByteArray): String =   
    Base64.getEncoder().encodeToString(array)

@JvmName("toBase64StringNullable")
fun toBase64String(array: ByteArray?): String? = 
    if (array == null) null else toBase64String(array)

We need @JvmName("...") to avoid the declaration clash in the bytecode. Also, this allows to distinguish the functions in Java.

Usage:

val nonNullBytes: ByteArray = TODO()
val nonNullString = toBase64String(nonNullBytes) // the inferred type is String

val nullableBytes: ByteArray? = TODO()
val nullableString = toBase64String(nullableBytes) // the inferred type is String?

When the argument is of the non-null type ByteArray , the compiler will choose the overload that returns a non-null String .

Probably overloading methods is the best solution for your case, but for the sake of completeness here are two other ways to realise that using only one method (the nullable one):

Not-Null-Asserted operator:

val base64: String = toBase64String(ByteArray(4))!!

Evlis operator:

val base64: String = toBase64String(ByteArray(4)) ?: "defaultString"

if argument was null output will be null as well

If that is the only thing the function does when it encounters null argument, it's better to declare it accepting non-null values and use safe call to deal with nulls:

fun toBase64String(array: ByteArray): String =   
    Base64.getEncoder().encodeToString(array)

val bytes: ByteArray? = ...
val base64 = bytes?.let { toBase64String(it) }
// the same can be written with function reference instead of lambda
val base64 = bytes?.let(::toBase64String)

Here let function is called only when bytes is not null, otherwise the result of the expression is null . When called it invokes the lambda function or the function reference specified as its argument, passing ByteArray which is already checked to be non-null to that function.

Also it can be more convenient to declare toBase64String as an extension for ByteArray , so it can be invoked with safe call without the helper function let "

fun ByteArray.toBase64String(): String =   
    Base64.getEncoder().encodeToString(this)

val bytes: ByteArray? = ...
val base64 = bytes?.toBase64String()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM