Сохранение заметок в JSON
Пока заметки живут только в памяти: закрыли окно — всё пропало. Сохраним их в файл в формате JSON. Писать JSON вручную не будем — это делает библиотека Jackson: она превращает список записей Note в текст и обратно. В курсе используется Jackson 3 — его пакеты начинаются с tools.jackson, а записи Java он понимает без настроек.
Зависимость и модуль
Добавьте в pom.xml:
<!-- pom.xml — новая зависимость -->
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.2.3</version>
</dependency>И подключите модуль Jackson в module-info.java:
// src/main/java/module-info.java
module com.example.notes {
requires javafx.controls;
requires javafx.fxml;
requires tools.jackson.databind;
opens com.example.notes to javafx.fxml;
exports com.example.notes;
}Запись Note публичная и лежит в экспортированном пакете, поэтому Jackson читает её через обычные методы title(), text() и публичный конструктор.
Класс хранилища
Работу с файлом вынесем в отдельный класс — контроллеру незачем знать, как устроен JSON:
// src/main/java/com/example/notes/NoteStorage.java
package com.example.notes;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper;
public class NoteStorage {
private final Path file;
private final JsonMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.build();
public NoteStorage(Path file) {
this.file = file;
}
public List<Note> load() {
if (Files.notExists(file)) {
return List.of();
}
return mapper.readValue(file.toFile(), new TypeReference<List<Note>>() {});
}
public void save(List<Note> notes) {
mapper.writeValue(file.toFile(), notes);
}
}JsonMapper — главный объект Jackson. INDENT_OUTPUT включает отступы, чтобы файл было удобно читать. TypeReference нужен, чтобы Jackson знал тип элементов списка: из-за стирания типов List.class этого не сообщает. Ошибки чтения в Jackson 3 — непроверяемые исключения, поэтому throws не нужен.
Подключение к контроллеру
Демонстрационные заметки больше не нужны — список начинается пустым и заполняется из файла:
// src/main/java/com/example/notes/MainController.java — поля и начало initialize()
private final ObservableList<Note> notes = FXCollections.observableArrayList();
private final NoteStorage storage = new NoteStorage(
Path.of(System.getProperty("user.home"), "notes.json"));
// в initialize(), после table.setItems(notes):
notes.setAll(storage.load());
notes.addListener((ListChangeListener<Note>) change -> storage.save(notes));Файл лежит в домашней папке пользователя: C:\Users\Имя\notes.json в Windows, /home/имя/notes.json в Linux. setAll заменяет содержимое списка загруженным. Слушатель срабатывает при каждом добавлении, удалении и замене, поэтому отдельная кнопка «Сохранить» не нужна. Приведение к ListChangeListener подсказывает компилятору, какой из двух методов addListener вызвать.
Проверка
Запустите приложение: таблица пуста, «Заметок: 0». Добавьте «Купить молоко» с текстом «2 литра» и флажком «Важная», затем «Позвонить Олегу». В домашней папке появится notes.json:
[ {
"title" : "Купить молоко",
"text" : "2 литра",
"category" : "Идеи",
"important" : true
}, {
"title" : "Позвонить Олегу",
"text" : "",
"category" : "Идеи",
"important" : false
} ]Закройте окно и запустите приложение снова — обе заметки на месте.
Частая ошибка
Если файл испорчен, например его поправили вручную и оставили лишнюю запятую, load() бросает исключение, и окно не открывается: tools.jackson.core.exc.StreamReadException: Unexpected character ('}' (code 125)): was expecting double-quote to start property name. В следующем уроке загрузка переедет в фоновую задачу, и такая ошибка станет сообщением в строке состояния, а не падением программы.