ycomp
ycomp

Reputation: 8583

Can I pass type information to simplify this somehow?

I have a lot of code like this, it is all the same except for the type PositionJson, it could be AnotherJson or FooJson or BarJson

Is there some way I can exctract all this code into one function that I can somehow pass into it the type? So that I don't have several of these big blocks of almost identical code littering my class?

I'm not sure if this is possible or not, just thought I'd ask because it would be nice to do...

    /**
     * @return the _open_ [PositionJson]s
     */
    val positions: Array<PositionJson>?
        @Throws(AccountsAPIException::class)
        get() {
            val service = constructServiceURL(POSITIONS, null, true)
            try {
                val messageJson = mapper.readValue<MessageJson<Array<PositionJson>>>(
                        callURL(service),
                        object: TypeReference<MessageJson<Array<PositionJson>>>() {

                        })

                val error = messageJson.error
                if (error != null) throw AccountsAPIException(error.errorCode, error.description)
                return messageJson.data
            } catch (e: Exception) {
                throw AccountsAPIException(e)
            }
        }

Upvotes: 0

Views: 73

Answers (1)

ean5533
ean5533

Reputation: 8994

You can do what you want with generics. However, to use generics we first need to extract that giant block of code into a method:

val positions: Array<PositionJson>? get() = getPositions()

fun getPositions(): Array<PositionJson>? {
    ...
}

We haven't solved the problem, but now we're in a position to be able to solve it by making getPositions generic (note that I also rename the function):

val positions: Array<PositionJson> get() = getArrayOf<PositionJson>()
// thanks to type inference I can omit the type on getArrayOf if desired:
val positions: Array<PositionJson> get() = getArrayOf()

fun <T> getArrayOf(): Array<T>? {
    val service = constructServiceURL(POSITIONS, null, true)
    try {
        val messageJson = mapper.readValue<MessageJson<Array<T>>>(
                callURL(service),
                object: TypeReference<MessageJson<Array<T>>>() {

                })

        val error = messageJson.error
        if (error != null) throw AccountsAPIException(error.errorCode, error.description)
        return messageJson.data
    } catch (e: Exception) {
        throw AccountsAPIException(e)
    }
}

Perfect! Except this won't compile thanks to type erasure. But we can fix this too by making the function inline and making the type parameter reified:

inline fun <reified T: Any> getArrayOf(): Array<T>? {
    ...
}

And that should do it. Now you can reuse this function as needed:

val positions: Array<PositionJson>? get() = getArrayOf()
val persons: Array<PersonJson>? get() = getArrayOf()
val bananas: Array<BananaJson>? get() = getArrayOf()

inline fun <reified T: Any> getArrayOf(): Array<T>? {
    val service = constructServiceURL(POSITIONS, null, true)
    try {
        val messageJson = mapper.readValue<MessageJson<Array<T>>>(
                callURL(service),
                object: TypeReference<MessageJson<Array<T>>>() {

                })

        val error = messageJson.error
        if (error != null) throw AccountsAPIException(error.errorCode, error.description)
        return messageJson.data
    } catch (e: Exception) {
        throw AccountsAPIException(e)
    }
}

One last thing: note that in all my examples I used property getters (get() = ...) as in your original code. However, I strongly suspect that you do NOT want to use a getter. Getters will be called every time someone accesses your property, which in this case means that every time someone reads the positions property you'll be calling constructServiceURL and making the service call, etc. If you want that code to only happen once then you should just call getArrayOf() once and assign the result to your property:

val positions: Array<PositionJson>? = getArrayOf()
// this syntax would also work:
val positions = getArrayOf<PositionJson>()

Upvotes: 2

Related Questions