如何使 JavaFX TreeView 和 TreeItem 可序列化?
How do I make JavaFX TreeView and TreeItem serializable?
我在尝试使用 ObjectOutputStream 保存我的 TreeView 时遇到此错误(java.io.NotSerializableException:javafx.scene.control.TreeView)。
我有 2 个 classes 实现了 Serializable 和 1 个 main class 没有实现 Serializable。
2 classes 是 Vendor 和 Address。 Vendorclass包含4个变量(姓名、年龄、性别、Addressclass类型的地址),构造函数使用set方法设置所有变量,构造函数使用set方法设置name变量仅和 get/set 变量方法。
地址class包含2个变量(街道名称和邮政编码),使用set方法设置变量的默认构造函数,以及变量的get/set方法。
这是我的主要class
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SimpleTreeView extends Application {
private TreeView<Vendor> treeView;
public void start(Stage stage) {
stage.setTitle("Simple TreeView");
treeView = new TreeView<>();
TreeItem<Vendor> root = new TreeItem<>(new Vendor("Root"));
root.setExpanded(true);
treeView.setRoot(root);
treeView.setShowRoot(false);
TreeItem<Vendor> start = new TreeItem<>(new Vendor("Start"));
root.getChildren().add(start);
Button saveButton = new Button("Save");
saveButton.setOnMouseClicked(event -> saveTreeView(stage));
VBox vBox = new VBox(20);
vBox.getChildren().addAll(treeView, saveButton);
stage.setScene(new Scene(vBox));
stage.show();
}
private void saveTreeView(Stage stage) {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save");
File file = fileChooser.showSaveDialog(stage);
if (file != null) {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
os.writeObject(treeView);
os.close();
}
catch (Exception e) {
System.out.println(e);
}
}
}
public static void main(String[] args) {
launch(args);
}
}
无法序列化节点(您自己实现的自定义节点可能除外)有太多内部状态太复杂而无法恢复。无法将 methods/interfaces 添加到节点(不扩展它们)使得无法添加 Serializable
接口并添加方法来保存恢复状态和读取所需的数据部分数据正确。
您最好创建一个可序列化的包装器 class,它允许您恢复您真正感兴趣的属性。恕我直言,最好不要尝试序列化节点;载入数据时新建一个节点,用载入的数据填充。
以下示例显示了如何使用 TreeItem<? extends Serializable>
执行此操作;有扩展属性等数据丢失,但你应该可以恢复 value
属性 和 children。 (实现比具有小深度的 TreeItem
结构所需的要复杂一些,但您需要注意某些深度,更简单的递归方法可能会导致 WhosebugError
s。)
在这种情况下,每个项目都是通过写入 children 的数量来序列化的,它是自己的值 属性,然后对每个 child 执行相同的操作。这会产生一系列可用于恢复数据的 int 和值对:
public class TreeItemSerialisation {
public static void main(String[] args) throws IOException, ClassNotFoundException {
TreeItem<String> root = new TreeItem<>("root");
TreeItem<String> c1 = new TreeItem<>("root.1");
TreeItem<String> c3 = new TreeItem<>("root.3");
root.getChildren().addAll(c1, new TreeItem<>("root.2"), c3);
TreeItem<String> c3_1 = new TreeItem<>("root.3.1");
c3_1.getChildren().add(new TreeItem<>("root.3.1.1"));
c3.getChildren().add(c3_1);
c1.getChildren().addAll(new TreeItem<>("root.1.1"), new TreeItem<>("root.1.2"));
// serialize
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(new TreeItemSerialisationWrapper(root));
}
// unserialize
TreeItem<String> root2;
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
root2 = (TreeItem<String>) ois.readObject();
}
// TODO do something with root2
}
}
public class TreeItemSerialisationWrapper<T extends Serializable> implements Serializable {
private static final long serialVersionUID = 1L;
private transient TreeItem<T> item;
public TreeItemSerialisationWrapper(TreeItem<T> item) {
if (item == null) {
throw new IllegalArgumentException();
}
this.item = item;
}
/**
* Custom way of writing the TreeItem structure
*/
private void writeObject(ObjectOutputStream out)
throws IOException {
Stack<TreeItem<T>> stack = new Stack<>();
stack.push(item);
out.defaultWriteObject();
do {
TreeItem<T> current = stack.pop();
int size = current.getChildren().size();
out.writeInt(size);
// write all the data that needs to be restored here
out.writeObject(current.getValue());
// "schedule" serialisation of children.
// the first one is inserted last, since the top one from the stack is
// retrieved first
for (int i = size - 1; i >= 0; --i) {
stack.push(current.getChildren().get(i));
}
} while (!stack.isEmpty());
}
/**
* happens before readResolve; recreates the TreeItem structure
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
class Container {
int count;
final TreeItem<T> item;
Container(ObjectInputStream in) throws ClassNotFoundException, IOException {
// read the data for a single TreeItem here
this.count = in.readInt();
this.item = new TreeItem<>((T) in.readObject());
}
}
in.defaultReadObject();
Container root = new Container(in);
this.item = root.item;
if (root.count > 0) {
Stack<Container> stack = new Stack<>();
stack.push(root);
do {
Container current = stack.peek();
--current.count;
if (current.count <= 0) {
// we're done with this item
stack.pop();
}
Container newContainer = new Container(in);
current.item.getChildren().add(newContainer.item);
if (newContainer.count > 0) {
//schedule reading children of non-leaf
stack.push(newContainer);
}
} while(!stack.isEmpty());
}
}
/**
* We're not actually interested in this object but the treeitem
* @return the treeitem
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return item;
}
}
有关 readObject
、readResolve
和 writeObject
工作原理的说明,请参阅 the javadoc of Serializable
我在尝试使用 ObjectOutputStream 保存我的 TreeView 时遇到此错误(java.io.NotSerializableException:javafx.scene.control.TreeView)。
我有 2 个 classes 实现了 Serializable 和 1 个 main class 没有实现 Serializable。
2 classes 是 Vendor 和 Address。 Vendorclass包含4个变量(姓名、年龄、性别、Addressclass类型的地址),构造函数使用set方法设置所有变量,构造函数使用set方法设置name变量仅和 get/set 变量方法。
地址class包含2个变量(街道名称和邮政编码),使用set方法设置变量的默认构造函数,以及变量的get/set方法。
这是我的主要class
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SimpleTreeView extends Application {
private TreeView<Vendor> treeView;
public void start(Stage stage) {
stage.setTitle("Simple TreeView");
treeView = new TreeView<>();
TreeItem<Vendor> root = new TreeItem<>(new Vendor("Root"));
root.setExpanded(true);
treeView.setRoot(root);
treeView.setShowRoot(false);
TreeItem<Vendor> start = new TreeItem<>(new Vendor("Start"));
root.getChildren().add(start);
Button saveButton = new Button("Save");
saveButton.setOnMouseClicked(event -> saveTreeView(stage));
VBox vBox = new VBox(20);
vBox.getChildren().addAll(treeView, saveButton);
stage.setScene(new Scene(vBox));
stage.show();
}
private void saveTreeView(Stage stage) {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Save");
File file = fileChooser.showSaveDialog(stage);
if (file != null) {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
os.writeObject(treeView);
os.close();
}
catch (Exception e) {
System.out.println(e);
}
}
}
public static void main(String[] args) {
launch(args);
}
}
无法序列化节点(您自己实现的自定义节点可能除外)有太多内部状态太复杂而无法恢复。无法将 methods/interfaces 添加到节点(不扩展它们)使得无法添加 Serializable
接口并添加方法来保存恢复状态和读取所需的数据部分数据正确。
您最好创建一个可序列化的包装器 class,它允许您恢复您真正感兴趣的属性。恕我直言,最好不要尝试序列化节点;载入数据时新建一个节点,用载入的数据填充。
以下示例显示了如何使用 TreeItem<? extends Serializable>
执行此操作;有扩展属性等数据丢失,但你应该可以恢复 value
属性 和 children。 (实现比具有小深度的 TreeItem
结构所需的要复杂一些,但您需要注意某些深度,更简单的递归方法可能会导致 WhosebugError
s。)
在这种情况下,每个项目都是通过写入 children 的数量来序列化的,它是自己的值 属性,然后对每个 child 执行相同的操作。这会产生一系列可用于恢复数据的 int 和值对:
public class TreeItemSerialisation {
public static void main(String[] args) throws IOException, ClassNotFoundException {
TreeItem<String> root = new TreeItem<>("root");
TreeItem<String> c1 = new TreeItem<>("root.1");
TreeItem<String> c3 = new TreeItem<>("root.3");
root.getChildren().addAll(c1, new TreeItem<>("root.2"), c3);
TreeItem<String> c3_1 = new TreeItem<>("root.3.1");
c3_1.getChildren().add(new TreeItem<>("root.3.1.1"));
c3.getChildren().add(c3_1);
c1.getChildren().addAll(new TreeItem<>("root.1.1"), new TreeItem<>("root.1.2"));
// serialize
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(new TreeItemSerialisationWrapper(root));
}
// unserialize
TreeItem<String> root2;
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
root2 = (TreeItem<String>) ois.readObject();
}
// TODO do something with root2
}
}
public class TreeItemSerialisationWrapper<T extends Serializable> implements Serializable {
private static final long serialVersionUID = 1L;
private transient TreeItem<T> item;
public TreeItemSerialisationWrapper(TreeItem<T> item) {
if (item == null) {
throw new IllegalArgumentException();
}
this.item = item;
}
/**
* Custom way of writing the TreeItem structure
*/
private void writeObject(ObjectOutputStream out)
throws IOException {
Stack<TreeItem<T>> stack = new Stack<>();
stack.push(item);
out.defaultWriteObject();
do {
TreeItem<T> current = stack.pop();
int size = current.getChildren().size();
out.writeInt(size);
// write all the data that needs to be restored here
out.writeObject(current.getValue());
// "schedule" serialisation of children.
// the first one is inserted last, since the top one from the stack is
// retrieved first
for (int i = size - 1; i >= 0; --i) {
stack.push(current.getChildren().get(i));
}
} while (!stack.isEmpty());
}
/**
* happens before readResolve; recreates the TreeItem structure
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
class Container {
int count;
final TreeItem<T> item;
Container(ObjectInputStream in) throws ClassNotFoundException, IOException {
// read the data for a single TreeItem here
this.count = in.readInt();
this.item = new TreeItem<>((T) in.readObject());
}
}
in.defaultReadObject();
Container root = new Container(in);
this.item = root.item;
if (root.count > 0) {
Stack<Container> stack = new Stack<>();
stack.push(root);
do {
Container current = stack.peek();
--current.count;
if (current.count <= 0) {
// we're done with this item
stack.pop();
}
Container newContainer = new Container(in);
current.item.getChildren().add(newContainer.item);
if (newContainer.count > 0) {
//schedule reading children of non-leaf
stack.push(newContainer);
}
} while(!stack.isEmpty());
}
}
/**
* We're not actually interested in this object but the treeitem
* @return the treeitem
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return item;
}
}
有关 readObject
、readResolve
和 writeObject
工作原理的说明,请参阅 the javadoc of Serializable