I currently have class which have some fields initialized in declaration, like this:
public class SomeClass implements Externalizable {
private long id;
private final List<Hit> hits = new ArrayList<>();
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(id);
out.writeInt(hits.size());
for (int i = 0; i < hits.size(); i++) {
out.writeObject(hits.get(i));
}
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
id = in.readLong();
int size = in.readInt();
for (int i = 0; i < size; i++) {
hits.add((Hit) in.readObject()); //<--Nullpointer here, hits == null
}
}
}
And this class is used in filebased chronicle-map
configured like this:
ChronicleMap<Long, SomeClass> storage = ChronicleMapBuilder
.of(Long.class, SomeClass.class)
.averageValueSize(avgEntrySize)
.entries(entries)
.createPersistedTo(new File(path));
The problem is that when I restart my application I get NullpointerException
when chronicle tries to read saved map, because hits
field wasn't initialized, meaning it is null
.
I did some investigation and found that before calling readExternal
chronical creates object of this class using UNSAFE.allocateInstance
(in ExternalizableMarshaller
):
protected E getInstance() throws Exception {
return (E) NativeBytes.UNSAFE.allocateInstance(classMarshaled);
}
So basically this is the reason why its not initialized. What I am trying to understand why it is using such approach instead of MethodHandle
or reflection?
And maybe there is another way to fix this without modifying SomeClass
, like some chronicle configuration property maybe?
This appears to be an issue with version 2.x which is no longer supported.
In version 3.x it should call the default constructor if one exists. It will use Unsafe if there is no default constructor. I have added a test case which shows this works in 3.x
For version 2.x I suggest you need to check whether the list is null
and set it as needed.