简体   繁体   English

javafx:如何为此对象编写writeObject()和readObject()?

[英]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() . 我有一个带有以下代码的Trade对象,它实现了Serializable接口,但是由于它包含javafx属性,因此我收到此java.io.NotSerializableException ,因此无法正确执行writeObject()readObject() My ultimate goal is to be able to write and read this object using ObjectOutputStream and ObjectInputStream 我的最终目标是能够使用ObjectOutputStreamObjectInputStream 写入读取此对象

I read the 3 links: 我阅读了3个链接:

NotSerializableException on SimpleListProperty SimpleListProperty上的NotSerializableException

Oracle doc Oracle文档

Java Custom Serialization Java自定义序列化

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. 由于我的Trade类正在运行ScheduledService以从Google财经获取收盘价,因此我知道我需要在readObject()调用startService() readObject()以确保在调用readObject()方法并反序列化该对象时,该线程将再次重新启动。

Furthermore, I understand that I need to I need to define these 2 private methods within my Trade Class. 此外,我知道我需要在我的Trade类中定义这两个私有方法。

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 ? 问:我已经阅读了上面的第3个链接,但是我仍然对上面这两个私有方法还有什么感到困惑?

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. 因为我的交易对象具有很多属性,但是它真正需要的只是buySell,transactionDate,symbol, double volume, double price来构造一个对象。 Should I set the rest of the properties to transient then? 我是否应该将其余属性设置为transient

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. 但是由于creationTime不是Trade的构造函数的参数,因此在我的情况下,我不知道如何对其进行序列化/反序列化。 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. 更准确地说,如果我在创建时说一个Trade对象,比如creationTime = 1443444301488 ,我希望该对象被序列化,当我读入该对象并将其反序列化时, 我希望creationTime与它完全相同(即1443444301488) ,我不知道该如何实现。 This is the problem that I am facing now. 这就是我现在面临的问题。

I would avoid serializing javafx objects. 我会避免序列化javafx对象。 Instead create a javabean object that contains the state that should be serialized. 而是创建一个包含应序列化状态的javabean对象。 Then you can have your Trade object build itself from the serialized proxy javabean. 然后,您可以让您的Trade对象从序列化代理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. 您可以在《有效Java》一书中看到类似的内容。 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. 我遵循的规则是仅序列化简单的Javabean对象。 Avoid serializing complicated objects. 避免序列化复杂的对象。

Also, if you use regular Java serialization, then you might run into version problems whenever your class implementation changes. 另外,如果您使用常规Java序列化,则每当类实现更改时,您都可能会遇到版本问题。 There are ways around this, like using JSON and GSON for serialization. 有多种解决方法,例如使用JSON和GSON进行序列化。 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. 因为我使用的是纯标准Java,并且没有外部libs / jars,所以我必须使用HashMap来完成此操作……在这里,我只能序列化HashMap,并使用传递给它的HashMap来使真实对象自行构建。 I had to do this to avoid getting the constant serial version mismatch exception and sticking to pure standard vanilla Java. 我这样做是为了避免遇到恒定的串行版本不匹配异常,并坚持使用纯标准的标准Java。

EDIT: This is an object that uses serialization proxy pattern. 编辑:这是一个使用序列化代理模式的对象。 This apporach is from Effective Java 2nd Edition item 78. 此方法来自有效Java 2nd Edition项目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. 这种方法被植入Java对象序列化协议中。 Another method I am using now utilizes a HashMap<String, Object> as the proxy. 我现在使用的另一种方法利用HashMap<String, Object>作为代理。

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. EDIT2:对第一种方法的更深入的说明。

You need to first understand some of the basics of Java serialization. 您需要首先了解Java序列化的一些基础知识。 Java does most of the heavy lifting for you, it actually has a writeObject and readObject that work just fine for most cases. Java为您完成了大部分繁重的工作,实际上它具有一个writeObject和readObject,在大多数情况下都可以正常工作。 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. 最后,Java甚至使您能够通过writeReplace和readResource方法使用代理进行读写。 The writeReplace for the main class will return an instance of the proxy, essentially telling Java to serialize that object instead. 主类的writeReplace将返回代理的实例,实际上是告诉Java将该对象序列化。 On the flip side in the proxy you'll need a readResolve to return an instance of the main object during deserialization. 在代理的另一面,您需要一个readResolve来在反序列化期间返回主对象的实例。

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. 1)确定哪些字段需要保存并创建您的代理类(我使用了内部嵌套类)来拥有这些字段。
2) Create the constructors in main and proxy class. 2)在主类和代理类中创建构造函数。 Main(Proxy obj) and Proxy(Main obj) . Main(Proxy obj)Proxy(Main obj)
3) Implement the writeReplace and readResolve on the main and proxy classes, respectively. 3)分别在主类和代理类上实现writeReplace和readResolve。

I hope that helps. 希望对您有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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