Reputation: 11
I have some trouble on javafx FILE IO. I have implemented Serializable but there is also some wrong. Thank you for your help.
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.Main class
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.Error Information
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)
My English is very good. I really really thank you if you can help me.
Upvotes: 1
Views: 1247
Reputation: 44414
First, you are trying to pass an ObservableList to ObjectOutputStream.writeObject. As fabian points out, that ObservableList was returned by FXCollections.observableArrayList() and is in no way guaranteed to be serializable.
Even if that ObservableList were serializable, serializing it would automatically serialize its elements—Customer instances—which are themselves going to have serialization problems. The non-static fields of Customer are SimpleStringProperty instances, which are not serializable, and an ObservableList obtained from another call to FXCollections.observableArrayList(), which we’ve already established is not guaranteed to be serializable.
As the author of a class, if you make the class implement Serializable, you are responsible for making sure its fields can be serialized. One way, of course, is to make sure each field’s type is a serializable type. But since you’re using JavaFX properties, that isn’t an option. This means you have to handle the serialization yourself. Luckily, the process for doing this is described fairly well in the documentation for the Serializable interface:
Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
…
The writeObject method is responsible for writing the state of the object for its particular class so that the corresponding readObject method can restore it. The default mechanism for saving the Object's fields can be invoked by calling out.defaultWriteObject. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.
The readObject method is responsible for reading from the stream and restoring the classes fields. It may call in.defaultReadObject to invoke the default mechanism for restoring the object's non-static and non-transient fields. The defaultReadObject method uses information in the stream to assign the fields of the object saved in the stream with the correspondingly named fields in the current object. This handles the case when the class has evolved to add new fields. The method does not need to concern itself with the state belonging to its superclasses or subclasses. State is saved by writing the individual fields to the ObjectOutputStream using the writeObject method or by using the methods for primitive data types supported by DataOutput.
What this means is that your class needs to add a writeObject
method to do its own serialization, writing objects of types that are known to be serializable:
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());
}
Note the use of the transient
keyword on the JavaFX properties and ObservableList. This prevents them from being automatically serialized and deserialized. The writeObject method does the work instead, passing objects which are known to correctly implement Serializable: Strings and a Java array.
The @serialData javadoc tag is not required, but it’s a very good idea, as it documents exactly what is getting written in place of each transient field.
Note that deserialization does not invoke any constructors.* All constructors are bypassed, so the readObject
must make sure to initialize non-static, non-transient fields. If it doesn’t, they will be null (or zero/false in the case of numeric/boolean fields).
In the above code, I have chosen to store the accounts
ObservableList in the serialized data stream as an array. There is a good reason for this: While I could use an ArrayList or other List implementation known to be serializable, the act of casting the Object returned by ObjectInputStream.readObject would not be safe. Due to type erasure, casting an object to List<Account> is not safe, since it tells the compiler to blindly assume the List contains Account objects even though there is no guarantee of that at runtime. On the other hand, array types are fully present (reified) at runtime, so when the code attempts to cast to Account[]
, it is guaranteed to either succeed (and thus guarantee every element is in fact an Account instance), or throw a ClassCastException.
That takes care of the Customer class. However, since serializing a Customer instance also involves serializing zero or more Account instances, the Account class must be given the same treatment:
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());
}
(Are you sure you want to store a balance as a String instead of, say, a BigDecimal?)
I should also point out that the getName
and setName
methods of both Customer and Account are not JavaFX bean compliant. If you look at any JavaFX class, such as Stage, you will see that each writable (as opposed to read-only) property has three methods. Consider, for example, the title
property of Stage:
public StringProperty titleProperty();
public String getTitle();
public void setTitle(String newTitle);
As you can see, standard JavaFX convention is to make the StringProperty object itself unchangeable, while making the StringProperty’s contained value modifiable. The get/set methods use String, but never StringProperty.
* Technically, there are circumstances where deserialization can invoke constructors, but those circumstances do not apply to any class which implements or inherits the Serializable interface.
Upvotes: 3