简体   繁体   中英

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?

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)
    }
}

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.

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM