Aplikasi versi Bagian 2 lupa segalanya setiap kali program ditutup: daftar tugas hidup di memori dan hilang bersama proses JVM. Bagian 3 memberi aplikasi ingatan permanen lewat berkas teks tugas.txt — daftar dimuat saat program dimulai dan disimpan ulang setiap kali berubah. Format penyimpanannya sengaja dibuat dapat dibaca manusia, sehingga isi data dapat diperiksa tanpa alat bantu.

Format Berkas yang Mudah Dibaca #

Satu tugas menjadi satu baris dengan tiga bagian terpisah karakter |:

text
1|Belajar Java dasar|selesai
2|Baca dokumentasi Files|aktif

Format teks berpemisah dipilih karena praktis: isi berkas bisa dibaca langsung, dibandingkan dengan diff, atau diperbaiki langsung. Format JSON juga mudah dibaca, tetapi memerlukan pustaka parser eksternal atau banyak kode — di luar target proyek pertama. Konsekuensi format berpemisah harus dipegang konsisten — karakter | di dalam judul merusak pemisahan bagian, jadi validasi judul kini melarang karakter tersebut.

Menyimpan dan Memuat lewat Class PenyimpananTugas #

Seluruh urusan berkas dikumpulkan di satu class agar sisa aplikasi tidak menyentuh I/O langsung:

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

/**
 * Menyimpan dan memuat DaftarTugas lewat berkas teks.
 * Format satu baris: id|judul|status  dengan status "aktif" atau "selesai".
 */
public class PenyimpananTugas {

    private final Path berkas;

    public PenyimpananTugas(Path berkas) {
        this.berkas = berkas;
    }

    /** Menulis seluruh isi daftar ke berkas; berkas lama ditimpa penuh. */
    public void simpan(DaftarTugas daftar) throws IOException {
        List<String> baris = new ArrayList<>();
        for (Tugas tugas : daftar.semua()) {
            String status = tugas.selesai() ? "selesai" : "aktif";
            baris.add(tugas.id() + "|" + tugas.judul() + "|" + status);
        }
        Files.write(berkas, baris, StandardCharsets.UTF_8);
    }

    /**
     * Membaca berkas menjadi DaftarTugas.
     * Baris kosong diabaikan; baris tanpa tiga bagian memicu IOException.
     */
    public DaftarTugas muat() throws IOException {
        DaftarTugas daftar = new DaftarTugas();
        for (String baris : Files.readAllLines(berkas, StandardCharsets.UTF_8)) {
            if (baris.isBlank()) {
                continue;
            }
            String[] bagian = baris.split("\\|", 3);
            if (bagian.length != 3) {
                throw new IOException("Baris rusak: \"" + baris + "\"");
            }
            int id = Integer.parseInt(bagian[0].strip());
            boolean selesai = bagian[2].strip().equals("selesai");
            daftar.tambahMuatan(new Tugas(id, bagian[1], selesai));
        }
        return daftar;
    }
}

Mari kita bedah bagian-bagian pentingnya:

  1. Constructor public PenyimpananTugas(Path berkas): lokasi berkas disuntik lewat constructor, bukan di-hardcode. Rancangan ini kelak memudahkan pengujian di Bagian 4 — pengujian cukup memakai nama berkas sementara.
  2. Baris Files.write(berkas, baris, StandardCharsets.UTF_8);: satu panggilan menulis seluruh baris sekaligus. Charset UTF-8 ditulis eksplisit agar berkas sama terbaca benar di sistem operasi mana pun, bukan bergantung pada charset bawaan mesin.
  3. Baris String[] bagian = baris.split("\\|", 3);: karakter | punya makna khusus pada regex, jadi harus ditulis \\| untuk dicocokkan literal. Batas 3 memastikan pemisahan berhenti di tiga bagian.
  4. Baris throw new IOException("Baris rusak: \"" + baris + "\"");: berkas yang bukan hasil aplikasi — misalnya satu baris tanpa pemisah — ditolak dengan pesan yang menampilkan barisnya. Saat diuji, aplikasi mencetak Gagal memuat tugas.txt: Baris rusak: "baris tanpa pemisah" lalu meneruskan dengan daftar kosong.

Dua Perubahan Kecil pada Class Lama #

Tugas kini punya constructor kedua untuk membangun ulang tugas dari berkas — id dan status dibaca apa adanya:

java Tugas.java
/**
 * Satu baris tugas pada aplikasi catatan tugas.
 * Class ini hanya menyimpan data; tidak membaca input dan tidak mencetak apa pun.
 */
public class Tugas {

    private final int id;
    private final String judul;
    private boolean selesai;

    /** Membuat tugas baru yang belum selesai. */
    public Tugas(int id, String judul) {
        this(id, judul, false);
    }

    /** Membuat tugas lengkap; dipakai saat memuat kembali dari berkas. */
    public Tugas(int id, String judul, boolean selesai) {
        this.id = id;
        this.judul = judul;
        this.selesai = selesai;
    }

    public int id() {
        return id;
    }

    public String judul() {
        return judul;
    }

    public boolean selesai() {
        return selesai;
    }

    public void tandaiSelesai() {
        this.selesai = true;
    }

    /** Bentuk baris siap tampil, misalnya "[ ] 1. Belajar Java dasar". */
    public String formatBaris() {
        String penanda = selesai ? "[x]" : "[ ]";
        return penanda + " " + id + ". " + judul;
    }
}

Constructor dua argumen kini hanya mengerjakan this(id, judul, false) — pola constructor chaining menempatkan logika pembuatan objek di satu tempat. DaftarTugas juga berubah di tiga titik: aturan baru pelarang karakter |, method tambahMuatan, dan perhitungan id:

java DaftarTugas.java
import java.util.ArrayList;
import java.util.List;

/**
 * Kumpulan tugas beserta seluruh aturan validasinya.
 * Class ini menjadi satu-satunya pihak yang boleh mengubah isi daftar.
 */
public class DaftarTugas {

    private final List<Tugas> daftar = new ArrayList<>();

    /**
     * Menambah tugas baru setelah memvalidasi judul.
     * Id ditetapkan otomatis mengikuti id tertinggi yang sudah ada.
     */
    public Tugas tambah(String judul) throws MasukanInvalidException {
        if (judul == null) {
            throw new MasukanInvalidException("Judul tidak boleh kosong.");
        }
        String judulBersih = judul.strip();
        if (judulBersih.isEmpty()) {
            throw new MasukanInvalidException("Judul tidak boleh kosong.");
        }
        if (judulBersih.length() > 100) {
            throw new MasukanInvalidException(
                    "Judul " + judulBersih.length() + " karakter; maksimal 100.");
        }
        if (judulBersih.contains("|")) {
            throw new MasukanInvalidException(
                    "Judul tidak boleh memuat karakter \"|\" karena dipakai pemisah berkas.");
        }
        Tugas tugas = new Tugas(idBerikutnya(), judulBersih);
        daftar.add(tugas);
        return tugas;
    }

    /**
     * Menandai tugas dengan id pada teksId sebagai selesai.
     * Masukan " 1 " tetap diterima karena spasi dipangkas lebih dulu.
     */
    public Tugas tandaiSelesai(String teksId)
            throws MasukanInvalidException, TugasTidakDitemukanException {
        Tugas tugas = cari(teksId);
        tugas.tandaiSelesai();
        return tugas;
    }

    /** Salinan daftar saat ini; urutan mengikuti urutan penambahan. */
    public List<Tugas> semua() {
        return List.copyOf(daftar);
    }

    public boolean kosong() {
        return daftar.isEmpty();
    }

    /**
     * Memasang tugas hasil pembacaan berkas tanpa validasi ulang.
     * Id dan status dibaca dari berkas apa adanya.
     */
    void tambahMuatan(Tugas tugas) {
        daftar.add(tugas);
    }

    private Tugas cari(String teksId)
            throws MasukanInvalidException, TugasTidakDitemukanException {
        int id;
        try {
            id = Integer.parseInt(teksId.strip());
        } catch (NumberFormatException e) {
            throw new MasukanInvalidException(
                    "Id harus berupa angka, diterima: \"" + teksId.strip() + "\".");
        }
        for (Tugas tugas : daftar) {
            if (tugas.id() == id) {
                return tugas;
            }
        }
        throw new TugasTidakDitemukanException(id);
    }

    /** Id berikutnya mengikuti id tertinggi, bukan jumlah baris,
     *  agar tugas tetap unik bila berkas pernah diubah tangan. */
    private int idBerikutnya() {
        int tertinggi = 0;
        for (Tugas tugas : daftar) {
            tertinggi = Math.max(tertinggi, tugas.id());
        }
        return tertinggi + 1;
    }
}

Tiga titik perubahan tersebut bekerja seperti ini:

  1. Blok if (judulBersih.contains("|")): judul Beli|Pulpen ditolak dengan pesan Judul tidak boleh memuat karakter "|" karena dipakai pemisah berkas — terverifikasi saat diuji. Aturan format berkas kini terjaga sejak pintu masuk data.
  2. Method tambahMuatan(Tugas tugas): tanpa pengubah akses public, method hanya terlihat dari berkas di direktori yang sama — PenyimpananTugas boleh memakai method tersebut untuk memuat, sedangkan class luar dipaksa melewati tambah yang tervalidasi.
  3. Method idBerikutnya(): id baru dihitung dari id tertinggi yang ada, bukan dari jumlah baris. Bila seseorang menghapus baris di tengah tugas.txt dengan editor teks, id yang tersisa tetap unik — tidak ada dua tugas bernomor sama.

Aplikasi dengan Penyimpanan Otomatis #

Titik masuk program kini memuat berkas lebih dulu, lalu menyimpan ulang setiap kali daftar berubah. Kedua berkas exception dari Bagian 2 tidak berubah dan tetap diperlukan untuk kompilasi:

/**
 * Exception terperiksa untuk masukan pengguna yang tidak lolos validasi.
 * Contoh: judul kosong atau id yang bukan angka.
 */
public class MasukanInvalidException extends Exception {

    public MasukanInvalidException(String pesan) {
        super(pesan);
    }
}
/**
 * Exception terperiksa untuk id tugas yang tidak ada di dalam daftar.
 */
public class TugasTidakDitemukanException extends Exception {

    public TugasTidakDitemukanException(int id) {
        super("Tugas dengan id " + id + " tidak ditemukan.");
    }
}
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Scanner;

/**
 * Titik masuk aplikasi catatan tugas versi ketiga:
 * daftar dimuat dari berkas saat mulai dan disimpan setiap kali berubah.
 */
public class AplikasiTugas {

    private static final Path BERKAS_DATA = Path.of("tugas.txt");

    public static void main(String[] args) {
        PenyimpananTugas penyimpanan = new PenyimpananTugas(BERKAS_DATA);
        DaftarTugas daftar = new DaftarTugas();

        if (Files.exists(BERKAS_DATA)) {
            try {
                daftar = penyimpanan.muat();
                System.out.println("Memuat " + daftar.semua().size()
                        + " tugas dari " + BERKAS_DATA + ".");
            } catch (IOException e) {
                System.out.println("Gagal memuat " + BERKAS_DATA + ": "
                        + e.getMessage());
                System.out.println("Meneruskan dengan daftar kosong.");
            }
        } else {
            System.out.println("Belum ada berkas " + BERKAS_DATA
                    + "; mulai dari daftar kosong.");
        }

        Scanner masukan = new Scanner(System.in);
        boolean berjalan = true;

        System.out.println("=== Aplikasi Catatan Tugas ===");

        while (berjalan) {
            tampilkanMenu();
            String pilihan = masukan.nextLine().strip();
            try {
                switch (pilihan) {
                    case "1" -> {
                        tambahTugas(daftar, masukan);
                        simpan(penyimpanan, daftar);
                    }
                    case "2" -> tampilkanDaftar(daftar);
                    case "3" -> {
                        tandaiSelesai(daftar, masukan);
                        simpan(penyimpanan, daftar);
                    }
                    case "0" -> {
                        berjalan = false;
                        System.out.println("Sampai jumpa!");
                    }
                    default -> System.out.println(
                            "Pilihan tidak dikenal: " + pilihan);
                }
            } catch (MasukanInvalidException | TugasTidakDitemukanException e) {
                System.out.println("Masukan ditolak: " + e.getMessage());
            }
        }
    }

    private static void tambahTugas(DaftarTugas daftar, Scanner masukan)
            throws MasukanInvalidException {
        System.out.print("Judul tugas: ");
        Tugas tugas = daftar.tambah(masukan.nextLine());
        System.out.println("Tugas ditambahkan: " + tugas.formatBaris());
    }

    private static void tandaiSelesai(DaftarTugas daftar, Scanner masukan)
            throws MasukanInvalidException, TugasTidakDitemukanException {
        System.out.print("Id tugas: ");
        Tugas tugas = daftar.tandaiSelesai(masukan.nextLine());
        System.out.println("Tugas ditandai selesai: " + tugas.formatBaris());
    }

    private static void simpan(PenyimpananTugas penyimpanan, DaftarTugas daftar) {
        try {
            penyimpanan.simpan(daftar);
            System.out.println("Daftar disimpan ke tugas.txt.");
        } catch (IOException e) {
            System.out.println("Gagal menyimpan tugas.txt: " + e.getMessage());
        }
    }

    private static void tampilkanMenu() {
        System.out.println();
        System.out.println("Menu:");
        System.out.println("1. Tambah tugas");
        System.out.println("2. Lihat daftar tugas");
        System.out.println("3. Tandai tugas selesai");
        System.out.println("0. Keluar");
        System.out.print("Pilihan: ");
    }

    private static void tampilkanDaftar(DaftarTugas daftar) {
        System.out.println();
        List<Tugas> semua = daftar.semua();
        if (semua.isEmpty()) {
            System.out.println("Belum ada tugas.");
            return;
        }
        System.out.println("Daftar tugas:");
        for (Tugas tugas : semua) {
            System.out.println(tugas.formatBaris());
        }
    }
}

Output yang Dihasilkan:

text
Belum ada berkas tugas.txt; mulai dari daftar kosong.
=== Aplikasi Catatan Tugas ===

Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Judul tugas: Tugas ditambahkan: [ ] 1. Belajar Java dasar
Daftar disimpan ke tugas.txt.

Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Judul tugas: Tugas ditambahkan: [ ] 2. Baca dokumentasi Files
Daftar disimpan ke tugas.txt.

Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Id tugas: Tugas ditandai selesai: [x] 1. Belajar Java dasar
Daftar disimpan ke tugas.txt.

Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Sampai jumpa!

Dua mekanisme baru di balik output tersebut:

  1. Blok if (Files.exists(BERKAS_DATA)) pada awal main: jalur pertama menjalankan main di direktori tanpa tugas.txt, sehingga aplikasi mencetak Belum ada berkas tugas.txt; mulai dari daftar kosong. Kegagalan memuat tidak menghentikan program — pesan galat dicetak dan daftar kosong dipakai.
  2. Pemanggilan simpan(penyimpanan, daftar) di dalam blok case "1" dan case "3": penyimpanan hanya terjadi setelah operasi berhasil, karena tambahTugas yang melempar exception akan melompati baris simpan. Method bantu simpan menangkap IOException sendiri sehingga kegagalan menulis tidak menjatuhkan seluruh sesi.

Bukti terakhir bahwa data bertahan: sesi berikut dikutip dari eksekusi sungguhan di direktori yang sama setelah sesi di atas — berkas hasil eksekusi pertama dibaca, lalu aplikasi dijalankan ulang:

text
$ cat tugas.txt
1|Belajar Java dasar|selesai
2|Baca dokumentasi Files|aktif
$ printf '2\n0\n' | java AplikasiTugas.java
Memuat 2 tugas dari tugas.txt.
=== Aplikasi Catatan Tugas ===

Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: 
Daftar tugas:
[x] 1. Belajar Java dasar
[ ] 2. Baca dokumentasi Files

Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Sampai jumpa!

Aplikasi sekarang menyimpan datanya sendiri: dua eksekusi berbagi daftar yang sama lewat tugas.txt, dan berkas rusak ditolak dengan pesan jelas. Bagian 4 menjawab pertanyaan penutup — bagaimana memastikan aturan DaftarTugas dan PenyimpananTugas tetap benar setiap kali kode diubah — lalu mengemas aplikasi menjadi JAR siap distribusi.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

AplikasiTugas.java

Unduh berkas
java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Scanner;

/**
 * Titik masuk aplikasi catatan tugas versi ketiga:
 * daftar dimuat dari berkas saat mulai dan disimpan setiap kali berubah.
 */
public class AplikasiTugas {

    private static final Path BERKAS_DATA = Path.of("tugas.txt");

    public static void main(String[] args) {
        PenyimpananTugas penyimpanan = new PenyimpananTugas(BERKAS_DATA);
        DaftarTugas daftar = new DaftarTugas();

        if (Files.exists(BERKAS_DATA)) {
            try {
                daftar = penyimpanan.muat();
                System.out.println("Memuat " + daftar.semua().size()
                        + " tugas dari " + BERKAS_DATA + ".");
            } catch (IOException e) {
                System.out.println("Gagal memuat " + BERKAS_DATA + ": "
                        + e.getMessage());
                System.out.println("Meneruskan dengan daftar kosong.");
            }
        } else {
            System.out.println("Belum ada berkas " + BERKAS_DATA
                    + "; mulai dari daftar kosong.");
        }

        Scanner masukan = new Scanner(System.in);
        boolean berjalan = true;

        System.out.println("=== Aplikasi Catatan Tugas ===");

        while (berjalan) {
            tampilkanMenu();
            String pilihan = masukan.nextLine().strip();
            try {
                switch (pilihan) {
                    case "1" -> {
                        tambahTugas(daftar, masukan);
                        simpan(penyimpanan, daftar);
                    }
                    case "2" -> tampilkanDaftar(daftar);
                    case "3" -> {
                        tandaiSelesai(daftar, masukan);
                        simpan(penyimpanan, daftar);
                    }
                    case "0" -> {
                        berjalan = false;
                        System.out.println("Sampai jumpa!");
                    }
                    default -> System.out.println(
                            "Pilihan tidak dikenal: " + pilihan);
                }
            } catch (MasukanInvalidException | TugasTidakDitemukanException e) {
                System.out.println("Masukan ditolak: " + e.getMessage());
            }
        }
    }

    private static void tambahTugas(DaftarTugas daftar, Scanner masukan)
            throws MasukanInvalidException {
        System.out.print("Judul tugas: ");
        Tugas tugas = daftar.tambah(masukan.nextLine());
        System.out.println("Tugas ditambahkan: " + tugas.formatBaris());
    }

    private static void tandaiSelesai(DaftarTugas daftar, Scanner masukan)
            throws MasukanInvalidException, TugasTidakDitemukanException {
        System.out.print("Id tugas: ");
        Tugas tugas = daftar.tandaiSelesai(masukan.nextLine());
        System.out.println("Tugas ditandai selesai: " + tugas.formatBaris());
    }

    private static void simpan(PenyimpananTugas penyimpanan, DaftarTugas daftar) {
        try {
            penyimpanan.simpan(daftar);
            System.out.println("Daftar disimpan ke tugas.txt.");
        } catch (IOException e) {
            System.out.println("Gagal menyimpan tugas.txt: " + e.getMessage());
        }
    }

    private static void tampilkanMenu() {
        System.out.println();
        System.out.println("Menu:");
        System.out.println("1. Tambah tugas");
        System.out.println("2. Lihat daftar tugas");
        System.out.println("3. Tandai tugas selesai");
        System.out.println("0. Keluar");
        System.out.print("Pilihan: ");
    }

    private static void tampilkanDaftar(DaftarTugas daftar) {
        System.out.println();
        List<Tugas> semua = daftar.semua();
        if (semua.isEmpty()) {
            System.out.println("Belum ada tugas.");
            return;
        }
        System.out.println("Daftar tugas:");
        for (Tugas tugas : semua) {
            System.out.println(tugas.formatBaris());
        }
    }
}

DaftarTugas.java

Unduh berkas
java
import java.util.ArrayList;
import java.util.List;

/**
 * Kumpulan tugas beserta seluruh aturan validasinya.
 * Class ini menjadi satu-satunya pihak yang boleh mengubah isi daftar.
 */
public class DaftarTugas {

    private final List<Tugas> daftar = new ArrayList<>();

    /**
     * Menambah tugas baru setelah memvalidasi judul.
     * Id ditetapkan otomatis mengikuti id tertinggi yang sudah ada.
     */
    public Tugas tambah(String judul) throws MasukanInvalidException {
        if (judul == null) {
            throw new MasukanInvalidException("Judul tidak boleh kosong.");
        }
        String judulBersih = judul.strip();
        if (judulBersih.isEmpty()) {
            throw new MasukanInvalidException("Judul tidak boleh kosong.");
        }
        if (judulBersih.length() > 100) {
            throw new MasukanInvalidException(
                    "Judul " + judulBersih.length() + " karakter; maksimal 100.");
        }
        if (judulBersih.contains("|")) {
            throw new MasukanInvalidException(
                    "Judul tidak boleh memuat karakter \"|\" karena dipakai pemisah berkas.");
        }
        Tugas tugas = new Tugas(idBerikutnya(), judulBersih);
        daftar.add(tugas);
        return tugas;
    }

    /**
     * Menandai tugas dengan id pada teksId sebagai selesai.
     * Masukan " 1 " tetap diterima karena spasi dipangkas lebih dulu.
     */
    public Tugas tandaiSelesai(String teksId)
            throws MasukanInvalidException, TugasTidakDitemukanException {
        Tugas tugas = cari(teksId);
        tugas.tandaiSelesai();
        return tugas;
    }

    /** Salinan daftar saat ini; urutan mengikuti urutan penambahan. */
    public List<Tugas> semua() {
        return List.copyOf(daftar);
    }

    public boolean kosong() {
        return daftar.isEmpty();
    }

    /**
     * Memasang tugas hasil pembacaan berkas tanpa validasi ulang.
     * Id dan status dibaca dari berkas apa adanya.
     */
    void tambahMuatan(Tugas tugas) {
        daftar.add(tugas);
    }

    private Tugas cari(String teksId)
            throws MasukanInvalidException, TugasTidakDitemukanException {
        int id;
        try {
            id = Integer.parseInt(teksId.strip());
        } catch (NumberFormatException e) {
            throw new MasukanInvalidException(
                    "Id harus berupa angka, diterima: \"" + teksId.strip() + "\".");
        }
        for (Tugas tugas : daftar) {
            if (tugas.id() == id) {
                return tugas;
            }
        }
        throw new TugasTidakDitemukanException(id);
    }

    /** Id berikutnya mengikuti id tertinggi, bukan jumlah baris,
     *  agar tugas tetap unik bila berkas pernah diubah tangan. */
    private int idBerikutnya() {
        int tertinggi = 0;
        for (Tugas tugas : daftar) {
            tertinggi = Math.max(tertinggi, tugas.id());
        }
        return tertinggi + 1;
    }
}

MasukanInvalidException.java

Unduh berkas
java
/**
 * Exception terperiksa untuk masukan pengguna yang tidak lolos validasi.
 * Contoh: judul kosong atau id yang bukan angka.
 */
public class MasukanInvalidException extends Exception {

    public MasukanInvalidException(String pesan) {
        super(pesan);
    }
}

PenyimpananTugas.java

Unduh berkas
java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

/**
 * Menyimpan dan memuat DaftarTugas lewat berkas teks.
 * Format satu baris: id|judul|status  dengan status "aktif" atau "selesai".
 */
public class PenyimpananTugas {

    private final Path berkas;

    public PenyimpananTugas(Path berkas) {
        this.berkas = berkas;
    }

    /** Menulis seluruh isi daftar ke berkas; berkas lama ditimpa penuh. */
    public void simpan(DaftarTugas daftar) throws IOException {
        List<String> baris = new ArrayList<>();
        for (Tugas tugas : daftar.semua()) {
            String status = tugas.selesai() ? "selesai" : "aktif";
            baris.add(tugas.id() + "|" + tugas.judul() + "|" + status);
        }
        Files.write(berkas, baris, StandardCharsets.UTF_8);
    }

    /**
     * Membaca berkas menjadi DaftarTugas.
     * Baris kosong diabaikan; baris tanpa tiga bagian memicu IOException.
     */
    public DaftarTugas muat() throws IOException {
        DaftarTugas daftar = new DaftarTugas();
        for (String baris : Files.readAllLines(berkas, StandardCharsets.UTF_8)) {
            if (baris.isBlank()) {
                continue;
            }
            String[] bagian = baris.split("\\|", 3);
            if (bagian.length != 3) {
                throw new IOException("Baris rusak: \"" + baris + "\"");
            }
            int id = Integer.parseInt(bagian[0].strip());
            boolean selesai = bagian[2].strip().equals("selesai");
            daftar.tambahMuatan(new Tugas(id, bagian[1], selesai));
        }
        return daftar;
    }
}

Tugas.java

Unduh berkas
java
/**
 * Satu baris tugas pada aplikasi catatan tugas.
 * Class ini hanya menyimpan data; tidak membaca input dan tidak mencetak apa pun.
 */
public class Tugas {

    private final int id;
    private final String judul;
    private boolean selesai;

    /** Membuat tugas baru yang belum selesai. */
    public Tugas(int id, String judul) {
        this(id, judul, false);
    }

    /** Membuat tugas lengkap; dipakai saat memuat kembali dari berkas. */
    public Tugas(int id, String judul, boolean selesai) {
        this.id = id;
        this.judul = judul;
        this.selesai = selesai;
    }

    public int id() {
        return id;
    }

    public String judul() {
        return judul;
    }

    public boolean selesai() {
        return selesai;
    }

    public void tandaiSelesai() {
        this.selesai = true;
    }

    /** Bentuk baris siap tampil, misalnya "[ ] 1. Belajar Java dasar". */
    public String formatBaris() {
        String penanda = selesai ? "[x]" : "[ ]";
        return penanda + " " + id + ". " + judul;
    }
}

TugasTidakDitemukanException.java

Unduh berkas
java
/**
 * Exception terperiksa untuk id tugas yang tidak ada di dalam daftar.
 */
public class TugasTidakDitemukanException extends Exception {

    public TugasTidakDitemukanException(int id) {
        super("Tugas dengan id " + id + " tidak ditemukan.");
    }
}