You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 21:07:03