简体   繁体   中英

javafx: How to write the writeObject() and readObject() for this object?

I have a Trade object with the code below and it implements the Serializable interface but since it contains javafx properties , I get this java.io.NotSerializableException and hence failed to properly do writeObject() and readObject() . My ultimate goal is to be able to write and read this object using ObjectOutputStream and ObjectInputStream

I read the 3 links:

NotSerializableException on SimpleListProperty

Oracle doc

Java Custom Serialization

Since my Trade class is running a ScheduledService to pick up the closing price from Google Finance, I know that I need to call startService() within the readObject() to ensure that when the readObject() method is called and the object is deserialized, the thread will restart again.

Furthermore, I understand that I need to I need to define these 2 private methods within my Trade Class.

private void writeObject(ObjectOutputStream out) throws IOException
{
     out.defaultWriteObject(); 
     // What else should I write in here?
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
     // our "pseudo-constructor"
     in.defaultReadObject();

    // Not sure what else I need to write in here

     // now we are a "live" object again, so let's run rebuild and start
     startService();
}

Question: I have read the 3rd link above and I am still confused about what else should go into these two private methods above ?

Because my trade object has quite a lot of properties, but all it really needs are just buySell,transactionDate,symbol, double volume, double price to construct an object. Should I set the rest of the properties to transient then?

public class Trade implements Serializable{
    // properties
    private Long creationTime;
    private int counter;
    private ObjectProperty<LocalDate> transactionDate;
    private StringProperty symbol;
    private StringProperty details;
    private StringProperty buySell;
    private DoubleProperty volume;
    private DoubleProperty price;
    private ReadOnlyDoubleWrapper transactionFee;

    private final ReadOnlyDoubleWrapper closingPrice;
    private final PauseTransition delay;    
    private ReadOnlyBooleanWrapper caution;

    private final ScheduledService<webScraping> stockService = new ScheduledService<webScraping>() {
           // web scrape google finance data
           ...
   }

   // constructor
    public Trade(BuySell buySell, LocalDate transactionDate, String symbol, double volume, double price){
           ...
           startService();
           creationTime = Calendar.getInstance().getTimeInMillis();
    }

    // getters and setters and instance methods that return the properties themselves
    public Long getCreationTime(){
           return this.creationTime;
    }

private Object writeReplace() {
    return new TradeProxy(this);
}

private void readObject(ObjectInputStream stream)
        throws InvalidObjectException {
    throw new InvalidObjectException("Proxy required");

}

    ...

    private static class TradeProxy implements Serializable{
         private String buySell;
         private LocalDate transactionDate;
         private String stockTicker;
         private double price;
         private double volume;
         private Long creationTime;

         private TradeProxy(Trade trade){
            this.buySell = trade.getBuySell();
            this.transactionDate = trade.getTransactionDate();
            this.stockTicker = trade.getStockTicker();
            this.price = trade.getPrice();
            this.volume = trade.getVolume();
            this.creationTime = trade.getCreationTime();
          }

          private void writeObject(ObjectOutputStream s ) throws IOException{
             s.defaultWriteObject();
           }

           private Object readResolve() throws ObjectStreamException{
              return new Trade(this.buySell,this.transactionDate, this.symbol, this.volume, this.price);
           }


       }
}

UPDATE: I have updated my code. But since creationTime is not an argument of the Trade 's constructor, I do not know how to serialize/deserialize it in my case. To be more precise, if I create a Trade object at time say creationTime = 1443444301488 , I want this object be serialized and when I read in the object and deserialize it, I want to the creationTime to be exactly the same as what it was (ie 1443444301488) and I don't know how to achieve this. This is the problem that I am facing now.

I would avoid serializing javafx objects. Instead create a javabean object that contains the state that should be serialized. Then you can have your Trade object build itself from the serialized proxy javabean.

class TradeSerialProxy {
    private String simpleBeanFields;
    private int moreSimpleStateFields;

    //getters and setters
}

then

public Trade (TradeSerialProxy proxy) {
    //build the Trade object using the proxy.
}

You see something similar to this in the Effective Java book. Though in that book he uses proxies for security purposes. The rule I follow is to only serialize simple javabean objects and that's that. Avoid serializing complicated objects.

Also, if you use regular Java serialization, then you might run into version problems whenever your class implementation changes. There are ways around this, like using JSON and GSON for serialization. Because I was using pure standard Java, and no external libs/jars, I had to accomplish this with HashMap... where I would only serialize the HashMap and have the real objects build themselves using a HashMap passed to it. I had to do this to avoid getting the constant serial version mismatch exception and sticking to pure standard vanilla Java.

EDIT: This is an object that uses serialization proxy pattern. This apporach is from Effective Java 2nd Edition item 78.

public class UserProfile implements Serializable {

///////////////////////////////////////////////////////////////////////////
//private variables
private String profileName = null;
private int version = 0;
private LeaderboardPermissions leaderboardState = LeaderboardPermissions.ASK;
private boolean upgradeWalkThrough = true;
private final Map<GameType, GameTypeStats> gameTypeStats;
private final String id;
private boolean offNet = true;

///////////////////////////////////////////////////////////////////////////
//serialization stuff
private static final long serialVersionUID = 7625672295622776890L;

private UserProfile(UserProfileProxy t) {
    this.profileName = t.profileName;
    this.version = t.version;
    this.leaderboardState = t.leaderboardState;
    this.upgradeWalkThrough = t.upgradeWalkThrough;
    this.gameTypeStats = t.gameTypeStats;
    this.id = t.id;
    this.offNet = t.offNet;
}

private Object writeReplace() {
    return new UserProfileProxy(this);
}

private void readObject(ObjectInputStream stream)
        throws InvalidObjectException {
    throw new InvalidObjectException("Proxy required");

}

///////////////////////////////
//serialization proxy
private static class UserProfileProxy implements Serializable {

    private String profileName = null;
    private int version = 0;
    private final LeaderboardPermissions leaderboardState;
    private boolean upgradeWalkThrough = true;
    private final Map<GameType, GameTypeStats> gameTypeStats;
    private String id;
    private static final long serialVersionUID = 6985672045622776890L;
    private boolean offNet;

    private UserProfileProxy(UserProfile t) {
        this.profileName = t.profileName;
        this.version = t.version;
        this.leaderboardState = t.leaderboardState;
        this.upgradeWalkThrough = t.upgradeWalkThrough;
        this.gameTypeStats = t.gameTypeStats;
        this.id = t.id;
        this.offNet = t.offNet;
    }

    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
    }

    private Object readResolve() throws ObjectStreamException {
        return new UserProfile(this);
    }

}

This approach is baked into Java object serialization protocol. Another method I am using now utilizes a HashMap<String, Object> as the proxy.

Here is the interface. I had to have the methods in this interface return their hash because my extensive use of encrypting the serialized object's hashes to prevent tampering with the saved files. I'm not necessarily recommending this but showing possibilities of serialization proxies.

public interface MapSerializable {

    public static String CLASS_KEY = "MapSerializable.CLASS_KEY";

/**
 * Object will populate a HashMap of objects that it can use at some later
 * point to reinitialize itself. Return the hash of the objects used to
 * build itself.
 *
 * @param serial
 * @return
 * @throws IOException
 */
    public int populateSerialMap(HashMap<String, Object> serial) throws IOException;

/**
 * Object will initialize itself using the input HashMap. Returns the hash
 * of the objects that were used to initialize itself from the Map.
 *
 * @param serial
 * @return hash of the objects that were used to load yourself.
 * @throws IOException
 */
    public int initializeFromMap(HashMap<String, Object> serial) throws IOException;

}

And here is an example of an object using it.

public class GameType implements MapSerializable {

    ////////////////////////////////////////////////////////////////////////////
    //private variables
    private String displayName = null;

    ////////////////////////////////////////////////////////////////////////////
    //constrcutor
    public GameType(String name) {
        this.displayName = name;
    }

    GameType() {
    }

    ////////////////////////////////////////////////////////////////////////////
    //public methods
    @Override
    public int populateSerialMap(HashMap<String, Object> serial) throws IOException {
        serial.put("displayName", displayName);
        return 17 * Objects.hashCode(displayName);
    }

    @Override
    public final int initializeFromMap(HashMap<String, Object> serial) throws IOException {
        int hash = 0;
        ObjectHashPair<String> ohp = model.utils.SerialUtils.getObjectFromMap(serial, "displayName", "");
        displayName = ohp.obj;
        hash += 17 * ohp.hash;
        return hash;
    }

}

EDIT2: Deeper explanation into the first method.

You need to first understand some of the basics of Java serialization. Java does most of the heavy lifting for you, it actually has a writeObject and readObject that work just fine for most cases. This is good news for you since all you need to do is deciding what fields need to go into the proxy just the things you want to serialize (the state) and not have to worry about actually doing the serialization (adding/removing objects to the stream). Next, you need to be able to initialize your main class using the proxy and vice versa. So create a constructor in your main class that takes a proxy object as input, in that constructor initialize your main class. Do the same for the proxy object. Lastly, Java even gives you the ability to use a proxy for writing and reading via the writeReplace and readResource methods. The writeReplace for the main class will return an instance of the proxy, essentially telling Java to serialize that object instead. On the flip side in the proxy you'll need a readResolve to return an instance of the main object during deserialization.

SO lets go through the steps in a bullet list:

1) Decide what fields need saving and create your proxy class (I used an inner nested class) to have those fields.
2) Create the constructors in main and proxy class. Main(Proxy obj) and Proxy(Main obj) .
3) Implement the writeReplace and readResolve on the main and proxy classes, respectively.

I hope that helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM