Java乐透彩票类LinhaCartao访问ArrayList索引1抛出越界异常
简易乐透游戏开发问题
项目结构
开发的简易乐透彩票游戏共包含3个功能类:
- LinhaCartao:彩票行类,负责管理单条彩票行的号码集合与相关操作
- NumeroLoto:彩票号码实体类,封装号码数值、是否开出状态及对应读写方法
- JogoLoto:程序入口测试类,用于验证功能逻辑
预期业务逻辑
实例化LinhaCartao时传入5个用户选定的号码,先存入holder临时列表,随后遍历每个号码,根据号码所属数值区间(1-9对应索引0、10-19对应索引1,依次类推至80-89对应索引8),将号码插入到初始化容量设为9的ArrayList<NumeroLoto>类型Nums集合的对应索引位置。
异常现象
传入参数(1, 20, 45, 58, 71)调用LinhaCartao构造方法时,执行到判断Nums.get(1) == null的代码分支抛出IndexOutOfBoundsException,异常提示为Index 1 out of bounds for length 1。
附完整代码与异常信息
LinhaCartao 类
package jogoloto; import java.util.ArrayList; /** * * @author Nuno */ public class LinhaCartao { ArrayList<NumeroLoto> Nums; ArrayList<Integer> holder; public LinhaCartao(int n1, int n2, int n3, int n4, int n5) { Nums = new ArrayList<>(9); holder = new ArrayList<>(); holder.add(n1); holder.add(n2); holder.add(n3); holder.add(n4); holder.add(n5); for(int n : holder){ if(Nums.isEmpty() && n >=1 && n<=9 || Nums.get(0) == null && n >=1 && n<=9){ NumeroLoto num = new NumeroLoto(n); Nums.add(0,num); } else if(Nums.isEmpty() && n >=10 && n<=19 || Nums.get(1) == null && n >=10 && n<=19){ // 异常抛出位置 NumeroLoto num = new NumeroLoto(n); Nums.add(1,num); } else if(Nums.isEmpty() && n >=20 && n<=29 || Nums.get(2) == null && n >=20 && n<=29){ NumeroLoto num = new NumeroLoto(n); Nums.add(2,num); } else if(Nums.isEmpty() && n >=30 && n<=39 || Nums.get(3) == null && n >=30 && n<=39){ NumeroLoto num = new NumeroLoto(n); Nums.add(3,num); } else if(Nums.isEmpty() && n >=40 && n<=49 || Nums.get(4) == null && n >=40 && n<=49){ NumeroLoto num = new NumeroLoto(n); Nums.add(4,num); } else if(Nums.isEmpty() && n >=50 && n<=59 || Nums.get(5) == null && n >=50 && n<=59){ NumeroLoto num = new NumeroLoto(n); Nums.add(5,num); } else if(Nums.isEmpty() && n >=60 && n<=69 || Nums.get(6) == null && n >=60 && n<=69){ NumeroLoto num = new NumeroLoto(n); Nums.add(6,num); } else if(Nums.isEmpty() && n >=70 && n<=79 || Nums.get(7) == null && n >=70 && n<=79){ NumeroLoto num = new NumeroLoto(n); Nums.add(7,num); } else if(Nums.isEmpty() && n >=80 && n<=89 || Nums.get(8) == null && n >=80 && n<=89){ NumeroLoto num = new NumeroLoto(n); Nums.add(8,num); } else System.out.println("N invalido"); } } public void imprimeLinha(){ for(int i = 0; i<9; i++){ if(Nums.get(i).getNumero()>0){ System.out.println(":"+Nums.get(i).getNumero()+":"); } else if(Nums.get(i)== null){ System.out.println(":_:"); } } } public void marcaNumeroAnunciado(int numero){ for(NumeroLoto n : Nums){ if(n.getNumero()==numero){ n.setSaiu(true); } } } public int quantosNumerosPorMarcar(){ int count = 0; for(NumeroLoto n : Nums){ if(n.getEstado()==false){ count++; } } return count; } public boolean verificaLinhaCompleta(){ int count = 0; for(NumeroLoto n : Nums){ if(n.getEstado()==true){ count++; } if(count==5){ return true; } } return false; } public int limpaLinha(){ int count = 0; for(NumeroLoto n : Nums){ if(n.getEstado()==true){ count++; Nums.remove(n); } } return count; } }
NumeroLoto 类
package jogoloto; /** * * @author Nuno */ public class NumeroLoto { int numero; public boolean saiu; public NumeroLoto(int numero) { this.numero = numero; saiu = false; } public int getNumero() { return numero; } public boolean getEstado(){ return saiu; } public void setSaiu(boolean saiu) { this.saiu = saiu; } }
JogoLoto 入口类
public class JogoLoto { public static void main(String[] args) { LinhaCartao l = new LinhaCartao(1, 20, 45, 58, 71); // 构造方法调用时触发异常 l.imprimeLinha(); l.quantosNumerosPorMarcar(); } }
异常栈轨迹
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1 at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100) at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106) at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302) at java.base/java.util.Objects.checkIndex(Objects.java:359) at java.base/java.util.ArrayList.get(ArrayList.java:427) at jogoloto.LinhaCartao.<init>(LinhaCartao.java:30) at jogoloto.JogoLoto.main(JogoLoto.java:14) C:\Users\Nuno\AppData\Local\NetBeans\Cache\14\executor-snippets\run.xml:111: The following error occurred while executing this line: C:\Users\Nuno\AppData\Local\NetBeans\Cache\14\executor-snippets\run.xml:68: Java returned: 1 BUILD FAILED (total time: 0 seconds)
问题原因
- ArrayList初始容量≠实际元素数量:
new ArrayList<>(9)只是给底层数组预分配了9个位置的内存,初始化完成后列表实际长度为0,不会自动填充9个null,只有调用add()方法才会增加实际元素长度。处理第一个号码1时,将元素add到索引0位置,此时列表实际长度只有1,仅索引0是合法可访问的。 - 逻辑运算符优先级错误:
&&运算优先级高于||,分支判断没有加括号,实际执行逻辑和预期不符。处理第二个号码20时,Nums已经非空,第一个if分支不成立,走到第二个else if时,会直接执行Nums.get(1)的判断,而此时列表长度只有1,访问索引1直接触发越界异常。 - 其他隐藏问题:
- 直接调用
add(index, element)向非连续索引插入元素会持续触发越界,因为add不会自动补空位,要求index必须≤当前列表长度 imprimeLinha方法先调用Nums.get(i).getNumero()再判断元素是否为null,会触发空指针limpaLinha方法在for-each遍历列表时直接调用remove删除元素,会触发并发修改异常
- 直接调用
修复方案
- 初始化Nums时先填充9个null,保证索引0-8都可正常访问:
Nums = new ArrayList<>(9); for (int i = 0; i < 9; i++) { Nums.add(null); }
- 去掉冗余的9个重复分支,直接通过公式计算号码对应的索引,用
set方法替换对应位置的null值,避免add导致的索引问题:
for(int n : holder){ if(n < 1 || n > 89){ System.out.println("N invalido"); continue; } int idx = (n - 1) / 10; // 自动计算对应区间索引,无需手写判断 if(Nums.get(idx) == null){ Nums.set(idx, new NumeroLoto(n)); } }
- 修正
imprimeLinha的空指针问题,先判空再读取属性:
public void imprimeLinha(){ for(int i = 0; i<9; i++){ NumeroLoto num = Nums.get(i); if(num == null){ System.out.println(":_:"); } else { System.out.println(":"+num.getNumero()+":"); } } }
- 修正
limpaLinha的遍历删除问题,使用迭代器遍历删除,避免并发修改异常。
内容的提问来源于stack exchange,提问作者nuno
相关产品推荐
相关产品推荐

