Correctly returning Int from Hashmap in a function

Viewed 91

I have a function in Kotlin which takes in an Int argument and return a value based on a formula. To speed things up I store intermediate results in a HashMap<Int, Int>

private val calculatedResults = HashMap<Int, Int>()

private fun q2(n: Int): Int {
    if(calculatedResults.containsKey(n)) {
            return calculatedResults[n]
    }

    val q = ...
    calculatedResults[n] = q
    return q
}

I'm getting a type mismatch of Int found but Int? required at

return calculatedResults[n]

Im not sure how to correctly write this. I had

return calculatedResults[n]!!

But I'm not sure, if it's a bit hacky.

Should the return type of the function be Int? because while the HashMap might contain the key n the value could be null? Wouldn't that mean the HashMap should be <Int, Int?>?

3 Answers

getOrPut will check if a value exists for n. If the value exists, it will be returned. If the values does not exist the value returned by the lambda will be assigned and afterwards returned by getOrPut.

Take a look at this example:

fun calculate(n: Int) = n * 5 // some calculation

private fun q2(n: Int) = calculatedResults.getOrPut(n) {
    calculate(n)
}

Should the return type of the function be Int? because while the HashMap might contain the key n the value could be null? Wouldn't that mean the HashMap should be ?

In this case the answer to the question is obviously "no" because if the value is missing you just add it. So, the value returned by q2 can never be null.

The underlying problem is that calculatedResults could have been modified by an other thread between contains (if(calculatedResults.containsKey(n))) and get (return calculatedResults[n]).

Even after checking with containsKey calculatedResults[n] could therefore possibly return a wrong result, including null, and this way of checking should be avoided.

Using !! should generally only be used when absolutely necessary, because it can cause a NPE. It definitely is hacky.

Use the following syntax to overcome the problem:

calculatedResults[n]?.let {
    return it
}

Basically this is a nice way of saying

val result = calculatedResults[n]
if(result != null) {
    return result
}

see https://kotlinlang.org/docs/reference/null-safety.html#safe-calls

Even better is to use the built-in getOrPut:

private fun q2(n: Int): Int {
    return calculatedResults.getOrPut(n) { calcQ() }
}

private fun calcQ(): Int {
    val q = ...
    return q
}

Should the return type of the function be Int? because while the HashMap might contain the key n the value could be null? Wouldn't that mean the HashMap should be <Int, Int?>?

No, you did exactly the right thing when you defined the method signature. The compiler only complained because what happened within the method did not fit it. If you change the method signature you just move the problem to the function calling q2 without handling it.

You should consider re-writing this to more idiomatic code. It's a rather basic task to compute a value if it is not set in the map already:

fun q2(n: Int): Int {
    return calculatedResults.getOrPut(n) {
        42 //q
    }
}
Related