У меня есть код с некоторыми бинарными зависимостями (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
.
AbstractLocation
в строке: 111, чтобы обернуть следующим: if (subLocations == null) {
subLocations = Collections.<Location>emptyList();
}
Но тайна неинициализированного private static final
(с empthasize на static
) при переходе на JDK8 все еще меня беспокоит.
Кажется, это статический цикл инициализации, как объясняется в предыдущем ответе Стюарта Маркса. Цикл, вероятно, связан с тем, что дочерний класс ссылается на статический инициализатор одного из интерфейсов, который он реализует:
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]);
}
Это выглядит как цикл инициализации класса для меня.
Правила инициализации класса состоят в том, что класс инициализируется до того, как создается экземпляр, и суперклас инициализируется перед подклассом. Исключением является то, что во время инициализации класса, если возникает путь кода, который встречает класс, который в настоящее время инициализируется этим потоком, код продолжается и может наблюдать частично инициализированное или неинициализированное состояние класса.
Упрощая приведенный здесь пример, мы имеем
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
.