关于“implements Serializable”的Javafx IO异常

时间:2017-07-05 16:31:14

标签: java eclipse exception serialization javafx

我在javafx FILE IO上遇到了一些麻烦。我已经实现了Serializable,但也有一些错误。谢谢你的帮助。

1.Appdata class

    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.Account class

    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.Customer class

    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)

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

1 个答案:

答案 0 :(得分:1)

首先,您尝试将ObservableList传递给ObjectOutputStream.writeObject。作为fabian points out,FXCollections.observableArrayList()返回了ObservableList,并且不能保证可序列化。

即使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.newArrayList((Account[]) in.readObject());
    }

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

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

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

在上面的代码中,我选择将accounts ObservableList作为数组存储在序列化数据流中。这有一个很好的理由:虽然我可以使用已知可序列化的ArrayList或其他List实现,但是由ObjectInputStream.readObject返回的转换对象的行为不会是安全的。由于类型擦除,将对象转换为List&lt; Account&gt;是不安全的,因为它告诉编译器盲目地假设List包含Account对象,即使在运行时无法保证。另一方面,数组类型在运行时完全存在(reified),因此当代码尝试强制转换为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接口的类。