Java 9+反序列化特定对象图时出现NullPointerException问题咨询
反序列化兼容性问题:Java 8与新版本JDK差异咨询
我们团队维护一款迭代15年以上的企业级Java应用,核心系统基于Oracle Java 8运行。目前计划开发基于Java 11或Java 21的REST API,调用核心系统的部分EJB。在POC验证阶段发现,特定EJB调用的返回对象在新版本Java环境中无法完成反序列化。
我已制作极简复现示例,当构建示例中的特定对象图并以Airport为根对象序列化到文件后,Java 9及以上版本反序列化时会抛出如下NullPointerException:
java.lang.NullPointerException at edu.gozke.BaseCodeObject.hashCode(BaseCodeObject.java:57) at java.base/java.util.HashMap.hash(HashMap.java:340) at java.base/java.util.HashMap.put(HashMap.java:608) at java.base/java.util.HashSet.readObject(HashSet.java:343) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1046) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2357) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2228) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687) at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2496) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2390) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2228) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687) at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2496) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2390) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2228) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687) at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2496) at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2390) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2228) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1687) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:489) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:447) at edu.gozke.SerializationTest.readObject(SerializationTest.java:47) at edu.gozke.SerializationTest.deserializationFailsIfAirportIsTheRoot(SerializationTest.java:26)
相同代码在Java 8环境中可正常运行,无NPE抛出。相关类代码如下:
public class City extends BaseCodeObject { private final Set<Airport> airports = new HashSet<>(); City(String iataCityCode) { super(iataCityCode); } public void addAirport(Airport airport) { airports.add(airport); } } public abstract class BaseCodeObject implements Serializable, Comparable<BaseCodeObject> { protected String code; protected int hash; protected BaseCodeObject(String code) { assert (code != null); this.code = code; } public final String getCode() { return code; } @Override public int compareTo(BaseCodeObject obj) { return code.compareTo(obj.code); } public boolean equals(Object otherObject) { if (otherObject == this) { return true; } if (otherObject == null) { return false; } if (!(otherObject instanceof BaseCodeObject)) { return false; } BaseCodeObject otherBaseObject = (BaseCodeObject) otherObject; return code.equals(otherBaseObject.code); } @Override public int hashCode() { if (hash == 0) { hash = code.hashCode(); } return hash; } } public class Airport extends CodedBaseDataContainer { Airport(String iataAirportCode) { super(iataAirportCode); } void addAirportData(AirportData data) { addBaseData(data); } } public abstract class CodedBaseDataContainer extends BaseCodeObject { private final static PeriodComparator COMPARATOR = new PeriodComparator(); protected final TreeMap<String, CodedPeriodBaseData> dataMap = new TreeMap<>(COMPARATOR); protected CodedBaseDataContainer(String code) { super(code); } protected final void addBaseData(CodedPeriodBaseData baseData) { dataMap.put(baseData.getEffectivePeriod(), baseData); } private static class PeriodComparator implements Comparator<String>, Serializable { public int compare(String period1, String period2) { return period1.compareTo(period2); } } } public class AirportData extends CodedPeriodBaseData { private final City city; AirportData(Airport airport, City city, String effectivePeriod) { super(airport, effectivePeriod); this.city = city; airport.addAirportData(this); } } public abstract class CodedPeriodBaseData extends BaseCodeObject { private final String effectivePeriod; protected final CodedBaseDataContainer container; protected CodedPeriodBaseData(CodedBaseDataContainer container, String effectivePeriod) { super(container.getCode()); this.container = container; this.effectivePeriod = effectivePeriod; } public String getEffectivePeriod() { return effectivePeriod; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o == null) { return false; } if (!(o instanceof CodedPeriodBaseData)) { return false; } CodedPeriodBaseData data = (CodedPeriodBaseData) o; return code.equals(data.code) && effectivePeriod.equals(data.effectivePeriod); } @Override public int hashCode() { if (hash == 0) { hash = 17; hash = 37 * hash + code.hashCode(); hash = 37 * hash + effectivePeriod.hashCode(); } return hash; } @Override public int compareTo(BaseCodeObject obj) { CodedPeriodBaseData data = (CodedPeriodBaseData) obj; int cmpCode = code.compareTo(data.code); if (cmpCode != 0) { return cmpCode; } return effectivePeriod.compareTo(data.effectivePeriod); } }
复现方法代码:
public void deserializationFailsIfAirportIsTheRoot() throws ClassNotFoundException, IOException { City city = new City("BB"); Airport airport = new Airport("BB"); city.addAirport(airport); new AirportData(airport, city, "dummyKey"); writeToFile(airport, "airport"); Object object = readObject("airport"); System.out.println(object.hashCode()); } private static final Object readObject(String fileName) throws IOException, ClassNotFoundException { try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) { return ois.readObject(); } } private static void writeToFile(Object obj, String fileName) throws FileNotFoundException, IOException { try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) { oos.writeObject(obj); } }
现咨询两个问题:
- 为何新版本JDK的反序列化行为与Java 8不同?
- 应在哪个技术论坛咨询该问题以获取更多信息?
内容的提问来源于stack exchange,提问作者Gozke
相关产品推荐
相关产品推荐

