java - 将项目迁移到 JDK 8 时出现奇怪的 ExceptionInInitializerError

标签 java static nullpointerexception java-8

我有一个带有一些二进制依赖项的代码(BioJava 3.1.0 是上述问题的根源),它可以在 JDK 7 上正常工作,但是当使用 JDK 8 进行使用和编译时,会发生一些奇怪的事情...这是堆栈跟踪的重要部分:

java.lang.ExceptionInInitializerError
        at org.biojava3.core.sequence.template.AbstractSequence.getSequenceAsString(AbstractSequence.java:527)
        at uk.ac.roslin.ensembl.datasourceaware.core.DADNASequence.getSequenceAsString(DADNASequence.java:465)
...
Caused by: java.lang.NullPointerException
        at java.util.Collections$UnmodifiableCollection.<init>(Collections.java:1026)
        at java.util.Collections$UnmodifiableList.<init>(Collections.java:1302)
        at java.util.Collections.unmodifiableList(Collections.java:1287)
        at org.biojava3.core.sequence.location.template.AbstractLocation.<init>(AbstractLocation.java:111)
        at org.biojava3.core.sequence.location.SimpleLocation.<init>(SimpleLocation.java:57)
        at org.biojava3.core.sequence.location.SimpleLocation.<init>(SimpleLocation.java:53)
...

这是 SimpleLocation 的二进制代码(位于我没有源代码的第 3 方链接库中),它有一个字段 EMPTY_LOCS 构造如下:

public class SimpleLocation extends AbstractLocation {

    private static final List<Location> EMPTY_LOCS = Collections.emptyList();
    ...
    public SimpleLocation(int start, int end, Strand strand) 
        this(new SimplePoint(start), new SimplePoint(end), strand); { //line 53
    }

    public SimpleLocation(Point start, Point end, Strand strand) { 
        super(start, end, strand, false, false, EMPTY_LOCS); //line 57
    ...

似乎当 EMPTY_LOCS 传递给 superAbstractLocation (第 57 行)时,会传递一个 null 并且 不是空列表(我检查了 JDK 7,并且传递了一个很好的旧空列表)。

这是为什么呢?我应该深入研究第 3 方依赖源代码并覆盖它吗? (对我来说听起来不太整洁)

当我自己使用 emptyList() 方法时,它确实返回一个空列表 - 但是这个隐藏在我的依赖项中的私有(private)静态字段有一些反对 Java 8 的东西,只是不想被初始化。

编辑:

AbstractLocation 依次调用 unmodifyingList() (第 111 行),其中包含 null(且非空列表):

public AbstractLocation(Point start, Point end, Strand strand,
        boolean circular, boolean betweenCompounds, AccessionID accession,
        List<Location> subLocations) {
    this.start = start;
    this.end = end;
    this.strand = strand;
    this.circular = circular;
    this.betweenCompounds = betweenCompounds;
    this.accession = accession;
    this.subLocations = Collections.unmodifiableList(subLocations); //line 111
    assertLocation();
}

然后构造UnmodifyingList(行:1287):

public static <T> List<T> unmodifiableList(List<? extends T> list) {
    return (list instanceof RandomAccess ?
            new UnmodifiableRandomAccessList<>(list) :
            new UnmodifiableList<>(list)); //line 1287
}

这调用了他的super(行:1302):

UnmodifiableList(List<? extends E> list) {
    super(list); //line 1302
    this.list = list;
}

并且由于将 null 传递给构造函数,因此会引发 NullPointerException (第 1026 行):

   static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 1820017752578914078L;

        final Collection<? extends E> c;

        UnmodifiableCollection(Collection<? extends E> c) {
            if (c==null)
                throw new NullPointerException(); //line 1026
            this.c = c;
        }

使用 JDK 7 时不会发生此抛出,并且不会发生 ExceptionInInitializerError

快速修复:

这是一个 Maven 依赖项,因此我手动访问了源代码,将 jar 工件导入到我自己的源代码中以覆盖 Maven 依赖项,并更改了 line:111 中的 AbstractLocation 以包含以下内容:

if (subLocations == null) {
    subLocations = Collections.<Location>emptyList();
}

但是迁移到 JDK8 时未初始化的 private static final 之谜(在 static 上带有 empthasize)仍然让我感到困扰。

最佳答案

对我来说,这看起来像是类初始化的一个循环。

类初始化的规则是类在创建实例之前初始化,父类(super class)在子类之前初始化。异常(exception)的是,在类初始化期间,如果出现代码路径遇到当前正在由该线程初始化的类,则代码将继续执行并可以观察该类的部分初始化或未初始化状态。 p>

简化此处给出的示例,我们有

public class SimpleLocation extends AbstractLocation {

    private static final List<Location> EMPTY_LOCS = Collections.emptyList();

    public SimpleLocation(...) { 
        super(..., EMPTY_LOCS);
    }
}

鉴于上述规则,看起来 EMPTY_LOCS 必须在构造函数在其父类(super class)上调用 super() 之前初始化。但父类(super class)的该参数得到 null 并崩溃。发生这种情况的唯一方法是在类完全初始化之前调用 SimpleLocation 构造函数。

类初始化按照从上到下(或从左到右,如规范所规定)的声明顺序进行。尽管EMPTY_LOCS是最终的,但它的初始状态null可以通过类中在其上方声明的静态初始值设定项和父类(super class)的静态初始值设定项来观察。例如,很容易看出是否在 EMPTY_LOCS 声明上方插入了如下行:

    static SimpleLocation defaultLocation = new SimpleLocation();

我查看了 BioJava 3.1 的源代码,但没有看到类似的内容。可能是您使用的是修改版本,或者有一些其他类初始化依赖项导致了我没有想到的问题。

我怀疑这里是否存在任何 Java 7 与 Java 8 特定行为差异。似乎更有可能的是,7 和 8 之间的差异导致您的系统采用不同的代码路径,这最终导致类初始化的顺序从某种幸运地发生的工作方式更改为导致此错误发生的方式。

如果您想跟踪类加载和初始化的顺序,请使用选项-verbose:class运行系统。

关于java - 将项目迁移到 JDK 8 时出现奇怪的 ExceptionInInitializerError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27647136/

相关文章:

java - 为什么当我停止从按钮 Action 监听器播放音频时出现此错误(音频从 Thread 运行)整个代码都在里面

java - 数据快照中出现空指针异常字符串错误

java - 从struts2中的非默认位置加载资源包

ios - swift/ios 如何检查我的 TableView 是否嵌入在 uitableview Controller 实例中?

java - 这个 NullPointerException 的原因可能是什么?

java - Java中的字符串数组

java - 无法获取静态字段的值

c# - 为什么从另一个类访问时static int总是0

programming-languages - 没有 null 的语言的最佳解释

android - 如果没有互联网连接,则关闭应用程序