Kerangka dari Bagian 1 punya celah nyata: judul kosong tersimpan begitu saja, dan operasi “tandai selesai” yang membutuhkan id belum sama sekali diuji terhadap masukan yang salah. Bagian 2 menutup celah tersebut dengan dua keputusan rancangan — seluruh aturan validasi dipindahkan ke satu class DaftarTugas, dan setiap pelanggaran dilaporkan lewat exception terperiksa buatan aplikasi sendiri. Hasil akhirnya: pengguna yang salah ketik tetap berada di aplikasi, bukan di hadapan stack trace.
Masalah: Masukan Salah Membuat Program Berhenti #
Cara paling naif membaca id adalah mengubah teks menjadi angka langsung:
int id = Integer.parseInt(teksId);Saat teksId berisi "abc", method Integer.parseInt melempar NumberFormatException, JVM mencetak stack trace ke stderr, dan program berhenti dengan exit code 1 — terverifikasi saat dijalankan: Exception in thread "main" java.lang.NumberFormatException: For input string: "abc". Pengguna kehilangan sesi dan seluruh tugas yang baru diketik karena masalah sesederhana salah ketik satu huruf.
Solusinya bukan memeriksa angka di sepuluh tempat berbeda. Kita pusatkan aturan di satu class, lalu biarkan class tersebut melempar exception bertipe domain setiap kali aturan dilanggar. Method pemanggil tinggal menangkap exception dan menampilkan pesannya.
Validasi Terpusat di Class DaftarTugas #
Class DaftarTugas membungkus ArrayList<Tugas> dan menjadi satu-satunya pihak yang boleh mengubah isi daftar. Setiap operasi divalidasi lebih dulu; operasi yang gagal dilempar keluar sebagai exception terperiksa:
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 urutan penambahan.
*/
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.");
}
Tugas tugas = new Tugas(daftar.size() + 1, 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();
}
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);
}
}Mari kita bedah aturan-aturannya:
- Blok
if (judul == null)danjudulBersih.isEmpty(): dua penolakan pertama —nulldari pemanggil lupa, dan teks yang hanya berisi spasi.strip()dipanggil sebelum pemeriksaan kosong sehingga" "dianggap kosong, bukan judul tiga spasi. - Blok
if (judulBersih.length() > 100): batas panjang menjaga baris daftar tetap terbaca. Angka panjang aktual ikut dimasukkan ke pesan agar pengguna tahu seberapa jauh melewati batas. - Baris
return tugas;di akhirtambah: method mengembalikan objek yang baru dibuat, sehingga pemanggil bisa langsung menampilkantugas.formatBaris()tanpa mencari ulang. - Method
cari: pengubahan teks menjadi angka dibungkustry-catchsendiri.NumberFormatExceptionyang tertangkap diterjemahkan menjadiMasukanInvalidExceptionberpesan jelas — pengguna membaca kalimat, bukan stack trace. - Baris
throw new TugasTidakDitemukanException(id);: angka yang sah tetapi tidak ada di daftar adalah masalah berbeda dari teks yang bukan angka, sehingga exception-nya juga berbeda. Pemanggil bisa membedakan kedua kegagalan tersebut bila perlu. - Method
semua(): mengembalikan salinanList.copyOf(daftar)— pemanggil tidak mendapat referensi keArrayListasli sehingga tidak bisa menambah tugas melewati aturan validasi.
Dua Exception Terperiksa Milik Aplikasi #
Kedua exception tersebut adalah class biasa yang mewarisi Exception:
/**
* 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.");
}
}Keduanya sengaja dibuat terperiksa (mewarisi Exception, bukan RuntimeException). Konsekuensinya: setiap method yang memanggil daftar.tambah(...) diwajibkan compiler menangkap atau meneruskan exception tersebut — pelanggaran validasi tidak mungkin dilupakan secara diam-diam. Constructor hanya perlu satu tugas: menyerahkan pesan ke super agar e.getMessage() mengembalikan kalimat siap tampil.
Menu yang Menangani Kegagalan #
Versi AplikasiTugas sekarang memakai DaftarTugas dan menangkap kedua exception di satu titik — sekeliling seluruh switch. Sesi berikut sengaja diisi masukan salah: judul " ", id "abc", dan id 9 yang tidak ada:
import java.util.List;
import java.util.Scanner;
/**
* Titik masuk aplikasi catatan tugas versi kedua:
* menu utama dengan validasi masukan dan penanganan exception.
*/
public class AplikasiTugas {
public static void main(String[] args) {
DaftarTugas daftar = new DaftarTugas();
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);
case "2" -> tampilkanDaftar(daftar);
case "3" -> tandaiSelesai(daftar, masukan);
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 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:
=== Aplikasi Catatan Tugas ===
Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Judul tugas: Masukan ditolak: Judul tidak boleh kosong.
Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Judul tugas: Tugas ditambahkan: [ ] 1. Belajar Java dasar
Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan:
Daftar tugas:
[ ] 1. Belajar Java dasar
Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Id tugas: Masukan ditolak: Id harus berupa angka, diterima: "abc".
Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Id tugas: Masukan ditolak: Tugas dengan id 9 tidak ditemukan.
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
Menu:
1. Tambah tugas
2. Lihat daftar tugas
3. Tandai tugas selesai
0. Keluar
Pilihan: Sampai jumpa!Tiga perubahan utama patut dicermati:
- Baris
try { switch (pilihan) { ... } }: seluruh cabang menu berada dalam satu bloktry, jadi satucatchmelindungi semua operasi — tidak ada cabang yang lupa diamankan. - Baris
catch (MasukanInvalidException | TugasTidakDitemukanException e): multi-catch menangkap dua tipe sekaligus dengan penanganan sama: cetakMasukan ditolak:diikuti pesan darie.getMessage(), lalu loop kembali menampilkan menu. Program tidak pernah berhenti karena salah ketik. - Deklarasi
throwspadatambahTugasdantandaiSelesai: kedua method bantu hanya membaca satu baris masukan dan mengerahkan validasi keDaftarTugas; compiler-lah yang memaksa daftarthrowstersebut ditulis jujur sampai kemain.
Aplikasi kini tahan masukan salah — setiap kesalahan ditolak dengan pesan yang jelas dan pengguna tetap berada di menu. Satu kelemahan besar masih tersisa: daftar tugas hidup di memori, sehingga menutup program berarti kehilangan seluruh data. Bagian 3 mengubah keadaan tersebut dengan penyimpanan ke berkas.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
AplikasiTugas.java
Unduh berkasimport java.util.List;
import java.util.Scanner;
/**
* Titik masuk aplikasi catatan tugas versi kedua:
* menu utama dengan validasi masukan dan penanganan exception.
*/
public class AplikasiTugas {
public static void main(String[] args) {
DaftarTugas daftar = new DaftarTugas();
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);
case "2" -> tampilkanDaftar(daftar);
case "3" -> tandaiSelesai(daftar, masukan);
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 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 berkasimport 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 urutan penambahan.
*/
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.");
}
Tugas tugas = new Tugas(daftar.size() + 1, 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();
}
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);
}
}
MasukanInvalidException.java
Unduh berkas/**
* 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);
}
}
Tugas.java
Unduh berkas/**
* 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 = id;
this.judul = judul;
this.selesai = false;
}
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/**
* 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.");
}
}