0

I've created a tree data structure that needs to be serializable, but every tree shares a common root object, which is a public static final constant.

The problem I'm having is illustrated below. After I serialize the ROOT object, deserialization creates a new Tree object instead of returning a reference to the original constant.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;

public class Main {

    public static final class Tree implements Serializable {

        public final ArrayList<Tree> branches = new ArrayList<>();
    }

    public static final Tree ROOT = new Tree();

    public static void main(String[] args) throws Exception {
        File file = new File("C:/Users/Stephen/Desktop/temp.bin");
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
        oout.writeObject(ROOT);
        oout.writeObject(ROOT);
        oout.close();
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
        Tree t1 = (Tree) oin.readObject();
        Tree t2 = (Tree) oin.readObject();
        oin.close();
        System.out.println(ROOT == t1); // false
        System.out.println(t1 == t2); // true
    }
}

The first print statement outputs "false," but I want it to be "true."

Stephen Ware
  • 109
  • 1
  • 6
  • Since a few comments have mentioned this: Yes, I do know the difference between == and #equals(Object). I want the former. When reading the object from the ObjectInputStream, I want it to return a reference to the exact same public static final ROOT object that was serialized, not a different Tree object, even if that different object is #equals to ROOT. – Stephen Ware Apr 05 '18 at 04:22
  • Further clarification: I understand why this code outputs "false." I have 15+ years of Java development experience, and don't need advice about the basics of the language. I'm looking for a way around this problem. I need a way to recognize when an existing public static final object is being deserialized and, instead of making a new object, returning a reference to the original. My first attempt was to write a custom deserialization method (as described here: http://www.oracle.com/technetwork/articles/java/javaserial-1536170.html), but this method appears to automatically create a new object. – Stephen Ware Apr 05 '18 at 04:41
  • 2
    Did you look into readResolve and writeResolve? I think they may do what you want. (It's not super simple to get right, though; Effective Java has a section it two on it, iirc) – yshavit Apr 05 '18 at 05:16
  • Specifically, I recommend looking at the implementation of the `Currency` class. – chrylis -cautiouslyoptimistic- Apr 05 '18 at 05:28
  • Yes, @yshavit! That's exactly what I need. If you post an answer, I'll mark it as correct. – Stephen Ware Apr 05 '18 at 05:34
  • 1
    Your 15 years of experience doesn't automatically show when you write questions in a simplified manner. People automatically assume simpler problems because SO is full of them. You have to be careful that this doesn't happen: https://stackoverflow.com/questions/49533897/is-a-method-with-receiver-parameter-override-equivalent-to-the-same-method-decla (it has -2 / +2 score because people misunderstood the question). – Kayaman Apr 05 '18 at 05:37

3 Answers3

1

Thanks to a comment from @yshavit, I was able to find an answer to my own question. The trick was using the #readResolve() method when deserializing.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;

public class Main {

    public static final class Tree implements Serializable {

        public final ArrayList<Tree> branches = new ArrayList<>();
        private final boolean isRoot = ROOT == null;

        private Object readResolve() throws ObjectStreamException {
            if(isRoot)
                return ROOT;
            else
                return this;
        }
    }

    public static final Tree ROOT = new Tree();

    public static void main(String[] args) throws Exception {
        File file = new File("C:/Users/Stephen/Desktop/temp.bin");
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
        oout.writeObject(ROOT);
        oout.writeObject(ROOT);
        oout.close();
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
        Tree t1 = (Tree) oin.readObject();
        Tree t2 = (Tree) oin.readObject();
        oin.close();
        System.out.println(ROOT == t1); // true
        System.out.println(t1 == t2); // true
    }
}
Stephen Ware
  • 109
  • 1
  • 6
0

== in java compares the memory location and if and only if it is same then the objects are considered equal. Having said that, equality in Java depends on the user. Do you want objects to be considered equal if and only if they are same objects(stored at same memory location) or do you have a set of rules(which properties of that object should be same to consider the objects are equal)?

In your case, you need to check the content of your ROOT and root object and content of their properties.== will always compare memory location and it will be different always for different objects (ROOT and root in this case)

humblefoolish
  • 409
  • 3
  • 15
  • I want the former. I know the difference between == and #equals(Object), and I want these objects to be == to one another. – Stephen Ware Apr 05 '18 at 04:24
  • @sgware == will never return true as 2 different objects on heap are compared. This will help you understand better.https://stackoverflow.com/questions/29051809/is-a-deserialised-object-the-same-instance-as-the-original – humblefoolish Apr 05 '18 at 04:32
  • I already understand this. My problem is not understanding how == works. My problem is finding a way to ensure that when a public static final constant is deserialized it is deserialized as itself, not as a new object. I suspect the solution will involve writing custom serialization and deserialization methods, but doing so already assumes the creation of a new object and does not appear to provide a way to return a reference to an exisiting object rather than a new one. – Stephen Ware Apr 05 '18 at 04:36
-1

Would root == ROOT not be returning the memory location? You would need to compare that root ArrayList is the same as ROOT ArrayList

Brandon
  • 1,158
  • 3
  • 12
  • 22
  • Yes, it is checking the memory location, because I want them to be the same. Sadly their ArrayLists also are not == to one another. – Stephen Ware Apr 05 '18 at 04:17
  • @sgware just remembered java is Pass by Value not Pass by reference, so you’ll need to explicitly ask for its memory location when saying oout.writeObject(ROOT); – Brandon Apr 05 '18 at 04:26