странная ошибка ExceptionInInitializerError при переносе проекта в JDK 8

1

У меня есть код с некоторыми бинарными зависимостями (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 и конструкция 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 передается super то есть AbstractLocation (строка: 57), передается нуль и НЕ пустой список (я проверил с JDK 7, и там был отправлен старый старый пустой список).

Почему это? Должен ли я просто копаться в исходном коде 3-й партии и перезаписывать его? (не звучит очень аккуратно для меня)

Когда я использую emptyList() методу сам, он возвращает пустое list-, но это частное статическое поле погребены в моих зависимости иметь что - то против Java 8, и просто не хочет быть инициализировано.

EDIT:

AbstractLocation в свою очередь вызывает unmodifiableList() (строка: 111) с нулевым (и НЕ пустым списком):

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();
}

Которая затем строит UnmodifiableList (строка: 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 и No ExceptionInInitializerError.

БЫСТРАЯ ПОЧИНКА:

Это была зависимость от Maven, поэтому я вручную установил исходный код, импортировал артефакт jar в свой собственный источник, чтобы перезаписать зависимость Maven, и изменил AbstractLocation в строке: 111, чтобы обернуть следующим:
if (subLocations == null) {
    subLocations = Collections.<Location>emptyList();
}

Но тайна неинициализированного private static final (с empthasize на static) при переходе на JDK8 все еще меня беспокоит.

  • 0
    Работает ли код под Java 8, когда вы просто передаете новый ArrayList <> () в строке 57 вместо EMPTY_LOCS? Кроме того, что происходит в org.biojava3.core.sequence.location.template.AbstractLocation. <Init> (строка 111)? У вас есть исходный код, и вы можете опубликовать его, чтобы получить полную картину?
  • 0
    Каковы ваши доказательства того, что проблема EMPTY_LOCS? Что происходит в Abstractequence: 527? Вы пытались поместить статический инициализатор в SimpleLocation, чтобы проверить, правильно ли инициализирована EMPTY_LOCS? Как выглядит DADNASequence: 465, если она у вас есть?
Показать ещё 3 комментария
Теги:
java-8
static
nullpointerexception

2 ответа

0

Кажется, это статический цикл инициализации, как объясняется в предыдущем ответе Стюарта Маркса. Цикл, вероятно, связан с тем, что дочерний класс ссылается на статический инициализатор одного из интерфейсов, который он реализует:

public interface Location extends Iterable<Location>, Accessioned {

    /**
     * Basic location which is set to the minimum and maximum bounds of
     * {@link Integer}. {@link Strand} is set to {@link Strand#UNDEFINED}.
     */
    public static final Location EMPTY =
        new SimpleLocation(Integer.MIN_VALUE, Integer.MAX_VALUE, Strand.UNDEFINED);
    ...
}

Таким образом, у нас есть ситуация, когда для местоположения требуется, чтобы SimpleLocation была инициализирована, а SimpleLocation требует, чтобы местоположение было инициализировано. Цикл должен быть каким-то образом сломан, и кажется, что JDK8 решает его по-разному с JDK7 и ранее.

Я смог разбить цикл с помощью простого трюка: ссылаясь на поле Location.EMPTY непосредственно перед любой ссылкой дочернего класса, он заставляет его инициализироваться в другом порядке. Я не уверен, является ли это решение надежным, может быть, порядок все еще произволен. Однако, по крайней мере, он работает в JDK8 без изменения сторонних библиотек:

public static void main(String[] args) {
    // This line prevents exception on the next line        
    Location l = Location.EMPTY;

    // This line will fail if the line above is commented out
    SimpleLocation s = new SimpleLocation(20,30, Strand.POSITIVE, new SimpleLocation[0]);
}
0

Это выглядит как цикл инициализации класса для меня.

Правила инициализации класса состоят в том, что класс инициализируется до того, как создается экземпляр, и суперклас инициализируется перед подклассом. Исключением является то, что во время инициализации класса, если возникает путь кода, который встречает класс, который в настоящее время инициализируется этим потоком, код продолжается и может наблюдать частично инициализированное или неинициализированное состояние класса.

Упрощая приведенный здесь пример, мы имеем

public class SimpleLocation extends AbstractLocation {

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

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

Учитывая вышеприведенные правила, похоже, что EMPTY_LOCS должен быть инициализирован перед конструктором, вызывающим super() на его суперклассе. Но суперкласс получает значение null для этого параметра и взрывается. Единственный способ, которым это может произойти, - SimpleLocation конструктор SimpleLocation до того, как класс будет полностью инициализирован.

Инициализация класса происходит в порядке декларации сверху вниз (или слева направо, как это имеет в спецификации). Несмотря на то, что EMPTY_LOCS является окончательным, его начальное состояние null можно наблюдать статическими инициализаторами, объявленными выше в классе и статическими инициализаторами суперкласса. Например, легко увидеть, была ли вставлена строка, подобная следующей, над объявлением EMPTY_LOCS:

    static SimpleLocation defaultLocation = new SimpleLocation();

Я посмотрел на источник для BioJava 3.1, и я не видел ничего подобного. Возможно, вы используете модифицированную версию или какую-то другую зависимость инициализации класса, которая вызывает это, о котором я не думал.

Я сомневаюсь, что здесь существует определенная разница в Java 7 против Java 8. Более вероятно, что есть что-то еще в разнице между 7 и 8, что заставляет вашу систему использовать другой путь кода, и это заканчивает тем, что меняет порядок инициализации класса с каким-то образом, что просто удавалось выполнить, который вызывает эту ошибку.

Если вы хотите отследить порядок загрузки и инициализации класса, запустите систему с помощью опции -verbose:class.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню