Java序列化ArrayList后反序列化偶尔加载失败,重启恢复原因咨询
兄弟,你的这个“有时反序列化加载失败,重启程序又正常”的问题,从代码里能看到几个核心原因,咱们一个个捋清楚:
1. 序列化和反序列化的顺序完全搞反了!
这是最致命的问题,你仔细对比下代码:
- 序列化(Save方法)时写入的顺序是:
ListaCliente→ListaFornecedor→ListaPessoa→ListaStocks→ListaVenda→ListaRecurso - 但反序列化(FormPrincipal构造方法)时读取的顺序是:
ListaCliente→ListaFornecedor→ListaPessoa→ListaRecurso→ListaStocks→ListaVenda
Java反序列化必须严格按照写入的顺序读取,不然会把原本属于ListaStocks的数据强行转成ListaRecurso,直接抛出类型转换异常。为什么重启后有时正常?大概率是某些情况下你没执行新的序列化,读的是之前顺序正确的旧文件;或者异常被你catch住只打了堆栈,程序继续跑但列表是空的,重启后如果文件是正确顺序写入的,就能正常加载。
2. 静态字段的序列化行为不符合预期
你ClassListas里的所有ArrayList都是静态字段,但Java序列化本来是用来序列化对象的实例字段的,静态字段属于类,默认不会被序列化。你现在直接把静态ArrayList逐个写入流,这种写法虽然能跑,但静态字段的状态和JVM进程绑定:
- 同一个JVM进程里,先序列化再反序列化,静态字段可能已经被之前的内容占用,覆盖时容易出冲突;
- 重启JVM后,静态字段会被重新初始化,此时反序列化的内容能正常覆盖,所以重启后就正常了。
3. 流关闭不规范,可能导致文件写入不完整
你的Save方法里手动调用close(),没用try-with-resources,如果在flush之后、close之前程序崩溃,文件就会写入不完整,此时反序列化肯定失败。但重启后如果之前有完整写入的文件,就能正常加载,这也会造成“时好时坏”的现象。
具体修复步骤
第一步:修正序列化/反序列化的顺序
把反序列化的顺序改成和序列化完全一致,代码如下:
// 修正后的反序列化代码 try(FileInputStream fx = new FileInputStream("Gravacao"); ObjectInputStream in = new ObjectInputStream(fx)){ ClassListas.ListaCliente = (ArrayList<ClassCliente>) in.readObject(); ClassListas.ListaFornecedor = (ArrayList<ClassFornecedor>) in.readObject(); ClassListas.ListaPessoa = (ArrayList<ClassPessoa>) in.readObject(); ClassListas.ListaStocks = (ArrayList<ClassStock>) in.readObject(); ClassListas.ListaVenda = (ArrayList<ClassVenda>) in.readObject(); ClassListas.ListaRecurso = (ArrayList<ClassRecurso>) in.readObject(); }catch(Exception e){ e.printStackTrace(); }
第二步:改用实例字段(更规范的序列化方式)
把ClassListas里的静态列表改成实例字段,然后序列化整个ClassListas对象,而不是逐个写列表,这样更符合Java序列化的设计:
// 修改后的ClassListas public class ClassListas implements Serializable { // 把静态字段改成实例字段 public ArrayList<ClassPessoa> ListaPessoa = new ArrayList<ClassPessoa>(); public ArrayList<ClassCliente> ListaCliente = new ArrayList<ClassCliente>(); public ArrayList<ClassFornecedor> ListaFornecedor = new ArrayList<ClassFornecedor>(); public ArrayList<ClassStock> ListaStocks = new ArrayList<ClassStock>(); public ArrayList<ClassVenda> ListaVenda = new ArrayList<ClassVenda>(); public ArrayList<ClassRecurso> ListaRecurso = new ArrayList<ClassRecurso>(); // 可以加个单例方法,方便全局获取实例 private static final ClassListas INSTANCE = new ClassListas(); public static ClassListas getInstance() { return INSTANCE; } } // 修改后的序列化代码 public void Save(){ try(FileOutputStream ficheiro = new FileOutputStream("Gravacao"); ObjectOutputStream out = new ObjectOutputStream(ficheiro)){ // 直接序列化整个ClassListas实例 out.writeObject(ClassListas.getInstance()); }catch(IOException e){ e.printStackTrace(); } } // 修改后的反序列化代码 public FormPrincipal() { initComponents(); try(FileInputStream fx = new FileInputStream("Gravacao"); ObjectInputStream in = new ObjectInputStream(fx)){ ClassListas loadedListas = (ClassListas) in.readObject(); // 把加载后的实例字段赋值给静态变量(如果需要保持全局访问) ClassListas.ListaCliente = loadedListas.ListaCliente; ClassListas.ListaFornecedor = loadedListas.ListaFornecedor; ClassListas.ListaPessoa = loadedListas.ListaPessoa; ClassListas.ListaStocks = loadedListas.ListaStocks; ClassListas.ListaVenda = loadedListas.ListaVenda; ClassListas.ListaRecurso = loadedListas.ListaRecurso; }catch(Exception e){ e.printStackTrace(); } }
第三步:用try-with-resources确保流正确关闭
不管是序列化还是反序列化,都用try-with-resources语法,它会自动帮你关闭流,避免文件写入不完整的问题,上面的代码已经用了这个写法,就不用手动调用close()了。
内容的提问来源于stack exchange,提问作者Miguelito

