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

标签 java javafx

我有一个Trade具有以下代码的对象,它实现 Serializable接口(interface),但由于它包含 javafx 属性,我得到这个 java.io.NotSerializableException因此未能正确执行 writeObject()readObject() 。我的最终目标是能够使用ObjectOutputStream写入读取这个对象。和ObjectInputStream

我阅读了 3 个链接:

NotSerializableException on SimpleListProperty

Oracle doc

Java Custom Serialization

自从我的Trade类正在运行 ScheduledService要从 Google 财经获取收盘价,我知道我需要调用 startService() readObject()内确保当 readObject()方法被调用并且对象被反序列化,线程将再次重新启动。

此外,我知道我需要在我的 Trade 中定义这 2 个私有(private)方法。类(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();
}

问题:我已阅读上面的第三个链接,但我仍然对上面这两个私有(private)方法还应该包含哪些内容感到困惑?

因为我的交易对象有相当多的属性,但都是它真正需要的 只是 buySell,transactionDate,symbol, double volume, double price构造一个对象。我应该将其余属性设置为 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);
           }


       }
}

更新:我已经更新了我的代码。但自从 creationTime不是 Trade 的参数的构造函数,我不知道在我的情况下如何序列化/反序列化它。更准确地说,如果我创建一个 Trade对象当时说 creationTime = 1443444301488 ,我希望这个对象被序列化,当我读入该对象并反序列化它时,我希望创建时间与它原来的时间完全相同(即1443444301488),但我不知道如何为了达成这个。这就是我现在面临的问题。

最佳答案

我会避免序列化 javafx 对象。相反,创建一个包含应序列化状态的 javabean 对象。然后您可以从序列化代理 javabean 构建自己的 Trade 对象。

class TradeSerialProxy {
    private String simpleBeanFields;
    private int moreSimpleStateFields;

    //getters and setters
}

然后

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

您可以在《Effective Java》一书中看到与此类似的内容。尽管在那本书中他出于安全目的使用代理。我遵循的规则是仅序列化简单的 javabean 对象,仅此而已。避免序列化复杂的对象。

此外,如果您使用常规 Java 序列化,那么每当您的类实现发生更改时,您都可能会遇到版本问题。有很多方法可以解决这个问题,例如使用 JSON 和 GSON 进行序列化。因为我使用的是纯标准 Java,并且没有外部库/jar,所以我必须使用 HashMap 来完成此操作...我只会序列化 HashMap,并使用传递给它的 HashMap 来构建真正的对象。我必须这样做以避免不断出现串行版本不匹配异常并坚持使用纯标准普通 Java。

编辑:这是一个使用序列化代理模式的对象。该方法来自《Effective Java》第二版第 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);
    }

}

这种方法已融入 Java 对象序列化协议(protocol)中。我现在使用的另一种方法利用 HashMap<String, Object> 作为代理。

这是界面。我必须让这个接口(interface)中的方法返回它们的哈希值,因为我广泛使用加密序列化对象的哈希值来防止篡改已保存的文件。我不一定推荐这样做,但展示了序列化代理的可能性。

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;

}

这是一个使用它的对象的示例。

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:对第一种方法进行更深入的解释。

您需要首先了解 Java 序列化的一些基础知识。 Java 为您完成了大部分繁重的工作,它实际上有一个 writeObject 和 readObject 在大多数情况下都可以正常工作。这对您来说是个好消息,因为您需要做的就是决定哪些字段需要进入代理,只是您想要序列化的内容(状态),而不必担心实际进行序列化(向代理添加/删除对象)溪流)。接下来,您需要能够使用代理初始化主类,反之亦然。因此,在主类中创建一个构造函数,该构造函数将代理对象作为输入,并在该构造函数中初始化您的主类。对代理对象执行相同的操作。最后,Java 甚至使您能够通过 writeReplace 和 readResource 方法使用代理进行写入和读取。主类的 writeReplace 将返回代理的实例,本质上是告诉 Java 序列化该对象。另一方面,在代理中,您需要一个 readResolve 来在反序列化期间返回主对象的实例。

让我们通过项目符号列表来完成这些步骤:

1)决定哪些字段需要保存并创建代理类(我使用内部嵌套类)来包含这些字段。
2)在主类和代理类中创建构造函数。 Main(Proxy obj)Proxy(Main obj)
3)分别在主类和代理类上实现writeReplace和readResolve。

希望对您有所帮助。

关于javafx:如何为此对象编写writeObject()和readObject()?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32770574/

相关文章:

java - 在 javafx 从另一个包加载 FXML 文件时出现错误?

java - 使用 Java 类更新 OpenLDAP

java - 从 html (JSOUP) 获取图标

eclipse - "Open with sceenbuilder"无法找到

java - 在一个窗口中制作多个按钮。 JavaFX

JavaFX - 对象数据到 TableView

java - Jena 模型中 RDF 资源的 URI

java - 如何在Java中实现自动更新?

java - 使用 while 或 do while 循环根据用户输入创建文件夹

java - *基本* 单字符串列的 JavaFX TableView