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

时间:2015-09-24 20:43:13

标签: java javafx

我有一个带有下面代码的Trade对象,它实现了Serializable接口,但由于它包含javafx属性,因此我得到了这个java.io.NotSerializableException,因此无法正确执行{{1} }和writeObject()。我的最终目标是能够使用readObject()ObjectOutputStream

撰写阅读此对象

我读了3个链接:

NotSerializableException on SimpleListProperty

Oracle doc

Java Custom Serialization

由于我的ObjectInputStream班级正在运行Trade以从Google财经中获取收盘价,我知道我需要在ScheduledService内拨打startService()以确保当调用readObject()方法并反序列化对象时,线程将再次重新启动。

此外,我知道我需要在readObject()类中定义这两个私有方法。

Trade

问题:我已经阅读了上面的第3个链接,我仍然对上面这两个私有方法应该采取的其他方法感到困惑?

因为我的交易对象有很多属性,但它确实需要它 只是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(); } 来构造一个对象。我应该将其余属性设置为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对象,我希望这个对象被序列化,当我读入对象并反序列化它时,我想创建时间到与它的完全相同(即1443444301488),我不知道如何实现这一目标。这是我现在面临的问题。

1 个答案:

答案 0 :(得分:3)

我会避免序列化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,而且没有外部的libs / jars,所以我必须用HashMap完成这个...我只会序列化HashMap并让真实的对象使用传递给它的HashMap来构建。我必须这样做,以避免获得恒定的串行版本不匹配异常并坚持使用纯标准的vanilla Java。

编辑:这是一个使用序列化代理模式的对象。该应用程序来自Effective 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);
    }

}

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

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

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类中创建构造函数。 Main(Proxy obj)Proxy(Main obj)
3)分别在main和proxy类上实现writeReplace和readResolve。

我希望有所帮助。