Patric
Patric

Reputation: 1637

Array<Number>: get and set Int values without casting

I am building a Matrix class and want to be able to store Numbers in a 2d Array.

var data: Array<Array<Number>> = Array(width, {Array(height, {0})})

This does not work because Array<Number> and Array<Int> are invariant. I can make it work by using Array<Array<out Number>>, but the Matrix will be immutable, and I don't want that...

Casting {0 as Int} makes the compiler error go away, but this does not seem like a good idea. I also want to do things like addition and I noticed that it's not possible to add Numbers:

var n: Number = 1
n + 1 // does not work

So how could I solve this problem? And why exactly can I not add two Numbers?

Upvotes: 3

Views: 69

Answers (1)

Zoe - Save the data dump
Zoe - Save the data dump

Reputation: 28288

Number is an abstract class and does not define anything for addition. And since there's no defined method for adding the numbers, you can't do numberInstane + otherNumberInstance. You can, however, create an operator function for it:

infix operator fun Number.plus(other: Number) : Number{
    return when (this) {
        is Double -> this + other.toDouble()
        is Int -> this + other.toInt()
        is Long -> this + other.toLong()
        is Float -> this + other.toFloat()
        is Short -> this + other.toShort()
        is Byte ->  this + other.toByte()
        else -> 0
    }
}

Note that this only applies to addition. The rest of the functions would follow the same pattern, but replacing the operator (here it's +) and the name of the function (here it's plus).


As mer msrd0's comment, the above would result in 1 + 1.5 being 2, because it rounds down. Kotlin supports adding number types across each other, which ends up with this slightly horrid solution:

infix operator fun Number.plus(other: Number) : Number{

    when {
        this is Double -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Int -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Long -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Float -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Short -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        this is Byte -> {
            return when(other){
                is Double -> this + other
                is Int -> this + other
                is Long -> this + other
                is Float -> this + other
                is Short -> this + other
                is Byte ->  this + other
                else -> 0
            }
        }
        else -> return 0
    }
}

The nested when-statement helps autocasting the values, which is necessary since Number isn't a specific known class. There might be a better solution though, but without knowing the specific type. The extension function is mostly just auto-casting based on the type, but there can't be a single variable because it would need to be defined as a Number to accept all the types, and since there are two vars where both need proper casting based on the passed type, it ends up being slightly messy.

Upvotes: 2

Related Questions