ste9206
ste9206

Reputation: 1892

IndexOutOfBoundsException for for-loop in Kotlin

I have two lists in Kotlin, of the same size, foodObjects: MutableList<ParseObject>? and checked: MutableList<Boolean>?. I need to do a for loop and get the objectId from foodObjects every time that an element of checked is true. So it is this in Java:

for (int i = 0; i < foodObjects.size(); i++) {
    // here
}

But in Kotlin, I don't know why, there are some problems. In fact, if I do this:

for (i in 0..foodObjects!!.size) {
    if (checked?.get(i) == true) {
        objectsId?.add(foodObjects.get(i).objectId)
    }
}

I've got IndexOutOfBoundsException. I don't know why, it continues the loop also at foodObjects.size. I could do it also with filter and map:

(0..foodObjects!!.size)
    .filter { checked?.get(it) == true }
    .forEach { objectsId?.add(foodObjects.get(it).objectId) }

but I'm getting the same error. I use this to stop the error and get it to work:

for (i in  0..foodObjects!!.size) {
    if (i < foodObjects.size) {
        if (checked?.get(i) == true) {
            objectsId?.add(foodObjects.get(i).objectId)
        }
    }
}

Everyone could tell me why in Kotlin I need to do it, when in Java it works good?

Upvotes: 3

Views: 10151

Answers (3)

Mahozad
Mahozad

Reputation: 24532

Here are various ways to ensure the index is valid:

if (index in myList.indices) {
  // index is valid
}
// The rangeUntil operator (..<) is still exprimental in Kotlin 1.7.20
if (index in 0..<myList.size) {
  // index is valid
}
if (index in 0 until myList.size) {
  // index is valid
}
if (index in 0..myList.lastIndex) {
  // index is valid
}
if (index >= 0 && index <= myList.lastIndex) {
  // index is valid
}
// Note: elements of the list should be non-null
if (myList.getOrNull(index) != null) {
  // index is valid
}
// Note: elements of the list should be non-null
myList.getOrNull(index)?.let { element ->
  // index is valid; use the element
}

Upvotes: 0

Willi Mentzel
Willi Mentzel

Reputation: 29844

Take a look at this example from the Kotlin documentation for ranges:

if (i in 1..10) { // equivalent of 1 <= i && i <= 10
    println(i)
}

As you can see

1, 2, 3, 4, 5, 6, 7, 8, 9, 10

will be printed. So, the 10 is included.

The highest index of your collection foodObjects is (foodObjects.size() - 1) because it starts with 0.

So, to fix your problem, just do this:

for(i in  0..(foodObjects.size - 1)) { 
   // ...
}

A better way to write this would be:

for((i, element) in foodObjects.withIndex()){
   // do something with element
   println("The index is $i")
}

This way you have the element and the index at once and don't need to worry about ranges.

*I removed the null checks for simplicity.

Upvotes: 0

zsmb13
zsmb13

Reputation: 89578

Ranges in Kotlin are inclusive, therefore 0..foodObjects!!.size starts at 0 and ends at foodObjects.size, including both ends. This causes the exception when your loop attempts to index the list with its own size, which is one more than the largest valid index.

To create a range that doesn't include the upper bound (like your Java loop), you can use until:

for(i in 0 until foodObjects!!.size) {
    // ...
}

You could also clean your code up a bit if you did null checks on the collections you're using up front:

if (foodObjects != null && checked != null && objectsId != null) {
    for (i in 0 until foodObjects.size) {
        if (checked.get(i) == true) {
            objectsId.add(foodObjects.get(i).objectId)
        }
    }
}
else {
    // handle the case when one of the lists is null
}

And to get rid of having to handle indexes altogether, you can use the indices property of a list (plus I use the indexing operator here instead of get calls):

for (i in foodObjects.indices) {
    if (checked[i]) {
        objectsId.add(foodObjects[i].objectId)
    }
}

You could also use forEachIndexed:

foodObjects.forEachIndexed { i, foodObject ->
    if (checked[i]) {
        objectsId.add(foodObject.objectId)
    }
}

Upvotes: 12

Related Questions