Kamu menjalankan program kecil yang menjumlahkan keranjang berisi empat item. Program itu mencetak tiga baris, dan baris kedua bilang totalnya 36,500. Baris ketiga memberi harga rata-rata. Prosesnya keluar normal, dan tidak satu pun dari ketiga baris itu terlihat rusak. Hanya aritmetikanya yang tidak cocok dengan struk.
Poin Penting
i < prices.length - 1menghentikan loop satu indeks lebih awal, jadi harga terakhir tidak pernah masuk ke total.- Rata-ratanya tetap dibagi
prices.length, jadi total dan rata-rata sama-sama salah sementara baris jumlah item tetap benar. - Batas cerminnya,
i <= prices.length, melemparArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4. for (int price : prices)menyusuri array tanpa indeks, jadi tidak ada batas yang bisa ditulis kurang satu, dan posisi elemennya ikut dilepas.whilemenguji sebelum badan loop dan tidak mencetak apa pun pada array kosong;do-whilemenguji sesudahnya dan menjalankan badan loop satu kali.- Checker yang ekspektasinya dipatok tetap gagal pada kode yang sudah benar ketika inputnya berubah, jadi ia menguji input sama banyak dengan menguji kode.
Loop yang berhenti di item ketiga
Berkasnya berisi array int dengan empat harga, sebuah penghitung, sebuah jumlah, dan sebuah rata-rata.
// Total.java
public class Total {
public static void main(String[] args) {
int[] prices = {12000, 15000, 9500, 10500};
System.out.println("items: " + prices.length);
int total = 0;
for (int i = 0; i < prices.length - 1; i++) {
total += prices[i];
}
System.out.println("total: " + total);
System.out.println("average: " + total / prices.length);
}
}
Kompilasi dan jalankan dengan javac Total.java && java Total. Status keluarnya 0, tanpa peringatan dan tanpa error.
$ javac Total.java && java Total
items: 4
total: 36500
average: 9125
Itu tiga baris, dan semuanya masuk akal. Jumlah item tertulis 4, totalnya angka yang masuk akal, begitu juga rata-ratanya. Tapi keempat harga itu berjumlah 47000, jadi totalnya kurang 10,500.
Kenapa item terakhir tidak pernah masuk loop
Lihat batasnya: prices.length - 1. Array-nya berisi empat harga, jadi nilainya 3, dan kondisi i < 3 berlaku untuk i sama dengan 0, 1, dan 2. Indeks 3 tidak pernah dibaca. Harga terakhir, 10500, tidak pernah sampai ke jumlah.
Kerusakannya punya babak kedua. Rata-ratanya tetap dibagi prices.length, yaitu 4. Jadi ada dua baris yang salah, bukan satu. Totalnya kurang 10500, lalu total yang kurang itu dibagi empat item padahal yang dijumlahkan hanya tiga. Rata-rata yang benar adalah 47000 / 4, yaitu 11750.
Kesalahan cerminnya memakai array yang sama dan badan loop yang sama, dan hanya mengubah batasnya. Tulis i <= prices.length sebagai gantinya. Versi ini tidak melewati satu item pun. Ia membaca melewati ujung array, dan program berhenti dengan java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4. Trace-nya menunjuk Total.main(Total.java:8), dan prosesnya keluar dengan status 1.
Satu dari dua kesalahan ini diam, yang lain berisik. Yang diam itulah yang terkirim.
Pemeriksaan yang bisa kamu jalankan
Versi yang sunyi itu bisa kamu tangkap dengan program kedua. Checker ini membaca tiga baris yang dicetak program tadi dan membandingkannya dengan nilai yang seharusnya dihasilkan keempat harga itu.
// CheckTotal.java
import java.util.List;
import java.util.Scanner;
public class CheckTotal {
public static void main(String[] args) {
List<String> expected = List.of("items: 4", "total: 47000", "average: 11750");
Scanner in = new Scanner(System.in);
int passed = 0;
for (int i = 0; i < expected.size(); i++) {
String got = in.hasNextLine() ? in.nextLine() : "<nothing>";
if (got.equals(expected.get(i))) {
passed++;
System.out.println("PASS line " + (i + 1) + ": " + got);
} else {
System.out.println("FAIL line " + (i + 1) + ": expected " + expected.get(i) + ", got " + got);
}
}
if (in.hasNextLine()) {
System.out.println("FAIL extra line: " + in.nextLine());
}
System.out.println();
System.out.println(passed + " of " + expected.size() + " checks passed");
if (passed != expected.size()) {
System.exit(1);
}
}
}
Salurkan keluaran program ke checker itu dengan javac CheckTotal.java && java Total | java CheckTotal.
$ javac CheckTotal.java && java Total | java CheckTotal
PASS line 1: items: 4
FAIL line 2: expected total: 47000, got total: 36500
FAIL line 3: expected average: 11750, got average: 9125
1 of 3 checks passed
Baris items: 4 lolos. Total dan rata-ratanya tidak, jadi checker mencetak 1 of 3 checks passed dan keluar dengan status bukan nol. Programnya sendiri tidak bilang apa-apa, dan checker langsung menangkapnya.
Berhenti menghitung
Perbaikannya adalah berhenti menghitung. Tukar loop berpenghitung dengan loop for-each dan jangan ubah apa pun selain itu.
// Total.java
public class Total {
public static void main(String[] args) {
int[] prices = {12000, 15000, 9500, 10500};
System.out.println("items: " + prices.length);
int total = 0;
for (int price : prices) {
total += price;
}
System.out.println("total: " + total);
System.out.println("average: " + total / prices.length);
}
}
$ javac Total.java && java Total
items: 4
total: 47000
average: 11750
$ java Total | java CheckTotal
PASS line 1: items: 4
PASS line 2: total: 47000
PASS line 3: average: 11750
3 of 3 checks passed
Loop for-each memberimu nilainya, bukan posisinya. Terhadap berkas ini checker mencetak 3 of 3 checks passed dan keluar dengan status 0. Loop itu mengunjungi setiap elemen tepat satu kali, berurutan. Tidak ada batas yang perlu ditulis, jadi tidak ada yang bisa keliru kurang satu.
Tukarannya jujur: kamu kehilangan indeksnya. Kalau kamu perlu tahu sedang di posisi mana, for berpenghitung tetap alat yang tepat.
Dua loop lainnya
Loop while berguna saat kamu mengulang pekerjaan selama sebuah kondisi terpenuhi dan tidak ada penghitung yang perlu dimajukan. Kamu menyatakan kondisinya, dan loop berjalan sampai kondisi itu jadi salah.
Loop do-while menguji kondisinya setelah badan loop. Artinya badan loop selalu berjalan satu kali sebelum pengujian pertama.
// Rows.java
public class Rows {
public static void main(String[] args) {
int[] empty = {};
int i = 0;
while (i < empty.length) {
System.out.println("while row " + i);
i++;
}
int j = 0;
do {
System.out.println("do-while row " + j);
j++;
} while (j < empty.length);
}
}
$ javac Rows.java && java Rows
do-while row 0
Loop while tidak pernah berjalan pada array kosong, karena kondisinya sudah salah sebelum badan loop dimulai, sementara loop do-while berjalan satu kali. Di dalam loop mana pun di atas, break keluar dari loop lebih awal dan continue melompat ke putaran berikutnya. Sebagai aturan praktis, pilih loop yang kondisi berhentinya bisa kamu nyatakan tanpa aritmetika.
Sebelum artikel berikutnya
Hapus satu harga dari array, supaya isinya tiga nilai seperti {12000, 15000, 10500}. Jalankan program yang sudah diperbaiki. Ia mencetak items: 3, total: 37500, dan average: 12500, dan ketiga barisnya benar.
Sekarang jalankan checker yang tidak diubah. Ia melaporkan 0 of 3 checks passed, padahal kodenya sekarang benar, karena ia mengharapkan empat item. Ekspektasi checker dipatok pada empat item, jadi ia menguji input sama banyak dengan menguji kode.
Baris mana di loop terakhirmu sendiri yang harus kamu ubah kalau besok array-nya bertambah satu?