Shai Avr
Shai Avr

Reputation: 1340

Initialize an array after it's created in Kotlin?

I have a Building class that represents an array of Room objects. Each room has some properties which are irrelevant for now.

I am getting the rooms from user input like this:

  1. First-line is the number of rooms.
  2. The next n lines give information about the rooms in this format: "[room index] [room properties...]". The index of each room ranges from 0 to n -1.

Example input:

3
0 [room 0 properties: a, b]
2 [room 2 properties: x, y]
1 [room 1 properties: u, v]

which should create the object:

Building: rooms: [Room(a, b), Room(u, v), Room(x, y)]

I can't guarantee that the rooms will be given in order, but I can promise that all rooms will be given as input. If I was in Java, I could easily write code like this:

Scanner in = new Scanner(System.in);
Room[] rooms = new Room[in.nextInt()];
for (int i = 0; i < rooms.length; i++) {
    int roomIndex = in.nextInt();
    // Get room properties from rest of line input
    rooms[roomIndex] = new Room(...);
}

However, in kotlin, there is a little problem with this approach: I have to initialize the rooms array when I create it. I can't create an uninitialized array, like in Java, and then initialize it in a loop. The Array constructor in kotlin takes a size and a lambda parameter to initialize the array in the order of the indices. As I said, I can't promise the input will be given in the order of indices. I can only promise that all rooms will be given and initialized.

I can create an array of nullable rooms (Array<Room?>), but I don't like this idea because all rooms will be given, so null checks and assertions will be useless when using the array later.

Is there a way to create an uninitialized array in kotlin, and tell the compiler not to freak out about this because I promise the array will be fully initialized after the loop runs?

I didn't find a way to cast an Array<Room?> to Array<Room> without creating a copy of the array, which seems wasteful for me. I also don't think I can use something like Array<lateinit Room> as I can do with regular non-collection properties. The only solution I came up with is to initialize the array with a dummy Room object and then actually initialize it in the loop:

val dummy = Room(...) // dummy object with useless properties
val rooms = Array(size) { dummy }

// Real initialization in a loop
repeat(rooms.size) {
    val properties = readline()!!.split(' ')
    val roomIndex = properties[0].toInt()
    // Parse rest of properties
    rooms[roomIndex] = Room(...)
}

What is the best way to create such an array? I don't think I should use Array<Room?> because I can promise every element of the array will be non-null after the loop, and I don't like the idea of a dummy object, but it might be the best bet? Do you have another suggestion?

Thanks in advance.

Upvotes: 1

Views: 212

Answers (1)

I didn't find a way to cast an Array<Room?> to Array without creating a copy of the array

Casting doesn't create a copy of the array (or any other object one cast). It just creates new variable with different type, but the same reference (see proof).

Also, you may create an auxilary function, wrapping nullable array creation and casting:

@Suppress("UNCHECKED_CAST")
inline fun <reified T> buildArray(size: Int, builderAction: Array<T?>.() -> Unit): Array<T> =
    arrayOfNulls<T>(size).also(builderAction) as Array<T>

Usage:

val rooms = buildArray<Room>(size) {
    val properties = readLine()!!.split(' ')
    val roomIndex = properties[0].toInt()
    set(roomIndex, Room(...))
}

Upvotes: 2

Related Questions