【问题标题】:Javafx IO Exception about "implements Serializable"关于“实现可序列化”的 Javafx IO 异常
【发布时间】:2017-12-09 10:33:03
【问题描述】:

我在 javafx 文件 IO 上遇到了一些问题。我已经实现了 Serializable 但也有一些错误。感谢您的帮助。

1.Appdata类

    package data;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import model.*;

public class AppData implements Serializable{
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    public static final ObservableList<Customer> cusInfo = FXCollections.observableArrayList();
    static{
        Customer Raven = new Customer();
        Raven.setName(new SimpleStringProperty("Raven"));
        Raven.setPassword("raven09");
        Customer Messi = new Customer();
        Messi.setName(new SimpleStringProperty("Messi"));
        Messi.setPassword("messi10");       
        Customer Ronaldo = new Customer();
        Ronaldo.setName(new SimpleStringProperty("Ronaldo"));
        Ronaldo.setPassword("ronaldo07");
        Customer Neymar = new Customer();
        Neymar.setName(new SimpleStringProperty("Neymar"));
        Neymar.setPassword("neymar11");

        Account RavenFund = new Account();
        RavenFund.setBalance("1000000");
        RavenFund.setName(new SimpleStringProperty("RavenFund"));
        RavenFund.setLimit("200000");
        RavenFund.setType("private");
        Account MeRo = new Account();
        MeRo.setBalance("200000");
        MeRo.setName(new SimpleStringProperty("MeRo"));
        MeRo.setLimit("60000");
        MeRo.setType("private");
        Account Barcelona = new Account();
        Barcelona.setBalance("40000");
        Barcelona.setName(new SimpleStringProperty("Barcelona"));
        Barcelona.setLimit("680000");
        Barcelona.setType("business");
        Account Madrid = new Account();
        Madrid.setBalance("70651");
        Madrid.setName(new SimpleStringProperty("Madrid"));
        Madrid.setLimit("8000");
        Madrid.setType("business"); 

        Raven.add(RavenFund);
        Raven.add(Barcelona);
        Messi.add(MeRo);
        Messi.add(Barcelona);
        Ronaldo.add(MeRo);
        Ronaldo.add(Madrid);
        Neymar.add(Barcelona);
        cusInfo.add(Raven);
        cusInfo.add(Messi);
        cusInfo.add(Ronaldo);
        cusInfo.add(Neymar);
    }
    public static void writeObject() {  
        try {    
            FileOutputStream outStream = new FileOutputStream("appdata.txt");  
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outStream);  

            objectOutputStream.writeObject(cusInfo);  
            outStream.close();  
            System.out.println("successful");  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
    public static void readObject(){  
        FileInputStream freader;  
        try {  
            freader = new FileInputStream("appdata.txt");  
            ObjectInputStream objectInputStream = new ObjectInputStream(freader);  
            ObservableList<Customer> cusINFO =FXCollections.observableArrayList();
             cusINFO = (ObservableList<Customer>) objectInputStream.readObject();  
             //System.out.println("The name is " + cusINFO.get("name"));  

        } catch (FileNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } catch (ClassNotFoundException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  

    }  
}

2.账户类

    package model;

import java.io.Serializable;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
 * @author dell
 *
 */
public class Account implements Serializable{
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
        private StringProperty name;
        private StringProperty type;
        private StringProperty limit;
        private StringProperty balance;

        public Account(){
            name = new SimpleStringProperty();
            type = new SimpleStringProperty();
            limit= new SimpleStringProperty();
            balance= new SimpleStringProperty();
        }
        public String getType() {
            return type.get();
        }
        public void setType(String type) {
            this.type.set(type);
        }
        public String getLimit() {
            return limit.get();
        }
        public void setLimit(String limit) {
            this.limit.set(limit);
        }
        public String getBalance() {
            return balance.get();
        }
        public void setBalance(String balance) {
            this.balance.set(balance);
        }
        public StringProperty getName() {
            return name;
        }
        public void setName(StringProperty name) {
            this.name = name;
        }
    }

3.客户类

    package model;

import java.io.Serializable;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

/**
 * @author Raven Xu
 * @version 1.2
 * @version 1.1
 * 
 */
public class Customer implements Serializable{
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private StringProperty name;
    private StringProperty password;
    private ObservableList<Account> accounts;

    public Customer() {
        accounts = FXCollections.observableArrayList();
        name = new SimpleStringProperty();
        password = new SimpleStringProperty();
    }

    public StringProperty getName() {
        return name;
    }

    public void setName(StringProperty name) {
        this.name = name;
    }

    public String getPassword() {
        return password.get();
    }

    public void setPassword(String password) {
        this.password.set(password);
    }

    public void add(Account a) {
        accounts.add(a);
    }

    public ObservableList<Account> getAccounts() {
        return accounts;
    }
}

4.主类

    package view;

import data.AppData;
import javafx.application.Application;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import stage.WelcomeStage;

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            AppData.writeObject();
            //AppData.readObject();
            primaryStage = WelcomeStage.getStage();
            primaryStage.getIcons().add(new Image("file:resources/images/RBClogo.png"));        
            } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

5.错误信息

java.io.NotSerializableException: com.sun.javafx.collections.ObservableListWrapper
    at java.io.ObjectOutputStream.writeObject0(Unknown Source)
    at java.io.ObjectOutputStream.writeObject(Unknown Source)
    at data.AppData.writeObject(AppData.java:74)
    at view.Main.start(Main.java:13)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Unknown Source)

我的英语很好。如果您能帮助我,我真的非常感谢您。

【问题讨论】:

  • FXCollections.observableArrayList()的返回值不可序列化!
  • 我将 observableArraylist 更改为 Hashmap。而且它也不起作用。

标签: java eclipse exception serialization javafx


【解决方案1】:

首先,您尝试将 ObservableList 传递给 ObjectOutputStream.writeObject。正如fabian points out,那个 ObservableList 是由 FXCollections.observableArrayList() 返回的,并且不能保证是可序列化的。

即使那个 ObservableList 是可序列化的,序列化它也会自动序列化它的元素——客户实例——它们本身就会有序列化问题。 Customer 的非静态字段是 SimpleStringProperty 实例,即not serializable,而我们已经建立的另一个调用 FXCollections.observableArrayList() 获得的 ObservableList 不保证可序列化。

作为一个类的作者,如果你让这个类实现 Serializable,你有责任确保它的字段可以被序列化。当然,一种方法是确保每个字段的类型都是可序列化的类型。但由于您使用的是 JavaFX 属性,所以这不是一个选择。这意味着您必须自己处理序列化。幸运的是,documentation for the Serializable interface 中很好地描述了这样做的过程:

在序列化和反序列化过程中需要特殊处理的类必须实现具有这些确切签名的特殊方法:

private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException

writeObject 方法负责为其特定类写入对象的状态,以便相应的 readObject 方法可以恢复它。可以通过调用 out.defaultWriteObject 来调用保存 Object 字段的默认机制。该方法不需要关注属于其超类或子类的状态。通过使用 writeObject 方法或使用 DataOutput 支持的原始数据类型的方法将各个字段写入 ObjectOutputStream 来保存状态。

readObject 方法负责从流中读取并恢复类字段。它可以调用 in.defaultReadObject 来调用用于恢复对象的非静态和非瞬态字段的默认机制。 defaultReadObject 方法使用流中的信息将保存在流中的对象的字段分配给当前对象中相应命名的字段。这可以处理类已经演变为添加新字段的情况。该方法不需要关注属于其超类或子类的状态。通过使用 writeObject 方法或使用 DataOutput 支持的原始数据类型的方法将各个字段写入 ObjectOutputStream 来保存状态。

这意味着你的类需要添加一个writeObject 方法来进行自己的序列化,编写已知可序列化类型的对象:

public class Customer
implements Serializable {
    private static final long serialVersionUID = 1;

    private transient StringProperty name;
    private transient StringProperty password;
    private transient ObservableList<Account> accounts;

    // ...

    /**
     * Performs custom serialization of this instance.
     * Automatically invoked by Java when this instance is serialized.
     *
     * @param out stream to write this object to
     *
     * @throws IOException if any error occurs while writing
     *
     * @serialData String representing {@link #name};
     *             String representing {@link #password};
     *             Account[] array representing {@link #accounts}
     *
     * @see #readObject(ObjectInputStream)
     */
    private void writeObject(ObjectOutputStream out)
    throws IOException {

        out.defaultWriteObject();   // always call this first

        out.writeObject(name.get());
        out.writeObject(password.get());
        out.writeObject(accounts.toArray(new Account[0]));
    }

    /**
     * Performs custom deserialization of transient fields.
     * Automatically invoked by Java during deserialization.
     *
     * @param in stream from which this object is being read
     *
     * @throws IOException if any error occurs while reading
     * @throws ClassNotFoundException if any object is read which belongs to an unknown class
     *
     * @see #writeObject(ObjectOutputStream)
     */
    private void readObject(ObjectInputStream in)
    throws IOException,
           ClassNotFoundException {

        in.defaultReadObject();    // always call this first

        name = new SimpleStringProperty((String) in.readObject());
        password = new SimpleStringProperty((String) in.readObject());
        accounts = FXCollections.observableArrayList((Account[]) in.readObject());
    }

注意在 JavaFX 属性和 ObservableList 上使用 transient 关键字。这可以防止它们被自动序列化和反序列化。 writeObject 方法代替了工作,传递已知正确实现 Serializable: Strings 和 Java 数组的对象。

@serialData javadoc 标签不是必需的,但这是一个非常好的主意,因为它准确记录了正在写入的内容来代替每个瞬态字段。

请注意,反序列化不会调用任何构造函数。* 所有构造函数都被绕过,因此readObject 必须确保初始化非静态、非瞬态字段。如果不是,则它们将为空(或在数字/布尔字段的情况下为零/假)。

在上面的代码中,我选择了将accounts ObservableList 作为数组存储在序列化数据流中。这有一个很好的理由:虽然我可以使用 ArrayList 或其他已知可序列化的 List 实现,但转换由 ObjectInputStream.readObject 返回的 Object 的行为将不安全。 由于类型擦除,将对象强制转换为 List 是不安全的,因为它告诉编译器盲目地假设 List 包含 Account 对象,即使在运行时不能保证这一点。另一方面,数组类型在运行时完全存在(具体化),因此当代码尝试强制转换为 Account[] 时,保证要么成功(从而保证每个元素实际上都是 Account 实例),要么抛出一个 ClassCastException。

它负责 Customer 类。但是,由于序列化 Customer 实例还涉及序列化零个或多个 Account 实例,因此必须给予 Account 类相同的处理:

public class Account
implements Serializable {
    private static final long serialVersionUID = 1;

    private transient StringProperty name;
    private transient StringProperty type;
    private transient StringProperty limit;
    private transient StringProperty balance;

    // ...

    /**
     * [javadoc omitted;  should be similar to javadoc of Customer.writeObject]
     *
     * @serialData String representation of {@link #name};
     *             String representation of {@link #type};
     *             String representation of {@link #limit};
     *             String representation of {@link #balance}
     */
    private void writeObject(ObjectOutputStream out)
    throws IOException {

        out.defaultWriteObject();   // always call this first

        out.writeObject(name.get());
        out.writeObject(type.get());
        out.writeObject(limit.get());
        out.writeObject(balance.get());
    }

    /**
     * [javadoc omitted;  should be similar to javadoc of Customer.readObject]
     */
    private void readObject(ObjectInputStream in)
    throws IOException,
           ClassNotFoundException {

        in.defaultReadObject();    // always call this first

        name = new SimpleStringProperty((String) in.readObject());
        type = new SimpleStringProperty((String) in.readObject());
        limit = new SimpleStringProperty((String) in.readObject());
        balance = new SimpleStringProperty((String) in.readObject());
    }

(您确定要将余额存储为字符串而不是 BigDecimal?)

我还应该指出,Customer 和 Account 的 getNamesetName 方法不符合 JavaFX bean。如果您查看任何 JavaFX 类,例如 Stage,您将看到每个可写(相对于只读)属性都有三个方法。例如,考虑 Stage 的 title 属性:

public StringProperty titleProperty();
public String getTitle();
public void setTitle(String newTitle);

如您所见,标准 JavaFX 约定是使 StringProperty 对象本身不可更改,同时使 StringProperty 包含的值可修改。 get/set 方法使用 String,但从不使用 StringProperty。


* 从技术上讲,在某些情况下反序列化可以调用构造函数,但这些情况不适用于任何实现或继承 Serializable 接口的类。

【讨论】:

  • OMG,这是我第三次提出堆栈溢出问题。非常感谢你能给我这么好的答案!非常感谢,我学到了很多。谢谢!
  • 还有,请问我应该什么时候调用writeObject方法和readObject方法?(我学Java才1个多月,hh)
  • 你不应该调用它们。它们是由 Java 的序列化机制自动检测和调用的特殊方法名称。有关详细说明,请参阅 the Serializable documentation
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-22
  • 2010-10-20
  • 2010-12-17
  • 1970-01-01
  • 1970-01-01
  • 2017-05-06
  • 1970-01-01
相关资源
最近更新 更多