I have 2 similar functions:

fun f1(String key, value: JsonNullable<ByteArray?>): Modification? {
    return if (value.isPresent) {
        when (val rawValue = value.get()) {
            null -> delete(key)
            else -> bar(field, rawValue)
        }
    } else {
        null
    }
}

==============

fun f2(String key, value: JsonNullable<String?>): Modification? {
    return if (value.isPresent) {
        when (val rawValue = value.get()) {
            null -> delete(key)
            else -> bar(field, rawValue)
        }
    } else {
        null
    }
}

Is there way to replace it with the single function using generic ?

bar function is overloaded:

fun bar(field: String, value: JsonNullable<ByteArray?>)...
fun bar(field: String, value: JsonNullable<String?>)...
2

There are 2 best solutions below

6
On

Following the Kotlin documentation on generics, you can write a single generic function using a type variable:

fun <T> f(String key, value: JsonNullable<T>): Modification? {
    return if (value.isPresent) {
        when (val rawValue = value.get()) {
            null -> delete(key)
            else -> bar(field, rawValue)
        }
    } else {
        null
    }
}
10
On

I think you are thinking about this incorrectly String does not inherit from ByteArray (or vice versa) and they don't inherit from any base class except Object.

Upper bounds The most common type of constraint is an upper bound, which corresponds to Java's extends keyword:

So unless you just want it to be anything that extends Object (Both String an ByteArray do extend object) you will need to use generics.

I think this example will give you a basic understanding

https://pl.kotl.in/umls8tYpV

You use a generic and then use an if or switch statement to do something that is specific per type.

In the case of using an upper bounds with Object it would be these

So if this doesn't answer your question you should probably reword it to make it a little more precise about what you are looking for.

Fair Warning... Java/Scala Developer do very little Kotlin so some stuff may be overly verbose

Also if you want to ONLY accept ByteArray or String you can do this...

sealed class JsonType {
    data class JsonString(val value: String) : JsonType()
    data class JsonByteArray(val value: ByteArray) : JsonType()
}

data class JsonNullable<T>(val value: T?)

fun f2(key: String, value: JsonNullable<JsonType>?): String? {
    when (val jsonType = value?.value) {
        is JsonType.JsonString -> {
            println("Welp it is String: ${jsonType.value}")
        }
        is JsonType.JsonByteArray -> {
            println("Welp it is Byte: ${String(jsonType.value)}")
        }
        null -> {
            println("JSON is null")
        }
        else -> {
            println("Unknown JSON type")
        }
    }
    return null
}

/**
 * You can edit, run, and share this code.
 * play.kotlinlang.org
 */
fun main() {
    f2("null", null)
    f2("String", JsonNullable(JsonType.JsonString("String Value")))
    f2("Byte", JsonNullable(JsonType.JsonByteArray("Byte Value".toByteArray())))
    f2("Null JSON", JsonNullable(null))
}