Kotlin - How can i return different types from my method?

Viewed 11696

I have this method.

    private fun getOffer(offers: OfferRepresentation, type: OfferType): ???? {
    return when (type) {
        OfferType.ADDON -> offers.addon 
        OfferType.SALE -> offers.sale
        OfferType.PLAN -> offers.plan
        OfferType.CUSTOMPLAN -> offers.customPlan
    }

How can i change this method to return the correct type?

4 Answers

It's hard to give you a difinitive answer without you giving more info, but the easiest way to return multiple types is to have them all share an interface or superclass:

interface Offer

class Addon : Offer
class Sale : Offer
class Plan : Offer
class CustomPlan : Offer

You can also use a sealed class if your options are static, it just depends on your use case. Either way, you can then just have the function return type be Offer

For more information see:

Here is how you can do that:

object Main {

    // concrete class for all "enum" values (such as OfferType.ADDON or OfferType.SALE)
    // that get passed to `getOffer` as second param
    class OfferType<T> {

        companion object Types {

            // "enum" values. Unfortunately Kotlin doesn't support
            // type parameters for real enums
            @JvmStatic
            val ADDON = OfferType<Addon>()

            @JvmStatic
            val SALE = OfferType<Sale>()

            data class Addon(val name: String)
            data class Sale(val name: String)
        }
    }

    data class OfferRepresentation(
        val addon: OfferType.Types.Addon,
        val sale: OfferType.Types.Sale
    )

    @JvmStatic
    fun main(args: Array<String>) {

        fun <T> getOffer(offers: OfferRepresentation, type: OfferType<T>): T {
            @Suppress("UNCHECKED_CAST")
            return when (type) {
                OfferType.ADDON -> offers.addon as T
                OfferType.SALE -> offers.sale as T
                else -> throw IllegalArgumentException("Unsupported type $type")
            }
        }

        val offer = OfferRepresentation(
            OfferType.Types.Addon("addon"),
            OfferType.Types.Sale("sale")
        )

        // types are specified explicitly for the sake of demonstration
        val addon: OfferType.Types.Addon = getOffer(offer, OfferType.ADDON)
        val sale: OfferType.Types.Sale = getOffer(offer, OfferType.SALE)

        println("$addon, $sale")
    }
}

Note that there is an UNCHECKED_CAST in getOffer function. It's your responsibility to ensure that OfferType.ADDON (OfferType.SALE, etc) values are parameterized with the correct OfferRepresentation's field type and when logic is also correct, otherwise you'll get ClassCast exception in runtime.


UPD. There is alternative implementation that uses a bit of kotlin magic, specifically, reified types. With this approach instead of having separate entities to define the field that you want to be returned from getOffer function you just specify explicit type parameter. Alternatively you can rely on implicit type inference:

object Main2 {

    object OfferType {
        data class Addon(val name: String)
        data class Sale(val name: String)
    }

    data class OfferRepresentation(
        val addon: OfferType.Addon,
        val sale: OfferType.Sale
    )

    //reified type parameters require function to be inline
    inline fun <reified T> getOffer(offers: OfferRepresentation): T {
        @Suppress("UNCHECKED_CAST")
        return when (T::class) {
            OfferType.Addon::class -> offers.addon as T
            OfferType.Sale::class -> offers.sale as T
            else -> throw IllegalArgumentException("Unsupported type ${T::class}")
        }
    }

    @JvmStatic
    fun main(args: Array<String>) {

        val offer = OfferRepresentation(
            OfferType.Addon("addon"),
            OfferType.Sale("sale")
        )

        // types needed to be specified explicitly here
        val addon: OfferType.Addon = getOffer(offer)
        val sale: OfferType.Sale = getOffer(offer)

        // alternatively `getOffer` should be invoked like this:
        getOffer<OfferType.Addon>(offer)

        println("$addon, $sale")
    }
}

You have the trivial solution of using Any as your return type. Any is not exactly like Object in Java but it's close enough in this case (https://kotlinlang.org/docs/reference/classes.html):

All classes in Kotlin have a common superclass Any, that is a default super for a class with no supertypes declared:

class Example // Implicitly inherits from Any

So if your return types are completely unrelated and it doesn't make sense for you to make them share a common interface, then you can use Any:

private fun getOffer(offers: OfferRepresentation, type: OfferType): Any {
    return when (type) {
        OfferType.ADDON -> offers.addon 
        OfferType.SALE -> offers.sale
        OfferType.PLAN -> offers.plan
        OfferType.CUSTOMPLAN -> offers.customPlan
    }

You cannot return more than 1 value from a function by the JVM. What you can do is return a Pair or Tuple or create a Data class with all the fields and then have them as optional

Eg:

data class Offer(val type1: String?=null, val type2: Integer? = 0)

Related