How are constructors called during serialization and deserialization?
Example:
public class ParentDeserializationTest {
public static void main(String[] args){
try {
System.out.println("Creating...");
Child c = new Child(1);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
c.field = 10;
System.out.println("Serializing...");
oos.writeObject(c);
oos.flush();
baos.flush();
oos.close();
baos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
System.out.println("Deserializing...");
Child c1 = (Child)ois.readObject();
System.out.println("c1.i="+c1.getI());
System.out.println("c1.field="+c1.getField());
} catch (IOException ex){
ex.printStackTrace();
} catch (ClassNotFoundException ex){
ex.printStackTrace();
}
}
public static class Parent {
protected int field;
protected Parent(){
field = 5;
System.out.println("Parent::Constructor");
}
public int getField() {
return field;
}
}
public static class Child extends Parent implements Serializable{
protected int i;
public Child(int i){
this.i = i;
System.out.println("Child::Constructor");
}
public int getI() {
return i;
}
}
}
Output:
Creating...
Parent::Constructor
Child::Constructor
Serializing...
Deserializing...
Parent::Constructor
c1.i=1
c1.field=5
So if you deserialized your object, its constructors doesn't called, but default constructor of its parent will be called.
And don't forget: all your serializable object should have a standard constructor without parameters.
Why default constructor is not called while deserialization process?
It's worth reading Java Object Serialization Specification: 3 - Object Input Classes where readObject
method is described in details along with step by step explanation.
How it works?
An instance of the class is allocated. The instance and its handle are added to the set of known objects.
The contents restored appropriately:
For serializable objects, the no-arg constructor for the first non-serializable supertype is run.
For serializable classes, the fields are initialized to the default value appropriate for its type.
Then the fields of each class are restored by calling class-specific
readObject
methods, or if these are not defined, by calling thedefaultReadObject
method.Note that field initializers and constructors are not executed for serializable classes during deserialization.
In the normal case, the version of the class that wrote the stream will be the same as the class reading the stream. In this case, all of the supertypes of the object in the stream will match the supertypes in the currently-loaded class.
If the version of the class that wrote the stream had different supertypes than the loaded class, the
ObjectInputStream
must be more careful about restoring or initializing the state of the differing classes.It must step through the classes, matching the available data in the stream with the classes of the object being restored. Data for classes that occur in the stream, but do not occur in the object, is discarded.
For classes that occur in the object, but not in the stream, the class fields are set to default values by default serialization.
For externalizable objects, the no-arg constructor for the class is run and then the
readExternal
method is called to restore the contents of the object.
Sample code to understand fist point For serializable objects, the no-arg constructor for the first non-serializable supertype is run.
Sample code;
class TestClass1 {
public TestClass1() {
System.out.println("TestClass1");
}
}
class TestClass2 extends TestClass1 implements Serializable {
public TestClass2() {
System.out.println("TestClass2");
}
}
public static void main(String[] args) throws Exception {
System.out.println("Object construction via calling new keyword");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("resources/dummy.dat"));
out.writeObject(new TestClass2());
System.out.println("Object construction via readObject method");
ObjectInputStream is = new ObjectInputStream(new FileInputStream("resources/dummy.dat"));
TestClass2 tc = (TestClass2) is.readObject();
}
output:
Object construction via calling new keyword
TestClass1
TestClass2
Object construction via readObject method
TestClass1
Java: Why doesn't deserialization invoke constructor & what's the best workaround?
Deserialization doesn't invoke the constructor because the purpose of it is to express the state of the object as it was serialized, running constructor code could interfere with that.
How can I call the constructor before deserialisation?
Create a constructor with the signature
protected YourClassName(SerializationInfo info, StreamingContext context)
{
}
and have your class implement ISerializable
. On serialization it calls ISerializable.GetObjectData()
on de-serialization it calls the above constructor.
See Custom Serialization: Implementing the ISerializable Interface on the MSDN
If you don't actually need a to do the work in the constructor you can use the attribute [OnDeserializing]
instead of [OnDeserialized]
to get the work done before deserialization instead of after.
[OnDeserializing]
private void SetValuesOnDeserializing(StreamingContext context)
{
// Code not shown.
}
Note: If you have more than one [OnDeserializing]
method in your object graph the order they are called in is not dertimistic.
Does the default constructor get called during object deserialization when using ObjectInputStream?
I was wondering if in the following code, when readObject gets called,
does it call the default constructor (if one exists) or not?
No, It doesn't ..
is there a MemoryStream (C#) equivalent in Java?
ByteArrayInputStream
and ByteArrayOutputStream
When is the class constructor called while deserialising using XmlSerializer.Deserialize?
No it is not OK to assume the properties will be set when the constructor runs. The opposite is true. The constructor is the very first piece of code which runs when an instance of an object is created. It's not possible for the properties to be set until after the constructor has started executing.
The XML deserialization process roughly looks like the following
- Call the parameterless constructor
- Set the properties to their deserialized values
A way to work around this is to use a factory method to do the deserialization and then run the logic which depends on the properties being set. For example
class MyClass {
...
public static MyClass Deserialize(string xmlContents) {
var local = ... // Do the XML deserialization
local.PostCreateLogic();
return local;
}
}
Serialization/Deserialization and non-default constructors
You can solve that in two ways. Use convention over configuration (name the constructor parameters as the properties and do not include a default constructor):
[Persistable]
public sealed class FileMoveTask : TaskBase
{
[PersistMember]
public string SourceFilePath { get; private set;}
[PersistMember]
public string DestFilePath { get; private set;}
public FileMoveTask(string sourceFilePath, string destFilePath)
{
this.SourceFilePath = srcpath;
this.DestFilePath = dstpath;
//possibly other IMPORTANT initializations
}
//code
}
Explicitly tag the constructor arguments with your attribute and search for the constructor which have been tagged:
[Persistable]
public sealed class FileMoveTask : TaskBase
{
[PersistMember]
public string SourceFilePath { get; private set;}
[PersistMember]
public string DestFilePath { get; private set;}
public FileMoveTask([PersistMember("SourceFilePath")]string srcpath, [PersistMember("DestFilePath")]string dstpath)
{
this.SourceFilePath = srcpath;
this.DestFilePath = dstpath;
//possibly other IMPORTANT initializations
}
//code
}
the attribute or the argument name isn't used to know which property to set, but to know which information from the serialize data to use when invoking the constructor.
Each parameter in the deserialization constructor on type must bind to an object property or field on deserialization
You are encountering two separate problems related to deserializing types with parameterized constructors. As explained in the documentation page How to use immutable types and non-public accessors with System.Text.Json:
System.Text.Json
can use a public parameterized constructor, which makes it possible to deserialize an immutable class or struct. For a class, if the only constructor is a parameterized one, that constructor will be used. For a struct, or a class with multiple constructors, specify the one to use by applying the[JsonConstructor]
attribute. When the attribute is not used, a public parameterless constructor is always used if present. The attribute can only be used with public constructors....
The parameter names of a parameterized constructor must match the property names. Matching is case-insensitive, and the constructor parameter must match the actual property name even if you use
[JsonPropertyName]
to rename a property.
Your first problem is with the type Repository
. You don't show it in your question, but I assume it looks something like this:
public class Repository
{
public Repository(string gitDirectory) => this.GitDirectory = new DirectoryInfo(gitDirectory);
[JsonConverter(typeof(DirectoryInfoConverter))]
public DirectoryInfo GitDirectory { get; }
}
public class DirectoryInfoConverter : JsonConverter<DirectoryInfo>
{
public override DirectoryInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
new DirectoryInfo(reader.GetString());
public override void Write(Utf8JsonWriter writer, DirectoryInfo value, JsonSerializerOptions options) =>
writer.WriteStringValue(value.ToString());
}
If so, your problem here is that either the name of the constructor argument corresponding to GitDirectory
is not the same as the property name or the type of the argument is not the same. Because, as it turns out, there is an undocumented restriction that types of the constructor arguments and corresponding properties must also match exactly. For confirmation see JsonConstructor fails on IEnumerable property?.
Demo fiddle #1 here.
To fix this, you must either:
Add a public parameterless constructor and make
Repository
be mutable (i.e. add a setter forGitDirectory
), orAdd a constructor with an argument of the same type and name as the property
GitDirectory
, and mark it with[JsonConstructor]
.
Adopting option #2, your Repository
type should now look like:
public class Repository
{
public Repository(string gitDirectory) => this.GitDirectory = new DirectoryInfo(gitDirectory);
[JsonConstructor]
public Repository(DirectoryInfo gitDirectory) => this.GitDirectory = gitDirectory ?? throw new ArgumentNullException(nameof(gitDirectory));
[JsonConverter(typeof(DirectoryInfoConverter))]
public DirectoryInfo GitDirectory { get; }
}
And now Respository
will deserialize successfully. Demo fiddle #2 here.
However, you will now encounter your second problem, namely that the Blob
type will not round-trip either. In this case, Blob
does have a unique parameterized constructor whose argument names and types correspond precisely to properties -- but the semantics of one of them, data
, are completely different:
public class Blob : GitObject
{
public string Data { get; set; }
public Blob(Repository repository, string data = null)
{
if (data != null)
Data = File.ReadAllText(data);
Repository = repository;
}
The property Data
corresponds to the textual contents of a file, while the argument data
corresponds to the file name of a file. Thus when deserializing Blob
your code will attempt to read a file whose name equals the file's contents, and fail.
This inconsistency is, in my opinion, poor programming style, and likely to confuse other developers as well as System.Text.Json. Instead, consider adding factory methods to create a Blob
from a file, or from file contents, and remove the corresponding constructor argument. Thus your Blob
should look like:
public class Blob : GitObject
{
public string Data { get; set; }
public Blob(Repository repository) => this.Repository = repository ?? throw new ArgumentNullException(nameof(repository));
public static Blob CreateFromDataFile(Repository repository, string dataFileName) =>
new Blob(repository)
{
Data = File.ReadAllText(dataFileName),
};
public static Blob CreateFromDataConents(Repository repository, string data) =>
new Blob(repository)
{
Data = data,
};
public override string Serialize() => JsonSerializer.Serialize(this);
public override void Deserialize(string data)
{
// System.Text.Json does not have a Populate() method so we have to do it manually, or via a tool like AutoMapper
Blob blobData = JsonSerializer.Deserialize<Blob>(data);
this.Repository = blobData.Repository;
this.Data = blobData.Data;
}
}
And you would construct and round-trip it as follows:
var firstBlob = GitObject.Blob.CreateFromDataFile(repository, file.FullName);
var json = firstBlob.Serialize();
var secondBlob = new GitObject.Blob(repository);
secondBlob.Deserialize(json);
Final working demo fiddle here.
How does java serialization deserialize final fields when no default constructor specified?
Deserialization is implemented by the JVM on a level below the basic language constructs. Specifically, it does not call any constructor.
Related Topics
How to Use Hibernate @Any-Related Annotations
Java List Sorting: How to Keep a List Permantly Sorted Automatically Like Treemap
Why String.Replaceall() in Java Requires 4 Slashes "\\\\" in Regex to Actually Replace "\"
Can't Find/Install Libxtst.So.6
@Valid Annotation Is Not Validating the List of Child Objects
Java 8 Localdatetime Is Parsing Invalid Date
Do Not Use System.Out.Println in Server Side Code
Any Recommended Java Profiling Tutorial
Getting Class Cast Exception Where Both Classes Are Exactly the Same
What Code Does the Compiler Generate for Autoboxing
How to Stop the Task Scheduled in Java.Util.Timer Class
How to Disable 'X-Frame-Options' Response Header in Spring Security
How to Set an Environment Variable at Runtime from Java
How to Read Excel Cell Having Date with Apache Poi
Any Simple Way to Explain Why I Cannot Do List<Animal> Animals = New Arraylist<Dog>()