简体   繁体   中英

Why this Kotlin/Android code throws java.lang.ArrayIndexOutOfBoundsException?

    var i = 0

    arrayOf<CheckBox>(binding.chkBackup, binding.chkBackupEnc, binding.chkDrive,
            binding.chkDriveEnc, binding.chkMp3).forEach {

        it.setOnClickListener { chk ->
            root.model.checkedBackups.value?.set(i++, (chk as? CheckBox)?.isChecked ?: false)
        }

    }

Line #267 id the one with 'set' function call

java.lang.ArrayIndexOutOfBoundsException: length=5; index=5
        at info.gryb.gac.mobile.fragments.StoreFragment$onViewCreated$$inlined$forEach$lambda$1.onClick(StoreFragment.kt:267)

There was a comment below about evaluating 'i' during the click event and this is really how it works, however, it contradicts to a common concept in many languages that primitive types are passed by values, eg you can find a good discussion here .

It uses the same principles like Java. It is always pass-by-value, you can imagine that a copy is passed. For primitive types, eg Int this is obvious, the value of such an argument will be passed into a function and the outer variable will not be modified. Please note that parameters in Kotlin cannot be reassigned since they act like val

It looks like in lambda case some kind of wrapper is created around primitive type and that wrapper preserves the state and makes the value mutable.

I've searched official docs about passing parameters to lambdas, but couldn't find anything except discussions similar to what I've quoted above.

Please provide any references describing treating parameters in lambdas. The question was not about fixing it, eg see code below, but about detailed explanation of how those wrappers are created, used and life-cycled for primitive types. Any refs to official Google or InelliJ docs are appreciated. var i = 0

    arrayOf<CheckBox>(binding.chkBackup, binding.chkBackupEnc, binding.chkDrive,
            binding.chkDriveEnc, binding.chkMp3).forEach {
        val t = i++
        it.setOnClickListener { chk ->
            root.model.checkedBackups.value?.set(t, (chk as? CheckBox)?.isChecked ?: false)
        }

    }

After thinking more about this: a wrapper is really a necessity in lambda case, since normal treatment of params by putting them on a stack won't work due delayed calls by target objects (check boxes in this case).

The question remains though: why a single wrapper is shared across all target objects? Would not it be more logical and safe to create a wrapper for each of them? The current pattern is not intuitive and is very dangerous, since there could be other issues eg coming from concurrency. Are calls to that shared wrapper thread safe?

you are accessing element out of bounds. arrays and lists are zero based, which means first element of an array is at index 0, and the last is at position array.length-1

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