【问题标题】:javafx: How to write the writeObject() and readObject() for this object?javafx:如何为此对象编写 writeObject() 和 readObject()?
【发布时间】:2015-09-24 20:43:13
【问题描述】:

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

writeread 这个对象

我阅读了 3 个链接:

NotSerializableException on SimpleListProperty

Oracle doc

Java Custom Serialization

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

此外,我知道我需要在我的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();
}

问题:我已经阅读了上面的第三个链接,但我仍然对上面这两个私有方法还有什么感到困惑?

因为我的交易对象有很多属性,但它真正需要的只是 只是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,我希望这个对象被序列化,当我读入对象并反序列化它时,我希望creationTime 完全相同原来如此(即 1443444301488) 我不知道如何实现这一点。这是我现在面临的问题。

【问题讨论】:

  • 要么对序列化中不需要的东西使用瞬态,要么将类分成两个类,一个简单的类,它只包含交易信息(符号、价格等),应该是可序列化的,并且可能也会导致更简洁的设计。
  • 您能否提供一些代码示例,说明在这种情况下应该在readObject()writeObject() 方法中使用什么?你能否也给我一个例子,在我的情况下,你将如何将班级分成 2 个班级?谢谢
  • 我不知道您指的是哪种情况,但在任何一种情况下,您可能甚至都不需要 writeObject 和 readObject 方法。默认会序列化所有非瞬态字段,我认为这是你想要的。

标签: java javafx


【解决方案1】:

我会避免序列化 javafx 对象。而是创建一个包含应该序列化的状态的 javabean 对象。然后你可以让你的 Trade 对象从序列化的代理 javabean 构建自己。

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 构建自己。我必须这样做以避免出现持续的序列版本不匹配异常并坚持使用纯标准 vanilla Java。

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

}

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

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

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。

希望对你有帮助。

【讨论】:

  • 抱歉,我没有完全理解您的回答。例如,如果我们只查看price 变量并使用您的代码,我们将在TradeSerialProxy 类中定义double price,在Trade 类中,我们将定义DoubleProperty price = new SimpleDoubleProperty(priceFromProxy)。我是否正确理解您的逻辑?如果您能提供一个更详细的代码示例,将不胜感激。
  • 正确。我很抱歉没有使用你的例子中的真实字段,我的错。
  • 而在您的代码中,由于它捕获了创建Trade 对象的基本变量,因此只需序列化TradeSerialProxy,无需序列化Trade。它是否正确 ?如果您可以在代码示例中添加更多详细信息,那就太好了
  • 正确。我确实有一些与 Effective Java 书籍非常匹配的示例。我回家后会发帖。
  • 谢谢。你介意在你的例子中包含 getter 和 setter,这样我就可以理解变量的变化是如何从代理馈送到对象的,反之亦然。非常感谢
猜你喜欢
  • 1970-01-01
  • 2018-01-05
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
  • 2013-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多