Following on from this question;
Please give a short example of how one would de-serialize an object with two references.
I've included the below code so all answers refer to the same object names.
public class Person implements Serializable{
private String name;
private int age;
private boolean single;
private int numKids;
private final static long serialVersionUID = 1L;
public Person(String name, int age, boolean single, int numKids) {
this.name = name;
this.age = age;
this.single = single;
this.numKids = numKids;
this.surname = surname;
}
}
Assuming the following:
A text file has already been created
We created a Person object jim
There is another Person bob that references the jimobject
The question already referenced describes how only jim is written to the file. The bob reference is not. How then do we read the values to jim and bob if the file might contain another few objects of the Person class? How can we ensure that bob has the correct value?
You seem to be confused a bit. Your Person class does not allow for referencing another Person object. You would need a Person field within the Person class to get there! From that perspective, your question doesn't make sense: jim and bob would be two completely independent objects, and serializing one of them will not at all serialize the other one!
But beyond that: typically, when you think in plurals of things (like multiple persons) then you would have an enclosing "container" object (like a List<Person>) that you think about. You don't deal with single Person objects then - but with such containers when thinking about serialization!
You are trying to build an artificial example that simply doesn't work out. Rather think of a situation where the Person class has maybe a field Person spouse. Then alice could be referenced by bob. And when you now have those Person objects, and some more in a list, and you serialize that list - then the system will make sure that alice and bob are serialized just once.
I am little confused your question - maybe because I do not see any issue with serialization and deserialization of the same object and/or multiple objects with or without references to any objects.
The point is that serialization is like creating copy of object (in file system or somewhere). This copy can be recreated in memory (deserialization).
You may create object in memory (deserialization) once or more times.
It something like:
object A --serialization--> file A
file A --deserialization--> object A'
file A --deserialization--> object A"
object A, A' and A" are different objects - but all fields will be have the same values.
If object A contains an sophisticated structure (which can be serialized/deserialized) it can be also another objects then the same mechanism are working also for these objects.
All fields will have the same values but object will be different.
see sample code:
package test;
import java.io.Serializable;
public class Person implements Serializable {
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
}
and kind of test
package test;
import java.io.*;
public class Main {
public static void main(String... args) {
Person p1 = new Person(1, "aaa");
Person p1a = null;
Person p1b = null;
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.data"))) {
oos.writeObject(p1);
} catch (IOException e) {
e.printStackTrace();
}
try (ObjectInputStream oos = new ObjectInputStream(new FileInputStream("test.data"))) {
p1a = (Person) oos.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (ObjectInputStream oos = new ObjectInputStream(new FileInputStream("test.data"))) {
p1b = (Person) oos.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
assert p1a != null && p1b != null;
assert p1a.id == p1b.id;
assert p1.id == p1b.id;
assert p1.name.equals(p1a.name);
assert p1a.name.equals(p1b.name);
System.out.println(String.format("Three different object: %s, %s, %s", p1, p1a, p1b));
}
}
PS. How java.lang.String objects are copied/stored/managed in memory it is different story.
Related
In our term project, we are responsible for making the desktop version of a board game. My role is to implement save and load methods. For this reason, I am working on serialization. I am trying to serialize objects that contain other objects. To visualize, I prepared some example classes.
import java.io.Serializable;
public class Player implements Serializable
{
static int id;
static String name;
public Player(String name)
{
this.name = name;
}
}
import java.io.Serializable;
public class Team implements Serializable
{
String name;
transient Player c;
int[] numbers;
public Team(String name, String capt){
this.name = name;
this.c = new Player(capt);
}
public Player getC()
{
return c;
}
public String getName()
{
return name;
}
}
public class test implements Serializable
{
public static void main(String args[])throws IOException
{
Team t1 = new Team("Juventus", "Ronaldo");
Team t2 = new Team("Barcelona", "Messi");
File file = createSave("1");
save(file,t1,t2);
Team[] teams = load(file);
System.out.println("Team 1 is: " + teams[0].getName());
System.out.println("Team 2 is: " + teams[1].getName());
System.out.println("Captain of team 1 is: " + teams[0].getC().name);
System.out.println("Captain of team 2 is: " + teams[1].getC().name);
}
private static File createSave(String gameId) throws IOException
{
File file = new File(gameId + ".txt");
if (file.createNewFile())
{
System.out.println("File is created!");
}
else
{
System.out.println("File already exists.");
}
return file;
}
private static void save(File file, Team t1, Team t2)throws IOException
{
FileOutputStream f = new FileOutputStream(file);
ObjectOutputStream o = new ObjectOutputStream(f);
o.writeObject(t1);
o.writeObject(t2);
o.close();
f.close();
}
private static Team[] load(File file)throws IOException
{
try {
Team[] teams = new Team[2];
FileInputStream fi = new FileInputStream(file);
ObjectInputStream oi = new ObjectInputStream(fi);
Team team1 = (Team) oi.readObject();
Team team2 = (Team) oi.readObject();
teams[0] = team1;
teams[1] = team2;
oi.close();
fi.close();
return teams;
} catch (FileNotFoundException e) {
System.out.println("File not found");
} catch (IOException e) {
System.out.println("Error initializing stream");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
To summarize the code part: I create a player class and a team class that contains player objects. In team class, there is a method that returns the captain of the team. I create two team instance, first one is Juventus and the captain is Ronaldo. The second one is Barcelona and the captain is Messi. Then I stored these two team objects to a txt file by using ObjectOutputStream. And in the load method, I loaded the two teams by using ObjectInputStream and return them as an array.
When I tried to reach the name variables of two team object I could. However, I could not reach the captains of the teams. Only last created player object is available. Let me share my output:
File already exists.
Team 1 is: Juventus
Team 2 is: Barcelona
Captain of team 1 is: Messi
Captain of team 2 is: Messi
As seen in the output I can only reach lastly created inner object. The Player Ronaldo is lost now.
I would be very happy if you can help me.
Well first of all you need practice and learn more about foundamentals of java.
The mistake what you did is in the class Player. You set name as a static field. All of you Players can have only 1 name.
Try replace you class like this:
import java.io.Serializable;
public class Player implements Serializable {
private int id;
private String name;
public Player(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Next in Team class just remove transient
Player c;
And in the main class use
System.out.println("Captain of team 1 is: " + teams[0].getC().getName());
System.out.println("Captain of team 2 is: " + teams[1].getC().getName());
Now your code should work :) It's not perfect but this should resolve your problem
I would always recommend GSON for serializing and deserializing objects. Take look at it :)
Presently, I am writing a Java application that uses the Singleton pattern and serialisation. I have a dedicated serialiser class that serialises and deserialises an object to and from a given file path. One of my objects is serialised and deserialised without issue: I make some changes to my application's state while it's open, then close the application, and those changes are still there when I reopen it. However, this does not work with another one of my objects, even though as far as I can tell there is no major difference between them that should cause this to be the case.
Here is the code for my Serialiser class:
public static Boolean serialise(Object target, String filePath){
try (FileOutputStream fileOut = new FileOutputStream(filePath);
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);){
objectOut.writeObject(target);
return true;
} catch (FileNotFoundException ex) {
Logger.getLogger(Serialiser.class.getName()).log(Level.SEVERE, null, ex);
return false;
} catch (IOException ex) {
Logger.getLogger(Serialiser.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
public static Object deserialise(String filePath){
try (FileInputStream fileIn = new FileInputStream(filePath);
ObjectInputStream objectIn = new ObjectInputStream(fileIn);){
Object readObject = objectIn.readObject();
return readObject;
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(Serialiser.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
And here is the relevant code for my objects. First, the one that works:
public class AccountManager implements Serializable {
private static final long serialVersionUID = 1L;
private static final String FILE_PATH = "data//account_manager.ser";
private static AccountManager instance;
private ArrayList<Account> accounts = null;
private AccountManager(){
accounts = new ArrayList<>();
}
public static AccountManager getInstance(){
if (instance == null){
instance = (AccountManager)Serialiser.deserialise(FILE_PATH);
if (instance == null){
instance = new AccountManager();
Serialiser.serialise(instance, FILE_PATH);
}
}
return instance;
}
And now the one that does not work:
public class MessageManager implements Serializable {
private static final long serialVersionUID = 1L;
private static final String FILE_PATH = "data//message_manager.ser";
private static MessageManager instance = null;
private ArrayList<Message> messages = null;
private MessageManager(){
messages = new ArrayList<>();
}
public static MessageManager getInstance(){
if (instance == null){
instance = (MessageManager)Serialiser.deserialise(FILE_PATH);
if (instance == null){
instance = new MessageManager();
Serialiser.serialise(instance, FILE_PATH);
}
}
return instance;
}
Essentially, both classes work the same way: they store an list of objects of a certain type and provide access to and perform operations on the contents of their respective lists. When the Singleton instance of the classes are accessed, it checks if it could deserialise an instance from a file. If it can't it instantiates a new one and serialises it. Again, this works for one, AccountManager, but not for the other, MessageManager. That is, if create new Account object and store it using the AccountManager while the application is running, it will still be there if I restart the application. The same is not true for MessageManager and Message objects.
When a new Account is created, the instance, and presumably its associated fields, i.e. the accounts list are serialised.
public Account createAccount(String password, String givenName, String surname, String address, Gender gender, LocalDate dateOfBirth){
String id = IDGenerator.getInstance().generateID(AccountType.PATIENT, accounts);
Account createdAccount = null;
if (id != null){
createdAccount = new PatientAccount(id, password, givenName, surname, address, gender, dateOfBirth);
if (createdAccount != null){
accounts.add(createdAccount);
Serialiser.serialise(instance, FILE_PATH);
}
return createdAccount;
}
return null;
}
In MessageManager, when a new Message instance is added its list, and the instance, and again, presumably its list, are serialised:
public Boolean sendMessage(Message message){
if (messages.add(message)){
Serialiser.serialise(instance, FILE_PATH);
return true;
}
return false;
}
Both Account and Message implement Serializable, and both have serialVersionUIDs. I do not get any NotSerializable exceptions.
Any help would in fixing this problem would be greatly appreciated. If necessary, I can provide more of my application's code.
I managed to solve my problem. My issue was in a different part of my application. Essentially, I had believed that MessageManager was not be serialised because I couldn't get the data I was expecting back from it. It turns out the issue was in the method that returned the data: I was using an object reference to identify what data to return. Of course, deserialisation creates new instances, so, after recreating my objects, I could not longer access data identified by an old instance.
To fix my issue, I used a different means of identifying data: a string property whose value would be the same across instances, before and after deserialisation.
Thank you to everyone who looked at my question and/or attempted to answer it.
I'm kind of stuck with a problem. I do understand the concept of serialization. Nevertheless I'm getting errors when I try to serialize/deserialize (deepCopy) an object:
I have a basic domain objects that hold information (two files):
public class DomainObject implements java.io.Serializable {
private String defaultDescription = "";
private List<Translation> translations;
public DomainObject() {
;
}
public void setTranslations(final List<Translation> newTranslations) {
this.translations = newTranslations;
}
public List<Translation> getTranslations() {
return this.translations;
}
public void setDefaultDescription(final String newDescription) {
this.defaultDescription = newDescription;
}
public String getDefaultDescription() {
return this.defaultDescription;
}
}
public class Translations implements java.io.Serializable {
private String description = "";
public Translation() {
;
}
public void setDescription(final String newDescription) {
this.description = newDescription;
}
public String getDescription() {
return this.description;
}
}
I also have a frame so the user can fill in all the necessary information for this domain object. Since I have multiple domain objects (this example only shows one) with different fields I have different frames for each domain object. Each of these frames includes a "MultiLanguageFrame" which gives the user the ability to add optional translations for this domain object's description.
public class MultiLanguageFrame extends org.eclipse.swt.widgets.Composite {
private List<Translation> translations = new ArrayList<Translation>();
public MultiLanguageFrame(final Composite parent, final int style) {
super(parent, style);
...
}
public List<Translation> getTranslations() {
return translations;
}
}
I deepCopy objects via this method:
...
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.flush();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception t) {
logger.error(deepCopy() error: " + t.getMessage()); //$NON-NLS-1$
throw new RuntimeException("deepCopy() error", t); //$NON-NLS-1$
}
So now to the error:
When i try to do something like this:
MultiLanguageFrame frame = new MultiLanguageFrame(parent, SWT.NONE);
DomainObject dom = new DomainObject();
dom.setDefaultDescription("Testobject");
dom.setTranslations(frame.getTranslations())
deepCopy(dom);
I receive an error telling me that MultiLanguageFrame is not Serializable. Why would Java try to serialize the frame when I only want that DomainObject?
I thought maybe it is because of the reference in frame. So when I add the Serializable-Interface to MultiLanguageFrame and markt the SWT-Components as transient it tells me that no valid constructor was found. I can't add a parameterless constructor because it would logically make no sense and also SWT-Components need a parent to exist.
I'm really stuck with this problem because I do not know how to work around this. Thanks for answers in advance!
I found the solution myself. I'll just post this so others can see it, it might help.
Thanks to #greg-449 who lead the way. I do have an inner class TranslationHelper which extends Translation in MultiLanguageFrame. The purpose of this is so I can save some flags (deleted, changed, new) for Translations without changing Translation itself. When I call frame.getTranslations() I cast the elements from TranslationsHelper to Translation. The instance of the object remains a TranslationHelper though.
Now it all makes sense that MultiLanguageFrame was involved in all of this.
How can I deserialize a class which was modified after serialization?
More specifically, I know this can be done when a class had serialVersionUID in its initial version. Is there any way to do it for classes without serialVersionUID?
I have an object
package com.test.serialize;
import java.io.Serializable;
public class MyObject implements Serializable{
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and I serialize classes like this
package com.test.serialize;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializeTest {
public static void main(String[] args) {
try {
MyObject myObject = new MyObject();
myObject.setName("Ajit");
ObjectOutputStream objectOStr = null;
ByteArrayOutputStream byteOStr = null;
try {
byteOStr = new ByteArrayOutputStream();
objectOStr = new ObjectOutputStream(byteOStr);
objectOStr.writeObject(myObject);
} catch (IOException e) {
System.out.println(e);
} finally {
try {
if (objectOStr != null)
objectOStr.close();
} catch (IOException e2) {
}
}
FileOutputStream fo = new FileOutputStream(new File("serialize"));
fo.write(byteOStr.toByteArray());
fo.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
and deserialize like this
package com.test.serialize;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.RandomAccessFile;
public class DeserializeTest {
public static void main(String[] args) {
try {
// File f = new File("serialize");
// FileInputStream fs = new FileInputStream(f);
RandomAccessFile raF = new RandomAccessFile("serialize", "r");
byte[] b = new byte[(int)raF.length()];
raF.read(b);
ObjectInputStream oIstream = null;
ByteArrayInputStream bIstream = null;
bIstream = new ByteArrayInputStream(b);
oIstream = new ObjectInputStream(bIstream);
Object finalResult = oIstream.readObject();
System.out.println(finalResult.toString());
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
After some time, I added
#Override
public String toString() {
return "MyObject [name=" + name + ", names=" + names + "]";
}
to MyObject. After adding that I got exceptions like
java.io.InvalidClassException: com.test.serialize.MyObject; local class in
compatible: stream classdesc serialVersionUID = 5512234731442983181, local class
serialVersionUID = -6186454222601982895
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at com.test.serialize.DeserializeTest.main(DeserializeTest.java:25)
Please help me with this.
Thanks #Gábor Bakos.
This can be solved by creating serialVersionUID for older class (Which signatures should be same as the one during serialization )and adding that serialVersionUID in current class.
serialver -classpath /***PATH***/bin com.test.serialize.MyObject
That returns
com.test.serialize.MyObject: static final long serialVersionUID = 5512234731442983181L;
After that I have added it to my MyObject as below
package com.test.serialize;
import java.io.Serializable;
public class MyObject implements Serializable{
/**
* Added serial version Id of old class, created before adding new fields
*/
private static final long serialVersionUID = 5512234731442983181L;
public MyObject() {
System.out.println("Constructor");
}
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
String names ="Altered after change!";
public String getNames() {
return names;
}
public void setNames(String names) {
System.out.println("Setting names");
this.names = names;
}
#Override
public String toString() {
return "MyObject [name=" + name + ", names=" + names + "]";
}
}
It works fine.
More info refer: serialver
First advice: use serialization, because everything is almost done.
Second advice: use a serialVersionUID and keep it fix with one version: it is here to warn you and prevent confusion between different serialized versions.
So : if you change fields or meaning of fields, change the serialVersionUID.
Then you have your backward compatibility problem.
See this for many ideas: Managing several versions of serialized Java objects
IMHO:
whatever solution you take, keep in mind that your program will be managing objects with partial datas: then you have to manage all cases with or without datas.
if you dont change often your version: use several different classes. Perhaps subclasses, or implementations of an interface: then you can get your program, and you manage several versions of object: MyClass_V1, MyClass_V2, etc. When you deserialize, you can test/retry and get the good Object. After that, you perhaps have to convert datas between your classes
if you change your version, by adding new fields (not changing old fields), it is a little more easy (subclasses, converting is direct to parents)
or you could consider use a XML structure to serialize and deserialize: you can have backward and forward compatibility because it is extensible: fields are there, or are null. You have to manage mapping yourself or use some libraries.
Hope it helps !
I would remember following points,
Every Serializable class contains a serialVersionUID ( it doesn't matter if you have specified the one explicitly or not ).
There are compatible changes and there are incompatible changes
e.g. adding a new field is a compatible change, removing a field is not a compatible change. Adding / removing / editing a method are generally compatible changes but in your case surely that is not the way it is ( serialVersionUID got changed after you added toString() method)
3.Prior to modify the class, you can use serialver utility to find serialVersionUID of old class and use that in new class
Don't think there are any other magic tricks :)
What happens during serialization in java, if two object refrences are pointing to the same serializable Object? Does the Serializable Objects get saved twice ?
for example :
class King implements java.io.Serializable {
private String name="Akbar";
}
class Kingdom implements java.io.Serializable {
King goodKing=new King();
King badKing=goodKing;
}
public class TestSerialization {
public static void serializeObject(String outputFileName,
Object serializableObject) throws IOException {
FileOutputStream fileStream=new FileOutputStream(outputFileName);
ObjectOutputStream outStream=new ObjectOutputStream(fileStream);
outStream.writeObject(serializableObject);
outStream.close();
}
public static void main(String[] args) {
Kingdom kingdom=new Kingdom();
try {
TestSerialization.serializeObject("Kingdom1.out", kingdom);
}catch(IOException ex) {
ex.getMessage();
}
}
}
Now, whether only one object state is saved for both goodKing and badKing refrences or the King object get saved twice ?
The documentation for ObjectOutputStream says what happens:
The default serialization mechanism for an object writes the class of the object, the class signature, and the values of all non-transient and non-static fields. References to other objects (except in transient or static fields) cause those objects to be written also. Multiple references to a single object are encoded using a reference sharing mechanism so that graphs of objects can be restored to the same shape as when the original was written.
(My emphasis)
E.g., if you have multiple references to a single object, when the graph is reconstituted, you end up with multiple references to a single reconstituted version of that object, not references to multiple equivalent instances of it.
Of course, if the container being serialized implements a different mechanism, the behavior is dictated by that mechanism, not the default one.
So for instance, if we have Thing and Test:
Thing.java:
import java.io.*;
import java.util.*;
public class Thing implements Serializable {
private Map<String,String> map1;
private Map<String,String> map2;
public Thing() {
this.map1 = new HashMap();
this.map2 = this.map1; // Referring to same object
}
public void put(String key, String value) {
this.map1.put(key, value);
}
public boolean mapsAreSameObject() {
return this.map1 == this.map2;
}
}
Test.java:
import java.io.*;
public class Test implements Serializable {
public static final void main(String[] args) {
try
{
// Create a Thing
Thing t = new Thing();
t.put("foo", "bar");
// Write it out
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("foo"));
os.writeObject(t);
os.close();
os = null;
// Read it in
Thing t2;
ObjectInputStream is = new ObjectInputStream(new FileInputStream("foo"));
t2 = (Thing)is.readObject();
is.close();
is = null;
// Same underlying map for both properties?
System.out.println("t2.mapsAreSameObject? " + t2.mapsAreSameObject());
}
catch (Exception e)
{
System.out.println("Exception: " + e.getMessage());
}
}
}
And run java Test, we get:
t2.mapsAreSameObject? true
...because both of Thing's members, map1 and map2 end up pointing to a single HashMap instance.