今天又碰上这老朋友——索引越界。写代码时间长,这种错时不时就会跳出来烦你一下,虽然不是啥大问题,但排查起来有时候也挺磨人的。正好今天解决,就顺手记一笔。
是这么个事儿,我当时正在捣鼓一个数据列表,想从里面按顺序取点东西出来处理。思路挺简单的,就是写个循环,从头到尾过一遍,拿到数据再干别的。
开始动手
我就噼里啪把代码写好,大概就是弄个循环,用个变量 `i` 从 0 开始,每次加 1,然后用 `list[i]` 这种方式去拿列表里的数据。心里想着这不挺简单的嘛跑起来应该没啥问题。
结果一运行,程序“咣”一下就停,控制台报一堆红字,定睛一看,核心就是那个 `ArrayIndexOutOfBoundsException` 或者类似的提示,反正意思就是“索引超出边界”。说我用的那个 `i` ,数字不对,列表里没那么多东西。
排查过程
第一反应:是不是循环条件写错?比如列表里明明只有 5 个东西,我循环条件写成 `i < 6` 或者 `i <= 5` ,那肯定一个就越界。回去仔细检查循环的头和尾,`for (int i = 0; i < *(); i++)` 这样的,看着没毛病,`*()` 获取的大小是动态的,`i` 是从 0 到 `size() - 1`,正好是合法的索引范围。
第二反应:难道是哪个地方直接用个固定的数字去访问列表?比如代码某个角落写死 `list[10]`,但列表根本没那么长。全局搜一下,也没有发现这种硬编码的、可能出问题的索引。
有点懵:循环条件没错,直接访问的固定索引也没找到问题。那怪,怎么还会越界?
我就开始用最笨的办法,在循环前后,还有循环里面,加打印语句。把列表的大小 `*()` 打印出来看看,再把每次循环的索引 `i` 也打印出来。
找到原因
重新跑一遍,盯着控制台输出看。一开始都好好的,列表大小是 5,索引从 0 到 4 正常打印和访问。但是!在某个特定的操作之后,下一次循环开始前,我发现打印出来的 `*()` 居然变成 0!但是我的循环逻辑没停,它还是尝试去执行 `list[0]`,这列表都空,你上哪儿找第 0 个元素去?可不就报错嘛
原来问题出在,我在循环的过程中,或者说在处理某个特定数据的时候,有可能触发一个逻辑,把这个列表给清空,或者移除所有元素。但我后续的循环代码并没有考虑到这个列表可能变空的情况,还是傻乎乎地按原来的逻辑去访问,自然就撞墙。
解决办法
找到原因就好办。处理方法也直接:
- 加判断: 在每次循环访问 `list[i]` 之前,加个判断。最简单的,判断一下列表是不是空的。
if (*() > 0 && i < *()) { ...访问list[i]... }
这样,确保列表有东西,并且当前的 `i` 确实在范围内,才去访问。或者更干脆,在循环开始前就判断 `if (*()) { // 直接跳过或做其他处理 }`。 - 调整逻辑: 更根本的是,审视一下为啥列表会被意外清空。如果那个清空操作是符合业务逻辑的,那就在清空后,确保后续的访问逻辑能正确处理空列表的情况,比如直接结束循环。
我选第一种,加个判断,简单有效。再跑,程序就顺利跑完,没再报错。
一点想法
说到底,这“索引越界”多数时候还是自己代码逻辑不够严谨造成的。特别是处理动态数据、列表长度会变化的情况,一定要多考虑边界条件,比如列表为空、列表只有一个元素等等这些特殊情况。写代码的时候稍微多想一步,加个判断,就能省下后面不少排查问题的时间。这回也算又给自己提个醒。