这是一个示例,它从 ObservableList 的人员(名字和姓氏)读取数据并将数据写入 json 格式的文件。
列表中保存的 json 数据的示例文件内容
[ {
"firstName" : "Fred",
"lastName" : "Flintstone"
}, {
"firstName" : "Wilma",
"lastName" : "Flintstone"
}, {
"firstName" : "Barney",
"lastName" : "Rubble"
} ]
实施说明
数据项存储为人员记录。
ObservableList 支持 TableView 并保存数据项的记录。
第 3 方 Jackson 库用于将数据列表序列化和反序列化为 JSON,从文件中存储和读取。
在启动时,应用程序会生成一个临时文件名,用于在应用程序的生命周期内存储保存的数据文件。
关机时,临时存档会自动删除。
module-info 允许 Jackson 数据绑定模块对包含要保存的项目的记录定义的包执行反射。
在保存和恢复数据项之前,它们会临时存储在 ArrayList 而不是 ObservableList 中。这样做是因为您不想尝试序列化整个 ObservableList。 ObservableList 还将包含可能附加到列表的更改侦听器的条目。您不想序列化这些侦听器。
使用 Jackson 执行序列化和反序列化的 ListSerializer 类使用 Java 泛型,因此它可以保存和加载可以通过 Jackson 序列化的任何类型的数据(包括示例中的 Person 记录)。泛型在代码中增加了一些复杂性,以确定在序列化和反序列化过程中使用的正确类型。泛型确实允许更通用的解决方案,因此,总的来说,我认为添加通用解决方案值得在实现中增加额外的复杂性。
ListSerializerController 演示了如何使用 ListSerializer 将数据保存和加载到支持 TableView 的 ObservableList。
Maven 被用作构建系统。
JRE 18 和 JavaFX 18 用作运行时。
示例解决方案
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ListSerialization</artifactId>
<version>1.0-SNAPSHOT</version>
<name>ListSerialization</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.8.1</junit.version>
<javafx.version>18</javafx.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>18</source>
<target>18</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
src/main/java/module-info.java
module com.example.listserialization {
requires javafx.controls;
requires com.fasterxml.jackson.databind;
opens com.example.listserialization to com.fasterxml.jackson.databind;
exports com.example.listserialization;
}
src/main/java/com/example/listserialization/ListSerializerApp.java
package com.example.listserialization;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ListSerializerApp extends Application {
private Path peoplePath;
@Override
public void init() throws IOException {
peoplePath = Files.createTempFile(
"people",
".json"
);
peoplePath.toFile().deleteOnExit();
System.out.println("Using save file name: " + peoplePath);
}
@Override
public void start(Stage stage) throws IOException {
ListSerializerController listSerializerController = new ListSerializerController(
peoplePath
);
stage.setScene(
new Scene(
listSerializerController.getLayout()
)
);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
src/main/java/com/example/listserialization/Person.java
package com.example.listserialization;
record Person(String firstName, String lastName) {}
src/main/java/com/example/listserialization/ListSerializer.java
package com.example.listserialization;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
public class ListSerializer<T> {
private static final ObjectMapper mapper =
new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT);
private final CollectionType listType;
public ListSerializer(Class<T> listItemClass) {
listType =
mapper.getTypeFactory()
.constructCollectionType(
ArrayList.class,
listItemClass
);
}
public void serializeList(Path path, ArrayList<T> list) throws IOException {
Files.writeString(
path,
mapper.writeValueAsString(
list
)
);
}
public ArrayList<T> deserializeList(Path path) throws IOException {
return mapper.<ArrayList<T>>readValue(
Files.readString(path),
listType
);
}
}
src/main/java/com/example/listserialization/ListSerializerController.java
package com.example.listserialization;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
public class ListSerializerController {
private final ListSerializer<Person> listSerializer = new ListSerializer<>(
Person.class
);
private final Person[] TEST_PEOPLE = {
new Person("Fred", "Flintstone"),
new Person("Wilma", "Flintstone"),
new Person("Barney", "Rubble")
};
private final TableView<Person> tableView = new TableView<>(
FXCollections.observableArrayList(
TEST_PEOPLE
)
);
private final Path peoplePath;
private final Parent layout;
public ListSerializerController(Path peoplePath) {
this.peoplePath = peoplePath;
layout = createLayout();
}
private Parent createLayout() {
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(p ->
new ReadOnlyStringWrapper(
p.getValue().firstName()
).getReadOnlyProperty()
);
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setCellValueFactory(p ->
new ReadOnlyStringWrapper(
p.getValue().lastName()
).getReadOnlyProperty()
);
//noinspection unchecked
tableView.getColumns().setAll(firstNameCol, lastNameCol);
tableView.setPrefHeight(150);
Button save = new Button("Save");
save.setOnAction(this::save);
Button clear = new Button("Clear");
clear.setOnAction(this::clear);
Button load = new Button("Load");
load.setOnAction(this::load);
Button restoreDefault = new Button("Default Data");
restoreDefault.setOnAction(this::restoreDefault);
HBox controls = new HBox(10, save, clear, load, restoreDefault);
VBox layout = new VBox(10, controls, tableView);
layout.setPadding(new Insets(10));
return layout;
}
public Parent getLayout() {
return layout;
}
private void save(ActionEvent e) {
try {
listSerializer.serializeList(
peoplePath,
new ArrayList<>(
tableView.getItems()
)
);
System.out.println("Saved to: " + peoplePath);
System.out.println(Files.readString(peoplePath));
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void clear(ActionEvent e) {
tableView.getItems().clear();
System.out.println("Cleared data in UI");
}
private void load(ActionEvent e) {
try {
if (!peoplePath.toFile().exists()) {
tableView.getItems().clear();
System.out.println("Saved data file does not exist, clearing data: " + peoplePath);
return;
}
tableView.getItems().setAll(
listSerializer.deserializeList(peoplePath)
);
System.out.println("Loaded data from: " + peoplePath);
System.out.println(Files.readString(peoplePath));
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void restoreDefault(ActionEvent e) {
tableView.getItems().setAll(TEST_PEOPLE);
System.out.println("Restored default data in UI");
}
}