-
Notifications
You must be signed in to change notification settings - Fork 0
Making Serializable Classes
In order to save data, all objects to be saved need to have ONE of the following implemented:
- Implement
DataSerializable(preferred) - Register a custom
DataConverterintoConverterRegistry
How to do both of the above are explained in the sections below.
Where possible, objects should be serialised through the DataSeializable interface.
public class MyObject implements DataSerializable {
// Some primitive data
final int a, b;
// Some object data (make sure all saved objects can be serialised)
final MyObject somethingElse;
// Constructor used to restore this object from the save file.
// A no arg constructor can also be used, but then the object wouldn't be able to have any data.
// Note:
// * Used the @Constructor annotation. This is not needed, but makes the purpose
// clearer and stops IDE warnings about an unused constructor
// * Visibility is private. Any level can be used. For classes which extend others,
// make sure to call the parent constructor. Therefore, if your class is intended
// to be extended, it is recommended to set the visibility to protected
@Constructor
private MyObject(DataIn data) {
// If this class extends another serializable class:
// super(data.readSection("parentdata")) // use a sub section for parent to avoid name clashes
// See 'Data Type Compatibility' page for more info on reading/writing data types
a = data.readInt("a");
b = data.readInt("b");
somethingElse = data.read(MyObject.class, "something");
// Note: at this point, read objects may not have been initialised. Therefore,
// never call methods or get data from an object here. Even final fields may not
// exist yet!
}
// The constructor used in normal Java code.
public MyObject(int a, int b, MyObject thing) {
this.a = a;
this.b = b;
this.somethingElse = thing;
}
// The method used to save the data.
@override
void bundleInto(DataOut data) {
// `data` is an empty bundle, thus, there will be no name conflicts.
// The only exception is keys beginning with "__" (double underscore) which are reserved
data.put("a", a);
data.put("b", b);
// somethingElse will be stored in a separate bundle.
// Never manually call the `bundleInto` method to save objects
data.put("something", somethingElse);
// If this class was to extend another class, then:
// super.bundleInto(data.createSection("parentdata"); // use a subsection to avoid name clashes
}
}It is preferable not to use DataConverters where possible and instead get the objects to implement the DataSerializable interface. However, when using third party libraries, it is often impractical to do so, thus, the purpose of this interface. When using a DataConverter, one has to be more careful of circular references.
Implementing a DataConverter is a two step process:
- Implement a
DataConverter - Register the
DataConverter
// This would be the converter for the MyObject class example above if it had not implemented
// the DataSerializable interface.
public class MyObjectConverter implements DataConverter<MyObject> {
// The method to save a MyObject.
// Notice that it is almost identical to constructor provided above
@Override
public void serialize (MyObject obj, DataOut bundle) {
// obj will never be null (the serializer library will handle that)
bundle.put("a", obj.a);
bundle.put("b", obj.b);
bundle.put("something", obj.somethingElse);
}
// The method to create the object.
// This method only needs to _create_ the object. The fields of the object do
// not need to be correct yet - they can be set latter in `initialise`
@Override
public MyObject create (DataIn bundle, Class<? extends MyObject> stored) {
int a = bundle.readInt("a");
int b = bundle.readInt("b");
MyObject somethingElse = null;
return new MyObject(a, b, somethingElse);
// Note: because somethingElse could potentially be the same reference
// as what is trying to be returned or form a circular loop, it should
// be initialised latter in the `initialise` method. This is the downside
// of using DataConverters - circular loops are a pain.
// Therefore, it is always safe to read out a DataSerializable object,
// but not always okay to read an object created with a DataConverter.
}
// Sometimes, it is preferable to do a late initialisation of objects. It is always called
// after the `create` method of all other objects.
@Override
public void initialise (DataIn bundle, MyObject obj) {
// this would require somethingElse to be non final.
obj.somethingElse = bundle.readObject("something");
}
}The DataConverter needs to be registered into ConverterRegistry so that the code knows what to use to encode/decode the class. To do so, all that needs to be done is to run the following code ONCE (per process. Static code blocks are useful here):
ConverterRegistry.register(MyObject.class, new MyObjectConverter());