Académique Documents
Professionnel Documents
Culture Documents
Karena
class ini adalah class utama yang akan langsung dijalankan oleh JRE (Java Runtime
Environment), click "public static void main(String[] args)" pada bagian "Which
method stubs would you like to create?"
Klik "Finish"
Eclipse akan membuat program kosong yang berisi package dan class sesuai
dengan nama yang Anda masukkan pada tahap sebelumnya
Sekarang ketik program berikut di bawah "// TODO"
System.out.println("Selamat Datang!");
Kemudian simpan hasilnya
Menjalankan program Java pertama Anda
Untuk menjalankan program Anda, klik "Run -> Run"
Di bagian bawah pada tab yang berjudul "Console" hasil program Anda
ditampilkan
Program ini akan menampilkan tulisan Selamat Datang! seperti pada gambar
berikut ini
Dalam situs ini, akan ada beberapa program yang cukup panjang dan mungkin
menyusahkan Anda apabila harus mengetik satu per satu. Anda dapat juga mendownload
berkas terkompresi untuk kemudian diimport ke dalam Eclipse.
Download file ini ke komputer Anda
Pada Eclipse, klik "New -> Import -> General -> Existing Project into
Workspace"
Lalu tik "Select Archieve", pilih file yang telah Anda download tersebut, dan klik
"Finish"
2. Klik
tombol "Add Site", kemudian masukkan alamatnya sebagai berikut :
http://subclipse.tigris.org/update_1.4.x
3. Setelah mengambil data plugin yang tersedia, pilih beberapa plugin seperti pada gambar
4. Window baru akan muncul, klik "Finish", jangan lupa untuk mengaccept license
agreementnya.
Setelah Anda berhasil melakukan instalasi plug-in Subversive, langkah-langkah berikut ini
akan membimbing Anda untuk menguji koneksi ke gudang (repository) kode pada SVN
server.
1. Klik "New -> Project -> SVN -> Checkout Project from SVN"
2. Klik "Create a new repository location"
3. Masukkan http://belajarjava.googlecode.com/svn/trunk
4. Pilih folder selamatdatang dan klik Finish
5. Project baru akan tersedia di sebelah kiri workspace Anda. Klik "Run -> Run" dan pilh
"Java Application" untuk menjalankan program ini.
6. Selamat, Anda telah mempersiapkan software yang diperlukan.
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("Selamat Datang!");
}
}
Mari kita bahas satu per satu.
Definisi paket (package)
package selamatdatang;
Package dalam Java merupakan kumpulan dari berbagai kode yang terangkum dalam satu
paket. Untuk memudahkan penulisan dan pembagian logika suatu program, satu paket
terbagi menjadi beberapa berkas (file) di mana setiap file memiliki fungsi atau tugas yang
sangat khusus, misalnya satu file berfungsi untuk mendeklarasikan konstanta dan kelas,
sementara file yang lain berisi implementasi kelas dan prosedurnya.
Pada contoh aplikasi SelamatDatang di atas, paket ini hanya berisi satu buah file yang
isinya terdiri dari satu kelas dan satu metode.
Definisi paket tidak selalu diperlukan, tetapi hal ini merupakan kebiasaan baik untuk
melatih kita berfikir secara logis dan sistematis.
Komentar
/**
* @param args
*/
// TODO Auto-generated method stub
Komentar tidak akan diproses oleh kompiler tetapi berguna bagi programmer lain. Bahasa
Java memiliki 3 jenis komentar :
/* text */ — Compiler akan mengabaikan kata kata antara /* dan */
/** documentation */ — Ini merupakan komentar yang dipergunakan khusus untuk
dokumentasi. Kompiler akan mengabaikan komentar dari /* hingga */. Alat bantu
javadoc akan memproses komentar dokumentasi untuk membuat dokumentasi
secara otomatis dari sumber program.
// text — Kompiler akan mengabaikan segala sesuatu dari // hingga akhir baris
Definisi Kelas
public class SelamatDatang {
...
}
Kelas merupakan bagian integral dari bahasa Java karena Java merupakan bahasa
berorientasi objek. Setiap aplikasi harus terdiri dari satu kelas. Di sini kita definisikan
kelas SelamatDatang sebagai kelas utama.
Metode main
Dalam bahasa pemrograman Java, setiap aplikasi harus memiliki satu buah metode main
yang bentuknya seperti berikut :
public static void main(String[] args) {
...
}
Metode main mirip dengan fungsi main pada bahasa C/C++ di mana fungsi ini merupakan
pintu gerbang dimulanya suatu program. Metoda main dapat dipanggil dengan
menyertakan variabel, baik hanya satu variabel, banyak variabel atau bahkan tidak ada
sama sekali.
Yang terakhir adalah perintah berikut untuk menampilkan Selamat Datang pada komputer
Anda.
System.out.println("Selamat Datang!");
Perintah tersebut menggunakan pustaka inti Java, yaitu kelas Sistem.
Variabel dalam bahasa Java didesign untuk menyimpan hanya 1 jenis tipe data. Kompiler
akan menampilkan kesalahan sintax apabila variabel ini dicoba untuk diberi tipe data jenis
lain. Oleh karena itu Java disebut bahasa pemrograman bertipe kuat atau strongly typed
language.
Ada 8 tipe data primitif dalam bahasa Java.
Jenis
Deskripsi Ukuran Minimum Maksimum
Data
Hanya bisa
boolean berisi benar 1-bit
atau salah
Karakter
char 16-bit
Unicode
1.40129846432481707e-
float Bilangan riil 32-bit 3.40282346638528860e+38
45
4.94065645841246544e-
double Bilangan riil 64-bit 1.79769313486231570e+308
324
Suatu variabel baru dapat digunakan apabila telah dideklarasikan. Pernyataan deklarasi
variabel digunakan untuk mendeklarasikan satu atau lebih variabel dan memberinya nama.
Ketika komputer mengeksekusi deklarasi variabel, komputer akan menyediakan ruangan di
memori kemudian menyimpan alamat ini sesuai dengan nama variabel yang diberikan.
Deklarasi variable berbentuk seperti :
nama_tipe nama_variabel;
nama_variabel dapat berupa sebuah nama variabel atau beberapa nama sekaligus yang
dipisah dengan koma. Gaya pemrograman yang baik yaitu dengan mendeklarasikan satu
variabel dalam satu pernyataan, kecuali variabel tersebut berhubungan erat satu sama lain.
Misalnya:
float num;
String nama;
String nama;
boolean bol;
int x,y;
Atau pendeklarasian variabel bisa juga dilakukan sekaligus dengan pemberian nilainya,
seperti pada contoh berikut:
int num = 1000;
char ch = 'e';
float angka = -1.504;
boolean bol = true;
Jenis-jenis Variabel
Java memiliki beberapa jenis variabel yang dapat dikelompokkan sebagai berikut :
Instance Variables (tidak statis). Dalam bahasa pemrograman berorientasi objek,
objek menyimpan variabel yang tidak dideklarasikan dengan kata kunci static
dalam kategori non-statis, atau dapat berubah-ubah. Suatu kelas dapat dijelmakan
ke dalam beberapa objek. Nilai yang terkandung dalam variabel tak-statis ini
berbeda untuk setiap objeknya.
Class Variables (statis). Variabel ini merupakan bagian integral dari suatu kelas,
dan tidak ada satu objek pun yang dapat menyatakan kepemilikan atas variabel ini.
Variabel yang dideklarasikan sebagai statis digunakan bersama oleh semua objek.
Variabel ini lebih bersifat global yang nilainya sama untuk setiap objek pada kelas
yang bersangkutan.
Local Variables. Variabel ini didefinisikan di dalam suatu metoda (method) atau
dalam suatu prosedur. Variabel ini bersifat lokal karena hanya dapat diakses oleh
metoda atau prosedur tersebut.
Parameter. Paramater atau argumen adalah variabel yang digunakan pada saat suatu
metoda atau prosedur dipanggil. Parameter berguna untuk memberikan nilai awal
untuk diteruskan (pass) ke dalam suatu prosedur atau metoda.
Literal
Pada bagian ini akan dijelaskan tentang literal, yaitu rangkaian kata atau huruf yang
menyatakan suatu nilai. Misalnya
int angka = 10;
Pada pernyataan di atas, yang dinamakan literal adalah 10, karena 10 berarti bilangan bulat
atau integer. Pada bahasa pemrograman java, terdapat beberapa jenis literal yang
melambangkan bilangan bulat, riil, kalimat, atau boolean.
Literal Bilangan Bulat
Bilangan bulat dapat dilambangkan dalam beberapa bentuk. Bilangan bulat biasa
dilambangkan dengan deretan angka yang dimulai dengan angka yang bukan nol.
int angka = -10;
Bilangan oktal adalah bilangan bulat berbasis 8, yang berarti hanya dapat terdiri dari
angka-angka 0 hingga 7. Bilangan oktal ditulis seperti bilangan bulat biasa dimulai dengan
0.
// 22 basis delapan atau 18 dalam desimal
int angka = 022;
Bilangan heksadesimal adalah bilangan berbasis 16. Bilangan heksadesimal dilambangkan
dengan 0 hingga 9 dan a hingga f dan dimulai dengan 0x.
// 2a heksadesimal atau 42 dalam desimal
int angka = 0x2a;
Long integer. Seperti dijelaskan pada bab terdahulu long integer membutuhkan memori
sebesar 64bit, yang artinya kita dapat menyimpan bilangan bulat hingga 2 ^ 64. Untuk
merepresentasikan long integer, tambahkan huruf L pada akhir bilangan.
int angka = 22L;
Literal Karakter
Kita dapat melambangkan suatu karakter dengan tanda petik tunggal misalnya ‘a’ atau ‘3′
atau ‘=’. Suatu karakter dapat juga dilambangkan dengan kode ASCII nya. Caranya dengan
memulainya dengan \u00 (garis miring terbalik) kemudian diikuti dengan kode ASCII nya
dalam bentuk heksadesimal.
// huruf 'A' dalam ASCII
char huruf = '\u0041';
Selain itu ada beberapa karakter lain selain alfabet yang dapat dilambangkan dengan
escape sequence. Berikut ini beberapa contohnya.
\n = tombol enter atau baris baru.
\r = carriage return.
\t = tombol tab.
\b = tombol backspace.
\\ = karakter \
\’ = karakter ‘
\” = karakter ”
Literal Boolean
Nilai true dan false pada java merupakan literal boolean. Suatu variabel bertipe boolean
hanya dapat memiliki nilai true atau false.
boolean ok = true;
Ingat bahwa boolean true atau false TIDAK menggunakan tanda petik tunggal seperti
ekspresi pada karakter.
Literal Bilangan Riil
Bilangan riil, misalnya -0.00127 atau 415.842, pada java dapat disimpan baik sebagai float
atau double. Bilangan real dapat direpresentasikan dalam bentuk desimal biasa, pecahan,
atau eksponen (dilambangkan dengan e atau E).
Ukuran
Tipe Rentang Presisi (jumlah digit)
bytes bit
Imbuhan akhir d atau D dan f atau F dapat pula ditambahkan untuk menentukan tipenya
secara eksplisit. Berikut beberapa contohnya.
double d = 3.27E+32;
float f = 4f;
float pi = 3.14159;
Literal String
String merupakan untaian huruf dan angka yang tersusun menjadi satu kalimat. Dalam
bahasa java, string bukan merupakan tipe primitif, tetapi merupakan kelas. String pada java
tidak disimpan dalam bentuk array seperti pada C. Java menyediakan beberapa metoda
untuk melakukan penggabungan, modifikasi, atau perbandingan. String ditulis di antara
dua tanda petik ganda seperti contoh berikut.
String salam = "Selamat Datang";
String juga dapat mengandung karakter spesial seperti dibahas pada literal karakter.
Misalnya
String hallo = "Selamat Datang \"Bapak Presiden\"";
System.out.println("Hallo Bambang\nSelamat pagi,\nSemoga hari anda
cerah\n";
Berikut ini adalah beberapa contoh lainnya.
// Contoh string kosong
String teks = "";
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
/* Deklarasi variable */
double pokok; // nilai investasi
double sukubunga; // suku bunga bank
double bunga; // nilai bunga
/* Perhitungan */
pokok = 20000;
sukubunga = 0.10; // sama dengan 10%
bunga = pokok * sukubunga;
}
}
Berikut adalah hasil keluarannya :
Kelas Math
Kelas Math memiliki banyak fungsi statik. Beberapa yang penting di antaranya:
Math.abs(x), menghitung nilai mutlak (absolut) dari x. Nilai mutlak bilangan
negatif adalah bilangan positif, dan bilangan positif tetap bilangan positif.
Fungsi trigonometri Math.sin(x), Math.cos(x), and Math.tan(x). (Untuk semua
fungsi trigonometri, sudut memiliki satuan radian, bukan derajat)
Fungsi trigonometri inverse, yang mencari sudut dari suatu nilai trigonometric,
kebalikan dari fungsi trigonometri, seperti arcus sin, arcus cos, dan arcus tangen.
Math.asin(x), Math.acos(x), and Math.atan(x).
Math.exp(x), menghitung pangkat dari bilangan natural e, atau ex. Dan logaritma
natural loge x atau ln x bisa dihitung dengan menggunakan fungsi Math.log(x).
Math.pow(x,y) menghitung xy atau x pangkat y
Math.floor(x) menghitung pembulatan ke bawah dari suatu bilangan riil,
misalnya 3.84 akan dibulatkan ke bawah menjadi 3.0
Math.random() memilih bilangan acak di antara 0.0 dan 1.0. Komputer memiliki
algoritma perhitungan tertentu yang hasilnya bilangan acak (meskipun bulan
bilangan yang betul-betul acak, tetapi cukup untuk kebanyakan fungsi)
Paremeter (nilai di dalam kurung) fungsi-fungsi di atas bisa bertipe numerik apa saja
(misalnya double, int, dll), tetapi keluarannya bertipe double, kecuali abs(x) yang tipe
keluarannya sama dengan tipe parameternya.
Math.random() tidak memiliki parameter, tetapi tanda kurungnya harus tetap ditulis untuk
membedakan fungsi dan variabel. Contoh fungsi lain yang tidak memiliki parameter adalah
System.currentTimeMillis() yang berguna untuk mengambil waktu saat ini dalam
satuan milidetik, dihitung sejak 1 Januri 1970 waktu GMT. Satu milidetik sama dengan 1
per 1000 detik. Keluarannya bertipe long.
Untuk menghitung waktu yang diperlukan untuk menjalankan suatu perintah, jalankan
fungsi System.currentTimeMillis() sebelum dan sesudah suatu instruksi dijalankan.
Perbedaannya adalah waktu yang diperlukan untuk menjalankan suatu instruksi.
/**
* Program ini akan melakukan beberapa perhitungan matematika,
* menampilkan hasilnya di layar, dan melaporkan waktu yang
diperlukan
* untuk melakukan perhitungan tersebut
*
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
long waktuMulai;
long waktuSelesai;
waktuMulai = System.currentTimeMillis();
waktuSelesai = System.currentTimeMillis();
System.out.print("\nTotal waktu perhitungan : ");
System.out.print((waktuSelesai - waktuMulai)/1000.0);
System.out.println(" detik");
}
}
Berikut adalah hasil keluarannya :
Kelas String
Nilai suatu String adalah objek. Objek ini berisi rangkaian huruf yang membentuk string.
Objek tersebut juga berisi subrutin. Misalnya length adalah fungsi yang menghitung
panjang suatu string, atau jumlah karakter dalam suatu string. Misalnya string str yang
dideklarasikan sebagai berikut :
String str;
str = "Hari ini cerah sekali!"
Untuk menghitung jumlah karakter dalam string str, panggil fungsi str.length() yang
keluarannya bertipe int. Fungsi ini tidak membutuhkan parameter. Fungsi length
merupakan anggota kelas String dan dapat digunakan oleh semua data yang bertipe
String. Dan juga bisa digunakan oleh literal string, misalnya program berikut menghitung
jumlah karakter dalam string "Indonesia Raya" :
System.out.print("Jumlah karakter dalam \"Indonesia Raya\" adalah ");
System.out.print("Indonesia Raya".length());
System.out.println(" karakter");
Kelas String memiliki beberapa fungsi di antaranya :
s1.equals(s2) adalah fungsi yang mengembalikan nilai boolean (true atau
false). Fungsi ini akan menghasilkan true jika s2 sama dengan s1, dan salah jika
tidak. Kesamaan yang diuji adalah kesamaan persis baik kapitalnya maupun urutan
huruf-hurufnya.
s1.equalsIgnoreCase(s2) juga menghasilkan nilai boolean yang menguji apakah
string s2 sama dengan s1 dengan tidak membandingkan kapitalnya.
"Kucing".equalsIgnoreCase("kucing") menghasilkan true.
s1.length(). Seperti diulas sebelumnya, fungsi ini menghitung jumlah karakter
dalam string s1.
s1.charAt(N). N adalah integer (bilangan bulat). Fungsi ini mengembalikan
karakter ke-N dari string s1. Karakter pertama dihitung sebagai posisi ke-0.
s1.charAt(0) berarti mengambil karakter pertama, sedangkan s1.charAt(1)
berarti mengambil karakter ke-2, dan seterusnya. Karakter terakhir memiliki indeks
s1.length() - 1. Fungsi ini akan mengeluarkan pesan kesalahan apabila N
bernilai negatif atau lebih besar dari s1.length() - 1.
s1.substring(N,M), di mana N dan M bilangan bulat. Fungsi ini mengambil
potongan string antara karakter ke-N hingga karakter M-1. Catatan bahwa karakter
ke-M tidak ikut diambil. Misalnya, "jalan layang".substring(1,4)
menghasilkan "ala".
s1.indexOf(s2) mengembalikan nilai integer. Fungsi ini mencari string s2 di
dalam string s1, dan apabila ditemukan mengembalikan posisi awal s2 di dalam s1.
Jika tidak ditemukan, fungsi ini akan mengembalikan -1. Fungsi ini bisa juga
digunakan untuk mencari string s2 dalam s1 setelah posisi ke-N dalam string s1.
Misalnya, "kelapa muda".indexOf("a") menghasilkan 3, sedangkan "kelapa
muda".indexOf("a",6) menghasilkan 10.
s1.compareTo(s2) membandingkan s2 dan s1. Jika s1 dan s2 sama, hasilnya 0.
Jika s1 kurang dari s2, hasilnya bilangan negatif , dan jika s1 lebih besar dari s2,
hasilnya bilangan positif. "Kurang dari" atau "lebih dari" mengacu pada urutannya
dalam abjad jika keduanya huruf kecil atau keduanya huruf besar. Jika kapitalnya
berbeda, perbandingannya bergantung pada nilai ASCII-nya.
s1.toUpperCase() adalah fungsi untuk mengubah seluruh huruf dalam s1 menjadi
huruf besar.
s1.toLowerCase() berfungsi untuk mengubah huruf dalam s1 menjadi hurug
kecil.
s1.trim() adalah berfungsi menghapus karakter yang tak bisa dicetak, misalnya
spasi, baris baru, enter, yang ada sebelum atau sesudah suatu kalimat. Misalnya "
selamat pagi bu ".trim() menghasilkan "selamat pagi bu".
Untuk s1.toUpperCase(), s1.toLowerCase(), s1.trim() nilai s1 tidak berubah. Fungsi
ini melakukan perhitungan kemudian mengembalikan string baru hasil perhitungan
tersebut.
Kita bisa juga menggunakan tanda + menyambung 2 string. Misalnya "mata" + "hari"
menjadi "matahari". Nilai yang dapat disambungkan bukan hanya string dan string, tetapi
juga string dan angka, misalnya "jalan" + 2 menjadi "jalan2".
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Operator Aritmatika
+ Operator penjumlahan (juga sebagai penyambung string)
- Operator pengurangan
* Operator perkalian
/ Operator pembagian
% Operator sisa pembagian
Operator aritmatika digunakan untuk melakukan operasi matematika, seperti penambahan,
pengurangan, pembagian, dan modulo (atau sisa pembagian). Contoh penggunaan :
Simbol Nama operator Contoh penggunaan
+ Operator penjumlahan n = n + 1;
- Operator pengurangan n = n - 1;
* Operator perkalian n = n * 1;
/ Operator pembagian n = n / 1;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int x = 2;
int y = 5;
int z = 3;
int g = 0;
g = x + y;
System.out.println("Penjumlahan (x+y) : " + g);
g = y - x;
System.out.println("Pengurangan (y-x) : " + g);
g = x * y;
System.out.println("Perkalian (x*y) : " + g);
g = y / x;
System.out.println("Pembagian (y/x) : " + g);
g = z % y;
System.out.println("Sisa pembagian (z%x) : " + g);
g = x + (y * (z/x));
System.out.println("Hasilnya sekarang : " + g);
}
}
Keluaran Program :
Operator Tunggal
+ Operator plus; menyatakan nilai positif (setiap angka tanpa tanda ini akan dianggap
sebagai positif)
- Operator minus; menyatakan nilai negatif, dapat pula digunakan untuk menegatifkan
suatu bilangan
++ Operator kenaikan; menambah suatu bilangan dengan 1
-- Operator penurunan; mengurangkan suatu bilangan dengan 1
! Operator lawan; membalik nilai suatu boolean
Operator tunggal hanya membutuhkan satu operan untuk melakukan operasinya. Operator
ini tidak dapat digunakan untuk variabel final, karena variabel final berupa konstanta yang
tidak dapat diubah-ubah. Beberapa jenis operator tunggal diberikan pada tabel di bawah
ini.
Nama
Simbol Operasi Contoh
operator
Operator
++ menambah suatu bilangan dengan 1 angka = ++angka;
kenaikan
Operator
-- mengurangkan suatu bilangan dengan 1 angka = --angka;
penurunan
Operator
! membalik nilai suatu boolean ok = !true;
lawan
Operator kenaikan dan penurunan dapat diletakkan di belakang atau di depan suatu
variabel. Jika diletakkan di depan (++x atau --x), penambahan/pengurangan dilakukan
sebelumnya, sedangkan apabila diletakkan di akhir (x++ atau x--)
penambahan/pengurangan dilakukan setelahnya. Walau bagaimanapun pada akhirnya
keduanya akan menghasilkan x = x+1 atau x = x-1.
Mari kita lihat contohnya untuk membedakan lebih jelas perbedaan penempatan operator
tunggal ++ dan --
Contoh kode program yang dapat Anda unduh dalam bentuk zip file atau melalui SVN di
alamat berikut : http://belajarjava.googlecode.com/svn/trunk/OperatorTunggal
Lihat cara mengimpor contoh-contoh program ke dalam Eclipse di Bab II - Instalasi.
package operatortunggal;
int x = 0;
int y = 0;
y = ++x;
System.out.println("Contoh operator pada prefix
(awalan)");
System.out.println("---------------------------");
System.out.println("Nilai x baru : " + x);
System.out.println("Nilai y = ++x : " + y);
x = 0;
y = 0;
y = x++;
System.out.println("\nContoh operator pada sufix
(akhiran)");
System.out.println("---------------------------");
System.out.println("Nilai x baru :" + x);
System.out.println("Nilai y = x++ :" + y);
}
}
Jalankan program tersebut dan lihat hasilnya. Apabila operator ++ diletakkan di awal
(prefix), maka nilai "x" dan "y" akan sama, karena penambahan nilai "x" dilakukan terlebih
dahulu, lalu hasilnya diberi kepada "y".
Apabila operator ++ diletakkan di akhir (sufix), nilai "y" adalah nilai "x" terdahulu. Java
akan memberi nilai "y" dengan nilai "x" sebelum operasi ++ dilakukan. Baru kemudian
nilai "x" ditambahkan.
Berikut ini adalah screenshot keluarannya :
Operator Pembanding, Boolean dan Kondisi
Operator kondisi (conditional operator) menghasilkan nilai true atau false tergantung dari
variabelnya, dalam hal ini operasinya dilakukan pada dua operand. Operator boolean
adalah operator kondisi yang kedua operandnya berupa nilai boolean (true atau false),
sedangkan Operator Pembanding membandingkan 2 nilai seperti pada operasi matematika.
Catatan : Pada objek, seperti String, operasi pembanding akan membandingkan alamat
memory tempat objek itu disimpan, bukan membandingkan isinya. Untuk membandingkan
isi String, gunakan equals(), equalsIgnoreCase(), dan compareTo() seperti dibahas
pada bagian sebelumnya.
! NOT b = !true;
Op1 Hasil
true false
false true
Operator Ternary (?:) Java memiliki operator berkondisi lain yang disebut ternary "?:",
yang pada dasarnya merupakan bentuk pendek dari if-then-else. Secara umum
kondisi ? jika_benar : jika_salah
Pada dasarnya operator "?:" akan mengevaluasi "kondisi". Apabila kondisi bernilai "true",
operator akan mengembalikan "jika_benar", tetapi apabila "kondisi" bernilai "false",
operator akan mengembalika "jika_salah". Misalnya
x = (1 > 2) ? 10 : 20
Dari contoh di atas, "x" akan memiliki nilai 20, karena ekspresi (1 > 2) adalah salah (atau
"false").
Contoh kode program yang dapat Anda unduh dalam bentuk zip file atau melalui SVN di
alamat berikut : http://belajarjava.googlecode.com/svn/trunk/OperatorKondisi
package operatorkondisi;
/**
* Contoh program menggunakan operator kondisi
*
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int x = 5;
int y = 10, angka = 0;
boolean bl = true;
if((x == 5) && (x < y))
System.out.println("Nilai x adalah " + x);
+= x += y; x = (x + y);
-= x -= y; x = (x - y);
*= x *= y; x = (x * y);
/= x /= y; x = (x / y);
%= x %= y; x = (x % y);
|= x |= y; x = (x | y);
^= x ^= y; x = (x ^ y);
/**
* Contoh program menggunakan operator pemberi nilai (assignment
operator)
*
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int x = 5;
int y = 10;
x += y;
System.out.println("Hasil penjumlahan : " + x);
x -= y;
System.out.println("Hasil pengurangan : " + x);
x *= y;
System.out.println("Hasil perkalian : " + x);
x /= y;
System.out.println("Hasil pembagian : " + x);
x %= y;
System.out.println("Sisa pembagian : " + x);
x &= y;
System.out.println("Hasil operasi AND : " + x);
x |= y;
System.out.println("Hasil operasi OR : " + x);
x <<= y;
System.out.println("Hasil operasi pergeseran bit ke kiri
:"+ x);
}
}
Keluaran programnya :
Intermezzo : Membaca Input dari User
Java bukan bahasa pemrograman untuk Console (seperti DOS atau Linux), sehingga untuk
mengambil input dari user diperlukan sedikit trik yang tidak sesederhana readln pada
bahasa pemrograman lain.
Di sini saya akan menjelaskan untuk membuat program yang bisa mengambil input dari
konsol teks. Di bagian lain, setelah kita belajar pemrograman berorientasi objek, kita akan
menggunakan GUI untuk mendapatkan interaksi dari user.
Membaca String yang diketik oleh user di konsol
Kita membutuhkan kelas yang beberapa kelas, yaitu BufferedReader,
InputStreamReader, dan System.in (lawan dari System.out yang kita gunakan untuk
menampilkan pesan di layar).
Dua kelas pertama terdapat dalam package yang dinamakan java.io. Untuk itu, kita harus
menambah satu baris perintah
import java.io.*
yang berarti mengimport semua kelas dalam paket java.io (tanda * berarti semua).
Kemudian kita harus juga membuat suatu objek dari kelas BufferedReader. Kelas
BufferedReader adalah kelas abstrak yang menangani baca tulis ke suatu media.
Kelas ini membutuhkan kelas lain sebagai pekerjanya, yaitu InputStreamReader. Dan
InputStreamReader membutuhkan media tempat baca tulis dilakukan, yaitu System.in.
Semua ini bisa dituliskan dalam satu perintah yaitu :
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Di sini variabel br merupakan objek yang merupakan jelmaan dari kelas BufferedReader.
Untuk memerintahkan Java mengambil input dari user, kita gunakan fungsi readline()
yang terdapat pada kelas BufferedReader, dalam hal ini terealisasi pada objek br.
nama = br.readLine();
Karena kita berhubungan langsung dengan sistem IO (input-output) komputer yang harus
diasumsikan tidak pasti (misalnya ada masalah pada sistem keyboard, atau komputer
sedang bekerja berat sehingga input dari user tidak bisa diambil), kita harus menempatkan
fungsi readLine() pada klausa
try {
...
} catch (IOException ioe) {
...
}
Perintah di dalam try { ... } adalah perintah yang kita ingin jalankan pada situasi yang
"mungkin" tidak berhasil.
Parameter pada catch, yaitu IOException ioe adalah jenis kesalahan yang ingin kita
tangkap. Dalam hal ini kita ingin menangkap adanya kesalahan IO, yaitu kesalahan yang
bertipe IOException.
Perintah di dalam catch { ... } adalah perintah yang akan dilakukan apabila kesalahan
ditangkap. Jika tidak ada kesalahan IO yang ditemukan, maka bagian ini akan dilewatkan
(tidak dijalankan).
Mari kita lihat program akhir untuk mengambil input dari user.
package ambilinputkonsol;
import java.io.*;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
nama = br.readLine();
} catch(IOException ioe) {
System.out.println("Kesalahan IO pada saat
menanyakan nama Anda");
System.exit(1);
}
import java.io.*;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
System.out.print("Masukkan nama Anda : ");
nama = br.readLine();
} catch(IOException ioe) {
System.out.println("Kesalahan IO pada saat
menanyakan nama Anda");
System.exit(1);
}
System.out.println("Selesai...");
Variabel angka kita inisialiasi (persiapkan) dan kita isi mula-mula dengan 1. Ketika
program sampai pada pernyataan while, program akan mengevaluasi apakah angka <= 5.
Pada saat program baru dimulai, angka masih bernilai 1, sehingga pernyataan angka <= 5
bernilai true. Dalam hal ini perintah di dalam blok akan dieksekusi, yaitu mencetak angka
ke layar, kemudian menambah angka dengan 1. Sekarang angka bernilai 2.
Setelah sampai pada akhir blok, program akan kembali pada awal pernyataan while.
Sekarang angka bernilai 2, dan karena 2 masih kurang dari atau sama dengan 5, program
akan kembali mengeksekusi perintah dalam blok. Begitu seterusnya hingga angka bernilai
6. Pada saat ini program akan berhenti melakukan perulangan dan berhenti melakukan
eksekusi perintah di dalam blok tersebut, kemudian melakukan perintah berikutnya, yaitu
menampilkan kata "Selesai...".
Percabangan
Pernyataan if memperintahkan komputer untuk memilih salah satu aksi yang akan
dilakukan, tergantung pada suatu kondisi tertentu. Bentuknya dapat ditulis sebagai berikut
if (suatu_kondisi)
perintah_1;
else
perintah_2;
Perintah_1 dan perintah_2 juga bisa berbentuk blok, sehingga pernyataan di atas dapat
ditulis juga sebagai berikut
if (suatu_kondisi) {
perintah_1;
} else {
perintah_2;
}
Ketika komputer sampai pada pernyataan if, komputer akan menghitung apakah
suatu_kondisi bernilai true. Jika iya, maka blok perintah berikutnya akan dieksekusi,
dalam hal ini perintah_1. Jika tidak, maka blok setelah pernyataan else akan dieksekusi,
yaitu perintah_2.
Sebagai contoh, mari kita kembali pada contoh program untuk membalik nilai x dan y,
dengan syarat x harus lebih besar dari y. Dalam hal ini, setelah program ini dieksekusi,
nilai x akan selalu bernilai lebih kecil dari y, karena jika nilai x lebih besar, nilai x akan
ditukar dengan nilai y.
if (x > y) { // jika x lebih besar dari y
// blok ini digunakan untuk menukar isi variable x dan y
int temp;
temp = x;
x = y;
y = temp;
}
Contoh berikut adalah program untuk menentukan apakah suatu bilangan merupakan
bilangan genap atau bilangan ganjil. Dengan menggunakan operator %, yaitu sisa
pembagian, kita dapat menentukan apabila sisa pembagian suatu bilangan dengan 2 adalah
0, maka bilangan tersebut merupakan bilangan genap. Jika tidak, maka bilangan tersebut
adalah bilangan ganjil.
if ((x % 2) == 0) {
System.out.println(x + " adalah bilangan genap");
} else {
System.out.println(x + " adalah bilangan ganjil");
}
Kita akan bahas tentang struktur kontrol di bagian berikutnya. Semoga bagian ini yang
merupakan bagian pendahuluan tentang struktur kontrol dapat dimengerti sehingga kita
bisa mempelajari konsep yang lebih kompleks lagi
Perancangan Algoritma
Komputer itu bodoh! Kenapa? Karena sebagai programmer, kita harus memberikan
perintah hingga sangat detail apa yang harus dikerjakan oleh komputer. Programmer
adalah orang yang bertugas untuk menerjemahkan suatu tugas menjadi instruksi detail
yang dapat dimengerti oleh komputer. Komputer hanya melakukan apa yang diperintahkan
baris demi baris, tetapi komputer tidak bisa berfikir bagaimana melakukan suatu tugas
seefisien mungkin.
Untuk itu programmer baru harus dilengkapi dengan cara berfikir dan peralatan yang
memungkinkan mereka untuk sukses dalam menerjemahkan suatu tugas menjadi rangkaian
perintah yang bisa dimengerti oleh komputer.
Program komputer itu seperti seni yang memuat suatu ide. Seorang programmer mulai
dengan suatu tugas di kepalanya, misalnya menghitung sisi miring dari segitiga siku-siku.
Kemudian dia akan berfikir tentang apa yang harus dilakukan untuk menyelesaikan tugas
tersebut dalam bahasa manusia. Dalam hal ini misalnya, sisi miring dapat dihitung dengan
mengambil akar kuadrat dari jumlah kuadrat sisi siku-sikunya. Pemecahan masalah ini
kemudian diterjemahkan ke dalam bahasa pemrograman yang berupa perintah langkah
demi langkah bagaimana komputer harus menyelesaikan tugas tersebut.
Perintah langkah demi langkah hingga detail ini disebut algoritma. (Secara teknis,
algoritma adalah kumpulan langkah-langkah sederhana yang jelas, tidak membingungkan
karena hanya ada satu cara untuk melakukan langkah sederhana tersebut, dilakukan selama
kurun waktu tertentu. Kita tidak ingin program menghitung selamanya tanpa batas waktu.)
Program ditulis dalam bahasa pemgrograman tertentu. Tetapi algoritma ditulis secara
umum atau generic, dalam bahasa manusia, sehingga bisa diimplementasikan
menggunakan bahasa pemrograman apapun. Atau dengan kata lain, algoritma mirip seperti
ide di belakang program yang akan kita tulis. Tetapi ide tersebut harus jelas, dan memuat
langkah demi langkah yang sederhana.
Jadi darimana algoritma itu datang? Biasanya orang harus membuat algoritma itu. Dengan
skill, latihan dan pengalaman, orang akan lebih mudah membuat suatu algoritma.
Di bagian ini akan dibahas tentang bagaimana membuat suatu algoritma. Jadi bahasa Java
akan kita tinggalkan sementara. Kita akan beralih untuk berfikir secara abstrak.
Misalnya, kita memiliki suatu tugas di kepala. Salah satu cara untuk menyelesaikan tugas
itu adalah menuliskan penyelesaiannya langkah demi langkah, dan dari sana kita membuat
algoritma untuk menyelesaikan masalah itu. Kemudian dari setiap langkah tersebut, kita
bisa membuat langkah-langkah lain yang lebih detail, sampai kita bisa menerjemahkan
langkah-langkah itu ke dalam bahasa pemrograman. Metode ini disebut penghalusan
bertahap (stepwise refinement), dan sifatnya top-down atau dari atas ke bawah. Sambil kita
menambah detail pada setiap langkah, kita bisa mulai menuliskan algoritma dalam bentuk
pseudocode (kode palsu) yang bentuknya tidak harus persis atau mengikuti suatu bahasa
pemrograman.
Misalnya, kita ambil contoh untuk menghitung rata-rata dari 5 bilangan. Kita bisa
menuliskan pseudocode dalam bentuk berikut :
ambil input user untuk bilangan pertama
masukkan ke variabel x
ambil input user untuk bilangan kedua
tambahkan variabel x dengan bilangan kedua
ambil input user untuk bilangan ketiga
tambahkan variabel x dengan bilangan ketiga
ambil input user untuk bilangan keempat
tambahkan variabel x dengan bilangan keempat
ambil input user untuk bilangan kelima
tambahkan variabel x dengan bilangan kelima
bagi variabel x dengan 5
tampilkan hasilnya di layar
Algoritma di atas betul, tetapi terlalu banyak perulangan. Bagaimana jika bilangan yang
akan dirata-ratakan ada 100? Kita bisa udah algoritma di atas dengan pseudocode yang
lebih mudah dimengerti, misalnya
while a kurang dari 5
ambil input user untuk bilangan ke-a
tambahkan nilai x dengan bilangan ke-a
tambah a dengan 1
bagi variabel x dengan 5
tampilkan di layar
Sekarang, ambil input user bisa dideskripsikan lebih jauh. Kita harus memberikan
pertanyaan kepada user. Dan seperti dipelajari pada bab sebelumnya, input dari user berupa
String yang harus diterjemahkan ke dalam tipe data bilangan untuk bisa dikalkulasi.
Pseudocode di atas dapat ditulis ulang sebagai berikut :
while a kurang dari 5
beri pertanyaan kepada user untuk memasukkan bilangan ke-a
ambil input user untuk bilangan ke-a
ubah input menjadi bilangan
tambahkan nilai x dengan bilangan ke-a
tambah a dengan 1
bagi variabel x dengan 5
tampilkan di layar
Dan juga dari pelajaran sebelumnya, untuk mengambil input user, kita bisa dihadapkan
pada permasalahan IO (input output). Selain itu kita juga harus menginisialisasi pembaca
dan tempat meletakkan string sementara. Untuk itu, kita harus mengubah pseudocode nya
menjadi seperti ini
inisialisasi pembaca
inisialisasi tempat string sementara
while a kurang dari 5
beri pertanyaan kepada user untuk memasukkan bilangan ke-a
"coba" ambil input user untuk bilangan ke-a
jika ada masalah keluar dari program
ubah input menjadi bilangan
tambahkan nilai x dengan bilangan ke-a
tambah a dengan 1
bagi variabel x dengan 5
tampilkan di layar
Kemudian, perlu diperhatikan bahwa variabel a dan x tidak diketahui nilai awalnya oleh
komputer. Bayangkan jika nilai a dan x ditentukan oleh komputer secara acak, bukan saja
hasil rata-ratanya menjadi tak tentu, tetapi juga perulangannya menjadi tak menentu. Untuk
itu kita harus tambahkan perintah untuk menginisialisasi a dan x dengan 0, sehingga
pseudocodenya menjadi
inisialisasi x dengan 0
inisialisasi a dengan 0
inisialisasi pembaca
inisialisasi tempat string sementara
while a kurang dari 5
beri pertanyaan kepada user untuk memasukkan bilangan ke-a
"coba" ambil input user untuk bilangan ke-a
jika ada masalah keluar dari program
ubah input menjadi bilangan
tambahkan nilai x dengan bilangan ke-a
tambah a dengan 1
bagi variabel x dengan 5
tampilkan di layar
Dari sini kita bisa menerjemahkan pseudocode tersebut menjadi program Java, yaitu
double x = 0;
int a = 0;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String strbilangan = null;
while (a < 5) {
System.out.print("Masukkan bilangan ke-" + a + " : ");
try {
strbilangan = br.readLine();
} catch (IOException ioe) {
System.out.println("Kesalahan IO, program berhenti");
System.exit(1);
}
x = x + Double.parseDouble(strbilangan);
a = a + 1;
}
x = x / 5;
System.out.println("Rata-rata bilangan yang dimasukkan adalah " + x);
Jangan lupa bahwa program tersebut membutuhkan paket java.io.*, sehingga kita harus
menambah
import java.io.*
di awal kelas.
Berikut ini adalah program lengkapnya dan dapat diunduh dalam bentuk zip file atau
melalui SVN di alamat berikut
http://belajarjava.googlecode.com/svn/trunk/HitungRataRata5Bil
package hitungratarata5bil;
import java.io.*;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
double x = 0;
int a = 0;
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String strbilangan = null;
while (a < 5) {
System.out.print("Masukkan bilangan ke-" + a + " :
");
try {
strbilangan = br.readLine();
} catch (IOException ioe) {
System.out.println("Kesalahan IO, program
berhenti");
System.exit(1);
}
x = x + Double.parseDouble(strbilangan);
a = a + 1;
}
x = x / 5;
System.out.println("Rata-rata bilangan yang dimasukkan
adalah " + x);
}
}
Untuk menguji program tersebut, jalankan "Run -> Run" atau Ctrl-F11. Kemudian
arahkan kursor Anda ke bagian bawah dan klik di kotak yang bernama Console.
Berikut ini adalah hasil keluarannya, Anda bisa mencoba-coba dengan bilangan apapun.
Sintaks adalah salah satu cara menentukan bagaimana komputer harus bekerja. Manusia
tidak membutuhkan sintaks untuk berbicara karena manusia bisa menentukan mana subjek,
predikat, atau objek dengan mudah, bahkan untuk kalimat yang belum pernah dibaca atau
didengar sekalipun.
Bagi komputer, sintaks membantu komputer mengelola logika, mana yang merupakan
variabel, mana yang berupa subrutin, mana perulangan, mana percabangan dan lain-lain.
Dalam bahasa Java, yang merupakan bahasa dengan sintaks ketat, perbedaan sintaks
sedikit saja membuat Java tidak mengerti apa yang dimaksud dalam program.
Bagi beberapa programmer, ketidakluwesan Java mungkin menghambat, tetapi perlu
diingat bahwa kesalahan hanya datang dari manusia. Ketidakluwesan Java membantu
programmer harus menggunakan logika yang benar, dan tidak boleh salah dalam
menggunakan variabel. Variabel yang sudah ditentukan tipenya tidak dapat diubah
ditengah jalan, kecuali dengan membuat variabel baru dengan tipe yang berbeda.
Debugging
Ketika program Anda tidak menunjukkan kesalahan sintaks, bukan berarti program Anda
bebas dari kesalahan. Program harus diuji apakah ia berjalan sesuai dengan yang
diharapkan. Idealya apabila program menerima input yang tidak sesuai dengan yang
diharapkan, program akan memberikan pesan kepada penggunanya tentang kesalahan
tersebut, bukan tiba-tiba keluar tanpa pesan.
Kita mungkin harus melakukan testing secara lebih perlahan-lahan. Apabila jumlah baris
dalam program kita kecil, mungkin kesalahan tersebut dengan mudah akan kita temui.
Tetapi apabila program tersebut sangat besar, maka mencari kesalahan yang kelihatannya
sederhana tidaklah mudah.
Pencarian kesalahan ini dalam bahasa pemrograman disebut dengan "debugging", yang
jika diterjemahkan ke dalam bahasa Indonesia berarti membersihkan kutu, karena
kesalahan kecil dianggap sebagai kutu yang mengganggu jalannya program.
Eclipse menyediakan peralatan untuk melakukan debugging, yaitu dengan menekan
tombol "Run -> Debug". Dalam Eclipse, debugging yang bisa dilakukan antara lain:
membuat breakpoint (tempat berhenti program), mengeksekusi satu demi satu perintah,
mengeksekusi satu subrutin, melihat isi variabel saat ini, dan bahkan mengganti variabel
pada saat program dijalankan.
Untuk mendemonstrasikan debugging ini, mari kita gunakan program menghitung rata-rata
pada bagian terdahulu.
Membuat breakpoint
Kita bisa menjalankan program baris demi baris, tetapi sebelum kita melakukannya, kita
harus memberhentikan program tersebut di satu tempat terlebih dahulu. Tempat
pemberhentian program sementara ini disebut breakpoint. Untuk mengeset breakpoint,
double klik bagian paling kiri dari baris di mana Anda ingin program tersebut berhenti.
Misalnya pada tampilan berikut, program berhenti di baris double x=0;. Perhatikan juga
bahwa baris yang diberi breakpoint memiliki tanda bulat di sampingnya.
Untuk menghapus breakpoint, double click lagi tanda bulat di baris yang sudah diberi
breakpoint tadi.
Menjalankan program hingga breakpoint
Coba tekan tombol F11 atau "Run -> Debug". Pada saat Anda memulai debugging,
Eclipse akan memberikan pertanyaan bahwa perspective (atau tampilan Eclipse) Anda
akan diubah ke debugging perspective. Tekan Yes untuk mengubah perspective tersebut.
Lakukan terus Step Over hingga program berakhir, dan Anda akan melihat langkah
demi langkah bagaimana program dijalankan secara bertahap. Perlu diingat, pada
saat program sampai pada instruksi untuk mengambil input Anda, klik bagian
bawah (tab yang bernama Console), dan masukkan angka sebagai input kemudian
tekan Enter. Lihat bagaimana variabel di kanan atas berubah sesuai dengan input
yang Anda berikan.
Melihat dan mengubah isi variable Di sini saya jalankan program setiap baris
hingga a = a + 1 seperti pada gambar berikut.
Setelah diganti coba jalankan program hingga selesai, misalnya dengan "Run ->
Resume". Jangan lupa untuk memasukkan inputnya di bagian Console. Lihat
sekarang bilangan ke-0 ditanyakan 2 kali, yang artinya user diperintahkan untuk
memasukkan 6 bilangan. Tentunya hasil rata-ratanya salah, karena perhitungan
rata-rata dilakukan dengan rumus x = x/5, dalam hal ini seharusnya adalah 6.
import java.io.*;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
double jumlah = 0;
double bilangan = 0;
int n = 0;
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String strbilangan = null;
while (bilangan != 0) {
jumlah += bilangan; // sama dengan : jumlah =
jumlah + bilangan
n++; // sama dengan : n = n+1
// hitung rata-rata
double ratarata = jumlah/n;
Jika suatu saat Anda harus menulis kondisi pada pernyataan while seperti ini while
(jawaban == true), Anda bisa mengganti pernyataan ini menjadi while (jawaban).
Menguji apakah jawaban sama dengan true sama artinya dengan melihat apakah jawaban
berisi "true".
Demikian juga dengan while (jawaban == false), bisa diganti dengan while
(!jawaban). Seperti dijelaskan pada bab tentang operator boolean, operator ! membalik isi
dari boolean, misalnya dari true menjadi false atau sebaliknya. Dengan menuliskan while
(!jawaban) berarti sama dengan menguji apakah jawaban berisi false.
Pernyataan do ... while memberikan keleluasaan kepada Anda untuk berekspresi
dengan lebih lugas. Sebenarnya, untuk memecahkan suatu masalah dengan perulangan do
... while juga bisa diekspresikan dengan perintah while, demikian juga sebaliknya.
do
perintah
while (suatu_kondisi);
bisa dituliskan dalam bentuk
perintah
while (suatu_kondisi)
perintah
Demikian juga dengan
while (suatu_kondisi)
perintah
bisa juga dituliskan dalam bentuk
if (suatu_kondisi) {
do {
perintah
while (suatu_kondisi);
}
tanpa merubah aliran program sama sekali.
Pernyataan break dan continue
Pernyataan while dan do ... while menguji kondisi di awal atau di akhir badan
perulangan. Pengujian bisa juga dilakukan di tengah-tengah badan perulangan, kemudian
memerintahkan program untuk keluar dari badan perulangan saat itu juga. Caranya dengan
menggunakan perintah
break<code>, sehingga program seperti
<code>while (suatu_kondisi)
perintah
bisa ditulis dalam bentuk
while (true)
perintah
if (!suatu_kondisi)
break;
<code>
Apa makna dari program di atas? <code>while (true)
artinya memerintahkan program untuk melakukan perulangan selamanya, karena true tidak
akan berubah. Di tengah-tengah program, kita uji apakah suatu_kondisi bernilai false.
Jika ya, maka perintah break akan dieksekusi yang menyebabkan program keluar dari
badan perulangan ke perintah berikutnya di luar badan perulangan.
Kadang-kadang gaya penulisan ini lebih masuk akal ketimbang gaya penulisan baku
seperti while atau do ... while, tapi tentu saja ini tergantung dari cara pikir masing-
masing programmer dan juga masalah yang akan dipecahkan.
Pernyataan while atau do ... while dapat dibuat bertingkat, misalnya membuat blok
while dalam blok while.
while (suatu_kondisi) {
perintah
while (kondisi_lain) {
perintah_lain
while (kondisi_lain_lagi) {
perintah_baru
}
}
}
Apabila perintah break diberikan, maka program akan keluar dari perulangan yang berada
persis di atasnya. Misalnya, apabila perintah break diberikan setelah perintah_lain
maka program akan keluar dari dalam perulangan while (kondisi_lain).
Perlu diingan juga apabila perintah break diberikan di dalam pernyataan if, maka
program akan keluar dari perulangan yang persis di atasnya.
Selain perintah break yang secara langsung menghentikan perulangan, perintah continue
digunakan untuk menghentikan operasi saat itu, mengabaikan perintah hingga perulangan
berakhir, kemudian kembali kepada perintah while lagi. Misalnya,
while (suatu_kondisi) {
perintah
continue;
perintah_lagi
perintah_lain_lagi
}
Perulangan akan menjalankan perintah, mengabaikan perintah_lagi dan
perintah_lain_lagi, kemudian kembali kepada pernyataan while untuk mengevaluasi
apakah suatu_kondisi bernilai true. Jika ya perulangan akan diteruskan. Tetapi karena
ada perintah continue, artinya selama perulangan tersebut berjalan, perintah_lagi dan
perintah_lain_lagi tidak akan pernah dieksekusi.
Perulangan for
Kita akan membahas bentuk perulangan lain, yaitu perulangan for. Setiap bentuk
perulangan for dapat diubah menjadi bentuk perulangan while dengan fungsi yang sama
tanpa mengubah alur program. Tetapi tergantung dari permasalahan yang akan kita
pecahkan, menulis program dengan for akan membuat alur program lebih mudah
dipahami.
Misalnya, kita akan menghitung 1+2+3+4+5+...+100. Kita bisa ekspresikan program
tersebut dalam bentuk
i = 1;
jumlah = 0;
while (i <= 100) {
jumlah += i;
i++;
}
Perulangan ini dapat ditulis juga dengan
jumlah = 0;
for (i = 1; i <= 100; i++)
jumlah += i
Apa point-point penting yang kita lihat dari perubahan ini? Pertama mari kita lihat bentuk
penggunaan while yang umum dilakukan
inisialisasi variabel
while (suatu_kondisi_variabel) {
perintah
update_variabel
}
Di sini perulangan while memiliki 3 komponen penting, yaitu inisialisasi, yaitu
memberikan nilai awal suatu variabel, suatu_kondisi_variabel, yaitu pengujian bahwa
perulangan akan terus dilakukan selama kondisi ini bernilai true, dan terakhir
update_variabel, yaitu instruksi mengubah nilai kondisi variabel untuk membatasi
perulangan sehingga akan selesai suatu saat, tidak berulang terus menerus.
Pada perulangan for, ketiga komponen ini dirangkai menjadi satu dalam bentuk
for (inisialisasi_variabel; kondisi_variabel; update_variabel)
perintah
atau jika perintah merupakan blok yang terdiri dari banyak perintah, dapat dituliskan juga
dalam bentuk
for (inisialisasi_variabel; kondisi_variabel; update_variabel) {
banyak_perintah
}
Di sini inisialisasi variabel bisa berupa apa saja yang berbentuk perintah, misalnya
memberikan variabel dengan nilai awal tertentu, dalam bentuk variabel = nilai_awal.
kondisi_variabel harus berbentuk pernyataan boolean seperti suatu_kondisi pada
pernyataan while. Sedangkan update_variabel juga berbentuk perintah.
inisialisasi_variabel, kondisi_variabel, atau update_variabel dapat
dikosongkan dan tidak harus selalu diisi. Bagian yang tidak diisi yang selalu digantikan
dengan true, yang artinya perulangan akan terus dieksekusi tanpa henti. Untuk
menghentikannya, perintah break harus diberikan ditengah-tengah badan perulangan.
Bentuk paling umum dari perulangan for adalah menghitung. Biasanya perulangan ini
memiliki bentuk seperti
for (variabel = nilai_minimum; variabel <= nilai_maksimum; variabel++) {
perintah
}
Perhatikan bagian terakhir adalah menaikkan nilai variabel dengan 1, yang artinya
perulangan akan dimulai dengan nilai_minimum, diakhiri dengan nilai_maksimum
dengan jeda 1.
Contoh, jika kita ingin berhitung 2,5,8,11,14,17, atau dengan kata lain, mulai dari 2 hingga
17 dengan jeda 3, kita bisa mengekspresikan for dengan
for (i = 2; i <= 17; i += 3) {
System.out.println(i);
}
Untuk menghitung mundur, kita bisa menggunakan perintah seperti
for (i = 20; i >= 0; i--) {
System.out.println(i);
}
Atau jika kita ingin menghitung maju dan mundur pada saat yang bersamaan, misalnya i
dari 1 hingga 10 dan j dari 10 hingga 1, kita bisa ekspresikan dengan
for (i = 1, j = 10; i <= 10; i++, j--) {
System.out.println(i + " " + j);
}
Catatan penting! Variabel yang akan digunakan, dalam contoh di atas i dan j, adalah
variabel yang harus dideklarasikan sebelumnya. Java adalah bahasa pemrograman ketat,
yang artinya semua harus didefinisikan dengan jelas sebelum digunakan. Untuk banyak
kasus, deklarasi variabel dan perulangannya bisa dilakukan serentak pada bagian
inisialisasi variabel. Misalnya
for (int i = 1; i <= 10; i++) {
System.out.println(i)
}
Perhatikan ada imbuhan int di depan inisialisasi variabel i, yang merupakan deklarasi
variabel i dengan tipe data int sekaligus menginisialisasi nilainya dengan 1.
Perulangan for bertingkat
Seperti pada perulangan while, perulangan for pun dapat dilakukan bertingkat, artinya
perulangan for di dalam perulangan for.
Kita ambil contoh sederhana misalnya membuat tabel perkalian seperti
1 2 3 4 5 6 7 8 9 10 11 12
2 4 6 8 10 12 14 16 18 20 22 24
3 6 9 12 15 18 21 24 27 30 33 36
4 8 12 16 20 24 28 32 36 40 44 48
5 10 15 20 25 30 35 40 45 50 55 60
6 12 18 24 30 36 42 48 54 60 66 72
7 14 21 28 35 42 49 56 63 70 77 84
8 16 24 32 40 48 56 64 72 80 88 96
9 18 27 36 45 54 63 72 81 90 99 108
10 20 30 40 50 60 70 80 90 100 110 120
11 22 33 44 55 66 77 88 99 110 121 132
12 24 36 48 60 72 84 96 108 120 132 144
Program untuk membuat tabel perkalian tersebut bisa diekspresikan dengan algoritma
pseudocode sebagai berikut
untuk setiap baris i = 1,2,3...,12
cetak perkalian i dengan 1,2,3..12
cetak baris baru
Kalau kita jabarkan lebih lanjut, perintah kedua juga merupakan perulangan dari 1 hingga
12, sehingga algoritma di atas bisa kita tulis sebagai
untuk setiap baris i = 1,2,3...,12
untuk setiap kolom j = 1,2,3...,12
cetak i*j
cetak baris baru
Kita bisa terjemahkan ke dalam bahasa Java sebagai
for (int i = 1; i <= 12; i++) {
for (int j = 1; j <= 12; j++) {
System.out.print(i*j + " ");
}
System.out.println("");
}
Berikut ini adalah contoh program yang bisa diunduh dalam bentuk zip file atau dari
gudang SVN di alamat http://belajarjava.googlecode.com/svn/trunk/TabelPerkalian :
package tabelperkalian;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
for (int i = 1; i <= 12; i++) {
for (int j = 1; j <= 12; j++) {
System.out.print(i*j + " ");
}
System.out.println("");
}
}
}
Berikut ini hasil kelurannya:
Pernyataan if
Pernyataan if merupakan salah satu pernyataan percabangan pada Java, dengan bentuk
umum seperti
if (suatu_kondisi)
perintah1
else
perintah2
Seperti biasa, perintah1 dan perintah2 bisa berbentuk blok yang terdiri dari beberapa
perintah. Pernyataan if merupakan bentuk percabangan 2 arah. Bagian else yang terdiri
dari kata "else" dan perintah2 tidak selalu harus ada.
Perhatikan bahwa baik perintah1 dan perintah2 bisa merupakan pernyataan if itu sendiri.
Ada beberapa hal menarik yang mungkin berguna. Ambil contoh dalam pernyataan berikut
if (x > 0)
if (y > 0)
System.out.println("perintah1");
else
System.out.println("perintah2");
Pertama-tama, komputer tidak peduli bagaimana Anda memformat paragraf dan indentasi
dari pernyataan if tersebut. Java akan menganggap else terkait dengan if terdekat,
sehingga kode di atas akan dianggap seperti
if (x > 0)
if (y > 0)
System.out.println("perintah1");
else
System.out.println("perintah2");
else di program di atas akan dianggap bagian dari pernyataan jika y > 0, padahal yang kita
maksud adalah else jika x > 0. Untuk memperbaikinya, kita tambahkan tanda kurung {}
sehingga menjadi
if (x > 0) {
if (y > 0)
System.out.println("perintah1");
}
else
System.out.println("perintah2");
Kedua pernyataan tersebut memiliki arti yang berbeda. Jika x <= 0, pada kode pertama
Java tidak mencetak apa-apa ke layar, sedangkan kode kedua java akan mencetak
"perintah2".
Lebih menarik lagi, perhatikan kode berikut
if (kondisi_pertama)
perintah1
else
if (kondisi_kedua)
perintah2
else
perintah3
Lagi-lagi karena Java tidak membedakan indentasi penulisan, maka kode tersebut akan
diterjemahkan Java seperti
if (kondisi_pertama)
perintah1
else if (kondisi_kedua)
perintah2
else
perintah3
Dengan kata lain perintah tersebut lebih seperti percabangan 3 arah. Komputer akan
mengeksekusi hanya salah satu dari perintah1, perintah2, atau perintah3. Komputer
akan mengevaluasi kondisi_pertama, jika true, maka perintah1 dieksekusi sementara
perintah2 dan perintah3 diabaikan. Jika false, maka kondisi_kedua akan dievaluasi.
Jika true, maka perintah2 akan dieksekusi dan perintah3 diabaikan. Jika false, maka
hanya perintah3 saja yang dieksekusi.
Berikut ini adalah contoh penggunaan percabangan 3 arah.
if (suhu < 20)
System.out.println("Dingin");
else if (suhu < 30)
System.out.println("Lumayan");
else
System.out.println("Panas");
Kita bahkan dapat membentuk pernyataan if-else ini menjadi percabangan N arah,
misalnya
if (kondisi_pertama)
perintah1
else if (kondisi_kedua)
perintah2
else if (kondisi_ketiga)
perintah3
else if (kondisi_keempat)
perintah4
.
.
.
else if (kondisi_keNminus1)
perintahNmin1
else
perintahN
Contoh berikut ini adalah mengurutkan 3 bilangan dari kecil ke besar. Misalnya kita
mempunyai 3 variabel a,b dan c. Bilangan yang paling kecil adalah bilangan yang lebih
kecil dari kedua bilangan yang lain. Sekarang mari kita rangkai logika untuk menentukan
urutan bilangan dari kecil ke besar. Mula-mula kita cek apakah a lebih kecil dari b dan c,
yaitu dengan pernyataan
if (a < b && a <c)
Jika a betul merupakan bilangan terkecil, maka kita uji apakah b lebih kecil dari c dengan
perintah
if (b < c)
Jika a bukan bilangan terkecil, maka b atau c, salah satunya bisa merupakan bilangan
terkecil. Kita hanya perlu membandingkan apakah b lebih kecil dari c dengan
if (b < c)
Jika b lebih kecil dari c, berarti kita tahu bahwa b adalah bilangan terkecil. Tetapi kita
belum tahu apakah bilangan terkecil berikutnya adalah a atau c, sehingga kita harus
menguji lagi dengan
if (a < c)
Jika a lebih kecil dari c, maka urutannya adalah b, a, c. Jika tidak, maka urutannya adalah
b, c, a.
Demikian halnya apabila jika b > c, maka kita bisa tentukan urutan bilangannya.
Keseluruhan logika ini, bisa kita tuangkan dalam bentuk :
if (a < b && a < c) {
if (b < c)
System.out.println(a + " " + b + " " + c);
else
System.out.println(a + " " + c + " " + b);
} else if (b < c) {
if (a < c)
System.out.println(b + " " + a + " " + c);
else
System.out.println(b + " " + c + " " + a);
} else {
if (a < b)
System.out.println(c + " " + a + " " + c);
else
System.out.println(c + " " + b + " " + a);
}
Logika di atas bisa juga dituangkan dengan cara lain, yaitu melihat urutannya. Pertama kita
cek apakah a < b. Jika ya, kita tahu bahwa urutannya pasti a terlebih dahulu baru b.
Kemudian kita lihat apakah c berada di sebelah kiri a atau disebelah kanan b atau di
tengah-tengah.
Demikian seterusnya jika urutannya b terlebih dahulu baru a. Sehingga kodenya bisa
dituliskan dalam bentuk :
if (a < b) {
if (c < a)
System.out.println(c + " " + a + " " + b);
else if (c > b)
System.out.println(a + " " + b + " " + c);
else
System.out.println(a + " " + c + " " + b);
} else {
if (c < b)
System.out.println(c + " " + b + " " + a);
else if (c > a)
System.out.println(b + " " + a + " " + c);
else
System.out.println(b + " " + c + " " + a);
}
Pernyataan switch
Penyataan percabangan kedua yang dimiliki Java adalah switch. Pernyataan switch lebih
jarang digunakan, tetapi sering bermanfaat apabila kita ingin menuliskan percabangan
multi arah.
Pernyataan switch memiliki bentuk sebagai berikut
switch (ekspresi) {
case nilai1:
perintah1
break;
case nilai2:
perintah2
break;
case nilai3:
perintah3
break;
default:
perintah_lain
}
Di sini pernyataan switch akan mencari nilai ekspresi yang sesuai dengan nilai-nilai yang
didaftarkan pada pernyataan case. Jika salah satu nilai ditemui, maka program akan
melompat ke cabang case tersebut dan melakukan perintah yang terdapat di sana. Jika
tidak ditemui, maka program akan melompat ke perintah yang terdapat pada pernyataan
default.
Catatan ekspresi hanya bisa berbentuk nilai bilangan bulat (int, short, dan sejenisnya) atau
karakter, sehingga kita tidak bisa menggunakan switch untuk mengevaluasi ekspresi yang
berbentuk String.
Pernyataan break di atas sebetulnya tidak harus selalu ada. Tetapi, perintah break di sini
memerintahkan komputer agar segera keluar dari blok switch apabila perintah tersebut
telah selesai dilaksanakan.
Apabila perintah break tidak diberikan, maka program akan terus mengeksekusi perintah
lain meskipun sudah berada di luar nilai yang tertera dalam pernyataan casenya.
Misalnya, lihat kode berikut ini :
switch (N)
case 1:
System.out.println("Angka tersebut bernilai 1");
break;
case 2:
case 3:
case 4:
case 5:
System.out.println("Angka tersebut bernilai 2, 3, 4, atau 5");
break;
case 6:
case 7:
case 8:
System.out.println("Angka tersebut bernilai 6, 7, atau 8");
break;
default:
System.out.println("Angka tersebut tidak bernilai 1 - 8");
}
Salah satu aplikasi di mana pernyataan switch berguna adalah untuk memproses menu.
Menu memiliki beberapa pilihan dan user akan diminta untuk memilih suatu pilihan. Kita
dapat menggunakan switch untuk menginstruksikan komputer untuk melakukan tugas
tertentu sesuai dengan menu yang dipilih oleh user.
Jika Anda ingat pernyataan main() pada program Java, pernyataan main memiliki
parameter String[] args, di mana args merupakan argumen yang diberikan pada saat
program dijalankan melalui konsol. Biasanya argumen yang diberikan berupa opsi
bagaimana program harus dilaksanakan. Di sini pernyataan switch juga berguna untuk
memilih bagaimana program akan berjalan.
Jika Anda terbiasa atau pernah bergaul dengan Linux, maka tidak asing untuk menemukan
perintah pada Linux seperti "ls -l" atau "tar xfz blabla". Di sini ls atau tar adalah nama
program dan "-l" atau "xfz blabla" adalah argumen yang diberikan pada saat program
dijalankan.
Pernyataan kosong
Pernyataan kosong sebenarnya merupakan blok kosong, atau sama dengan {} tanpa
perintah apa-apa di dalamnya. Dalam Java, pernyataan kosong juga berarti tanda ; sehingga
apabila Anda menulis kode seperti
if (x > 0);
perintah1
maka perintah1 akan tetap dilaksanakan walaupun x <= 0.
Perintah kosong sering merupakan sumber kesalahan dan salah satu fitur yang sulit untuk
dicari kesalahannya pada saat debugging. Misalnya perintah berikut
for (i = 1; i <= 10; i++);
System.out.println("hallo");
Anda berharap untuk mencetak kata hallo 10x di layar, pada kenyataannya hanya 1 hallo
yang dicetak. Kenapa? Karena tanda ";" setelah for menyatakan bahwa program tidak
melakukan apa-apa di dalam perulangan. Perintah System.out.println("hallo") tidak
berada di dalam perulangan for, sehingga perintah ini hanya dijalankan 1x saja.
Pernyataan-pernyataan lain
Hingga saat ini kita sudah membahas hampir semua pernyataan yang Java sediakan. Ada
beberapa yang akan kita bahas kemudian, tetapi pernyataan-pernyataan ini merupakan
pernyataan lanjutan Java, seperti return yang digunakan untuk membuat subrutin sendiri,
atau try...catch dan throw untuk mengontrol alur kesalahan apabila ditemui di tengah
program (atau dengan kata lain eksepsi atau pengecualian), dan synchronized untuk
mengatur kontrol untuk multi-threading.
Beberapa kata kunci lain akan juga dibahas pada subjek tentang pemrograman berorientasi
objek yang akan kita bahas kemudian.
import java.io.*;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int suitKomputer = 0;
int suitUser = 0;
Listing program di atas dapat Anda unduh melalui zip file atau pada gudang SVN di
alamat http://belajarjava.googlecode.com/svn/trunk/MainSuit
Bab V - Subrutin
Pemrograman kompleks
Salah satu cara yang digunakan untuk memecah permasalahan kompleks menjadi
permasalahan yang lebih kecil adalah dengan subrutin. Subrutin terdiri dari sekelompok
perintah yang melakukan suatu tugas tertentu. Subrutin memiliki nama, sehingga bisa
dipanggil suatu saat di bagian lain program. Ketika komputer mengeksekusi program,
setiap kali nama subrutin tersebut dipanggil, program akan melaksanakan semua perintah
di dalam subrutin tersebut.
Subrutin boleh digunakan berulang-ulang, dari mana pun di dalam program, bahkan dari
dalam subrutin lain. Dengan cara ini kita bisa membuat subrutin sederhana yang digunakan
berulang-ulang di seluruh program. Dengan cara ini, program besar dapat dipecah menjadi
bagian-bagian kecil. yang masing-masing melakukan perintah sederhana.
Seperti dijelaskan sebelumnya, subrutin bisa berbentuk statik atau non-statik. Dalam
bagian ini hanya subrutin statik yang akan dijelaskan lebih lanjut. Subrutin non-statik
adalah inti dari pemrograman berorientasi objek, dan akan dijelaskan lebih lanjut pada bab
berikutnya.
Kotak Hitam
Subrutin terdiri dari beberapa instruksi yang melakukan suatu tugas tertentu, dikumpulkan
dalam satu himpunan, dan diberi nama. Kumpulan instruksi ini bisa berupa suatu "tugas"
yang cukup kompleks yang disatukan sebagai konsep. Dengan cara ini, kita tidak perlu lagi
memikirkan bagaimana komputer melakukan tugas hingga sedetail mungkin. Kita hanya
perlu memanggil nama subrutin tersebut untuk menjalankan suatu "tugas".
Subrutin sering juga disebut dengan kotak hitam (atau black box) karena kita tidak perlu
(atau tidak mau tahu) secara detail apa yang subrutin tersebut lakukan. Kita hanya ingin
tahu hubungan kotak hitam tersebut dengan dunia luar. Hubungan ini disebut antar muka
(interface). Di kotak itu mungkin ada tombol yang bisa kita tekan, ada keyboard yang bisa
kita ketik, atau mungkin ada jendela untuk memberi dan mengambil informasi dari
dalamnya.
Karena kita bermaksud untuk menyembunyikan kompleksitas (dalam hal ini program yang
akan kita buat), ada beberapa prinsip penting dari suatu kotak hitam :
Antar muka harus sederhana, jelas, tak berbelit-belit, dan mudah dimengerti.
Apa contoh kotak hitam di sekitar kita? Sebetulnya banyak, misalnya TV, handphone,
DVD player, MP3 player, kulkas, AC, dan sebagainya. Kita bisa menyalakan TV,
mengganti channel, menaikkan dan menurunkan volume dengan mudah tanpa harus
mengetahui bagaimana TV bekerja. Sama halnya dengan AC, Anda cukup menekan
tombol, menaikkan dan menurunkan temperatur tanpa harus mengerti bagaimana AC
bekerja.
Sekarang mari kita lihat bagian dalam dari kotak hitam. Bagaimana AC atau TV bekerja
disebut dengan implementasi. Aturan kedua dari suatu kotak hitam adalah :
Untuk menggunakan kotak hitam, kita tidak perlu mengetahui tentang implementasi.
Yang kita butuh adalah antar muka.
Sebetulnya kita bisa mengganti implementasi selama apa yang dihasilkan dan apa yang
diminta tetap sama. Atau dengan kata lain, perilakunya tidak berubah. Misalnya dulu TV
menggunakan tabung hampa, tetapi sekarang menggunakan transistor. Tetapi perubahan
implementasi (dari tabung hampa ke transistor) tidak mengubah bagaimana TV
dioperasikan dan apa yang kita lihat di TV. Seperti halnya dengan program, kita bisa
mengubah implementasi suatu subrutin, misalnya mengoptimasi subrutin tersebut agar
lebih cepat, tanpa mengubah perilaku program secara keseluruhan, dan tanpa mengubah
perilaku fungsi lain yang memanggil subrutin tersebut.
Tentunya untuk membuat kotak hitam, kita perlu mengetahui detail tentang
implementasinya. Kotak hitam digunakan untuk membantu baik si pembuat kotak hitam
maupun penggunanya. Pada akhirnya, kotak hitam tersebut akan digunakan dalam
bermacam-macam situasi. Orang yang membuat kotak hitam ini tidak perlu mengerti
bagaimana kotaknya akan digunakan. Pembuatnya hanya menjamin bahwa kotak yang
dibuat harus bekerja sesuai dengan yang diharapkan. Aturan ketiga dari kotak hitam :
Pembuat (implementor) kotak hitam tidak perlu tahu bagaimana kotak hitam itu
akan digunakan.
Dengan kata lain, kotak hitam membagi dunia menjadi 2 bagian, yaitu bagian luar, untuk
apa ia digunakan, dan bagian dalam, yaitu detail bagaimana ia bekerja.
Antar muka kotak hitam tidak melulu harus berupa koneksi fisik antara kotak hitam
dengan dunia luar. Antar muka bisa juga berupa spesifikasi yang menjelaskan apa yang
dilakukan kotak hitam tersebut, dan perilakunya terhadap input yang diterimanya. Tidak
cukup untuk menyebutkan bahwa TV membutuhkan colokan kabel, tetapi harus disebutkan
bahwa colokan kabel digunakan untuk menyambung aliran listrik ke TV supaya TV bisa
dinyalakan.
Dalam bahasa pemrograman, antar muka dari suatu subrutin harus terdiri dari komponen
sintaks dan semantik. Bagian sintaks dari subrutin mengandung penjelasan tentang apa
yang harus diketik untuk memanggil subrutin tersebut. Dan bagian semantik menjelaskan
bagaimana secara khusus apa yang akan dikerjakan oleh subrutin tersebut. Untuk menulis
program yang benar, kita harus mengetahui spesifikasi sintaks dari subrutin tersebut.
Untuk mengerti dan bisa menggunakan subrutin tersebut seefektif mungkin, kita harus
mengetahui spesifikasi semantiknya. Kedua bagian dari subrutin tersebut bisa disebut
sebagai kontrak subrutin.
Kontrak subrutin bisa dijelaskan seperti "Ini yang harus Anda lakukan untuk menggunakan
saya, dan ini yang akan saya lakukan untuk Anda". Ketika kita menulis subrutin, komentar
yang kita tulis untuk menjelaskan subrutin itu harus memuat kontrak subrutin tersebut.
Kadang kala kontrak ini seringkali tidak dituliskan dengan benar, sehingga programmer
yang menggunakannya harus menerka apa yang akan dilakukan. Tentu saja ini tidak
efisien dan menghabiskan banyak waktu apabila kita harus menerka semua subrutin yang
terdapat dalam bahasa pemrograman.
Subrutin Statik dan Variabel Statik
Setiap subrutin yang dideklarasikan dalam Java harus dideklarasikan di dalam suatu Kelas
(Class). Hal ini mungkin membuat Java sedikit tidak normal, karena dalam bahasa
pemrograman lain, subrutin bisa diletakkan di mana saja termasuk di luar kelas. Salah satu
dari fungsi kelas adalah menggabungkan subrutin dan variabel bersama. Dan ini tidak
mudah apabila subrutin dan variabel berada di luar kelas, terutama apabila beberapa paket
harus digabungkan menjadi satu seperti dalam program kompleks. Akan terdapat banyak
kebingungan yang mungkin diakibatkan dari nama subrutin atau nama variabel yang sama.
Subrutin yang dideklarasikan di dalam kelas disebut dengan metode (method). Di bab
kemudian kita akan menggunakan istilah metode, tetapi bab ini kita akan menggunakan
subrutin dalam artiannya sebagai subrutin statik. Metode akan digunakan untuk subrutin
non-statik yang lebih merupakan sifat dari objek, dan bukan bagian dari kelas itu sendiri.
Definisi subrutin dalam bahasa pemrograman Java dapat dituliskan dalam bentuk
sifat tipe_keluaran nama_subrutin ( daftar parameter ) {
perintah
}
Kita sudah pernah mendefinisikan suatu subrutin, yaitu subrutin main(). Paling tidak kita
sudah kenal bagaimana subrutin didefinisikan.
perintah yang terdapat di antara { dan } disebut juga badan subrutin. Perintah ini
merupakan badan atau implementasi suatu subrutin, seperti yang dibahas sebelum pada
penjelasan tentang kotak hitam. Perintah ini merupakan instruksi yang akan dieksekusi
oleh komputer pada saat subrutin ini dipanggil.
sifat adalah sifat dari subrutin itu sendiri. Beberapa sifat yang pernah kita lihat adalah
static dan public. Ada lebih dari selusin sifat yang bisa diberikan kepada subrutin.
Jika kita akan membuat fungsi, yaitu subrutin yang menghitung suatu nilai kemudian
mengembalikan hasilnya, maka tipe_keluaran adalah tipe data dari keluaran yang
dihasilkan oleh fungsi tersebut. Kita akan membahas lebih lanjut tentang keluaran pada
bagian berikutnya. Jika subrutin kita bukan fungsi dan tidak menghasilkan nilai apa-apa,
kita gunakan tipe data spesial yang dinamakan void untuk menunjukkan bahwa tidak ada
nilai keluaran yang akan dikembalikan oleh subrutin tersebut.
Akhirnya kita sampai pada daftar parameter. Parameter adalah bagian dari antar muka
suatu subrutin. Parameter adalah informasi yang diberikan kepada suatu subrutin dari dunia
luar, untuk digunakan dalam eksekusi subrutin tersebut. Kasus sederhana misalnya televisi
memiliki subrutin gantiChannel(). Pertanyaan yang akan muncul adalah ganti channel ke
mana? Dalam hal ini parameter dapat digunakan, misalnya channel berbentuk bilangan
bulat (int) dan deklarasi subrutin gantiChannel dapat berbentuk seperti
public void gantiChannel(int channel) {
...
}
Pernyataan tersebut berarti subrutin gantiChannel() memiliki parameter channel yang
bertipe int. Akan tetapi channel belum memiliki nilai. Nilainya akan diberikan pada saat
subrutin ini dipanggil, misalnya dengan
gantiChannel(17);
Daftar parameter dari suatu subrutin bisa juga kosong, atau bisa berisi lebih dari satu
parameter dalam bentuk
tipe_data nama_parameter
Jika ada lebih dari satu parameter, maka parameter-perameter tersebut dihubungkan
dengan koma. Catatan bahwa masing-masing parameter harus terdiri dari satu tipe data dan
satu nama, misalnya double x, double y dan bukan double x, y.
Parameter akan dijelaskan lebih lanjut pada bagian berikutnya.
Berikut ini adalah beberapa contoh deklarasi subrutin yang umum dilakukan :
public static void mainGame() {
// "public" dan "static" ada sifat; "void" adalah tipe_keluaran
// "mainGame" adalah nama subrutin
// daftar parameternya kosong
... // perintah untuk memainkan game ditulis di bagian ini
}
int ambilNdata(int N) {
// tidak ada sifat, "int" adalah tipe_keluaran
// "ambilNdata" adalah nama subrutin
// dan parameternya adalah N yang memiliki tipe data int
... // perintah untuk mengambil N data ditulis di bagian ini
}
Kita akan membuat subrutin statik yang dinamakan ambilDouble() yang tugasnya memberi
pertanyaan kepada user, mengambil input dari user dan mengubah input dari user menjadi
bilangan real (tipe data double).
Kita bisa definisikan subrutin ini dengan
public static double ambilDouble(String pertanyaan) { ... }
"public" dan "static" adalah sifat subrutin ini, "double" merupakan tipe data keluarannya
karena kita ingin subrutin ini mengembalikan input bertipe double sehingga siap untuk
digunakan. "ambilDouble" adalah nama subrutin ini, dan "String pertanyaan" adalah
parameternya yaitu berupa pertanyaan yang diberikan kepada user pada saat data akan
diambil.
Apa yang harus dilakukan sekarang? Kita ambil perintah untuk mengambil data dari
RataRata yaitu :
double bilangan = 0;
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String strbilangan = null;
import java.io.*;
System.out.print(pertanyaan);
try {
strbilangan = br.readLine();
} catch (IOException ioe) {
System.out.println("Kesalahan IO, program berhenti");
System.exit(1);
}
bilangan = Double.parseDouble(strbilangan);
return bilangan;
}
}
Kemudian kita buat kelas baru yang dinamakan RataRata2, seperti pada gambar berikut :
Dengan menggunakan subrutin yang baru kita buat, kita modifikasi program RataRata
menjadi RataRata2 sebagai berikut :
package ratarata2;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
double jumlah = 0;
double bilangan = 0;
int n = 0;
while (bilangan != 0) {
jumlah += bilangan; // sama dengan : jumlah = jumlah +
bilangan
n++; // sama dengan : n = n+1
// hitung rata-rata
double ratarata = jumlah/n;
Program di atas dapat diunduh dalam bentuk zip file atau melalui gudang SVN di alamat :
http://belajarjava.googlecode.com/svn/trunk/RataRata2
switch (delta) {
case 0:
seri++;
System.out.println("Hasilnya : Seri");
break;
case 1:
case -2:
userMenang++;
System.out.println("Hasilnya : Anda menang");
break;
case -1:
case 2:
komputerMenang++;
System.out.println("Hasilnya : Anda kalah");
break;
}
Kemudian, kita tambahkan lagi variabel statik yang fungsinya digunakan untuk mendata
berapa kali komputer menang, berapa kali Anda menang dan berapa kali seri.
public static int userMenang = 0;
public static int komputerMenang = 0;
public static int seri = 0;
Kemudian kita akan pindahkan sebagian kode pada subrutin main sehingga dapat dipanggil
berkali-kali tanpa harus menulis seluruh kode lagi. Kita namakan subrutin ini mainGame().
Kodenya sebagai berikut :
public static int userMenang = 0;
public static int komputerMenang = 0;
public static int seri = 0;
int suitKomputer = 0;
int suitUser = 0;
switch (delta) {
case 0:
seri++;
System.out.println("Hasilnya : Seri");
break;
case 1:
case -2:
userMenang++;
System.out.println("Hasilnya : Anda menang");
break;
case -1:
case 2:
komputerMenang++;
System.out.println("Hasilnya : Anda kalah");
break;
}
}
Sekarang kita ubah subrutin main()-nya, sehingga komputer akan menanyakan Anda untuk
main lagi atau tidak. Jika jawabannya ya, maka permainan akan diteruskan, dan statistik
menang-kalah-seri akan ditampilkan. Jika tidak, hanya tampilkan statistiknya saja,
kemudian program akan keluar.
public static void main(String[] args) {
// TODO Auto-generated method stub
// cetak aturan main dan input dari user
System.out.println("Permainan suit");
System.out.println("==============");
System.out.println("Masukkan salah satu dari 3 kemungkinan :");
System.out.println("J untuk Jempol");
System.out.println("T untuk Telunjuk");
System.out.println("K untuk Kelingking");
System.out.println(""); // baris kosong
while (true) {
mainGame();
// tampilkan statistik
System.out.println("Statistik :");
System.out.println("Komputer = " + komputerMenang + " Anda
= " + userMenang + " Seri = " + seri);
System.out.println(""); // baris kosong
Parameter
Jika subrutin adalah kotak hitam, maka parameter merupakan alat atau mekanisme untuk
memberikan informasi dari dunia luar ke dalam kotak. Parameter merupakan bagian dari
antar muka dengan suatu subrutin. Dengan menggunakan parameter, kita dapat mengatur
perilaku suatu subrutin sesuai dengan input yang diberikan.
Sebagai analogi, kita ambil contoh AC -- yaitu alat yang mengatur suhu ruangan pada
temperatur konstan. AC memiliki parameter, yaitu tombol remote yang digunakan untuk
memasukkan suhu yang diinginkan. AC akan selalu melakukan tugas yang sama, yaitu
mengatur suhu ruangan. Akan tetapi, tugas persis yang dilakukan tergantung suhu yang
diterima dari remote control.
Kita ambil contoh pada kelas KonsolInput pada bagian terdahulu.
public static double ambilDouble(String pertanyaan) {
String strbilangan = null;
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
double bilangan;
System.out.print(pertanyaan);
try {
strbilangan = br.readLine();
} catch (IOException ioe) {
System.out.println("Kesalahan IO, program berhenti");
System.exit(1);
}
bilangan = Double.parseDouble(strbilangan);
return bilangan;
}
Di sini subrutin ambilDouble memiliki parameter tunggal yang bertipe String dan bernama
pertanyaan. Ketika subrutin ini dipanggil, suatu nilai harus ikut disertakan, dan nilai ini
dimasukkan dalam parameter "pertanyaan". Misalnya, subrutin dapat dipanggil dengan a =
ambilDouble("hallo");. Ketika komputer menjalankan perintah ini, komputer akan
memasukkan "hallo" ke dalam parameter "pertanyaan", kemudian subrutin ambilDouble
menjalankan semua perintah di dalamnya.
Jika parameter yang diberikan berupa variabel, misalnya variabel str berisi "kabar",
kemudian subrutin ambilDouble panggil dengan perintah ambilDouble(str), maka isi
dari str akan dikopi ke dalam parameter "pertanyaan" terlebih dahulu sebelum seluruh
perintah dalam subrutin ambilDouble dilaksanakan.
Perlu dicatat bahwa istilah "parameter" digunakan dalam dua konsep berbeda tetapi
berkaitan. Yang pertama adalah "parameter" digunakan untuk mendefinisikan suatu
subrutin. Parameter yang digunakan pada definisi suatu subrutin disebut parameter
formal. "Parameter" yang digunakan untuk memanggil suatu subrutin disebut parameter
aktual. Ketika suatu subrutin dipanggil, parameter aktual akan dievaluasi, dan hasilnya
akan dimasukkan ke dalam parameter formal.
Parameter formal berbentuk seperti pengenal atau nama, dan bersifat seperti halnya
variabel dan memiliki tipe -- misalnya int, double, atau String. Parameter aktual bersifat
seperti nilai atau angka, jadi bisa berbentuk ekspresi atau perintah apapun yang
menghasilkan nilai atau angka. Sewaktu kita memanggil subrutin, kita harus memberi
parameter aktual untuk setiap parameter formal yang didefinisikan.
Misalnya, lihat contoh berikut.
static void lakukanSesuatu(int N, double d, boolean b) {
... // perintah lainnya di sini
}
Subrutin ini bisa saja dipanggil dengan instruksi berikut.
lakukanSesuatu(1, Math.random() * 3, x == 3);
Ketika komputer mengevaluasi subrutin ini, pada dasarnya sama dengan menglakukan
perintah-perintah berikut.
{
int N;
double d;
boolean b;
N = 1;
d = Math.random() * 3;
b = (x == 3);
... // perintah lainnya di sini
}
Untuk memanggil subrutin, yang kita perlukan adalah nama, berapa banyak parameter
formal yang dimilikinya, dan tipe dari parameter formal tersebut. Infomasi ini disebut juga
tanda subrutin. Tanda subrutin lakukanSesuatu dapat ditulis sebagai
lakukanSesuatu(int,double,boolean). Perhatikan bahwa tanda subrutin tidak memiliki nama
parameter; sebetulnya jika kita hanya ingin menggunakan subrutin tersebut, nama
parameter tidaklah penting, karena nama bukan merupakan bagian dari antar muka
subrutin.
Java agak sedikit berbeda karena memungkinkan dua atau lebih subrutin yang berbeda
dalam kelas yang sama memiliki tanda subrutin yang berbeda. (Mirip seperti bahasa C++)
Subrutin yang seperti ini disebut subrutin yang dibebanlebihkan (overloaded). Misalnya,
void taruh(String s) { ... }
void taruh(int i) { ... }
void taruh(double d) { ... }
void taruh(boolean b) { ... }
Kita sudah menggunakan subrutin yang dibebanlebihkan ini misalnya pada
System.out.println(). Jika kita coba ketik System.out.println pada Eclipse, Eclipse akan
membantu kita untuk melihat parameter apa saja yang dibutuhkan, termasuk juga dapat
menunjukkan apakah suatu subrutin dibebanlebihkan.
Catatan : Overload tidak berlaku pada tipe keluaran subrutin. Dua atau lebih subrutin
dengan nama sama dalam suatu kelas tetapi memiliki tipe keluaran yang berbeda tidak
diperbolehkan dalam Java. Misalnya, kalau kita coba definisikan 2 fungsi seperti berikut,
maka Java akan memberi pernyataan kesalahan sintaks.
int ambil(String s) { ... }
boolean ambil(String s) { ... }
double ambil(String s) { ... }
Terakhir, hingga saat ini kita sudah mengenal 3 jenis variabel: variabel lokal, yang
didefinisikan di dalam suatu blok, variabel statik, yang didefinisikan di dalam suatu kelas,
dan parameter formal, yang didefinisikan pada definisi subrutin di dalam suatu kelas.
Variabel lokal tidak memiliki hubungan dengan dunia luar sama sekali. Parameter
digunakan untuk berhubungan dengan dunia luar sebagai alat untuk menerima nilai atau
angka dari dunia luar. Perubahan nilai pada variabel lokal dan parameter tidak
mempengaruhi variabel itu sendiri karena sifatnya terisolasi.
Hal yang sama sekali berbeda akan terjadi apabila suatu subrutin menggunakan variabel
yang didefinisikan di luar subrutin itu. Variabel tersebut hidup secara terpisah dari
subrutin, dan bisa diakses oleh apapun di luar subrutin itu. Variabel ini disebut variabel
global. Variabel ini terlihat oleh semua subrutin dalam kelas tersebut. Perubahan yang
terjadi di sini dapat mempengaruhi cara kerja subrutin lain yang menggunakan variabel
tersebut.
Hal ini kita lihat pada contoh sebelumnya tentang subrutin dan variabel statik untuk
menghitung statistik komputerMenang, userMenang, dan seri.
Tidak ada yang salah dalam menggunakan variabel global, akan tetapi kita harus terus
ingat bahwa variabel global itu harus sebagai bagian dari antar muka subrutin kita dengan
keseluruhan dunia luar. Cara ini sebetulnya jalan belakang yang tidak terdokumentasi
seperti pada definisi subrutin, yang apabila aturan-aturannya tidak dipenuhi secara disiplin
mungkin akan menyebabkan bug atau kesalahan lain di luar kontrol kita.
Paling tidak ada satu alasan untuk menggunakan variabel global, yaitu apabila kelas secara
keseluruhan dianggap sebagai kotak hitam, akan lebih masuk akal apabila subrutin bisa
mengintip sedikit keluar dan saling menukar informasi jika cara ini jauh lebih mudah
diimplementasikan dan dilihat dari dunia luar.
Tipe Keluaran
Suatu subrutin yang mengembalikan nilai disebut fungsi. Suatu fungsi hanya dapat
mengembalikan nilai dengan tipe tertentu, yang disebut tipe keluaran. Fungsi biasanya
dipanggil di tempat suatu nilai atau angka ditempatkan, misalnya disebelah kanan tanda =,
pada parameter sewaktu subrutin dipanggil, atau di tengah-tengah ekspresi yang panjang.
Fungsi dengan tipe keluaran boolean juga bisa ditempatkan sebagai kondisi pada
pernyataan if atau while.
Sebetulnya boleh-boleh saja memanggil suatu fungsi sendirian tanpa diletakkan di sebelah
kanan tanda =, misalnya ambilDouble("hallo");. Dalam hal ini keluarannya akan
diabaikan oleh komputer. Tergantung program yang akan kita buat, kadang-kadang
program kita memang sengaja membuang nilai yang dikembalikan oleh fungsi karena satu
dan lain hal.
Untuk membuat fungsi yang mengeluarkan suatu nilai kembali kepada pemanggilnya, kita
harus memberikan pernyataan return, dengan bentuk
return ekspresi;
Pernyataan return ini hanya boleh ditulis di dalam sebuah subrutin (termasuk subrutin
main()), dan ekspresi harus menghasilkan nilai yang memiliki tipe yang sama dengan tipe
keluaran yang didefinisikan pada deklarasi subrutin tersebut.
Ketika komputer menjalankan pernyataan return, komputer akan menghitung "ekspresi",
menghentikan eksekusi subrutin tersebut saat itu juga, dan kembali pada si pemanggil
dengan membawa nilai hasil perhitungan "ekspresi".
Misalnya, kita akan menghitung luas suatu lingkaran. Kita buat fungsi luasLingkaran
dengan parameter jarijari dan keluarannya bertipe double, sebagai berikut :
static double luasLingkaran(double jarijari) {
return 3.14 * jarijari * jarijari;
}
Anggap komputer sedang melakukan perintah "ruangkosong = 20*20 -
return keluaran;
}
Fungsi di atas berfungsi untuk mengulang karakter c sebanyak n kali dan mengembalikan
hasilnya. Misalnya jika fungsi di atas kita panggil dengan "ulang =
mengimplementasikan fungsi dasar untuk applet. Paket yang paling dasar disebut
java.lang. Di dalam paket ini terdapat kelas-kelas fundamental misalnya String dan
Math.
Mungkin akan lebih jelas jika kita lihat ilustrasi berikut tentang tingkatan paket java, sub
paket, kelas di dalam sub paket, dan subrutin di dalam kelas.
Misalkan kita akan menggunakan kelas java.awt.Color dalam program. Salah satu
caranya adalah dengan menggunakan nama lengkap dari kelas tersebut, misalnya
java.awt.Color warnaKotak;
untuk mendekalarasi variabel bernama warnaKotak yang tipenya adalah java.awt.Color.
Tentu saja akan terlalu cape jika kita harus menulis nama kelas dengan lengkap setiap kali
akan digunakan. Java mengijinkan kita untuk menggunakan langsung nama kelasnya jika
kita berikan perintah import, seperti
import java.awt.Color;
di awal program, dan kemudian di bagian lain, kita bisa menyingkat java.awt.Color
hanya dengan nama kelasnya saja, sehingga menjadi
Color warnaKotak;
(Satu-satunya keuntungan yang diberikan perintah import adalah untuk menyingkat
penulisan nama kelas, bukan untuk mengimport keseluruhan kelas ke dalam program kita.
Seandainya perintah import tidak kita berikan, dan kita panggil dengan nama lengkapnya,
Java akan tetap bisa mengakses kelas tersebut.)
Ada lagi jalan pendek untuk mengimport seluruh kelas pada suatu paket. Kita bisa import
seluruh kelas di dalam paket java.awt dengan
import java.awt.*;
Ingat pada kode kita sebelumnya yang berbentuk seperti ini?
import java.io.*;
Ketika program yang akan kita buat semakin besar, maka ada kemungkinan kita harus
mengimport banyak paket. Satu hal penting yang perlu diingat adalah ada kemungkinan
beberapa paket memiliki nama kelas yang sama. Misalnya java.awt dan java.util
memiliki kelas yang bernama List. Jika kita import kedua kelas dengan perintah
java.awt.* dan java.util.*, maka nama List menjadi rancu.
Jika kita mencoba membuat variabel dengan tipe data List, java akan menampilkan pesan
kesalahan tentang adanya nama kelas yang membingungkan. Solusinya? Gunakan nama
lengkap kelasnya, yaitu java.awt.List atau java.util.List.
Karena paket java.lang.* adalah paket yang sangat penting, setiap kelas dalam
java.lang akan diimport otomatis ke dalam semua program. Seakan-akan semua program
dimulai dengan perintah "import java.lang.*;". Artinya, kita bisa memanggil kelas
seperti String saja, bukan java.lang.String, atau Math.sqrt() bukan
java.lang.Math.sqrt().
Programmer juga bisa membuat paket baru. Misalnya kita akan membuat kelas yang akan
diletakkan di paket yang bernama alatbantu. Maka pada awal program kita harus
didefinisikan dengan perintah "package alatbantu;" Program lain yang menggunakan
paket ini bisa menambah "import alatbantu.*;" untuk mengabmbil semua kelas dalam
paket alatbantu.
Masalahnya hal ini agak sedikit lebih rumit. Ingat bahwa ketika suatu program
menggunakan kelas, maka kelas tersebut harus "tersedia" ketika program tersebut
dikompilasi dan ketika program terus dieksekusi. Tentunya lebih pas tergantung dari
lingkungan Java yang kita lakukan. biasanya paket yang bernama alatbantu harus berada di
dalam direktori alatbantu, dan direktori tersebut harus diletakkan pada direktori yang sama
dengan program yang akan menggunakan kelas itu.
Pada program yang menggunakan banyak kelas, maka sangat masuk akal apabila kita
membagi dan mengorganisir kelas tersebut dalam satu atau beberapa paket. Dan juga
masuk akal untuk membuat paket baru sebagai toolbox atau dengan kata lain membuat API
baru yang belum dikover oleh API Java. (Seringkali orang yang membuat API lebih
disanjung akan kehebatannya dibandingkan dengan orang yang hanya menggunakan API).
Dalam program Eclipse kita, kita sudah menggunakan paket dari awal. Bab ini
menjelaskan lebih detail tentang paket. Sebetulnya tidak ada salahnya kita mengorganisir
setiap program yang kita buat sejak awal. Pada saat program kita besar, maka perubahan
yang diperlukan untuk mengorganisir akan lebih signifikan.
Tentang Deklarasi
Nama adalah hal yang paling dasar dalam pemrograman. Banyak sekali tetek bengek detail
di dalam deklarasi dan menggunakan nama. Dalam bagian ini akan kita singgung beberapa
hal penting tentang deklarasi dan penggunaan variabel pada Java.
Menggabungkan Inisialisasi dan Deklarasi
Ketika perintah deklarasi variabel dilaksanakan, komputer akan menyiapkan memori untuk
digunakan oleh variabel ini. Memori ini harus diinisialisasi, yaitu diberi suatu nilai awal
sebelum bisa digunakan dalam perintah berikutnya. Pada variabel lokal, pernyataan
deklarasi sering diikuti oleh perintah pemberi nilai awal. Misalnya,
int i; // Deklarasi variabel yang bernama i
i = 0; // Beri nilai awal
Kita bisa juga melakukan deklarasi sekaligus inisialisasi variabel tersebut. Kedua
pernyataan di atas bisa kita gabung menjadi
int i = 0; // Deklarasi variabel i sekaligus memberi nilai awal 0
Komputer akan tetap melakukan perintah di atas dalam dua langkah: deklarasi variabel,
kemudian mengisi nilai awal 0. Nilai awal tidak harus berupa suatu bilangan, tetapi bisa
juga berupa ekspresi yang menghasilkan suatu nilai. Dan juga kita boleh melakukan
inisialisasi beberapa variabel sekaligus dalam 1 baris. Misalnya,
int x = 0, y = 1;
int awal, akhir = 'A'; // Ok, tapi hanya akhir yang akan
diinisialisasi
int N = 3, M = N+2; // Ok, karena N sudah diberi nilai awal sebelum
digunakan oleh M
Fitur ini biasa ditemui pada perulangan for, karena pernyataan 1 baris ini bisa dimasukkan
sebagai variabel kontrol di awal pernyataan for. Karena variabel kontrol biasanya tidak
berhubungan dengan bagian program di luar perulangan, maka akan lebih praktis jika
deklarasi variabel kontrol itu dilakukan di awal perulangan. Misalnya
for ( int i = 0; i < 10; i++ ) {
System.out.println(i);
}
Perlu diingat bahwa ekspresi di atas merupakan singkatan dari blok berikut. Saya sengaja
tambahkan { dan } di awal dan akhir untuk mempertegas bahwa i hanya bersifat lokal di
dalam perulangan yang tidak bisa diakses lagi setelah perulangan selesai.
{
int i;
for ( i = 0; i < 10; i++ ) {
System.out.println(i);
}
}
Variabel anggota juga bisa diinisialisasi di mana dia dideklarasikan. Misalnya,
public class Bank {
static double sukuBunga = 0.1; // sama dengan 10%
static long maxPenarikan = 2000000;
.
. // Perintah dan variabel lain
.
}
Variabel anggota statik akan dibuat pada saat kelas mulai dimasukkan memori oleh
interpreter Java, inisialisinya pun dilaksanakan pada saat itu. Pada variabel anggota,
deklarasi dan inisialisasi ini bukan hanya sekedar singkatan dari 2 perintah seperti pada
variabel lokal. Pernyataan deklarasi adalah pernyataan yang bisa dibuat di luar subrutin,
sedangkan inisialisasi tidak bisa dilakukan di luar subrutin. Contoh di bawah tidak
diperbolehkan dalam Java.
public class Bank {
static double sukuBunga;
sukuBunga = 0.1; // ILEGAL, perintah ini harus ada di dalam
subrutin
}
Karenanya, deklarasi variabel anggota biasanya juga dirangkai dengan inisialisasi nilai
awalnya. Jika tidak ada nilai awal yang diberikan, maka nilai awal bawaan akan
digunakan. Misalnya "static int i;" setara dengan "static int i = 0;".
Konstanta dan Sifat "final"
Kadang-kadang dalam kondisi tertentu, kita ingin nilai suatu variabel tidak boleh berubah
setelah diberi nilai awal. Misalnya, pi diinisialisasi dengan nilai 3.14159, maka nilai ini
akan bisa diubah oleh bagian lain dalam program. Di sini mungkin sang programmer ingin
mendefinisikan suatu konstanta yang diberi nama "pi" sebagai pengganti dari bilangan
3.14159.
Dengan cara ini, program akan lebih mudah dimengerti apabila program tersebut ditulis
dalam "keliling = pi*diamater;" daripada "pokok = 3.14159*diameter;".
Dalam java, sifat "final" bisa digunakan pada deklarasi variabel untuk membuat nilai suatu
variabel tidak bisa diubah setelah diinisialisasi. Misalnya
final static double pi = 3.14159;
Apabila kita mencoba mengganti isi variabel tersebut, misalnya di tengah program dengan
perintah "pi = 20;" komputer akan menampilkan pesan kesalahan sintaks pada saat
kompilasi.
Sifat "final" boleh diberikan pada variabel lokal dan bahkan pada parameter formal, akan
tetapi paling berguna apabila diberikan pada variabel anggota. Variabel anggota statik yang
dideklarasikan dengan sifat "final" juga disebut sebagai konstanta yang diberi nama.
Program akan jauh lebih mudah dibaca dengan menggunakan konstanta bernama ini jika
diberikan pada sesuatu angka penting yang digunakan dalam program. Gaya penulisan
yang dianjurkan untuk konstanta bernama ini adalah dengan menggunakan huruf besar
dengan baris bawah (jika diperlukan). Misalnya
final static double PI = 3.14159;
Gaya penulisan seperti ini juga digunakan oleh kelas standar Java, yang di dalamnya
terdapat banyak sekali konstanta bernama. Misalnya, konstanta PI sudah didefinisikan Java
dalam kelas Math, yaitu Math.PI, sehingga kita tidak perlu mendefinisikannya sendiri.
Contoh lain adalah memberikan nama untuk gaya huruf, seperti Font.PLAIN, Font.BOLD
dan Font.ITALIC. Konstanta ini digunakan untuk menandai gaya huruf untuk digunakan
oleh subrutin lain dalam kelas Font.
Salah satu alasan utama untuk menggunakan konstanta bernama adalah supaya kita bisa
mengubah isinya dengan cepat apabila di kemudian hari kita ingin memiliki nilai konstanta
yang berbeda. Apabila nilai ini diubah, maka kita harus mengkompilasi ulang program
kita, karena nilai ini tidak bisa diubah ketika program dieksekusi.
Misalnya kita definisikan sukuBunga pada contoh di atas sebagai konstanta bernama, yaitu
static final double SUKU_BUNGA = 0.1;
Suatu saat misalnya 2 tahun kemudian, bank tersebut ingin mengubah suku bunganya
menjadi 5% atau 0.05. Kita bisa mengubah nilainya hanya di satu tempat yaitu di mana
deklarasi SUKU_BUNGA berada. Kita tidak perlu mencari semua nilai 0.1 di dalam
program untuk menggantinya satu per satu. Selain repot, mungkin membingungkan karena
0.1 bisa digunakan untuk nilai lainnya, bukan hanya nilai suku bunga.
Aturan Penamaan dan Jangkauan (scope)
Ketika deklarasi variabel dieksekusi, komputer akan menyediakan tempat di memori untuk
variabel tersebut. Nama variabel dapat digunakan oleh kode program untuk mengacu pada
alamat di mana data tersebut disimpan di dalam memori. Bagian di dalam kode sumber
(yaitu program Java yang kita tulis, masih dalam bentuk yang dimengerti manusia sebelum
dikompilasi menjadi bahasa mesin) di mana variabel tersebut valid dan bisa digunakan
disebut jangkauan variabel. Bukan hanya variabel yang memiliki jangkauan, tetapi juga
nama subrutin dan nama parameter formal.
Untuk subrutin statik, jangkauannya lebih mudah dipahami. Jangkauan subrutin statik
adalah kelas di mana ia didefinisikan. Kita bisa juga memanggil subrutin itu dari dalam
kelas dirinya sendiri. Dalam pemrograman tingkat lanjut, teknik ini disebut rekursi
(recursion), yaitu subrutin yang memanggil dirinya sendiri. Teknik ini digunakan dalam
bab yang akan datang yang berbicara detail tentang struktur data dan algoritma.
Untuk variabel anggota suatu kelas, aturannya sama, tetapi dengan sedikit pengecualian.
Kita boleh memiliki variabel lokal atau parameter formal yang namanya sama dengan
variabel anggota static. Jika ini terjadi, maka variabel anggota akan disembunyikan oleh
Java. Dalam contoh berikut :
public class Game {
static int hitung; // variabel anggota
static void mainGame() {
int hitung; // variabel lokal
.
. // Perintah untuk main game
.
}
.
. // Variabel dan subrutin lain
.
akan membuat objek yang merupakan instansi dari kelas Murid. Variabel amir akan
menyimpan referensi ke objek yang baru saja diciptakan.
Sekarang anggap variabel amir merujuk pada objek yang diciptakan dari kelas Murid.
Dalam objek tersebut terdapat variabel nama, nilai1, nilai2, dan nilai3. Variabel
instansi ini bisa dipanggil dengan amir.nama, amir.nilai1, amir.nilai2, dan
amir.nilai3. (Ingat aturan penulisan nama lengkap, akan tetapi karena kelas ini tidak
memiliki anggota statik, dan hanya objek yang diciptakan dari kelas ini memiliki variabel
atau metode ini, maka nama lengkapnya diturunkan dari nama objek yang memilikinya).
Misalnya, program berikut :
System.out.println("Hai, " + amir.nama + " . Nilai Anda adalah : ");
System.out.println(amir.nilai1);
System.out.println(amir.nilai2);
System.out.println(amir.nilai3);
Program di atas akan mencetak nama dan nilai-nilai yang disimpan oleh objek amir.
Begitu juga kita bisa menghitung rata-rata pada suatu objek dengan menggunakan
amir.hitungRataRata(). Sehingga untuk menghitung rata-rata murid tersebut, bisa kita
perintahkan dengan:
System.out.println("Nilai rata-rata Anda adalah : " +
amir.hitungRataRata());
Lebih umum lagi, kita bisa menggunakan amir.nama seperti layaknya variabel bertipe
String, artinya kita bisa menghitung jumlah karakter dengan menggunakan
amir.nama.length().
Kita juga bisa membuat variabel seperti amir untuk tidak menunjuk atau memiliki
referensi ke obek mana pun. Dalam hal ini kita sebut bahwa objek amir berisi referensi
kosong (null reference). Referensi kosong ini ditulis dalam Java dengan ekspresi "null".
Dan kita bisa menyimpan nilai "null" pada variabel amir dengan perintah :
amir = null;
Dan kita juga bisa uji apakah amir berisi referensi kosong dengan perintah
if (amir == null) . . .
Jika suatu variabel berisi referensi kosong, maka tentu saja, kita tidak bisa mengambil
variabel instansi dan menjalankan metode instansi, karena tidak ada objek yang dirujuk
pada variabel tersebut. Misalnya jika variabel amir berisi null, maka kita tidak bisa
mengambil variabel amir.nilai1.
Jika program kita mencoba mengakses referensi kosong secara ilegal, maka di tengah-
tengah jalannya program, program akan menampilkan pesan kesalahan "null pointer
exception".
Mari kita lihat beberapa pernyataan yang bisa digunakan dengan objek :
Murid mrd1, mrd2, mrd3, mrd4; // mendeklarasikan 4 variabel yang
bertipe Murid
mrd1 = new Murid(); // membuat objek baru dari kelas Murid,
kemudian menyimpan referensinya pada variabel mrd1
mrd2 = new Murid(); // membuat objek baru dari kelas Murid,
kemudian menyimpan referensinya pada variabel mrd2
mrd3 = mrd1; // menkopi „referensi" yang disimpan
pada mrd1 ke mrd3
mrd4 = null; // menyimpan referensi kosong ke mrd4
Seperti telah disebutkan sebelumnya bahwa String juga merupakan objek. Sehingga
membandingkan String yang satu dengan String yang lain dengan menggunakan tanda ==
adalah membandingkan apakah alamat memori yang ditunjuk oleh String yang satu sama
dengan alamat memori yang ditunjuk oleh String yang lain. Ingat == pada objek bukan
membandingkan isi objek, tetapi membandingkan alamat memori yang ditunjuk oleh
variabel tersebut. Untuk membandingkan isi String, kita dapat menggunakan
String.equals() di mana parameternya adalah objek String yang akan dibandingkan.
Metode ini akan membandingkan karakter per karakter dari kedua String.
Misalnya, variabel salam berisi String "Selamat". Untuk menguji apakah variabel salam
berisi "Selamat", bisa kita gunakan perintah
salam.equals("Selamat")
Konsekuensi lainnya adalah apabila suatu variabel merujuk pada objek yang
dideklarasikan sebagai final. Ini berarti nilai variabel itu tidak bisa berubah setelah
diinisialisasi, sedangkan isi variabel itu adalah rujukan ke alamat memori tempat objek
berada. Dengan demikian variabel tersebut akan selalu menunjuk pada alamat memori
yang sama. Akan tetapi hal ini tidak berlaku untuk objek itu sendiri. Apabila ada variabel
lain yang tidak "final" tetapi menunjuk pada alamat memori yang sama, kemudian
variabel ini mengubah isi objek tersebut, maka isi objek tersebut bisa berubah.
Kita boleh saja untuk menulis perintah sebagai berikut.
final Murid mrd = new Murid();
murid.nama = "Ahmad Basir";
Perhatikan pada kode di atas bahwa isi objek bisa diubah. Akan tetapi jika kita mencoba
menulis seperti ini
Murid mrd99 = new Murid();
mrd = mrd99; // ILEGAL, karena mrd adalah variabel final
Pada contoh di atas, new PasanganDadu() adalah perintah untuk membuat objek,
meletakkannya di memori dan menyimpan alamat objek ini di memori pada variabel dadu.
Bagian ekspresi PasanganDadu() mirip seperti memanggil subrutin. Sebetulnya itulah
yang terjadi, yaitu program memanggil subrutin spesial yang dinamakan konstruktor
(constructor). Mungkin Anda heran karena kita tidak melihat adanya subrutin bernama
PasanganDadu(). Akan tetapi semua kelas memiliki konstruktor, yang jika kita tidak buat
secara khusus, Java akan menambahkannya secara otomatis, yang disebut konstruktor
bawaan.
Konstruktor bawaan melakukan hal-hal umum seperti mengalokasi memori, mengisi nilai
variabel instansi dengan nilai bawaannya, dan mengembalikan alamat objek yang dibuat di
memori. Jika kita menginginkan hal lain yang ikut dilaksanakan ketika suatu objek dibuat,
maka kita harus membuat konstruktor sendiri.
Bagaimana cara mendefinisikan konstruktor? Konstruktor dideklarasikan mirip dengan
deklarasi subrutin, dengan 3 perkecualian. Konstruktor tidak memiliki tipe keluaran (void
pun tidak dibolehkan. Namanya harus sama dengan nama kelas di mana ia dideklarasikan.
Sifat yang bisa digunakan hanya sifat akses, yaitu public, private, dan protected (static
tidak diperbolehkan).
Di lain pihak, konstruktor memiliki blok yang terdiri dari kumpulan perintah seperti pada
subrutin. Kita bisa menggunakan perintah apapun seperti pada subrutin biasa, termasuk
memiliki satu atau lebih parameter formal. Sebetulnya salah satu alasan untuk
menggunakan parameter adalah kita bisa membuat beberapa konstruktor yang menerima
data dalam berbagai bentuk, sehingga objek yang kita buat bisa dinisialisasi dengan cara
yang berbeda-beda sesuai dengan kondisi dan kebutuhan dari program yang akan kita buat.
Misalnya, kelas PasanganDadu di atas kita ubah sehingga kita bisa memberi nilai awal
sendiri. Dalam hal ini kita buat konstruktor yang menerima 2 nilai sebagai nilai awal dadu,
yaitu nilai1 dan nilai2.
class PasanganDadu {
public int dadu1; // Angka pada dadu pertama
public int dadu2; // Angka pada dadu kedua
public PasanganDadu() {
// Isi dadu1 dan dadu2 dengan bilangan acak, dengan memanggil
metode
// kocok()
kocok();
}
/**
* @param args
*/
public static void main(String[] args) {
PasanganDadu daduPertama = new PasanganDadu(); // pasangan dadu
pertama
PasanganDadu daduKedua = new PasanganDadu(); // pasangan dadu
kedua
jumlahKocokan = 0;
do {
daduPertama.kocok(); // kocok dadu pertama
total1 = daduPertama.dadu1 + daduPertama.dadu2; // hitung
jumlahnya
System.out.println("Pasangan dadu pertama berjumlah " +
total1);
jumlahKocokan++;
System.out.println(); // cetak baris kosong
}
}
Keluarannya adalah sebagai berikut
Konstruktor adalah subrutin, tetapi bukan subrutin biasa, dan bukan metode instansi,
karena konstruktor tidak dimiliki oleh suatu objek. Karena tugasnya membuat objek, maka
konstruktor dijalankan sebelum objek dibuat. Konstruktor mirip dengan subrutin anggota
statik, tetapi dia tidak bisa dideklarasikan "static". Bahkan menurut spesifikasi Java,
konstruktor bukan anggota suatu kelas sama sekali.
Tidak seperti subrutin lain, konstruktor hanya bisa dipanggil melalui operator "new",
dalam bentuk
new nama_kelas(parameter);
di sini "parameter" boleh kosong. Hasil keluarannya adalah alamat memori di mana objek
yang baru dibuat tersebut disimpan. Seringkali, kita akan simpan hasil keluarannya di
dalam suatu variabel, atau bisa juga hasil keluarannya diberikan ke dalam suatu fungsi
sebagai parameter.
Memanggil konstruktor lebih rumit daripada memanggil subrutin atau fungsi biasa. Hal-hal
berikut sangat membantu untuk lebih memahami apa yang dilakukan oleh konstruktor
ketika ia dipanggil untuk membuat suatu objek:
1. Pertama, komputer memberi daerah pada memori yang tidak digunakan, cukup
untuk dipakai oleh objek yang akan dibuat
2. Komputer akan mengisi variabel instansi objek tersebut dengan nilai bawaannya.
Jika deklarasi variabel instansi pada kelas memiliki nilai awal tertentu, maka nilai
tersebut akan dimasukkan sebagai nilai awalnya.
3. Parameter aktual pada konstruktor (jika ada) akan dievaluasi dan nilainya diberikan
kepada parameter formal konstruktor tersebut.
4. Perintah pada konstruktor (jika ada) akan dilaksanakan.
5. Referensi objek akan dikembalikan kepada si pemanggil.
Hasil keluarannya adalah referensi ke objek yang baru saja dibuat. Kita bisa gunakan
referensi ini untuk mengambil data pada variabel instansi objek tersebut atau memanggil
metode instansinya.
Contoh lain, mari kita ganti kelas Murid pada bagian sebelumnya. Kita akan tambahkan
konstruktor dan juga kita ganti variabel instansi "nama" menjadi bersifat privat.
class Murid {
private String nama; // Nama murid
public double nilai1, nilai2, nilai3; // Nilai-nilai ujian
Murid(String namaMurid) {
// Konstruktor objek Murid
nama = namaMurid;
}
Pada versi aslinya, isi variabel nama harus diisi dengan perintah terpisah setelah objek
dibuat. Masalahnya programmer tidak selalu ingat untuk mengisi nilai nama. Pada versi
baru di atas, setiap kali kita membuat objek, parameter namaMurid harus disertakan, karena
ini dideklarasikan pada konstruktornya. Dengan demikian potensi bug karena kelalaian
programmer dapat dihilangkan dengan mudah.
Contoh keamanan lainnya adalah dengan membuat variabel instansi nama bersifat private.
Ini berarti variabel ini tidak bisa diakses oleh dunia luar secara langsung. Variabel ini
hanya bisa diambil nilainya dengan metode instansi getNama, dan karena tidak bisa diakses
langsung dari luar, maka isi variabel ini tidak bisa diganti dari luar kelas. Sekali objek
Murid dibuat, maka namanya tidak bisa diganti selama murid tersebut ada.
Anggap mobilku adalah variabel dengan tipe Mobil akan dideklarasikan dan diinisialisasi
dengan pernyataan berikut
Mobil mobilku = new Mobil();
Dengan deklarasi seperti ini, maka program akan bisa mengakses mobilku.jumlahPintu,
karena jumlahPintu adalah variabel instansi dari kelas Mobil. Akan tetapi karena kelas
Mobil merupakan turunan dari kelas Kendaraan, maka mobil ini juga memiliki stuktur dan
perilaku dari kendaraan. Artinya program juga bisa mengakses mobilku.nomorPolisi,
mobilku.pemilik, dan menjalankan metode mobilku.gantiPemilik()
Dalam dunia nyata mobil, truk dan motor memang kendaraan (bukan hanya pada
program). Dalam arti objek yang memiliki tipe Mobil atau Truk atau Motor juga secara
otomatis objek bertipe Kendaraan. Fakta penting berikutnya :
Variabel yang dapat diisi referensi ke objek suatu kelas A juga dapat diisi referensi
ke objek kelas turunan dari kelas A.
Efek praktis dari penyataan ini adalah, objek dengan tipe Mobil dapat diisi ke dalam
variabel bertipe Kendaraan, atau dengan kata lain perintah berikut adalah valid
Kendaraan kendaraanku = mobilku;
atau bahkan juga perintah berikut
Kendaraan kendaraanku = new Mobil();
Setelah pernyataan di atas, variabel kendaraanku berisi referensi ke objek Kendaraan,
yang kebetulan merupakan instansi dari kelas turunannya, yaitu kelas Mobil. Objek akan
"mengingat" bahwa yang disimpan dalam variabel tersebut adalah objek bertipe Mobil,
bukan Kendaraan. Informasi tentang objek apa yang disimpan pada memori ikut
disertakan bersama objek tersebut, sehingga variabel yang bertipe Kendaraan akan tahu
dengan pasti tipe objek yang dirujuknya. Kita juga dapat menguji jenis objek yang
disimpan suatu variabel dengan menggunakan operator instanceof. Misalnya
if (kendaraanku instanceof Mobil) { ... }
menguji apakah objek yang dirujuk pada variabel kendaraanku merupakan objek bertipe
Mobil.
Kebalikannya, pernyataan berikut tidak bisa dilakukan
mobilku = kendaraanku;
karena kendaraanku bisa bertipe objek lain seperti Truk atau Motor. Apabila kita tahu
persis bahwa kendaraanku bertipe Mobil, kita bisa menggunakan casting, untuk memberi
tahu komputer untuk memperlakukan variabel kendaraanku memiliki tipe Mobil. Jadi kita
bisa gunakan perintah
mobilku = (Mobil)kendaraanku;
Atau kita juga bisa mengakses ((Mobil)kendaraanku).jumlahPintu. Mari kita gunakan
kelas ini dalam program, dan kita ingin mencetak informasi yang sesuai dengan suatu
kendaraan. Misalnya:
System.out.println("Data Kendaraan:");
System.out.println("Nomor polisi: " + kendaraanku.nomorPolisi);
if (kendaraanku instanceof Mobil)
System.out.println("Jenis kendaraan: Mobil");
Mobil m = (Mobil)kendaraanku;
System.out.println("Jumlah pintu: " + m.jumlahPintu);
}
else if (kendaraanku instanceof Truk) {
System.out.println("Jenis kendaraan: Truk");
Truk t = (Truk)kendaraanku ;
System.out.println("Jumlah roda: " + t.jumlahRoda);
}
else if (kendaraanku instanceof Motor) {
System.out.println("Jenis kendaraan: Motor");
Motor sm = (Motor)kendaraanku ;
System.out.println("Jumlah tak: " + sm.jumlahTak);
}
Lihat bahwa untuk setiap jenis objek, komputer akan menguji satu per satu tipe objek yang
disimpan dalam kendaraanku. Jika kendaraanku[code] merujuk pada objek bertipe
Truk maka casting [code](Mobil)kendaraanku akan menampilkan pesan kesalahan.
Contoh lain, mari kita buat program untuk menggambar suatu bentuk geometri pada layar.
Misalnya bentuk geometri tersebut terdiri dari persegi panjang, oval, dan kotak bersudut
lingkar dengan berbagai warna.
Kelas yang akan kita buat adalah PersegiPanjang, Oval, dan KotakLingkar. Ketiga kelas
tersebut memiliki kelas super yang sama yang disebut BentukGeometris. Kelas
BentukGeometris memiliki variabel instansi warna, lokasi, dan ukuran. Untuk mengganti
warna kita bisa mengganti variabel instansi warna pada kelas ini, kemudian menjalankan
metode instansi gambar() untuk menggambar bentuk tersebut dengan warna baru:
class BentukGeometris {
Color warna; // Warna suatu bentuk geometri
// (Kelas Color diimport dari paket java.awt)
Dalam Java, setiap kelas yang kita buat akan memiliki kelas super, atau dengan kata lain
setiap kelas merupakan turunan dari kelas lain. Jika kita tidak memberi kelas supernya
(melalui operator extends), maka kelas tersebut otomatis memiliki kelas super Object,
yaitu kelas bawaan yang sudah didefinisikan dalam paket java.lang. Kelas Object adalah
satu-satunya kelas yang tidak memiliki kelas super.
Java juga memiliki variabel spesial yang dinamakan "super" untuk digunakan dalam
metode instansi. Variabel super digunakan dalam kelas turunan. super mengacu pada
objek di mana metode tersebut berada, akan tetapi ia merupakan bagian dari kelas super
dari objek tersebut.
Suatu kelas bisa ditambah atau dimodifikasi dari kelas turunannya. Variabel super hanya
mengacu pada bagian objek sebelum ia ditambah atau dimodifikasi, atau dengan kata lain
bentuk aslinya sebelum dia diturunkan, yang dalam hal ini sama dengan kelas supernya.
Misalnya kita akan menulis suatu kelas, dan kelas tersebut memiliki metode instansi
bernama suatuMetode(). Pernyataan super.suatuMetode() berarti menjalankan
suatuMetode() pada kelas supernya. Jika tidak ada metode sutuMetode() pada kelas
supernya, Java akan menampilkan pesan kesalahan sintaks.
Alasan mengapa Java memiliki variabel super adalah karena banyak hal yang mungkin
tersembunyi pada kelas turunan. Misalnya karena kita mengimplementasikan fungsi lain
pada kelas turunan dengan nama yang sama (dalam kaitannya dengan polimorfisme,
misalkan).
Ketika kita membuat metode baru pada kelas turunan yang memiliki nama dan jenis
parameter yang sama dengan metode pada kelas supernya, metode dari kelas super akan
disembunyukan. Dalam bahasa pemrograman, metode ini menimpa (override) metode dari
kelas supernya. Variabel super bisa digunakan untuk mengakses metode aslinya yang
didefinisikan di kelas supernya.
Kegunaan utama dari super biasanya untuk memperluas kegunaan metode yang sudah
ada, bukan menggantinya secara keseluuruhan. Metode baru bisa menggunakan super
untuk menjalankan instruksi pada kelas supernya, kemudian menambahkan instruksi lain
pada kelas turunannya.
Misalnya, kita memiliki kelas PasanganDadu yang memiliki metode kocok. Kemudian kita
akan membuat kelas turunan yaitu DaduGrafis yang berfungsi untuk menggambar dadu
pada layar. Metode kocok() dalam DaduGrafis harus melakukan apa yang dilakukan oleh
PasanganDadu, dengan tambahan perintah untuk menggambar dadu tersebut di layar. Kita
bisa tulis definisi kelas DaduGrafis dalam bentuk :
public class DaduGrafis extends PasanganDadu {
public void kocok() {
// Mengocok dadu, kemudian menggambarnya di layar
super.kocok(); // Panggil metode kocok() di kelas PasanganDadu
gambar(); // Gambar ulang dadu
}
.
. // Metode dan variabel lain, termasuk metode gambar()
.
}
Dengan cara ini kita bisa memperluas apa yang dilakukan oleh metode kocok() pada kelas
supernya tanpa mengetahui dengan detail apa yang dilakukan langkah per langkah di kelas
supernya.
Konstuktor pada Kelas Turunan
Konstruktor tidak bisa diturunkan, artinya jika kita membuat kelas turunan dari suatu kelas,
konstruktor pada kelas supernya tidak termasuk bagian yang diturunkan. Jika kita ingin
konstruktor tersebut pada kelas turunannya, maka kita harus membuat kembali konstruktor
tersebut di kelas yang baru. Jika tidak, maka Java akan membuat konstruktor standar tanpa
parameter apa-apa.
Hal ini mungkin menjadi masalah jika konstruktor pada kelas supernya melakukan banyak
tugas. Artinya kita harus mengulang kembali menuliskan semua instruksi pada kelas
supernya di kelas turunan yang baru. Masalahnya akan lebih rumit jika kita tidak
mengetahui sama sekali apa yang dilakukan kelas supernya, misalnya apabila tidak ada
kode sumbernya.
Cara yang paling mudah adalah menggunakan super. Di baris pertama konstruktor baru
kita di kelas turunannya, kita bisa menggunakan super untuk memanggil konstruktor kelas
supernya. Sintaksnya sedikit aneh dan membingungkan, dan hanya bisa digunakan untuk
satu situasi saja : Sintaksnya mirip seperti memanggil subrutin super (meskipun
sebenarnya super bukan subrutin dan kita tidak bisa memanggil konstruktor seperti kita
memanggil subrutin biasa).
Misalnya kelas PasanganDadu memiliki konstruktor yang memiliki dua parameter bertipe
int. Maka kita bisa membuat konstruktor pada kelas DaduGrafis seperti :
public class DaduGrafis extends PasanganDadu {
Kelas Bertingkat
Suatu kelas merupakan blok bangunan suatu program, yang melambangkan suatu ide
beserta data dan perilaku yang dimilikinya. Kadang-kadang kita mungkin berasa sedikit
aneh untuk membuat kelas kecil hanya untuk menggabungkan beberapa data. Akan tetapi
kadang-kadang kelas-kelas kecil ini sering bermanfaat dan penting. Untungnya Java
membolehkan kita untuk membuat kelas di dalam kelas lain, sehingga kelas-kelas kecil ini
tidak perlu berdiri sendiri. Kelas kecil ini menjadi bagian dari suatu kelas besar yang bisa
melakukan hal-hal kompleks lainnya. Kelas kecil ini misalnya berguna untuk mendukung
operasi yang akan dikerjakan oleh kelas besarnya.
Dalam Java, kelas bertingkat atau kelas bagian dalam adalah kelas yang ditulis di dalam
definisi kelas lain. Kelas bagian dalam ini bisa memiliki nama atau anonim (tanpa nama).
Kelas bagian dalam yang memiliki nama tampak seperti kelas biasa, tetapi ia ditulis di
dalam kelas lain. (Kelas bagian dalam ini juga bisa memiliki kelas bagian dalam yang lain,
akan tetapi ingat akan konsekuensi kerumitannya apabila kita membuat terlalu banyak
tingkatan).
Seperti komponen lain dalam suatu kelas, kelas bagian dalam yang memiliki nama bisa
berupa kelas statik atau kelas non-statik. Kelas bertingkat statik merupakan bagian dari
struktur statik dari kelas yang menaunginya. Kelas tersebut bisa digunakan di dalam kelas
induknya untuk membuat objek seperti biasa. Jika tidak dideklarasikan sebagai private,
makan kelas tersebut juga bisa digunakan dari luar kelas induknya. Jika digunakan dari
luar kelas induknya, namanya harus jelas mencantumkan nama kelas induknya. Mirip
seperti komponen statik dari suatu kelas : kelas bertingkat statik adalah bagian kelas di
mana kelas tersebut mirip dengan variabel anggota statik lainnya di dalam kelas tersebut.
Misalnya, suatu kelas bernama ModelRangkaKawat melambangkan kumpulan garis dalam
ruang 3 dimensi. Misalnya kelas ModelRangkaKawat memiliki kelas bertingkat statik yang
bernama Garis yaitu sebuah garis. Maka dari luar kelas ModelRangkaKawat, kelas Garis
akan dipanggil sebagai ModelRangkaKawat.Garis.
Kelas ModelRangkaKawat dan kelas bagian dalamnya dapat dituliskan seperti berikut :
public class ModelRangkaKawat {
. . . // anggota lain kelas ModelRangkaKawat
Kelas bertingkat yang tidak statik, pada prakteknya, tidak jauh berbeda dengan kelas
bertingkat statik, akan tetapi kelas bertingkat non-statik berkaitan dengan suatu objek,
bukan kelas induknya.
Anggota non-statik dari suatu kelas sebenarnya bukan merupakan bagian dari kelas itu. Hal
ini juga berlaku untuk kelas bertingkat non-statik seperti juga bagian kelas non-statik
lainnya. Anggota non-statik suatu kelas menjelaskan apa yang akan disimpan dalam objek
yang diciptakan dari kelas tersebut. Hal ini juga berlaku (secara logis) dari kelas bertingkat
non-statik.
Dengan kata lain, setiap objek yang diciptakan dari kelas induknya memiliki kopi kelas
bertingkat masing-masing. Kopi ini memiliki akses ke semua variabel dan metode instansi
objek tersebut. Dua objek kelas bagian dalam pada dua objek induk merupakan objek
berbeda karena metode dan variabel instansi yang bisa diakses berasal dari objek yang
berbeda.
Pada dasarnya, aturan untuk menentukan kapan suatu kelas bisa dimasukkan ke dalam
kelas lain sebagai kelas statik atau non-statik adalah : Jika kelas tersebut perlu
menggunakan variabel atau metode instansi suatu objek (bukan variabel atau metode statik
kelas), maka kelas tersebut harus dibuat non-statik, jika tidak maka harus dibuat statik.
Dari luar kelas induknya, kelas bertingkat non-statik harus dipanggil dalam bentuk
namaVariabel.NamaKelasBertingkat, misalnya namaVariabel adalah variabel yang
merujuk pada objek yang memiliki kelas bertingkat tersebut. Sebetulnya cara ini agak
langka. Kelas bertingkat non-statik biasanya digunakan hanya di dalam kelas induknya,
sehingga bisa diakses dengan nama yang sederhana.
UNtuk membuat objek yang merupakan kelas bertingkat non-statik, kita harus membuat
objek yang merupakan kelas induknya. (Ketika bekerja di dalam kelas, objek "this" akan
secara otomatis digunakan). Objek dari kelas bertingkat tersebut dihubungkan secara
permanen dengan objek dari kelas induknya, dan memiliki akses penuh atas anggota kelas
induknya.
Mari lihat contoh berikut, dan mungkin bisa memberi pemahaman lebih baik bagaimana
kelas bertingkat non-statik sebetulnya merupakan hal yang sangat alami. Misalnya suatu
kelas yang melambangkan permainan kartu. Kelas ini memiliki kelas beringkat yang
melambangkan para pemainnya. Struktur MainKartu bisa berbentuk seperti :
class MainKartu { // Melambangkan permainan kartu
class Pemain { // Melambangkan salah satu pemain game ini
.
.
.
} // akhir kelas Pemain
private Tumpukan tumpukan; // Tumpukan kartu
.
.
.
Dalam beberapa kasus, mungkin kita harus menulis kelas bertingkat dan kemudian
menggunakan kelas tersebut hanya 1 kali dalam program kita. Apakah berguna membuat
kelas bertingkat jika begini kondisinya? Mungkin ya mungkin tidak. Dalam kasus seperti
ini kita juga bisa membuat kelas bertingkat anonim. Kelas anonim dapat dibuat dengan
menggunakan variasi dari operator new dengan bentuk
new kelassuper_atau_interface () {
metode_dan_variabel
}
Konstruktor ini membuat suatu kelas baru tanpa memberi nama, dan pada saat yang sama
membuat objek dari kelas tersebut. Bentuk operator [code] seperti ini bisa digunakan
dalam pernyataan apapun di mana pernyataan new biasa digunakan. Maksud dari
pernyataan di atas adalah untuk membuat : "objek baru di dalam suatu kelas yang namanya
sama dengan kelassuper_atau_interface dengan ditambah dengan
metode_dan_varaibel baru."
Artinya pernyataan di atas sama dengan membuat objek baru dengan konfigurasi yang baru
pula. Kita juga bisa membuat kelas anonim yang diturunkan dari interface. Dalam hal
ini, kelas anonim tersebut harus mengimplementasikan semua metode yang dideklarasikan
oleh interface tersebut.
Kelas anonim sering digunakan untuk menangani event pada GUI (graphical user
interfaces). Misalnya interface Gambar seperti didefinisikan di awal bagian ini. Misalnya
kita ingin membuat objek berupa gambar bujur sangkar berisi warna merah dengan ukuran
100 x 100 piksel. Daripada membuat kelas baru kemudian menggunakan kelas tersebut
untuk membuat objek, kita bisa menggunakan kelas anonim untuk membuat objek
sekaligus dalam satu pernyataan :
Gambar kotakMerah = new Gambar() {
void gambar(Graphics g) {
g.setColor(Color.red);
g.fillRect(10,10,100,100);
}
};
Tanda titik koma (;) di akhir pernyataan ini bukan bagian dari definisi suatu kelas, tapi
merupakan bagian dari pernyataan secara keseluruhan.
Ketika kelas Java dikompilasi, setiap kelas bertingkat anonim akan dibuat dalam file kelas
terpisah. Jika nama kelas utama adalah KelasUtama, misalnya, maka nama file kelas untuk
setiap kelas bertingkat anonimnya menjadi KelasUtama$1.class, KelasUtama$2.class,
KelasUtama$3.class dan seterusnya.
public PasanganDadu() {
// Konstruktor. Membuat pasangan dadu dengan angka
// awal berupa bilangan acak
kocok();
}
Murid(String namaBaru) {
// Konstruktor objek Murid:
// memberi nama, dan memberi nomor murid baru
nama = namaBaru;
nomorBerikutnya++;
nomorMurid = nomorBerikutnya;
}
Banyak pengguna komputer memiliki pengalaman di mana program tidak bekerja atau
crash. Dalam banyak hal, masalah tersebut hanya mengganggu saja, tapi kadang-kadang
masalahnya lebih kompleks dari itu, misalnya hilangnya data atau uang. Jika komputer
diberi tugas penting, konsekuensinya akan lebih serius apabila program tersebut
berperilaku tidak normal.
Beberapa tahun yang lalu, kegagalan dua misi ruang angkasa ke Mars masuk dalam berita.
Kedua kegagalan tersebut dipercaya karena masalah pada software, akan tetapi pada kedua
kasus tersebut masalahnya bukan pada program yang tidak benar. Pada bulan September
1999, Orbiter Iklim Mars terbakar di atmosfer Mars karena data yang ditulis dalam satuan
Inggris (inci, kaki, dll) dimasukkan ke dalam program komputer yang didesain untuk
menerima input satuan Metrik (sentimeter, kilometer, dll). Beberapa bulan kemudian,
Pendarat Kutub Mars jatuh karena softwarenya mematikan mesinnya terlalu cepat.
Program yang dibuat seharusnya bisa mendeteksi tumpuan ketika pesawat mendarat dan
baru kemudian mematikan mesin. Akan tetapi, roda pendarat kemungkinan macet yang
menyebabkan program pemati mesin aktif sebelum pesawat menyentuh tanah. Sistem yang
lebih tangguh akan mengecek terlebih dahulu ketinggian pesawat sebelum mesin
dimatikan.
Masih banyak beberapa kisah tentang masalah yang disebabkan oleh jeleknya desain atau
implementasi suatu software. Silakan lihat buku Computer Ethics karangan Tom Forester
dan Perry Morrison untuk melihat beberapa insiden yang pernah terjadi. (Buku ini
menceritakan tentang isu etika dalam bidang komputer. Buku ini mungkin penting sekali
untuk dibaca oleh orang yang berkecimpung dalam dunia ilmu komputer).
Pada tahun 1985 dan 1986, satu orang tewas dan beberapa lainnya terluka karena overdosis
radiasi, pada saat melakukan perawatan radiasi dengan mesin radiasi yang komputernya
tidak diprogram dengan benar. DI kasus lain, selama 10 tahun hingga tahun 1992, sekitar
1000 pasien kanker menerima dosis radiasi sekitar 30% lebih rendah dari yang diberikan
dokter karena kesalahan pemrograman.
Pada tahun 1985, sebuah komputer di Bank of New York menghancurkan data-data
transaksi sekuritas yang sedang berjalan karena adanya kesalahan pada program. Butuh
kurang dari 24 jam untuk memperbaiki program tersebut, akan tetapi pada saat itu, bank
sudah kehilangan sekitar 5 juta US dollar karena bunga overnight yang harus dipinjam
untuk mengkover masalah tersebut.
Pemrograman sistem kendali inersia dari pesawat tempur F-16 bisa membalik pesawat dari
atas ke bawah ketika digunakan di atas khatulistiwa, untungnya masalah ini sudah
ditemukan dalam simulasi. Pemindai luar angkasa Mariner 18 hilang karena kesalahan di
satu baris program. Kapsul luar angkasa Gemini V salah mendarat beberapa ratus
kilometer lebih jauh karena programmer lupa untuk memasukkan perputaran bumi ke
dalam perhitungan.
Pada tahun 1990, layanan telephon jarak jauh AT&T terganggu di seluruh Amerika Serikat
ketika program komputer yang baru dijalankan terbukti memiliki bug.
Contoh-contoh di atas adalah beberapa yang pernah terjadi. Masalah software adalah
masalah yang sangat umum. Sebagai programmer, kita harus mengerti kenapa itu bisa
terjadi dan bagaimana cara mengatasinya.
Salah satu bagian dari masalahnya dapat dilacak kepada bahasa pemrogramannya itu
sendiri, begitu kata para penemu Java. Java didesain untuk memberikan proteksi terhadap
beberapa jenis kesalahan. Bagaimana caranya suatu bahasa pemrograman menghindari
kesalahan? Mari kita lihat beberapa contohnya.
Bahasa pemrograman terdahulu tidak membutuhkan variabel untuk dideklarasikan. Pada
bahasa pemrograman tersebut, ketika suatu nama variabel digunakan dalam program,
variabel akan otomatis dibuat. Mungkin ini terlihat lebih mudah dan nyaman daripada
harus mendeklarasikan variabel beserta tipenya terlebih dahulu. Akan tetapi, ada
konsekuensinya : Kesalahan ketik sedikit saja akan membuat komputer menciptakan
variabel baru yang sebetulnya tidak kita inginkan. Kesalahan seperti ini pernah terjadi dan
mengakibatkan hilangnya pesawat ruang angkasa.
Dalam bahasa pemrograman FORTRAN, perintah "DO 20 I = 1,5" adalah pernyataan
pertama dari suatu perulangan. Sekarang, spasi tidak lagi suatu hal yang penting pada
bahasa FORTRAN, sehingga perintah ini akan sama dengan "DO20I=1,5". Di lain pihak,
perintah "DO20I=1.5" dengan tanda titik bukan koma, merupakan pernyataan pemberi
nilai yang memberi nilai 1.5 ke dalam variabel DO20I. Misalnya ada kesalahan dalam
mengetik koma menjadi titik, bisa jadi akan menyebabkan suatu roket meledak sebelum
diluncurkan.
Karena FORTRAN tidak memerlukan variabel untuk dideklarasi, kompilernya akan
senang menerima perintah "DO20I=1.5". Ia akan membuat variabel baru bernama DO20I.
Jika FORTRAN membutuhkan variabel untuk dideklarasikan di awal, kompiler akan
mengeluarkan pesan kesalahan di awal karena variabel DO20I tidak pernah dideklarasikan
sebelumnya.
Hampir semua bahasa pemrograman saat ini perlu mendeklarasikan variabel sebelum
digunakan, akan tetapi masih ada beberapa fitur pada bahasa pemrograman yang bisa
menyebabkan kesalahan. Java sudah membuang fitur ini. Beberapa orang tidak suka
karena ini membuat Java menjadi kurang feksibel dan kurang ampuh. Walaupun mungkin
kritik ini benar, meningkatnya tingkat keamanan dan ketangguhan suatu program mungkin
lebih dipentingkan dalam beberapa hal.
Pertahanan yang paling baik untuk mencegah beberapa macam jenis kesalahan adalah
mendesain bahasa pemrograman di mana membuat kesalahan tidak mungkin sama sekali.
Dalam kasus lain, di mana kesalahan tidak bisa dihilangkan sama sekali, bahasa
pemrograman bisa didesain sehingga apabila kesalahan terjadi, maka kesalahan ini akan
dapat dideteksi secara otomatis. Paling tidak cara ini akan mencegah kesalahan tersebut
membuat bencana yang lebih besar, karena akan memberi peringatan kepada programmer
bahwa ada sesuatu bug yang harus diperbaiki. Mari lihat beberapa contoh yang diberikan
Java untuk mengatasi permasalahan ini.
Suatu array dibuat dengan beberapa lokasi, dimulai dengan 0 hingga ke indeks
maksimumnya. Kita tidak dibolehkan untuk menggunakan lokasi array di luar rentang
yang sudah dibuat. Pada Java, jika kita memaksakan untuk melakukan itu, sistem akan
otomatis mendeteksi hal ini. Pada bahasa pemrograman lain seperti C dan C++,
programmer diberi keleluasaan penuh untuk memastikan bahwa indeks array berada di
dalam rentang tersebut.
Misalnya suatu array, A, memiliki tiga lokasi A[0], A[1], dan A[2]. Maka A[3], A[4], dan
berikutnya adalah lokasi pada memori di luar array tersebut. Pada Java, apabila kita
mencoba untuk menyimpan data pada A[3], Java akan mendeteksi ini. Program akan
dihentikan saat itu juga (kecuali kesalahan ini "ditangkap" yang akan dibahas kemudian).
Pada bahasa C atau C++, komputer akan diam saja dan melakukan penyimpanan di lokasi
ini. Hasilnya akan tidak bisa diprediksi. Konsekuensinya akan jauh lebih berat daripada
jika program berhenti (Kita akan diskusikan tentang tumpahan buffer di bagian ini nanti).
Pointer (penunjuk memori) juga merupakan kesalahan pemrograman yang paling sulit.
Dalam Java, variabel dari suatu objek menyimpan pointer atau rujuan ke alamat memori di
mana objek tersebut disimpan, atau isinya bisa juga null. Jika kita mencoba untuk
menggunakan nilai null seperti layaknya rujukan ke objek sungguhan, maka sistem
komputer akan mendeteksinya. Dalam bahasa pemrograman lain, lagi-lagi, adalah
tanggung jawab programmer untuk mencegah digunakannya rujukan ke null. Pada
komputer Macintosh lama, alamat null merupakan alamat ke lokasi di memori dengan
alamat 0. Program dapat menggunakan memori di dekat alamat 0. Sayangnya, Macintosh
menyimpan data penting tentang sistem di lokasi tersebut. Mengubah data di lokasi
tersebut akan membuat sistem crash atau hang, bukan hanya program tersebut saja tetapi
keseluruhan sistem operasi akan berhenti.
Kesalahan pointer lain adalah jika isi pointer menunjuk pada tipe data yang salah atau
lokasi di memori yang tidak memiliki objek sama sekali. Kesalahan seperti ini tidak
mungkin dalam bahasa Java, karena programmer tidak diperbolehkan untuk mengganti
pointer sama sekali. Di dalam bahasa pemrograman lain, programmer bisa mengganti
lokasi pointer ke lokasi lain, intinya, ke lokasi memori manapun. Jika tidak dilakukan
dengan benar, pointer ini bisa menunjuk pada lokasi berbahaya atau menghasilkan sesuatu
yang tidak bisa diperkirakan.
Kesalahan lain yang bisa terjadi pada Java adalah kebocoran memori. Pada Java, sewaktu
tidak ada lagi pointer yang merujuk ke pada suatu objek, objek tersebut akan diambil oleh
pemulung memori, sehingga memori tersebut dapat digunakan lagi oleh bagian program
lain. Dalam bahasa pemrograman lain, programmer bertanggung jawab untuk
mengembalikan memori yang tidak digunakan kepada sistem operasi. Jika programmer
tidak melakukannya, makan memori yang tidak terpakai akan terakumulasi, sehingga
jumlah memori yang tersedia akan berkurang. Ini adalah salah satu contoh masalah umum
yang terjadi pada komputer Windows di mana banyak sekali kebocoran memori yang
terjadi, sehingga komputer harus direstart ulang setiap beberapa hari.
Banyak program yang terjangkit masalah tumpahan buffer (buffer overflow error).
Tumpahan buffer sering menjadi berita utama karena hal ini sering mengakibatkan
kompromi masalah keamanan komputer. Ketika komputer menerima data dari komputer
lain dari network atau internet misalnya, data tersebut akan disimpan dalam buffer. Buffer
adalah bagian memori yang telah dialokasikan program untuk menyimpan data tersebut.
Tumpahan buffer terjadi jika data yang diterima lebih banyak dari jumlah data yang bisa
ditampung oleh buffer. Pertanyaannya adalah kapan ini terjadi?
Jika kesalahan ini bisa dideteksi oleh program atau program yang mengatur lalu lintas
network, maka satu-satunya kemungkinan adalah pada karena kesalahan transmisi data
pada network. Masalah utamanya terjadi ketika program tidak bisa mendeteksi tumpahan
buffer secara benar. Dalam hal ini, software terus mensuplai data ke memori meskipun
buffer telah terisi penuh, dan data lebihnya disimpan pada bagian memori yang tidak
dialokasikan untuk buffer tersebut. Bagian memori yang tertunpah tersebut mungkin
digunakan untuk fungsi lain. Mungkin juga digunakan untuk menyimpan data penting lain.
Atau bahkan mungkin menyimpan kode program itu sendiri. Ini yang akan menjadi
masalah keamanaan. MIsalnya tumpahan buffer ini menimpa bagian dari program. Ketika
komputer mengeksekusi bagian program yang telah diganti, maka sebetulnya komputer
akan menjalankan data yang diterima dari komputer lain. Data ini bisa berisi apa saja. Bisa
jadi program untuk menghentikan komputer atau bahkan mengendalikan komputer.
Programmer jahat yang bisa menemukan kesalahan tumpahan memori dalam software
pengendali network bisa menggunakan lubang ini untuk menjalankan program-program
jahatnya.
Untuk software yang ditulis dalam Java, kesalahan tumpahan buffer tidak dimungkinkan.
Bahasa Java tidak mungkin menyimpan data di memori yang tidak dialokasikan
kepadanya. Untuk bisa menyimpan data, komputer membutuhkan pointer yang menunjuk
pada lokasi memori yang belum terpakai, atau menggunakan lokasi array yang berada di
luar lokasi yang disediakan untuk array tersebut. Seperti dijelaskan sebelumnya, kedua
kemungkinan tersebut tidak diperbolehkan sama sekali pada Java. (Akan tetapi, masih
mungkin kesalahan seperti ini muncul pada kelas standar Java, karena beberapa metode
pada kelas ini sebenarnya ditulis dalam bahasa C bukan Java).
Sudah jelas desain bahasa bisa membantu mencegah kesalahan atau membantu mendeteksi
masalah yang mungkin terjadi. Atau dibutuhkan pengujian, misalnya menguji apakah
pointer bernilai null. Beberapa programmer mungkin merasa harus mengorbankan
kecanggihan dan efisiensi. Akan tetapi, ada banyak situasi di mana keamanan merupakan
prioritas utama. Java didesain untuk situasi seperti ini.
Ada satu bagian di mana desainer Java tidak memasukkan pendeteksi masalah secara
otomatis, yaitu perhitungan numerik. Pada Java, nilai suatu bilangan int dinyatakan dalam
bilangan biner 32-bit. Dengan 32 bit, maka terdapat kurang lebih 4 milyar bilangan yang
bisa dibentuk. Nilai int memiliki rentang antara -2147483648 hingga 2147483647. Apa
yang terjadi jika hasil perhitungan berada di luar rentang ini? Misalnya, berapa
2147483647 + 1? Dan berapa 2000000000 * 2? Jawaban yang benar secara matematis
berada di luar nilai int. Contoh-contoh di atas disebut tumpahan bilangan bulat (integer
overflow). Dalam banyak kasus, tumpahan bilangan bulat termasuk suatu kesalahan. Akan
tetapi Java tidak otomatis mendeteksi kesalahan tersebut. Misalnya, perhitungan
2147483647 + 1 akan bernilai negatif -2147483648 (Apa yang terjadi sebenarnya adalah
bit tambahan di luar bit ke-32 diabaikan. Nilai yang lebih besar dari 2147483647 akan
"terpotong" sehingga menjadi nilai negatif. Secara matematis, hasilnya akan selalu
merupakan sisa pembagian dari pembagian dengan 232).
Banyak kesalahan program yang disebabkan oleh kesalahan semacam ini. Program
tersebut benar, akan tetapi tidak bisa menangani bilangan lebih besar daripada 32 bit.
Contoh sederhana adalah kesalahan Y2K sebenarnya merupakan kesalahan yang mirip
dengan ini.
Untuk jenis bilangan real seperti double, masalahnya bahkan lebih kompleks lagi. Bukan
hanya tumpahan yang mungkin terjadi. Untuk jenis double, rentangnya berlaku hingga
10308. Nilai yang lebih dari nilai ini tidak "terpotong" menjadi negatif. Akan tetapi ia akan
diubah menjadi suatu konstanta yang bernilai tak berhingga. Nilai
Double.POSITIVE_INFINITY dan Double.NEGATIVE_INFINITY melambangkan nilai
positif tak hingga dan negatif tak hingga. Nilai spesial lainnya dari tipe data double adalah
Doube.NaN atau bukan bilangan (not a number), yang melambangkan suatu nilai yang
tidak berarti. Misalnya pembagian dengan 0 atau akar kuadrat suatu bilangan negatif. Kita
bisa menguji apakah suatu variabel berisi bukan bilangan dengan memanggil fungsi yang
bertipe keluaran boolean, yaitu Double.isNaN(x).
Untuk bilangan real, ada komplikasi tambahan yaitu hampir semua bilangan real hanya
bisa dilambangkan dalam bentuk pendekatan. Bilangan real bisa memiliki jumlah digit di
belakang koma yang tak terhingga banyaknya. Nilai bertipe double biasanya akurat sekitar
15 digit di belakang koma. Bilangan real 1/3, misalnya, berarti 0.33333333......, dan
bilangan ini tidak bisa digantikan dengan bilangan dengan jumlah bit terbatas. Perhitungan
dengan bilangan real biasanya memiliki kesalahan akurasi. Sebenarnya, jika kita kurang
berhati-hati, akan menyebabkan perhitungan sama sekali salah. Ada bidang tertentu dalam
ilmu komputer yang dinamakan analisis numerik yang berkonsentrasi pada algoritma untuk
memanipulasi bilangan real.
Tidak semua kesalahan yang mungkin terjadi bisa dideteksi otomatis oleh Java. Lebih jauh,
bahkan ketika suatu kesalahan bisa dideteksi secara otomatis, reaksi standar dari sistem
adalah melaporkan kesalahan dan menghentikan jalannya program. Ini bukan ciri program
yang tangguh! Sehingga programmer harus mempelajari teknik untuk mencegah dan
mengatasi kesalahan. Topik ini akan dibahas pada bab ini.
System.out.print("A = ");
A = KonsolInput.ambilDouble();
System.out.print("B = ");
B = KonsolInput.ambilDouble();
System.out.print("C = ");
C = KonsolInput.ambilDouble();
System.out.print("A = ");
A = KonsolInput.ambilDouble();
System.out.print("B = ");
B = KonsolInput.ambilDouble();
System.out.print("C = ");
C = KonsolInput.ambilDouble();
if (i == A.length)
System.out.println("x berada di luar array");
else
System.out.println("x berada pada posisi " + i);
Pengecualian dan Pernyataan "try ... catch"
Membuat program untuk bekerja dalam kondisi ideal jauh lebih mudah daripada membuat
program yang tangguh. Program tangguh dapat tahan dari kondisi yang tidak biasa atau
dalam kondisi "pengecualian". Salah satu pendekatanya adalah dengan melihat
kemungkinan masalah yang mungkin terjadi dan menambahkan tes/pengujian pada
program tersebut untuk setiap kemungkinan masalah yang mungkin terjadi.
Misalnya, program akan berhenti jika mencoba mengakses elemen array A[i], di mana i
tidak berada di dalam rentang yang dibolehkan. Program tangguh harus dapat
mengantisipasi kemungkinan adanya indeks yang tak masuk akal dan menjaganya dari
kemungkinan itu. Misalnya bisa dilakukan dengan :
if (i < 0 || i >= A.length) {
... // Lakukan sesuatu untuk menangani indeks i diluar rentang
} else {
... // Proses elemen A[i]
}
Ada beberapa masalah yang mungkin terjadi dengan pendekatan seperti ini. Adalah hal
yang sangat sulit dan kadang kala tidak mungkin untuk mengantisipasi segala
kemungkinan yang dapat terjadi. Kadang tidak selalu jelas apa yang harus dilakukan
apabila suatu kesalahan ditemui. Untuk mengantisipasi semua masalah yang mungkin
terjadi bisa jadi membuat program sederhana menjadi lautan pernyataan if.
Java (seperti C++) membutuhkan metode alternatif yang lebih rapi dan terstruktur untuk
menghadapi masalah yang mungkin terjadi ketika program dijalankan. Metode ini disebut
sebagai penanganan pengecualian (exception-handling). Kata "pengecualian" diartikan
sesuatu yang lebih umum daripada "kesalahan". Termasuk di antaranya adalah kondisi
yang mungkin terjadi yang berada di luar aliran suatu program. Pengecualian, bisa berupa
kesalahan, atau bisa juga kasus tertentu yang kita inginkan terpisah dari algoritma kita.
Ketika pengecualian terjadi dalam eksekusi suatu program, kita sebut bahwa pengecualian
tersebut di-lempar-kan (thrown). Ketika ini terhadi, aliran program artinya terlempar dari
jalurnya, dan program berada dalam bahaya akan crash. Akan tetapi crash bisa dihindari
jika pengecualian tersebut ditangkap (catch) dan ditangani dengan cara tertentu. Suatu
pengecualian dapat dilempar oleh satu bagian program dan ditangkap oleh bagian program
lain. Pengecualian yang tidak ditangkap secara umum akan menyebabkan program
berhenti. (Lebih tepat apabila disebut thread yang melemparkan pengecualian tersebut
akan berhenti. Dalam program multithreading, mungkin saja thread lain akan terus berjalan
apabila thread yang satu berhenti.)
Karena program Java dijalankan di dalam interpreter, program yang crash berarti bahwa
program tersebut berhenti berjalan secara prematur. Tidak berarti bahwa interpreter juga
akan crash. Atau dengan kata lain, interpreter akan menangkap pengecualian yang tidak
ditangkap oleh program. Interpreter akan merespon dengan menghentikan jalannya
program. Dalam banyak bahasa pemrograman lainnya, program yang crash sering
menghentikan seluruh sistem hingga kita menekan tombol reset. Dalam Java, kejadian
seperti itu tidak mungkin -- yang artinya ketika hal itu terjadi, maka yang salah adalah
komputer kita, bukan program kita.
Ketika pengecualian terjadi, yang terjadi adalah program tersebut melemparkan suatu
objek. Objek tersebut membawa informasi (dalam variabel instansinya) dari tempat di
mana pengecualian terjadi ke titik di mana ia bisa ditangkap dan ditangani. Informasi ini
selalu terdiri dari tumpukan panggilan subrutin (subrutin call stack), yaitu daftar di mana
dan dari mana subrutin tersebut dipanggil dan kapan pengecualian tersebut dilemparkan.
(Karena suatu subrutin bisa memanggil subrutin yang lain, beberapa subrutin bisa aktif
dalam waktu yang sama.) Biasanya, objek pengecualian juga memiliki pesan kesalahan
mengapa ia dilemparkan, atau bisa juga memiliki data lain. Objek yang dilemparkan harus
diciptakan dari kelas standar java.lang.Throwable atau kelas turunannya.
Secara umum, setiap jenis pengecualian dikelompokkan dalam kelas turunan Throwable.
Throwable memiliki dua kelas turunan langsung, yaitu Error dan Exception. Kedua
kelas turunan ini pada akhirnya memiliki banyak kelas turunan lain. Kita juga bisa
membuat kelas pengecualian baru untuk melambangkan jenis pengecualian baru.
Kebanyakan turunan dari kelas Error merupakan kesalahan serius dalam mesin virtual
Java yang memang seharusnya menyebabkan berhentinya program karena sudah tidak
dapat diselamatkan lagi. Kita sebaiknya tidak mencoba untuk menangkap dan menangani
kesalahan jenis ini. Misalnya ClassFormatError dilempar karena mesin virtual Java
menemukan data ilegal dalam suatu file yang seharusnya berisi kelas Java yang sudah
terkompilasi. Jika kelas tersebut dipanggil ketika program sedang berjalan, maka kita tidak
bisa melanjutkan program tersebut sama sekali.
Di lain pihak, kelas turunan dari kelas Exception melambangkan pengecualian yang
memang seharusnya ditangkap. Dalam banyak kasus, pengecualian seperti ini adalah
sesuatu yang mungkin biasa disebut "kesalahan", akan tetapi kesalahan seperti ini adalah
jenis yang bisa ditangani dengan cara yang baik. (Akan tetapi, jangan terlalu bernafsu
untuk menangkap semua kesalahan hanya karena kita ingin program kita tidak crash. Jika
kita tidak memiliki cara untuk menanganinya, mungkin menangkap pengecualian dan
membiarkannya akan menyebabkan masalah lain di tempat lain).
Kelas Exception memiliki kelas turunan lainnnya, misalnya RuntimeException. Kelas
ini mengelompokkkan pengecualian umum misalnya ArithmeticException yang terjadi
misalnya ada pembagian dengan nol, ArrayIndexOutOfBoundsException yang terjadi
jika kita mencoba mengakses indeks array di luar rentang yang diijinkan, dan
NullPointerException yang terjadi jika kita mencoba menggunakan referensi ke null di
mana seharusnya referensi objek diperlukan.
RuntimeException biasanya menginidikasikan adanya bug dalam program yang harus
diperbaiki oleh programmer. RuntimeException dan Error memiliki sifat yang sama
yaitu program bisa mengabaikannya. ("Mengabaikan" artinya kita membiarkan program
crash jika pengecualian tersebut terjadi). Misalnya, program yang melemparkan
ArrayIndexOutOfBoundsException dan tidak menanganinya akan menghentikan
program saat itu juga. Untuk pengecualian lain selain Error dan RuntimeException
beserta kelas turunannya, pengecualian wajib ditangani.
Diagram berikut menggambarkan hirarki suatu kelas turunan dari kelas Throwable. Kelas
yang membutuhkan penanganan pengecualian wajib ditunjukkan dalam kotak merah.
Untuk menangkap pengecualian pada program Java, kita menggunakan pernyataan try.
Maksudnya memberi tahu komputer untuk "mencoba" (try) menjalankan suatu perintah.
Jika berhasil, semuanya akan berjalan seperti biasa. Tapi jika pengecualian dilempar pada
saat mencoba melaksanakan perintah tersebut, kita bisa menangkapnya dan menanganinya.
Misalnya,
try {
double determinan = M[0][0]*M[1][1] - M[0][1]*M[1][0];
System.out.println("Determinan matriks M adalah " + determinan);
}
catch ( ArrayIndexOutOfBoundsException e ) {
System.out.println("Determinan M tidak bisa dihitung karena ukuran M
salah.");
}
Komputer mencoba menjalankan perintah di dalam blok setelah kata "try". Jika tidak ada
pengecualian, maka bagian "catch" akan diabaikan. Akan tetapi jika ada pengecualian
ArrayIndexOutOfBoundsException, maka komputer akan langsung lompat ke dalam
blok setelah pernyataan "catch (ArrayIndexOutOfBoundsException)". Blok ini disebut
blok yang menangani pengecualian (exception handler) untuk pengecualian
ArrayIndexOutOfBoundsException". Dengan cara ini, kita mencegah program berhenti
tiba-tiba.
Mungkin kita akan sadar bahwa ada kemungkinan kesalahan lagi dalam blok di dalam
pernyataan try. Jika variabel M berisi null, maka pengecualian NullPointerException
akan dilemparkan. Dalam pernyataan try di atas, pengecualian NullPointerException
tidak ditangkap, sehingga akan diproses seperti biasa (yaitu menghentikan program saat itu
juga, kecuali pengecualian ini ditangkap di tempat lain). Kita bisa menangkap
pengecualian NullPointerException dengan menambah klausa catch lain, seperti :
try {
double determinan = M[0][0]*M[1][1] - M[0][1]*M[1][0];
System.out.println("Determinan matriks M adalah " + determinan);
}
catch ( ArrayIndexOutOfBoundsException e ) {
System.out.println("Determinan M tidak bisa dihitung karena ukuran M
salah.");
}
catch ( NullPointerException e ) {
System.out.print("Kesalahan program! M tidak ada: " + );
System.out.println( e.getMessage() );
}
Contoh ini menunjukkan bagaimana caranya menggunakan beberapa klausa catch. e
adalah nama variabel (bisa kita ganti dengan nama apapun terserah kita). Ingat kembali
bahwa ketika pengecualian terjadi, sebenarnya yang dilemparkan adalah objek. Sebelum
menjalankan klausa catch, komputer mengisi variabel ini dengan objek yang akan
ditangkap. Objek ini mengandung informasi tentang pengecualian tersebut.
Misalnya, pesan kesalahan yang menjelaskan tentang pengecualian ini bisa diambil dengan
metode getMessage() seperti contoh di atas. Metode ini akan mencetak daftar subrutin
yang dipanggil sebelum pengecualian ini dilempar. Informasi ini bisa menolong kita untuk
melacak dari mana kesalahan terjadi.
Ingat bahwa baik ArrayIndexOutOfBoundsException dan NullPointerException
adalah kelas turunan dari RuntimeException. Kita bisa menangkap semua pengecualian
dalam kelas RuntimeException dengan klausa catch tunggal, misalnya :
try {
double determinan = M[0][0]*M[1][1] - M[0][1]*M[1][0];
System.out.println("Determinan matriks M adalah " + determinan);
}
catch ( RuntimeException e ) {
System.out.println("Maaf, ada kesalahan yang terjadi.");
e.printStackTrace();
}
Karena objek yang bertipe ArrayIndexOutOfBoundsException maupun
NullPointerException juga bertipe RuntimeException, maka perintah seperti di atas
akan menangkap kesalahan indeks maupun kesalahan pointer kosong dan juga
pengecualian runtime yang lain. Ini menunjukkan mengapa kelas pengecualian disusun
dalam bentuk hirarki kelas. Kita bisa membuat klausa catch secara spesifik hingga tahu
persis apa yang salah, atau menggunakan klausa penangkap umum.
Contoh di atas mungkin tidak begitu realistis. Sepertinya kita jarang harus menangani
kesalahan indeks ataupun kesalahan pointer kosong seperti di atas. Masalahnya mungkin
terlalu banyak, dan mungkin kita akan bosan jika harus menulis try ... catch setiap kali
kita menggunakan array. Yang penting kita mengisi variabel tersebut dengan sesuatu yang
bukan null dan menjaga agar program tidak keluar indeks sudah cukup. Oleh karena itu
kita sebut penanganan ini tidak wajib. Akan ada banyak hal yang bisa jadi masalah.
(Makanya penanganan pengecualian tidak menyebabkan program makin tangguh. Ia hanya
memberikan alat yang mungkin kita gunakan dengan cara memberi tahu di mana kesalahan
mungkin muncul).
Bentuk pernyataan try sebenarnya lebih kompleks dari contoh di atas. Sintaksnya secara
umum dapat ditulis seperti:
try {
perintah
}
klausa_catch_tambahan
klausa_finally_tambahan
Ingat bahwa di sini blok ( yaitu { dan } ) diperlukan, meskipun jika perintah hanya terdiri
dari satu perintah. Pernyataan try boleh tidak dimasukkan, dan juga klausa finally boleh
ada boleh tidak. (Pernyataan try harus memiliki satu klausa finally atau catch). Sintaks
dari klausa catch adalah:
catch (nama_kelas_pengecualian nama_variabel) {
pernyataan
}
dan sintaks klausa finally adalah
finally {
pernyataan
}
Semantik dari klausa finally yaitu pernyataan di dalam klausa finally akan dijamin
untuk dilaksanakan sebagai perintah akhir dari pernyataan try, tidak peduli apakah ada
pengecualian yang dilempar atau tidak. Pada dasarnya klausa finally dimaksudkan untuk
melakukan langkah pembersihan yang tidak boleh dihilangkan.
Ada beberapa kejadian di mana suatu program memang harus melempar pengecualian.
Misalnya apabila program tersebut menemukan adanya kesalahan pengurutan atau
kesalahan lain, tapi tidak ada cara yang tepat untuk menanganinya di tempat di mana
kesalahan tersebut ditemukan. Program bisa melempar pengecualian dengan harapan di
bagian lain pengecualian ini akan ditangkap dan diproses.
Untuk melempar pengecualian, kita bisa menggunakan pernyataan throw dengan sintaks :
throw objek_pengecualian;
objek_pengecualian harus merupakan objek yang bertipe kelas yang diturunkan dari
Throwable. Biasanya merupakan kelas turunan dari kelas Exception. Dalam banyak
kasus, biasanya objek tersebut dibuat dengan operator new, misalnya :
throw new ArithmeticException("Pembagian dengan nol");
Parameter dalam konstruktor adalah pesan kesalahan dalam objek pengecualian.
Pengecualian bisa dilempar baik oleh sistem (karena terjadinya kesalahan) atau oleh
pernyataan throw. Pengecualian ini ditangani dengan cara yang sama. Misalnya suatu
pengecualian dilempar dari pernyataan try. Jika pernyataan try tersebut memiliki klausa
catch untuk menangani tipe pengecualian tersebut, maka klausa ini akan melompat ke
klausa catch dan menjalankan perintah di dalamnya. Pengecualian tersebut telah
ditangani.
Setelah menangani pengecualian tersebut, komputer akan menjalankan klausa finally,
jika ada. Kemudian program akan melanjutkan program seperti biasa. Jika suatu
pengecualian tidak ditangkap dan ditangani, maka pengolahan pengecualian akan
berlanjut.
Jika pengecualian dilempar pada saat eksekusi suatu subrutin dan pengecualian tersebut
tidak ditangani di dalam subrutin yang sama, maka subrutin tersebut akan dihentikan
(setelah menjalankan klausa finally, jika tersedia). Kemudian rutin yang memanggil
subrutin tersebut memiliki kesempatan untuk menangani pengecualian tersebut. Artinya,
jika subrutin tersebut dipanggil di dalam pernyataan try dan memiliki klausa catch yang
cocok, maka klausa catch tersebut akan dieksekusi dan program akan berlanjut seperti
biasa.
Lagi-lagi jika rutin tersebut tidak menangani pengecualian tersebut, rutin tersebut akan
dihentikan dan rutin yang memanggilnya akan mencoba menangani pengecualian tersebut.
Pengecualian akan menghentikan program secara keseluruhan jika keseluruhan rantai
panggil subrutin hingga main() tidak menangani pengecualian tersebut.
Suatu subrutin yang mungkin melempar pengecualian dapat memberi tahu dengan
menambahkan klausa "throws nama_kelas_pengecualian" pada definisi suatu subrutin.
Misalnya :
static double akar(double A, double B, double C)
throws IllegalArgumentException {
// Menghasilkan akar yang lebih besar
// dari persamaan kuadrat A*x*x + B*x + C = 0.
// (Lempar pengecualian jika A == 0 atau B*B-4*A*C < 0.)
if (A == 0) {
throw new IllegalArgumentException("A tidak boleh nol.");
}
else {
double diskriminan = B*B - 4*A*C;
if (diskriminan < 0)
throw new IllegalArgumentException("Diskriminan < nol.");
return (-B + Math.sqrt(diskriminan)) / (2*A);
}
}
Seperti kita diskusikan pada bagian sebelumnya, perhitungan dalam subrutin ini memiliki
kondisi awal di mana A != 0 dan B*B-4*A*C >= 0. Subrutin akan melempar pengecualian
dengan tipe IllegalArgumentException jika salah satu dari kondisi awal ini tidak
dipenuhi. Jika kondisi ilegal ditemukan dalam suatu subrutin, melempar pengecualian
kadang kala menjadi pilihan yang lebih bijaksana. Jika program yang memanggil subrutin
tersebut mengetahui bagaimana cara yang tepat untuk menangani pengecualian tersebut,
program tersebut dapat menangkapnya. JIka tidak, maka program akan crash dan
programmer akan tahu apa yang harus diperbaiki.
Jika pernyataan throw tidak terdapat dalam pernyataan try yang menangkap kesalahan
tersebut, maka subrutin yang melemparnya harus dideklarasikan di awal bahwa subrutin
tersebut bisa melempar KesalahanBaca, yaitu dengan menambah klausa "throws
KesalahanBaca". Misalnya :
void ambilNamaUser() throws KesalahanBaca {
. . .
}
Klausa ini tidak diperlukan apabila KesalahanBaca didefinisikan sebagai turunan dari
kelas RuntimeException, karena pengecualian ini tidak wajib untuk ditangani.
Suatu subrutin yang ingin menangani KesalahanBaca dapat menggunakan pernyataan try
dengan klausa catch untuk menangkap KesalahanBaca. Misalnya
try {
ambilNamaUser();
olahNamaUser();
}
catch (KesalahanBaca kb) {
. . . // Tangani kesalahan
}
Ingat bahwa karena KesalahanBaca adalah kelas turunan dari Exception, maka klausa
catch dalam bentuk "catch (Exception e)" juga akan menangkap KesalahanBaca dan
juga objek yang bertipe Exception.
Kadang-kadang, ada gunanya untuk menyimpan data dalam objek pengecualian, misalnya :
class KapalMeledak extends RuntimeException {
Kapal kapal; // Kapal yang meledak
int lokasi_x, lokasi_y; // Lokasi tempat kapal meledak
KapalMeledak(String pesan, Kapal k, int x, int y) {
// Konstruktor: Buat objek KapalMeledak yang menyimpan
// pesan kesalahan dan informasi bahwa kapal k
// meledak pada lokasi x,y pada layar
super(pesan);
kapal = k;
lokasi_x = x;
lokasi_y = y;
}
}
Di sini, objek KapalMeledak berisi pesan kesalahan dan informasi tambahan tentang kapal
yang meledak, yang bisa digunakan dalam perintah berikut:
if ( kapalUser.isTertembak() )
throw new KapalMeledak("Anda Tertembak!", kapalUser, xLok, yLok);
Ingat bahwa kondisi objek KapalMeledak mungkin bukan suatu kesalahan. Mungkin
hanya merupakan jalan lain dari alur suatu game. Pengecualian bisa juga digunakan
sebagai percabangan besar seperti ini dengan cara yang lebih rapi.
Kelas dan Subrutin Pengecualian
Kemungkinan untuk melempar pengecualian akan berguna dalam penulisan subrutin dan
kelas umum yang digunakan oleh lebih dari satu program. Dalam hal ini orang yang
menulis subrutin atau kelas tersebut tidak memiliki cara yang umum untuk menangani
kesalahan tersebut. Terutama karena ia tidak tahu bagaimana subrutin atau kelas tersebut
akan digunakan. Dalam kondisi seperti itu, programmer pemula biasanya lebih memilih
untuk mencetak pesan kesalahan dan melanjutkan program, akan tetapi cara ini tidak
memuaskan karena mungkin akan ada masalah di kemudian hari. Mencetak pesan
kesalahan dan menghentikan program juga bukan solusi karena program tidak
berkesempatan untuk mengatasi kesalahan tersebut.
Program yang memanggil subrutin atau menggunakan kelas tersebut perlu tahu bahwa
suatu kesalahan telah terjadi. Dalam bahasa yang tidak memiliki pengecualian, satu-
satunya alternatif adalah mengembalikan nilai khusus atau mengeset nilai variabel tertentu
untuk memberi tahu bahwa suatu kesalahan telah terjadi. Misalnya, fungsi ambilDouble()
bisa saja mengembalikan nilai NaN jika input dari user salah. Akan tetapi, cara ini hanya
efektif jika program utama mengecek nilai keluarannya. Pengecualian akan lebih rapi jika
suatu subrutin memiliki cara untuk tahu apabila suatu kesalahan telah terjadi.
Asersi
Ingat bahwa kondisi awal adalah kondisi yang harus benar pada suatu titik di dalam
program sehingga program akan berjalan benar dari titik tersebut dan seterusnya. Dalam
hal ini, ada kemungkin bahwa suatu kondisi awal mungkin tidak bisa dipenuhi. Untuk itu,
akan lebih baik jika kita letakkan pernyataan if untuk mengujinya. Pertanyaan berikutnya
adalah apa yang harus kita lalukan jika kondisi awal tidak benar? Salah satunya adalah
melempar pengecualian, yang kemudian akan menghentikan program kecuali jika
pengecualian tersebut ditangkap dan ditangani di tempat lain.
Bahasa pemrograman seperti C dan C++ memiliki fasilitas untuk menambah asersi
(assertions) dalam program. Asersi dapat berbentuk assert(kondisi), di mana kondisi
adalah ekspresi bernilai boolean. Kondisi adalah suatu kondisi awal yang harus benar pada
satu titik di dalam program. Ketika komputer menemukan asersi dalam eksekusi suatu
program, ia akan mengevaluasi kondisi tersebut. Jika kondisi tersebut salah, maka program
akan berhenti. Jika benar, maka program akan terus berjalan. Asersi dalam bentuk ini tidak
tersedia pada Java, akan tetapi sesuatu yang mirip seperti ini bisa dilakukan dengan
pengecualian.
Bentuk asersi assert(kondisi) dapat diganti dalam bahasa Java dalam bentuk :
if (kondisi == false)
throw new IllegalArgumentException("Asersi gagal.");
Kita bisa mengganti pesan kesalahan dengan pesan yang lebih baik, dan mungkin akan
lebih cantik apabila kelas pengecualiannya juga diganti dengan kelas yang lebih spesifik.
Asersi sangat umum digunakan dalam pengujian dan debugging. Setelah kita merilis
program kita, kita tidak ingin program kita crash. Akan tetapi banyak program yang dibuat
pada dasarnya seperti
try {
.
. // Jalankan program
.
}
catch (Exception e) {
System.out.println("Pengecualian dalam program terjadi.");
System.out.println("Harap kirimkan pesan bug kepada programmernya.");
System.out.println("Detail kesalahan:"):
e.printStackTrace();
}
Jika suatu program memiliki banyak asersi, maka akan menyebabkan program lebih
lambat. Salah satu keuntungan asersi pada C dan C++ adalah asersi bisa "dimatikan".
Dalam arti jika program dikompilasi dengan cara lain, maka asersi akan dibuang dari
dalam program utama. Versi rilis dari program dikompilasi dengan asersi yang dimatikan.
Dengan cara ini versi rilis akan lebih efisien, karena komputer tidak perlu mengevaluasi
semua asersi tersebut. Keuntungan lainnya adalah kita tidak perlu membuang asersi
tersebut dari kode sumber programnya.
Ada cara seperti ini yang mungkin juga bisa diterapkan pada Java, yang tergantung dari
seberapa canggih kompiler kita. Misalnya kita tentukan suatu konstanta
static final boolean DEBUG = true;
dan kita menulis asersi seperti
if (DEBUG == true && kondisi <span style="color: #00bb00;"></span>==
false)
throw new IllegalArgumentException("Asersi Gagal.");
Karena DEBUG bernilai true, maka nilai "DEBUG == true && kondisi == false" sama
dengan nilai kondisi, sehingga pernyataan if ini sama dengan pengujian suatu kondisi
awal. Sekarang misalnya kita telah selesai melakukan debugging. Sebelum kita
mengkompilasi versi rilis suatu program, kita ganti definisi DEBUG menjadi
static final boolean DEBUG = false;
Sekarang, nilai "DEBUG == true && kondisi == false" selalu bernilai false, dan
kompiler canggih akan bisa mendeteksi ini pada saat kompilasi. Karena nilai if ini akan
selalu bernilai false, kompiler canggih akan mengabaikan perintah ini dalam hasil
kompilasinya, karena if ini tidak akan pernah dieksekusi. Hasilnya, kode ini tidak akan
dimasukkan ke dalam versi rilis program. Dan kita hanya cukup mengganti satu baris saja
pada kode sumbernya.
Bab VIII - Pengenalan Struktur Data dan Algoritma
Komputer bisa menjadi canggih seperti sekarang karena struktur data. Struktur dala adalah
koleksi dari suatu data yang saling berhubungan. Suatu objek adalah struktur data, tapi tipe
struktur data seperti ini -- yang memiliki sedikit variabel instansi -- hanya awalnya saja.
Dalam banyak hal, programmer akan membuat struktur data kompleksnya sendiri, yaitu
dengan menghubungkan objek satu sama lain.
Bab ini akan membahas tentang array dan beberapa struktur data dalam Java.
Array
Array adalah struktur data yang terdiri dari item berurutan, di mana semua itemnya bertipe
sama. Dalam Java, item dalam array selalu dinomori dari nol hingga nilai maksimum
tertentu, yang nilainya ditentukan pada saat array tersebut dibuat. Misalnya, suatu array
berisi 100 bilangan bulat, yang dinomori dari nol hingga 99. Item dalam array bisa bertipe
salah satu tipe Java primitif. Item-item ini bisa juga berupa referensi ke objek, sehingga,
misalnya kita bisa membuat array yang berisi semua komponen dalam applet.
Bagian ini akan membahas bagaimana array dibuat dan digunakan pada Java. Juga
mencakup kelas standar java.util.ArrayList. Suatu objek bertipe ArrayList sangat
mirip dengan array dari Object, akan tetapi ia bisa bertambah ukuran secara dinamis.
Membuat dan Menggunakan Array
Jika sekumpulan data digabungkan dalam satu unit, hasilnya adalah suatu struktur data.
Data struktur dapat berupa struktur yang sangat kompleks, akan tetapi dalam banyak
aplikasi, data struktur yang cocok hanya terdiri dari kumpulan data berurutan. Struktur data
sederhana seperti ini bisa berupa array atau record.
Istilah "record" sebetulnya tidak digunakan pada Java. Suatu record pada intinya mirip
dengan objek pada Java yang hanya memiliki variabel instansi tanpa metode instansi.
Beberapa bahasa pemrograman lain yang tidak mendukung objek biasanya mendukung
record. Dalam bahasa C yang bukan bahasa berorientasi objek, misalnya, memiliki tipe
data record, dimana pada C disebut "struct". Data pada record -- dalam Java, adalah
variabel instansi suatu objek -- disebut field suatu record. Masing-masing item disebut
nama field. Dalam Java, nama field adalah nama variabel instansi. Perbedaan sifat dari
suatu record adalah bahwa item pada record dipanggil berdasarkan namanya, dan field
yang berbeda dapat berupa tipe yang berbeda. Misalnya, kelas Orang didefisikan sebagai :
class Orang {
String nama;
int nomorID;
Date tanggalLahir;
int umur;
}
maka objek dari kelas Orang bisa disebut juga sebagai record dengan 4 field. Nama
fieldnya adalah nama, nomorID, tanggalLahir dan umur. Lihat bahwa tipe datanya
berbeda-beda yaitu String, int, dan Date.
Karena record hanya merupakan bagian lebih kecil dari objek, kita tidak akan bahas lebih
lanjut di sini.
Seperti record, suatu array adalah kumpulan item. Akan tetapi, item pada record dipanggil
dengan nama, sedangkan item pada array dinomori, dan masing-masing item dipanggil
besarkan nomor posisi pada array tersebut. Semua item pada array harus bertipe sama.
Definisi suatu array adalah : kumpulan item bernomor yang semuanya bertipe sama.
Jumlah item dalam suatu array disebut panjang array. Nomor posisi dari array disebut
indeks item tersebut dalam array. Tipe dari item tersebut disebut tipe dasar dari array.
Tipe dasar suatu array bisa berupa tipe Java apa saja, baik berupa tipe primitif, nama kelas,
atau nama interface. Jika tipe dasar suatu array adalah int, maka array tersebut disebut
"array int". Suatu array bertipe String disebut "array String". Akan tetapi array bukan
urutan int atau urutan String atau urutan nilai bertipe apapun. Lebih baik jika array adalah
urutan variabel bertipe int atau String atau tipe lainnya.
Seperti biasa, ada dua kemungkinan kegunaan variabel : sebagai nama suatu lokasi di
memori, dan nama suatu nilai yang disimpan pada lokasi memori. Setiap posisi pada array
bersifat seperti variabel. Setiap posisi dapat menyimpan nilai dengan tipe tertentu (yaitu
tipe dasar array). Isinya bisa diganti kapanpun. Nilai tersebut disimpan di dalam array.
Array merupakan kontainer bukan kumpulan nilai.
Item pada array (maksudnya setiap anggota variabel dalam array tersebut) sering juga
disebut elemen array. Dalam Java, elemen array selalu dinomori mulai dari nol. Yaitu,
indeks dari elemen pertama suatu array adalah nol. Jika panjang array adalah N, maka
indeks elemen terakhir adalah N-1. Sekali array dibuat, maka panjangnya tidak bisa diubah
lagi.
Dalam Java, array adalah objek. Ada beberapa konsekuensi yang lahir dari fakta ini. Array
harus dibuat dengan operator new. Variabel tidak bisa menyimpan array; variabel hanya
bisa merujuk pada array. Variabel lain yang bisa merujuk array juga bisa bernilai null
yang berarti ia tidak merujuk pada lokasi memori apapun. Seperti objek lain, array juga
bagian dari suatu kelas, di mana seperti kelas lain adalah kelas turunan dari kelas Object.
Elemen array pada dasarnya adalah variabel instansi dalam objek array, kecuali mereka
dipanggil dalam indeksnya bukan namanya.
Meskipun array berupa objek, ada beberapa perbedaan antara array dan objek lainnya, dan
ada beberapa fitur khusus Java untuk membuat dan menggunakan array.
Misalnya A adalah variabel yang merujuk pada suatu array. Maka indeks k di dalam A bisa
dipanggil dengan A[k]. Item pertama adalah A[0], yang kedua adalah A[i], dan seterusnya.
A[k] adalah suatu variabel dan bisa digunakan seperti variabel lainnya. Kita bisa
memberinya nilai, bisa menggunakannya dalam ekspresi, dan bisa diberikan sebagai
parameter pada subrutin. Semuanya akan didiskusikan di bawah nanti. Untuk sekarang
ingat sintaks berikut
variabel_array [ekspresi_integer]
untuk merujuk pada suatu array.
Meskipun setiap array merupakan suatu objek, kelas array tidak harus didefinisikan
sebelumnya. Jika suatu tipe telah ada, maka kelas array dari tipe tersebut otomatis ada. Jika
nama suatu tipe adalah TipeDasar, maka nama kelas arraynya adalah TipeDasar[].
Artinya, suatu objek yang diciptakan dari kelas TipeDasar[] adalah array dari item yang
tiap itemnya bertipe TipeDasar. Tanda kurung "[]" dimaksudkan untuk mengingat sintaks
untuk mengambil item di dalam suatu array. "TipeDasar[]" dibaca seperti "array
TipeDasar". Mungkin perlu juga dijelaskan bahwa jika KelasA adalah kelas turunan dari
KelasB maka KelasA[] otomatis menjadi kelas turunan KelasB[].
Tipe dasar suatu array dapat berupa tipe apapun yang ada atau sudah didefinisikan pada
Java. Misalnya tipe primitif int akan diturunkan kelas array int[]. Setiap elemen di
dalam array int[] adalah variabel yang memiliki tipe int dan bisa diisi dengan nilai
dengan tipe int. Dari kelas yang bernama String diturunkan tipe array String[]. Setiap
elemen di dalam array String[] adalah variabel dengan tipe String, yang bisa diisi
dengan nilai bertipe String. Nilai ini bisa null atau referensi ke objek yang bertipe
String (dan juga kelas turunan dari String)
Mari kita lihat contoh lebih konkrotnya menggunakan array bilangan bulat sebagai contoh
pertama kita. Karena int[] adalah sebuah kelas, maka kita bisa menggunakannya untuk
mendeklarasikan variabel. Misalnya,
int[] daftar;
yang membuat variabel bernama daftar dengan tipe int[]. Variabel ini bisa menunjuk
pada array int, akan tetapi nilai awalnya adalah null (jika merupakan variabel anggota
suatu kelas) atau tak tentu (jika merupakan variabel lokal di dalam suatu metode). Operator
new digunakan untuk membuat objek array baru, ayng kemudian bisa diberikan kepada
daftar. Sintaksnya sama seperti sintaks sebelumnya, yaitu :
daftar = new int[5];
membuat array 5 buah integer. Lebih umum lagi, konstruktor "new TipeDasar[N]"
digunakan untuk membuat array bertipe TipeDasar[]. Nilai N di dalam kurung
menyatakan panjang array, atau jumlah elemen yang bisa ditampung. Panjang array adalah
variabel instansi di dalam objek array, sehingga array tahu berapa panjangnya. Kita bisa
mendapatkan panjang suatu array, misalnya daftar menggunakan daftar.length (akan
tetapi kita tidak bisa mengubahnya)
Hasil dari pernyataan "daftar = new int[5];" dapat diilustrasikan sebagai berikut
Perlu dicatat bahwa array integer secara otomatis diisi dengan nol. Dalam Java, array yang
baru dibuat akan selalu diisi dengan nilai tertentu: nol untuk angka, false untuk boolean,
karakter dengan nilai Unicode 0 untuk char dan null untuk objek.
Elemen di dalam array daftar dapat dirujuk dengan daftar[0], daftar[1], daftar[2],
daftar[3], dan daftar[4] (ingat juga bahwa nilai indeks terbesar adalah panjangnya
array dikurang satu). Akan tetapi, referensi array sebetulnya lebih umum lagi. Tanda
kurung di dalam referensi array bisa berisi ekspresi apapun yang nilainya suatu integer.
Misalnya jika idks adalah variabel bertipe int, maka daftar[idks] dan
daftar[2*idks+3] secara sintaks benar.
Contoh berikut akan mencetak semua isi integer di dalam array daftar ke layar :
for (int i = 0; i < daftar.length; i++) {
System.out.println( daftar[i] );
}
Perulangan pertama adalah ketika i = 0, dan daftar[i] merujuk pada daftar[0]. Jadi
nilai yang disimpan pada variabel daftar[0] akan dicetak ke layar. Perulangan kedua
adalah i = 1, sehingga nilai daftar[i] dicetak. Perulangan berhenti setelah mencetak
daftar[4] dan i menjadi sama dengan 5, sehingga kondisi lanjutan "i <
daftar.length" tidak lagi benar. Ini adalah contoh umum dari menggunakan perulangan
untuk mengolah suatu array.
Penggunaan suatu variabel dalam suatu program menyatakan lokasi di memori. Pikirkan
sesaat tentang apa yang akan komputer lakukan ketika ia menemukan referensi ke elemen
suatu array daftar[k] ketika program berjalan. Komputer harus menentukan lokasi
memori di mana ia dijadikan referensi. Untuk komputer, daftar[k] berarti : "Ambil
pointer yang disimpan di dalam variabel daftar. Ikuti pointer ini untuk mencari objek
array. Ambil nilai k. Pergi ke posisi ke-k dari array tersebut, dan di sanalah alamat memori
yang Anda ingin."
Ada dua hal yang bisa salah di sini. Misalnya nilai daftar adalah null. Dalam kasus ini,
maka daftar tidak memiliki referensi sama sekali. Percobaan merujuk suatu elemen pada
suatu array kosong adalah suatu kesalahan. Kasus ini akan menampilkan pesan kesalahan
"pointer kosong". Kemungkinan kesalahan kedua adalah jika daftar merujuk pada suatu
array, akan tetapi k berada di luar rentang indeks yang legal. Ini akan terjadi jika k < 0
atau jika k >= daftar.length. Kasus ini disebut kesalahan "indeks array keluar batas".
Ketika kita menggunakan array dalam program, kita harus selalu ingat bahwa kedua
kesalahan tersebut mungkin terjadi. Dari kedua kasus di atas, kesalahan indeks array keluar
batas adalah kesalahan yang lebih sering terjadi.
Untuk suatu variabel array, seperti variabel lainnya, kita bisa mendeklarasikan dan
mengisinya dengan satu langkah sekaligus, misalnya :
int[] daftar = new int[5];
Jika daftar merupakan variabel lokal dalam subrutin, maka perintah di atas akan persis
sama dengan dua perintah :
int[] daftar;
daftar = new int[5];
(Jika daftar adalah variabel instansi, tentukan kita tidak bisa mengganti "int[] daftar
= new int[5];" dengan "int[] daftar; daftar = new int[5];" karena ini hanya
bisa dilakukan di dalam subrutin)
Array yang baru dibuat akan diisi dengan nilai awal yang tergantung dari tipe dasar array
tersebut seperti dijelaskan sebelumnya. Akan tetapi Java juga menyediakan cara untuk
memberi isi array baru dengan daftar isinya. Dalam pernyataan yang untuk membuat array,
ini bisa dilakukan dengan menggunakan penginisialiasi array (array initializer), misalny :
int[] daftar = { 1, 4, 9, 16, 25, 36, 49 };
akan membuat array baru yang berisi 7 nilai, yaitu 1, 4, 9, 16, 25, 36, dan 49, dan mengisi
daftar dengan referensi ke array baru tersebut. Nilai daftar[0] berisi 1, nilai daftar[1]
berisi 4, dan seterusnya. Panjang daftar adalah 7, karena kita memberikan 7 nilai awal
kepada array ini.
Suatu penginisialisasi array memiliki bentuk daftar angka yang dipisahkan dengan koma
dan diapit dengan tanda kurung kurawal {}. Panjang array tersebut tidak perlu diberikan,
karena secara implisit sudah bisa ditentukan dari jumlah daftar angkanya. Elemen di dalam
penginisialisasi array tidak harus selalu berbentuk konstanta. Juga bisa merupakan variabel
atau ekspresi apa saja, asalkan nilainya bertipe sama dengan tipe dasar array tersebut.
Misalnya, deklarasi berikut membuat array dari delapan jenis Color beberapa warna telah
dibentuk dengan ekspresi "new Color(r,g,b);"
Color[] palette =
{
Color.black,
Color.red,
Color.pink,
new Color(0,180,0), // hijau gelap
Color.green,
Color.blue,
new Color(180,180,255), // biru muda
Color.white
};
Inisialisasi array bentuk seperti ini hanya bisa digunakan dalam deklarasi suatu variabel
baru, akan tetapi tidak bisa digunakan seperti operator pemberi nilai (=) di tengah-tengah
suatu program. Akan tetapi ada cara lain yang bisa digunakan sebagai pernyataan
pemberian nilai atau diberikan ke dalam subrutin. Yaitu menggunakan jenis lain dari
operator new untuk membuat atau menginisialisasi objek array baru. (Cara ini agak kaku
dengan sintaks aneh, seperti halnya sintaks kelas anonim yang telah didiskusikan
sebelumnya). Misalnya untuk memberi nilai kepada suatu variabel daftar, kita bisa
menggunakan :
daftar = new int[] { 1, 8, 27, 64, 125, 216, 343 };
Sintaks umum dari bentuk operator new seperti ini adalah
new TipeDasar [ ] { daftar_nilai_nilai }
Ini adalah suatu ekspresi yang isinya merupakan objek, dan bisa digunakan untuk banyak
situasi di mana suatu objek dengan tipe TipeDasar dipentingkan. Misalnya buatTombol
merupakan metode yang mengambil array String sebagai parameter, maka kita bisa
menuliskan
buatTombol( new String[] { "Stop", "Jalan", "Berikut", "Sebelum" } );
Catatan terakhir : untuk alasan sejarah, maka deklarasi
int[] daftar;
akan bernilai sama dengan
int daftar[];
di mana sintaks tersebut digunakan dalam bahasa C dan C++. Akan tetapi sintaks alternatif
ini tidak begitu masuk akan dalam Java, atau mungkin lebih baik dihindari. Lagian,
maksudnya adalah mendeklarasikan variabel dengan tipe tertentu dan namanya adalah
int[]. Akan lebih masuk akan untuk mengikuti siintaks "nama_tipe nama_variabel"
seperti pada bentuk bertama.
Pemrograman dengan Array
Array merupakan jenis struktur data yang sangat dasar dan sangat penting. Teknik
pengolahan array merupakan teknik pemrograman yang paling penting yang kita harus
kuasai. Dua jenis teknik pengolahan array -- pencarian dan pengurutan -- akan dibahas
kemudian. Bagian ini akan memperkenalkan beberapa ide dasar pengolahan array secara
umum.
Dalam banyak hal, pengolahan array berarti menggunakan operasi yang sama kepada
setiap elemen di dalam array. Biasanya sering dilakukan dengan perulangan for.
Perulangan untuk mengolah semua elemen dalam array A dapat ditulis dalam bentuk :
// lakukan inisialiasi yang diperlukan sebelumnya
for (int i = 0; i < A.length; i++) {
. . . // proses A[i]
}
Misalnya, A adalah array dengan tipe double[]. Misalnya kita ingin menjumlah semua
nilai dalam array tersebut. Algoritma umum untuk melakukannya adalah :
Mulai dengan 0;
Tambah A[0]; (proses elemen pertama di dalam A)
Tambah A[1]; (proses elemen kedua di dalam A)
.
.
.
Tambah A[ A.length - 1 ]; (proses elemen terakhir di dalam A)
Dengan menggunakan pengetahuan yang kita telah pelajari tentang perulangan, kita bisa
ubah algoritma di atas menjadi bentuk perulangan for seperti berikut:
double jumlah; // Jumlah nilai di dalam A
jumlah = 0; // Mulai dengan 0
for (int i = 0; i < A.length; i++)
jumlah += A[i]; // tambah A[i] ke dalam jumlah untuk i = 0, 1, ...,
A.length - 1
Lihat bahwa kondisi kelanjutan "i < A.length" menyatakan bahwa nilai i terakhir yang
akan diolah adalah A.length - 1 yaitu elemen terakhir dalam array. Ingat bahwa kita
menggunakan "<" bukan "<=" karena dengan "<=" komputer akan memberikan kesalahan
indeks di luar batas.
Pada akhirnya, nanti Anda akan bisa membuat perulangan seperti di atas di luar kepala.
Kita akan lihat beberapa contohnya. Di sini perulangan akan menghitung banyaknya
elemen di dalam array A yang nilainya kurang dari nol :
int hitung; // Untuk menghitung elemen
hitung = 0; // Mulai dengan nol
for (int i = 0; i < A.length; i++) {
if (A[i] < 0.0) // Jika elemen ini kurang dari nol
hitung++; // tambah hitung dengan 1
}
// Di sini nilai "hitung" adalah banyaknya elemen yang kurang dari 0.
Kita bisa mengganti "A[i] < 0.0" jika kita ingin menghitung banyaknya elemen di dalam
array yang memiliki sifat tertentu. Variasinya akan memiliki tema yang sama. Misalnya
kita ingin menghitung banyaknya elemen di dalam array A yang sama dengan elemen
sesudahnya. Elemen setelah A[i] adalah A[i+1], sehingga kita bisa mengganti klausa if
dengan "if (A[i] == A[i+1])". Akan tetapi tunggu dulu : Tes ini tidak bisa digunakan
apabila A[i] adalah elemen terakhir dalam array, karena tidak ada lagi array sesudahnya.
Komputer akan menolak pernyataan ini. Sehingga kita harus berhenti satu elemen sebelum
array terakhir, sehingga menjadi
int hitung = 0;
// lihat kondisi for berubah dibandingkan dengan contoh sebelumnya
for (int i = 0; i < A.length - 1; i++) {
if (A[i] == A[i+1])
hitung++;
}
Masalah umum lainnya adalah mencari nilai terbesar di dalam array A. Strateginya adalah
lihat semua isi array, catat nilai terbesar saat itu. Kita akan simpan nilai terbesar yang kita
temui dalam variabel maks. Pada saat kita melihat elemen array satu per satu, kapanpun
kita melihat nilai elemen tersebut lebih besar dari maks kita akan mengganti nilai maks
dengan nilai yang lebih besar tersebut. Setelah semua elemen array diproses, maka maks
merupakan nilai elemen terbesar di dalam array tersebut. Pertanyaannya adalah, apa nilai
awal maks? Salah satu kemungkinannya adalah mulai dengan nilai maks sama dengan
A[0], baru kemudian melihat isi elemen array lainnya mulai dengan A[1]. Misalnya,
double maks = A[0]; // nilai maks berisi elemen array pertama
for (int i = 1; i < A.length; i++) { // i mulai dari elemen kedua
if (A[i] > maks)
max = A[i];
}
// Di sini maks berisi nilai elemen array yang paling besar
(Ada masalah yang lebih penting di sini. Java membolehkan array memiliki panjang nol.
Artinya bahkan A[0] pun tidak ada di dalam array, sehingga memanggil A[0] akan
menghasilkan kesalahan indeks keluar batas. Akan tetapi array biasanya array dengan
panjang nol biasanya sesuatu yang kita ingin hindarkan dalam kehidupan sehari-hari.
Lagian apa artinya mencari nilai terbesar di dalam suatu array yang panjangnya nol?)
Contoh terakhir dari operasi array, misalnya kita ingin mengkopi suatu array. Untuk
mengkopi array A, tidak cukup untuk menggunakan perintah
double[] B = A;
karena perintah ini tidak membuat objek array baru. Yang dibuat di sini adalah variabel
baru yang merujuk pada objek yang sama dengan A. (Sehingga perubahan yang terjadi
pada A[i] akan juga menyebabkan B[i] berubah). Untuk membuat array baru yang
merupakan kopi dari array A, kita harus membuat objek array baru, dan mengkopi isinya
satu per satu dari array A ke array baru, sehingga
// Buat objek array baru, yang panjangnya sama dengan panjang A
double[] B = new double[A.length];
di mana arraySumber dan arrayTujuan bisa berbentuk array dengan tipe apapun. Nilai
akan dikopi dari arraySumber ke arrayTujuan. jumlah adalah berapa banyak elemen
yang akan dikopi. Nilai akan dikopi dari arraySumber mulai dari posisi
indeksAwalSumber dan akan disimpan pada arrayTujuan mulai dari posisi
indeksAwalTujuan. Misalnya kita akan mengkopi array A, maka kita bisa menggunakan
perintah
double B = new double[A.length];
System.arraycopy( A, 0, B, 0, A.length );
Suatu tipe array, misalnya double[] adalah tipe Java biasa, sehingga kita bisa
menggunakannya seperti tipe-tipe Java lainnya. Termasuk juga digunakan sebagai
parameter formal di dalam suatu subrutin. Juga bisa digunakan sebagai tipe keluaran suatu
fungsi. Misalnya, kita bisa menulis fungsi yang membuat kopi array dengan tipe double
sebagai berikut :
double[] kopi( double[] sumber ) {
// Membuat dan mengembalikan kopi array sumber
// Jika sumber null, maka kembalikan null
if ( sumber == null )
return null;
double[] kpi; // Kopi array sumber
kpi = new double[sumber.length];
System.arraycopy( sumber, 0, kpi, 0, sumber.length );
return kpi;
}
Rutin main() memiliki parameter dengan tipe String[] yang merupakan array String.
Ketika sistem memanggil rutin main(), string di dalam array ini adalah parameter dari
baris perintah. Jika kita menggunakan konsol, user harus mengetikkan perintah untuk
menjalankan program. User bisa menambahkan input tambahan dalam perintah ini setelah
nama program yang akan dijalankan.
Misalnya, jika kelas yang memiliki rutin main() bernama programKu, maka user bisa
menjalankan kelas tersebut dengan perintah "java programKu" di konsol. Jika kita tulis
dengan "java programKu satu dua tiga", maka parameter dari baris perintahnya adalah
"satu", "dua", dan "tiga". Sistem akan memasukkan parameter-parameter ini ke dalam
array String[] dan memberikan array ini pada rutin main().
Berikut ini adalah contoh program sederhana yang hanya mencetak parameter dari baris
perintah yang dimasukkan oleh user.
public class CLDemo {
public static void main(String[] args) {
System.out.println("Anda memasukkan " + args.length
+ " parameter dari baris perintah");
if (args.length > 0) {
System.out.println("Parameter tersebut adaah :");
for (int i = 0; i < args.length; i++)
System.out.println(" " + args[i]);
}
} // akhir main()
} // akhir kelas CLDemo
Perhatikan bahwa parameter args tidak mungkin null meskipun tidak ada parameter yang
dimasukkan. Jika tidak ada parameter dari baris perintah yang dimasukkan, maka panjang
array ini adalah nol.
Hingga sekarang, contoh yang telah diberikan adalah bagaimana mengolah array dengan
mengakses elemennya secara berurutan (sequential access). Artinya elemen-elemen array
diproses satu per satu dalam urutan dari awal hingga akhir. Akan tetapi salah satu
keuntungan array adalah bahwa array bisa digunakan untuk mengakses elemennya secara
acak, yaitu setiap elemen bisa diakses kapan saja secara langsung.
Misalnya, kita ambil contoh suatu masalah yang disebut dengan masalah ulang tahun:
Misalnya ada N orang di dalam suatu ruangan. Berapa kemungkinan dua orang di dalam
ruangan tersebut memiliki ulang tahun yang sama (yang dilahirkan pada tanggal dan bulan
yang sama, meskipun tahunnya berbeda)? Kebanyakan orang salah menerka jawabannya.
Sekarang kita lihat dengan versi masalah yang berbeda: Misalnya kita memilih orang
secara acak dan menanyakan ulang tahunnya. Berapa orang yang Anda harus tanya untuk
mendapatkan hari ulang tahun yang sama dengan orang sebelumnya?
Tentunya jawabannya akan tergantung pada faktor yang bersifat acak, akan tetapi kita bisa
simulasikan dengan program komputer dan menjalankan beberapa kali hingga kita tahu
berapa kira-kira orang harus dicek.
Untuk mensimulasikan percobaan ini, kita harus mencatat semua ulang tahun yang kita
sudah tanyakan. Ada 365 kemungkinan hari ulang tahun (Kita abaikan sementara tahun
kabisat). Untuk setiap kemungkinan hari ulang tahun, kita perlu tahu, apakah hari ulang
tahun tersebut telah digunakan? Jawabannya adalah nilai boolean true atau false. Untuk
menyimpan data ini, kita bisa gunakan array dari 365 nilai boolean:
boolean[] sudahDitanya;
sudahDitanya = new boolean[365];
Tanggal-tanggal pada satu tahun dinomori dari 0 hingga 364. Nilai sudahDitanya[i] akan
bernilai true jika orang yang kita tanya berulang tahun pada hari tersebut. Pada awalnya
semua nilai pada array sudahDitanya[i] bernilai false. Ketika kita memilih satu orang
dan menanyakan hari ulang tahunnya, misalnya i, kita akan mengecek terlebih dahulu
apakah sudahDitanya[i] bernilai true. Jika tidak maka orang ini adalah orang kedua
dengan ulang tahun yang sama. Artinya kita sudah selesai.
Jika sudahDitanya[i] bernilai false, maka belum ada orang sebelum ini yang memiliki
hari ulang tahun tersebut. Kita akan ganti sudahDitanya[i] dengan true, kemudian kita
akan tanyakan lagi kepada orang lain, dan begitu seterusnya hingga semua orang di dalam
ruangan ditanyakan.
static void masalahUlangTahun() {
// Melakukan simulasi dengan memilih seseorang secara acak
// dan mengecek hari ulang tahunnya. Jika hari ulang tahunnya
// sama dengan orang yang pernah kita tanya sebelumnya,
// hentikan program dan laporkan berapa orang yang sudah dicek
boolean[] sudahDitanya;
// Untuk mencatat ulang tahun yang sudah ditanyakan
// Nilai true pada sudahDitanya[i] berarti orang lain
// sudah ada yang berulang tahun pada hari i
int hitung;
// Jumlah orang yang sudah pernah ditanya
hitung = 0;
while (true) {
// Ambil ulang tahun secara acak dari 0 hingga 364
// Jika ulang tahun telah ditanya sebelumnya, keluar
// Jika tidak catat dalam array
int ultah; // Ulang tahun seseorang
ultah = (int)(Math.random()*365);
hitung++;
if ( sudahDitanya[ultah] )
break;
sudahDitanya[ultah] = true;
}
} // akhir masalahUlangTahun()
Subrutin ini menggunakan fakta bahwa array boolean yang baru dibuat memiliki seluruh
elemen yang bernilai false. Jika kita ingin menggunakan array yang sama untuk simulasi
kedua, kita harus mereset ulang semua elemen di dalamnya menjadi false kembali dengan
perulangan for
for (int i = 0; i < 365; i++)
sudahDitanya[i] = false;
Array paralel adalah menggunakan beberapa array dengan indeks yang sama. Misalnya
kita ingin membuat beberapa kolom secara paralel -- array x di kolom pertama, array y di
kolom kedua, array warna di kolom ketiga, dan seterusnya. Data untuk baris ke-i bisa
didapatkan dari masing-masing array ini. Tidak ada yang salah dengan cara ini, akan tetapi
cara ini berlawanan dengan filosofi berorientasi objek yang mengumpulkan data yang
berhubungan di dalam satu objek. Jika kita mengikuti aturan seperti ini, amaka kita tidak
harus membayangkan hubungan data yang satu dan yang lainnya karena semua data akan
dikelompokkan di dalam satu tempat.
Misalnya saya menulis kelas seperti
class DataString {
// Data dari salah satu pesan
int x,y; // Posisi pesan
Color warna; // Warna pesan
}
Untuk menyimpan data dalam beberapa pesan, kita bisa menggunakan array bertipe
DataString[], yang kemudian dideklarasikan sebagai variabel instansi dengan nama data
sehingga
DataString[] data;
Isi dari data bernilai null hingga kita membuat array baru, misalnya dengan
data = new DataString[JUMLAH_PESAN];
Setelah array ini dibuat, nilai setiap elemen array adalah null. Kita ingin menyimpan data
di dalam objek yang bertipe DataString, akan tetapi tidak ada objek yang dibuat. Yang
kita sudah buat hanyalah kontainernya saja. Elemen di dalamnya berupa objek yang belum
pernah kita buat. Untuk itu elemen di dalamnya bisa kita buat dengan perulangan for
seperti :
for (int i = 0; i < JUMLAH_PESAN; i++)
data[i] = new DataString();
Sekarang kita bisa mengambil data setiap pesan dengan data[i].x, data[i].y, dan
data[i].warna.
Terakhir berkaitan dengan pernyataan switch. Misalnya kita memiliki nilai bulan dari 0
hingga 11, yang melambangkan bulan dalam satu tahun dari Januari hingga Desember.
Kita ingin mencetaknya di layar, dengan perintah
switch (bulan) {
case 0:
bulanString = "Januari";
break;
case 1:
bulanString = "Februari";
break;
case 2:
bulanString = "Maret";
break;
case 3:
bulanString = "April";
break;
.
.
.
case 11:
bulanString = "Desember";
break;
default:
bulanString = "Salah bulan";
}
Kita bisa mengganti keseluruhan perintah switch tersebut dengan menggunakan array,
misalnya dengan array namaBulan yang dideklarasikan sebagai berikut :
static String[] namaBulan = { "Januari", "Februari", "Maret",
"April", "Mei", "Juni", "Juli", "Agustus", "September",
"Oktober", "November", "Desember" };
Kemudian kita bisa ganti keseluruhan switch di atas dengan
bulanString = namaBulan[bulan];
Sangat mudah bukan?
Array Dinamis
Panjang suatu array tidak bisa diubah setelah dibuat. Akan tetapi, sering kali jumlah data
yang disimpan dalam array bisa berubah setiap saat. Misalnya dalam contoh berikut : Suatu
array menyimpan baris teks dalam program pengolah kata. Atau suatu array yang
menyimpan daftar komputer yang sedang mengakses halaman suatu website. Atau array
yang berisi gambar yang ditambahkan user oleh program gambar. Di sini jelas, bahwa kita
butuh sesuatu yang bisa menyimpan data di mana jumlahnya tidak tetap.
Array Setengah Penuh
Bayangkan suatu aplikasi di mana sejumlah item yang ingin kita simpan di dalam array
akan berubah-ubah sepanjang program tersebut berjalan. Karena ukuran array tidak bisa
diubah, maka variabel terpisah digunakan untuk menyimpan berapa banyak sisa tempat
kosong yang masih bisa diisi di dalam array.
Bayangkan misalnya, suatu program yang membaca bilangan bulat positif dari user,
kemudian menyimpannya untuk diproses kemudian. Program akan berhenti membaca
input apabila input yang diterima bernilai 0 atau kurang dari nol. Bilangan input n tersebut
kita simpa di dalam array bilangan dengan tipe int[]. Katakan banyaknya bilangan yang
bisa disimpan tidak lebih dari 100 buah. Maka ukuran array bisa dibuat 100.
Akan tetapi program tersebut harus melacak berapa banyak bilangan yang sudah diambil
dari user. Kita gunakan variabel terpisah bernama jmlBilangan. Setiap kali suatu bilangan
disimpan di dalam array, nilai jmlBilangan akan bertambah satu.
Sebagai contoh sederhana, masi kita buat program yang mengambil bilangan yang diinput
dari user, kemudian mencetak bilangan-bilangan tersebut dalam urutan terbalik. (Ini adalah
contoh pengolahan yang membutuhkan array, karena semua bilangan harus disimpan pada
suatu tempat. Banyak lagi contoh program misalnya, mencari jumlah atau rata-rata atau
nilai maksimum beberapa bilangan, bisa dilakukan tanpa menyimpan bilangan tersebut
satu per satu)
public class BalikBilanganInput {
while (true) {
System.out.print("? ");
bil = KonsolIO.ambilInt();
if (bil <= 0)
break;
bilangan[jmlBilangan] = bil;
jmlBilangan++;
}
} // akhir main();
Setelah beberapa pemain masuk ke dalam game, variabel jumlahPemainAktif akan lebih
dari 0, dan objek pemailn akan disimpan dalam array, misalnya ArrayPemain[0],
ArrayPemain[1], ArrayPemain[2], dan seterusnya. Ingat bahwa pemain
ArrayPemain[jumlahPemainAktif] tidak ada. Prosedur untuk menambah pemain baru,
secara sederhana :
// Tambah pemain di tempat kosong
ArrayPemain[jumlahPemainAktif] = pemainBaru;
// And increment playerCt to count the new player.
jumlahPemainAktif++;
Untuk menghapus seorang pemain mungkin sedikit lebih sulit karena kita tidak ingin
meninggalkan lubang di tengah-tengah array. Misalnya kita ingin menghapus pemain pada
indeks k pada ArrayPemain. Jika kita tidak peduli urutan pemainnya, maka salah satu
caranya adalah memindahkan posisi pemain terakhir ke posisi pemain yang meninggalkan
game, misalnya :
ArrayPemain[k] = ArrayPemain[jumlahPemainAktif - 1];
jumlahPemainAktif--;
Pemain yang sebelumnya ada di posisi k, tidak lagi ada di dalam array. Pemain yang
sebelumnya ada di posisi jumlahPemainAktif -1 sekarang ada di array sebanyak 2 kali.
Akan tetapi sekarang ia berada di bagian yang valid, karena nilai jumlahPemainAktif kita
kurangi dengan satu. Ingat bahwa setiap elemen di dalam array harus menyimpan satu
nilai, akan tetapi satu-satunya nilai dari posisi 0 hingga jumlahPemainAktif - 1 akan
tetap diproses seperti biasa.
Misalnya kita ingin menghapus pemain di posisi k, akan tetapi kita ingin agar urutan
pemain tetap sama. Untuk melakukannya, semua pemain di posisi k+1 ke atas harus
dipindahkan satu posisi ke bawah. Pemain k+ mengganti pemain k yang keluar dari game.
Pemain k+2 mengganti pemain yang pindah sebelumnya, dan berikutnya. Kodenya bisa
dibuat seperti
for (int i = k+1; i < jumlahPemainAktif; i++) {
ArrayPemain[i-1] = ArrayPemain[i];
}
jumlahPemainAktif--;
Perlu ditekankan bahwa contoh Pemain di atas memiliki tipe dasar suatu kelas. Elemennya
bisa bernilai null atau referensi ke suatu objek yang bertipe Pemain, Objek Pemain sendiri
tidak disimpan di dalam array, hanya referensinya saja yang disimpan di sana. Karena
aturan pemberian nilai pada Java, objek tersebut bisa saja berupa kelas turunan dari
Pemain, sehingga mungkin juga array tersebut menyimpan beberapa jenis Pemain
misalnya pemain komputer, pemain manusia, atau pemain lainnya, yang semuanya adalah
kelas turunan dari Pemain.
Contoh lainnya, misalnya kelas BentukGeometri menggambarkan suatu bentuk geometri
yang bisa digambar pada layar, dan ia memiliki kelas-kelas turunan yang merupakan
bentuk-bentuk khusus, seperti garis, kotak, kotak bertepi bulat, oval, atau oval berisi
warna, dan sebagainya. (BentukGeometri sendiri bisa berbentuk kelas abstrak, seperti
didiskusikan sebelumnya). Kemudian array bertipe BentukGeometri[] bisa menyimpan
referensi objek yang bertipe kelas turunan dari BentukGeometri. Misalnya, perhatikan
contoh pernyataan berikut
BentukGeometri[] gambar = new BentukGeometri[100]; // Array untuk
menyimpan 100 gambar.
gambar[0] = new Kotak(); // Letakkan beberapa objek di dalam
array.
gambar[1] = new Garis(); // (Program betulan akan menggunakan
beberapa
gambar[2] = new OvalBerwarna(); // parameter di sini
int jmlGambar = 3; // Lacak jumlah objek di dalam array
bisa diilustrasikan sebagai berikut.
Array tersebut bisa digunakan dalam program gambar. Array bisa digunakan untuk
menampung gambar-gambar yang akan ditampilkan. Jika BentukGeometri memiliki
metode "void gambarUlang(Graphics g)" untuk menggambar pada grafik g, maka
semua grafik dalam array bisa digambar dengan perulangan sederhana
for (int i = 0; i < jmlGambar; i++)
gambar[i].gambarUlang(g);
Pernyataan "gambar[i].gambarUlang(g);" memanggil metode gambarUlang() yang
dimiliki oleh masing-masing gambar pada indeks i di array tersebut. Setiap objek tahu
bagaimana menggambar dirinya sendiri, sehingga perintah dalam perulangan tersebut
sebetulnya melakukan tugas yang berbeda-beda tergantung pada objeknya. Ini adalah
contoh dari polimorfisme dan pengolahan array.
Array Dinamis
Dalam contoh-contoh di atas, ada batas tententu dalam jumlah elemennya, yaitu 100 int,
100 Pemain, dan 100 BentukGeometris. Karena ukuran array tidak bisa berubah, array
tersebut hanya bisa menampung maksimum sebanyak elemen yang didefinisikan pada
pembuatan array. Dalam banyak kasus, adanya batas maksimum tersebut tidak diinginkan.
Kenapa harus bekerja dengan hanya 100 bilangan bulat saja, bukan 101?
Alternatif yang umum adalah membuat array yang sangat besar sehingga bisa digunakan
untuk dalam kehidupan sehari-hari. Akan tetapi cara ini tidak baik, karena akan sangat
banyak memori komputer yang terbuang karena tidak digunakan. Memori itu mungkin
lebih baik digunakan untuk yang lain. Apalagi jika komputer yang akan digunakan tidak
memiliki memori yang cukup untuk menjalankan program tersebut.
Tentu saja, cara yang lebih baik adalah apabila kita bisa mengubah ukuran array sesuka
kita kapan saja. Ingat bahwa sebenarnya variabel array tidak menyimpan array yang
sesungguhnya. Variabel ini hanya menyimpan referensi ke objek tersebut. Kita tidak bisa
membuat array tersebut lebih besar, akan tetapi kita bisa membuat array baru yang lebih
besar, kemudian mengubah isi variabel array tersebut ke array baru itu.
Tentunya kita harus mengkopi semua isi di array yang lama ke array baru. Array lama akan
diambil oleh pemulung memori, karena ia tidak lagi digunakan.
Mari kita lihat kembali contoh game di atas, di mana ArrayPemain adalah array dengan
tipe Pemain[] dan jumlahPemainAktif[/code] adalah jumlah pemain yang sudah
digunakan array tersebut. Misalnya kita tidak ingin membuat limit banyaknya pemainnya
yang bisa ikut main. Jika pemain baru masuk dan array tersebut sudah penuh, kita akan
membuat array baru yang lebih besar.
Variabel ArrayPemain akan merujuk pada array baru. Ingat bahwa setelah ini dilakukan,
ArrayPemain[0] akan menunjuk pada lokasi memori yang berbeda, akan tetapi nilai
ArrayPemain[0] sama dengan sebelumnya. Berikut ini adalah kode untuk melakukan hal
di atas:
// Tambah pemain baru, meskipun array sudah penuh
if (jumlahPemainAktif == ArrayPemain.length) {
// Array sudah penuh. Buat array baru yang lebih besar,
// kemudian kopi isi array lama ke array baru lalu ubah
// ArrayPemain ke array baru.
int ukuranBaru = 2 * ArrayPemain.length; // Ukuran array baru
Pemain[] temp = new Pemain[ukuranBaru]; // Array baru
System.arraycopy(ArrayPemain, 0, temp, 0, ArrayPemain.length);
ArrayPemain = temp; // Ubah referensi ArrayPemain ke array baru.
}
// Di sini kita sudah tahu bahwa pasti ada tempat di array baru.
while (true) {
System.out.print("? ");
bil = KonsolIO.ambilInt();
if (bil <= 0)
break;
bilangan.put(jmlBilangan,bil);
jmlBilangan++;
}
} // akhir main();
Jika pemainBaru adalah variabel yang bertipe Pemain, maka pemain baru tersebut bisa
ditambahkan ke dalam ArrayList dan ke dalam game dengan perintah :
paraPemain.add(pemainBaru);
dan jika pemain nomor i keluar dari game, maka kita hanya perlu memberikan perintah
paraPemain.remove(i);
Atau jika pemain adalah objek bertipe Pemain yang akan kita keluarkan dari game, maka
kita bisa menggunakan perintah
paraPemain.remove(pemain);
Semuanya terlihat sangat mudah. Satu-satunya kesulitan yang akan kita temui adalah
ketika kita ingin mengambil nilai yang disimpan pada posisi i di dalam ArrayList. Tipe
keluaran fungsi ini adalah Object. Dalam hal ini objek yang diambil oleh fungsi ini
sebenarnya bertipe Pemain. Supaya kita bisa menggunakan hasil keluarannya, maka kita
perlu menggunakan casting tipe untuk mengubahnya menjadi tipe Player, dengan cara :
Pemain pmn = (Pemain)paraPemain.get(i);
Misalnya, jika kelas Pemain memiliki metode instansi yang dinamakan jalan() yang
dipanggil ketika seorang pemain menjalankan dadu atau kartu, maka kita bisa menuliskan
kode untuk memberi giliran kepada semua pemain untuk jalan, yaitu dengan
for (int i = 0; i < paraPemain.size(); i++) {
Pemain pmn = (Pemain)paraPemain.get(i);
pmn.jalan();
}
Dua baris di dalam perulangan for tersebut dapat digabungkan dengan satu perintah :
((Pemain)paraPemain.get(i)).jalan();
Perintah ini akan mengambil elemen pada ArrayList mengcasting tipenya, kemudian
memanggil metode jalan() pada pemain yang baru diambil tersebut. Tanda kurung di
sekitar "(Pemain)paraPemain.get(i)" diperlukan karena aturan dalam Java sehingga
perintah dalam kurung akan dijalankan terlebih dahulu sebelum metode jalan()
dipanggil.
Vector
Kelas ArrayList diperkenalkan pada Java versi 1.2, sebagai salah satu kumpulan kelas
yang digunakan untuk bekerja dengan sekumpulan koleksi objek. Jika akan bahas lebih
lanjut tentang "kelas koleksi" pada bagian berikutnya. Versi awal Java tidak memiliki
ArrayList, akan tetapi memiliki kelas yang sangat mirip yaitu java.util.Vector. Kita
masih bisa melihat Vector digunakan pada program lama, dan dalam beberapa kelas
standar Java, sehingga kita perlu tahu tentang kelas ini.
Menggunakan Vector mirip dengan menggunakan ArrayList. Perbedaannya adalah nama
metode yang berbeda untuk melakukan tugas yang sama, atau nama metode yang berbeda
untuk melakukan tugas yang sama.
Seperti ArrayList, suatu Vector mirip dengan array Object yang bisa berkembang jika
diperlukan. Konstruktor new Vector() membuat vektor tanpa elemen.
Misalnya vec adalah suatu Vector. Maka :
vec.size() adalah fungsi untuk mengembalikan jumlah elemen di dalam vektor.
vec.addElement(obj) akan menambahkan Object obj di akhir vektor. Sama
dengan metode add() pada ArrayList.
vec.removeElement(obj) menghapus obj dari dalam vektor, kalau ada. Hanya
objek pertama yang ditemui akan dihapus. Sama dengan remove(obj) pada kelas
ArrayList
vec.removeElementAt(N) menghapus elemen ke-N. N harus berada pada rentang
0 hingga vec.size() - 1. Sama dengan remove(N) pada ArrayList
vec.setSize(N) akan mengubah ukuran vektor menjadi N. Jika di dalam vektor
terdapat elemen yang jumlahnya lebih banyak dari N, maka elemen lainnya akan
dihapus. Jika lebih sedikit, maka tempat kosong akan diisi dengan null. Kelas
ArrayList tidak memiliki metode seperti ini.
Kelas Vector memiiki banyak metode lagi, akan tetapi ini adalah metode yang sering
digunakan.
Kita bisa mengisi array multi dimensi sekaligus pada saat dideklarasikan. Ingat
sebelumnya bagaimana array 1 dimensi biasa dideklarasikan, dan bagaimana isinya
diinisialisasikan, yaitu seperti daftar nilai-nilainya yang dipisahkan dengan koma, dan
diletakkan di dalam tanda kurung kurawal { dan }.
Inisialisasi array bisa juga digunakan untuk array multi dimensi, yang terdiri dari beberapa
inisialisasi array 1 dimensi, masing-masing untuk setiap barisnya. Misalnya, array A pada
gambar di atas dapat dibuat dengan perintah :
int[][] A = { { 1, 0, 12, -1 },
{ 7, -3, 2, 5 },
{ -5, -2, 2, 9 }
};
Jika tidak ada inisialisasi yang diberikan untuk suatu array, maka nilainya akan diisi
dengan nilai awal tergantung pada tipenya : nol untuk bilangan, false untuk boolean dan
null untuk objek.
Seperti halnya array 1 dimensi, array 2 dimensi juga sering diolah dengan menggunakan
perulangan for. UNtuk mengolah semua elemen pada array 2 dimensi, kita bisa
menggunakan pernyataan for bertingkat. Jika array A dideklarasikan seperti
int[][] A = new int[3][4];
maka kita bisa mengisi 0 untuk semua elemen pada A dengan menggunakan
for (int baris = 0; baris < 3; baris++) {
for (int kolom = 0; kolom < 4; kolom++) {
A[baris][kolom] = 0;
}
}
Pertama kali perulangan for bagian luar akan memproses dengan baris = 0. Bagian
dalamnya akan mengisi keempat kolom pada baris pertama, yaitu A[0][0] = 0, A[0][1]
= 0, A[0][2] = 0, dan A[0][3] = 0. Kemudian perulangan for bagian luar akan mengisi
baris kedua, dan seterusnya.
Dan juga, kita bisa menjumlah semua elemen pada A dengan
int jml = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; i++)
jml = jml + A[i][j];
Untuk mengolah array 3 dimensi, tentunya kita harus menggunakan perulangan for
bertingkat 3.
Suatu array 2 dimensi bisa digunakan untuk menyimpan data yang secara alami memang
tersusun sebagai baris dan kolom. Misalnya papan catur terdiri dari 8 baris dan 8 kolom.
Jika suatu kelas dinamakan PapanCatur untuk merepresentasikan papan catur, maka kita
bisa deklarasikan dengan perintah
PapanCatur[][] papan = new PapanCatur[8][8];
Kadang-kadang array 2 dimensi juga digunakan untuk masalah yang tidak terlalu jelas
matriksnya. Misalnya perusahaan yang memiliki 25 toko. Anggap masing-masing toko
memiliki keuntungan yang didapat pada masing-masing toko tersebut setiap bulan pada
tahun 2009. Jika toko-toko tersebut memiliki nomor 0 hingga 24, dan 12 bulan dari Januari
09 hingga Desember 09 dinomori 0 hingga 11, maka data keuntungan dapat disimpan
dalam array untung yang dideklarasikan seperti :
double[][] untung = new double[25][12];
untung[3][2] adalah keuntungan yang dibuat oleh toko nomor 3 di bulan Maret. Atau
secara umum, untung[noToko][noBulan] adalah keuntungan toko noToko pada bulan
noBulan. Dalam contoh ini array 1 dimensi untung[noToko] memiliki arti : Data
keuntungan satu toko selama satu tahun.
Anggap array untung telah diisi dengan data. Data ini bisa diolah lebih lanjut. Misalnya,
total keuntungan seluruh perusahaan -- sepanjang tahun dari seluruh toko -- dapat dihitung
dengan menjumlahkan semua elemen pada array :
double totalUntung; // Total keuntungan perusahaan tahun 2009
totalUntung = 0;
for (int toko = 0; toko < 25; toko++) {
for (int bulan = 0; bulan < 12; bulan++)
totalUntung += untung[toko][bulan];
}
Kadang-kadang kita juga perlu menghitung hanya satu baris atau satu kolom saja, bukan
keseluruhan array. Misalnya, kita ingin menghitung keuntungan total perusahaan pada
bulan Desember, yaitu bulan nomor 11, maka kita bisa gunakan perulangan :
double untungDesember = 0.0;
for (noToko = 0; noToko < 25; noToko++)
untungDesember += untung[noToko][11];
Sekarang mari kita buat array 1 dimensi yang berisi total keuntungan seluruh toko setiap
bulan :
double[] untungBulanan; // Keuntungan setiap bulan
untungBulanan = new double[12];
} // akhir for
return -1;
}
Metode seperti ini dimana pencarian dilakukan dengan menguji setiap item disebut
pencarian linier (linear search). Jika kita tidak mengetahui apa-apa tentang isi dan urutan
item pada array, maka tidak ada lagi algoritma alternatif yang lebih baik dari ini. Akan
tetapi jika kita tahu bahwa elemen di dalam array diurut dalam urutan menaik atau urutan
menurun, maka kita bisa menggunakan algoritma lain yang lebih cepat. Tentu saja, waktu
yang dibutuhkan untuk mengurutkan array tidak sebentar, akan tetapi jika array ini akan
dicari berulang kali, maka waktu pengurutan array akan terbayarkan dengan cepat.
Pencarian biner (binary search) adalah metode untuk mencari suatu item dalam array yang
sudah diurutkan. Meskipun implementasinya tidak mudah, akan tetapi ide dasarnya sangat
mudah : Jika kita mencari suatu item dalam suatu array yang terurut, maka kita bisa
menghapus setengah dari keseluruhan elemen hanya dengan melihat satu nilai. Misalnya
kita ingin mencari bilangan 42 dalam array yang sudah diurutkan yang terdiri dari 1000
bilangan bulat. Anggap bahwa array tersebut diurutkan dalam urutan menaik (dari kecil ke
besar). Kemudian kita cek item ke-500 dalam array, dan ternyata isinya adalah 93. Karena
42 kurang dari 93, dan karena elemen di dalam array tersebut dalam urutan menaik, kita
bisa simpulkan bahwa 42 tidak mungkin ada di item ke-501 ke atas. Maka elemen tersebut
pasti ada di lokasi tertentu sebelum posisi ke-500.
Cara berikutnya adalah melihat di lokasi 250. Jika misanya lokasi tersebut berisi 21, maka
kita bisa menghilangkan lokasi 0 hingga 250 dan memfokuskan pencarian antara 251 dan
499. Yang berikutnya adalah kira-kira di lokasi ke-125 setelah itu, yang berikutnya adalah
sekitar 62 lokasi setelah itu. Setelah kira-kira 10 langkah pengujian, hanya ada satu lokasi
yang akan kita cek.
Cara ini akan jauh lebih mudah dan lebih cepat daripada harus mencari semua elemen di
dalam array. Jika ada satu juta elemen di dalam array, maka kita hanya perlu mencari 20
kali. (Secara matematika, jumlah langkah yang diperlukan adalah logaritmik dari jumlah
item di dalam array).
Untuk membuat subrutin pencarian biner pada Java yang mencari item N pada array A,
kita hanya perlu mencatat rentang lokasi di mana kira-kira N bisa ditemukan. Pada setiap
langkah, kita bisa mengurangi kemungkinan dan mengurangi rentang pencarian. Operasi
dasarnya adalah mencari item di tengah-tengah rentang tersebut. Jika item ini lebih besar
dari N, maka rentang bagian atasnya bisa dibuang. Jika kurang dari N, maka rentang
bawahnya bisa dibuang.
Jika nilai di tengah-tengah tersebut persisi sama denan N, maka pencarian selesai. Jika
ukurang pencarian berkurang menjadi nol, maka nilai N tidak ada dalam array. Berikut ini
adalah subrutin yang mengembalikan lokasi di mana N berada di dalam array terurut A.
Jika N tidak ditemukan, maka nilai -1 akan dikembalikan.
static int pencarianBiner(int[] A, int N) {
// Mencari bilangan N pada array A
// Kondisi awal : A harus diurut menaik (dari kecil ke besar)
// Kondisi akhir : Jika N ada dalam array, maka kembalikan
// nilai i, di mana A[i] == N. Jika tidak kembalikan -1
int lokasiTerkecil = 0;
int lokasiTerbesar = A.length - 1;
return -1;
}
List Asosiasi
Salah satu aplikasi pencarian yang banyak digunakan adalah yang menggunakan list
asosiasi (association list). Contoh umum dari suatu list asosiasi adalah kamus. Kamus
menghubungan kata dengan definisi. Dengan kata tertentu, kita bisa menggunakan kamus
untuk mencari definisinya. Kita bisa membayangkan kamus sebagai daftar suatu pasangan
(pair) dalam bentuk (k,d) di mana k adalah kata dan d adalah definisi. Secara umum, kita
menganggap bahwa tidak ada dua pasangan dalam list yang memiliki kunci yang sama.
Operasi dasar dari list asosiasi adalah sebagai berikut : Dengan kunci k, cari nilai n yang
berhubungan dengan k, jika ada.
List asosiasi digunakan secara luas dalam ilmu komputer. Misalnya, suatu kompiler harus
melacak di mana lokasi suatu variabel pada memori. Kompiler dapat menggunakan list
asosiasi di mana kuncinya adalah nama variabel dan nilainya adalah alamat variabel
tersebut di memori. Contoh lainnya adalah mailing list, yang menghubungkan nama dan
alamat email dalam daftar tersebut. Contoh lain yang mungkin berhubungan adalah
direktori telepon yang menghubungkan nama dan nomor telepon. Item di dalam list
tersebut mungkin berupa objek dari kelas :
class EntriTelepon {
String nama;
String noTelp;
}
Data dalam direktori telepon adalah array yang bertipe EntriTelepon[] dan variabel
integer untuk menyimpan berapa banyak item yang disimpan dalam direktori tersebut.
(Contoh ini adalah contoh dari "array setengah penuh" yang dibahas pada bagian
sebelumnya. Mungkin lebih baik jika kita menggunakan array dinamis atau ArrayList
untuk menyimpan daftar telepon.) Direktori telepon mungkin berupa objek di dalam kelas
class DirektoriTelepon {
Rekursi
Definisi rekursi adalah definisi yang menggunakan konsep atau sebagian dari definisi
tersebut menjadi definisi yang komplit.
Misalnya : "keturunan" bisa berarti anak atau keturunan dari anak. "Kalimat" bisa berarti
dua kalimat yang digabung dengan kata hubung "dan". "Direktori" adalah bagian pada hard
disk yang berisi file dan direktori. Dalam matematika, "himpunan" adalah koleksi elemen,
di mana elemen tersebut bisa berupa himpunan. "Pernyataan" pada Java misalnya
pernyataan while yang didalamnya terdapat kata while, kondisi bernilai boolean dan
pernyataan lainnya.
Definisi rekursi bisa menjelaskan situasi yang sangat kompleks dalam beberapa kata.
Definisi istilah "keturunan" tanpa menggunakan rekursi bisa jadi "anak, cucu, cicit, dan
seterusnya". Akan tetapi mengatakan "dan seterusnya" bukan arti "keturunan" secara
lengkap.
Kita juga akan kesulitan jika kita mencoba mendefinisikan "direktori" sebagai "file yang
berisi daftar file, dimana beberapa file bisa berisi daftar file, di mana beberapa file tersebut
bisa berisi daftar file, dan seterusnya". Mencoba untuk menjelaskan pernyataan Java tanpa
menggunakan rekursi dalam definisinya akan sulit dilakukan.
Rekursi bisa digunakan dalam teknik pemrograman. Subrutin rekursif adalah subrutin yang
memanggil dirinya sendiri, baik langsung maupun tak langsung. Subrutin tersebut
memanggil dirinya sendiri secara tidak langsung yaitu jika ia memanggil subrutin lain yang
akhirnya memanggil subrutin pertama (baik langsung maupun tak langsung).
Suatu subrutin rekursi bisa menyelesaikan tugas kompleks dalam beberapa baris perintah.
Kita akan lihat beberapa contohnya pada bagian ini.
Mari kita mulai dengan contoh yang sudah kita lihat sebelumnya: algorithma pencarian
biner pada bagian sebelumnya. Pencarian biner digunakan untuk mencari suatu nilai dalam
list terurut (atau jika item nilai tersebut tidak ada di dalam list tersebut, akan memberitahu
hasilnya misalnya dengan mengembalikan -1).
Caranya adalah dengan mengecek elemen di tengah list tersebut. Jika elemen tersebut sama
dengan nilai yang dicari, maka pencarian tersebut selesai. Jika nilai yang dicari lebih kecil
daripada nilai elemen di tengah list tersebut, maka kita harus mencari di separuh awal dari
list tersebut. Jika lebih besar, kita harus mencari di separuh akhir list tersebut. Kemudian,
pada separuh list yang kita pilih tersebut, kita akan mengecek kembali elemen tengahnya.
Kita akan melihat kembali apakah nilainya sama dengan nilai yang kita cari, lebih besar
atau lebih kecil, yang dari sini kita tahu paruh mana yang akan kita cari berikutnya. Dan
begitu seterusnya, hingga besar list yang akan dicari berkurang menjadi 0.
Ini adalah definisi rekursif, dan kita bisa membuat subrutin rekursif untuk
mengimplementasikannya.
Sebelumnya, ada dua pertimbahangan yang harus kita masukkan ke dalam perhitungan
kita, yang merupakan fakta tentang subrutin rekursif. Pertama, algoritma pencarian biner
dimulai dengan mengecek "elemen tengah suatu list". Apa yang terjadi jika list tersebut
kosong? Jika tidak ada elemen di dalam list, maka kita tidak mungkin melihat elemen di
tengahnya. Atau dengan kata lain, ini disebut "kondisi awal" untuk mengecek elemen di
tengah list, yaitu memiliki list yang tidak kosong.
Apa yang terjadi kita ternyata harus mencari nilai di list kosong? Jawabannya mudah : Kita
bisa mengatakan bahwa nilai yang kita cari tidak ada di dalam list. List kosong adalah
kasus dasar untuk algoritma pencari biner. Kasus dasar untuk algoritma rekursif adalah
kasus yang akan ditangani secara langsung, bukan dilakukan secara rekursif. Algoritma
pencarian biner memiliki kasus dasar lain, yaitu jika kita menemukan nilai yang kita cari di
tengah suatu list, maka program tersebut selesai. Kita tidak perlu melakukan rekursi lebih
lanjut.
Pertimbangan kedua adalah parameter subrutin tersebut. Dalam subrutin non-rekursif dari
pencarian biner yang dibahas sebelumnya, parameternya adalah suatu array. Akan tetapi
dalam pendekatan rekursif, kita harus bisa menerapkan subrutin secara rekursif hanya
sebagian dari list aslinya. Pada subrutin aslinya yang non-rekursif, kita melakukan
pencarian di seluruh array, sedangkan subrutin rekursif harus bisa mencari di sebagian
array. Parameter subrutin tersebut harus bisa memberi tahu di bagian mana array akan
dicari.
Berikut ini adalah algoritma pencarian biner rekursif yang mencari suatu nilai dalam
bagian dari array integer:
static int cariBiner(int[] A, int idksRendah, int idksTinggi, int nilai)
{
// Cari "nilai" pada array "A" dari posisi "idksRendah" ke
"idksTinggi",
// Asumsinya adalah array diurut dalam urutan menaik
// Jika nilai ditemukan kembalikan indeks pada array
// dimana nilai tersebut berada. Jika tidak kembalikan -1
Kita akan memindahkan 10 piringan dari tiang 0 ke tiang 1, dan tiang 2 bisa dijadikan
cadangan. Bisakah kita mengurangi persoalan ini menjadi persoalan yang sama dengan
skala yang lebih kecil? Mungkin kita harus sedikit mengeneralisir persoalannya sehingga
kita bisa melihat lebih jelas.
Jika ada N piringan pada tiang 0, kita tahu bahwa pada akhirnya kita haris memindahkan
piringan paling bawah dari tiang 0 ke tiang 1. Akan tetapi sebelum itu, menurut aturan di
atas, piringan 0 hingga N-1 harus dipindahkan terlebih dahulu ke tiang 2. Setelah kita
pindahkan piringan ke-N ke tiang 1, maka kita harus memindahkan kembali N-1 piringan
dari tiang 2 ke tiang 1 untuk menyelesaikan masalahnya. Lihat bahwa memindahkan N-1
piringan adalah persoalan yang sama seperti memindahkan N piringan, akan tetapi
sekarang persoalannya menjadi lebih kecil.
Ini bisa dilakukan dengan mudah dengan rekursi.
Suatu persoalan harus digeneralisir terlebih dahulu, di sini kita lihat bahwa persoalan yang
lebih kecil adalah memindahkan piringan dari tiang 0 ke tiang 2 dan kemudian dari tiang 2
ke tiang 1, bukan dari tiang 0 ke tiang 1. Untuk subrutin rekursiif yang akan kita buat, kita
harus menyatakan tiang asal, tiang tujuan, dan tiang cadangannya. Kasus dasarnya adalah
jika hanya ada satu piringan yang akan dipindahkan, yang mana jawabannya mudah :
pindahkan satu piringan itu dalam satu langkah.
Berikut ini adalah contoh subrutin untuk menyelesaikan masalah ini :
void MenaraHanoi(int piringan, int asal, int tujuan, int cadangan) {
// Memecahkan persoalan untuk memindahkan sejumlah "piringan"
// dari tiang "asal" ke tiang "tujuan". Tiang "cadangan" bisa
// digunakan sebagai tempat sementara
if (piringan == 1) {
// Hanya ada satu piringan, pindahkan langsung
System.out.println("Pindahkan piringan dari tiang "
+ asal + " ke tiang " + tujuan);
}
else {
// Pindahkan semua piringan minus 1 ke tiang cadangan,
// kemudian pindahkan piringan paling bawah ke tiang tujuan,
// kemudian pindahkan sisanya dari tiang cadangan ke
// tiang tujuan.
MenaraHanoi(piringan-1, asal, cadangan, tujuan);
System.out.println("Pindahkan piringan dari tiang "
+ asal + " ke tiang " + tujuan);
MenaraHanoi(piringan-1, cadangan, tujuan, asal);
}
}
Subrutin ini mengekspresikan solusi persoalan secara alami. Rekursi bisa bekerja dengan
benar karena setiap panggil rekursif selalu akan dipanggil dengan jumlah piringan yang
semakin sedikit. Dan ketika sampai pada kasus dasarnya, yaitu hanya ada satu piringan,
persoalannya bisa diselesaikan langsung dengan memindahkan satu piringan tersebut ke
tiang tujuannya.
Untuk memecahkan persoalan ini pada "level paling atas", yaitu memindahkan N piringan
dari tiang 0 ke tiang 1, subrutin ini bisa dipanggil dengan perintah
MenaraHanoi(N,0,1,2).
Ceritanya, dahulu kala ada kisah yang merupakan nama dari persoalan ini. Menurut cerita
ini, pada saat bumi pertama kali dicipkatan, sekumpulan biksu pada suatu menara di dekat
Hanoi diberi tumpukan 64 piringan dan diberi tugas untuk memindahkan satu piringan
setiap hari dengan aturan yang sama seperti di atas. Pada hari ketika tugas ini selesai, alam
semesta akan kiamat. Tapi tenang saja, karena jumlah langkah yang diperlukan untuk
menyelesaikan tugas ini untuk N piringan adalah 2N - 1, dan 264 - 1 hari sama dengan lebih
dari 50 trilyun tahun.
Pengurutan Cepat
Aplikasi rekursi yang cukup populer adalah Pengurutan Cepat (Quicksort) yang digunakan
untuk mengurutkan array. Di bagian sebelumnya kita telah melihat bagaimana
mengurutkan array dengan menggunakan pengurutan pilihan dan pengurutan penyisipan
yang relatif lebih mudah, akan tetapi akan berjalan lebih lambat untuk array yang besar.
Algoritma pengurutan yang lebih cepat sudah tersedia. Salah satunya adalah pengurutan
cepat (quick sort), yaitu algoritma rekursif yang ternyata paling cepat hampir pada segala
kondisi.
Algoritma pengurutan cepat dibuat berdasarkan ide yang sederhana namun cerdas : Dari
beberapa item, pilih satu item. Item ini kita sebut pivot. Pindahkan semua item yang lebih
kecil dari pivot ke awal array, dan pindahkan item yang lebih besar ke akhir array.
Kemudian letakkan pivot di tengah-tengah kedua grup tersebut. Atau dengan kata lain,
posisi pivot adalah posisi terakhir dan tidak perlu dipindahkan lagi.
LangkahUrutCepat bukan algoritma rekursif. Kecepatan pengurutan cepat tergantung dari
kecepatan LangkahUrutCepat. Karena ini bukan diskusi utama kita, kita akan tuliskan
algoritma LangkahUrutCepat tanpa diskusi lebih lanjut.
static int LangkahUrutCepat(int[] A, int rendah, int tinggi) {
// Jalankan LangkahUrutCepat pada array dengan indeks
// antara rendah dan tinggi pada array A. Nilai yang dikembalikan
// adalah posisi akhir pivot dalam array
// Nilai antara rendah dan tinggi adalah nilai yang belum pernah
// kita uji. Kurangi tinggi dan naikkan rendah hingga keduanya
// bernilai sama, pindahkan nilai yang lebih besar dari pivot
// sehingga nilai tersebut berada di atas tinggi dan pindahkan
// nilai yang lebih kecil dari pivot sehingga nilainya berada
// di bawah rendah. Ketika kita mulai, A[rendah] adalah tempat
// kosong, karena ini adalah posisi awal nilai pivot
if (tinggi == rendah)
break;
// Nilai pada A[tinggi] kurang dari pivot. Pindahkan ke tempat
// kosong pada A[rendah], sehingga tempat di A[tinggi]
// menjadi kosong
A[rendah] = A[tinggi];
rendah++;
if (tinggi == rendah)
break;
A[tinggi] = A[rendah];
tinggi--;
} // akhir while
A[rendah] = pivot;
return rendah;
} // akhir LangkahUrutCepat
Degan subrutin ini, maka pengurutan cepat mudah dilakukan. Algoritma untuk
mengurutkan deretan nilai yaitu dengan menjalankan LangkahUrutCepat pada nilai-nilai
tersebut, kemudian menjalankan pengurutan cepat secara rekursif terhadap item yang ada
di sebelah kiri pivot dan item yang ada di sebelah kanan pivot. Tentunya kita juga
membutuhkan kasus dasar. Yaitu jika list hanya memiliki satu item atau kosong, maka list
tersebut telah diurutkan.
static void urutcepat(int[] A, int rendah, int tinggi) {
// Jalankan pengurutan cepat untuk mengurutkan array
// antara posisi rendah dan posisi tinggi dalam urutan naik
if (tinggi <= rendah) {
// List memiliki panjang nol atau 1. Tidak ada yang perlu
// dilakukan, jadi kita keluar dari subrutin
return;
}
else {
// Jalankan LangkahUrutCepat dan dapatkan posisi pivot
// Kemudian jalankan urutcepat untuk mengurut item sebelum
// pivot dan item setelah pivot
int posisiPivot = LangkahUrutCepat(A, rendah, tinggi);
urutcepat(A, rendah, pivotPosition - 1);
urutcepat(A, pivotPosition + 1, tinggi);
}
}
Seperti biasa, kita telah melihat masalah dengan mengeneralisirnya. Masalah awalnya
adalah untuk mengurutkan array, akan tetapi algoritma rekursif dibuat untuk mengurutkan
sebagian array. Untuk mengurut keseluruhan array A, kita bisa menggunakan
urutcepat() yaitu dengan perintah urutcepat(A, 0, A.length -1).
Kita bisa membuat objek baru dengan misalnya bernama "ListString" yang memiliki
variabel instansi bernama "kepala". Variabel ini bertipe Simpul dan menyimpan referensi
ke simpul pertama dari list berantai. Jika listnya kosong, maka kepala berisi null.
public class ListString {
Simpul kepala;
.
.
. // metode dan variabel instansi lainnya
}
Misalnya kita ingin tahu apakah suatu string, itemDicari ada di salah satu simpul di
dalam list. Kita bisa membandingkan itemDicari dengan isi setiap simpul di dalam list.
Caranya, kita akan menggunakan variabel bertipe Simpul yang bernama pointer untuk
digunakan sebagai penunjuk ke simpul-simpul yang akan kita lihat. Kita hanya bisa
mengakses list melalui variabel kepala, jadi kita bisa mulai dengan mengisi pointer
dengan isi kepala.
Simpul pointer = kepala; // Mulai dari simpul pertama.
Kita harus membuat variabel baru ini karena kita akan mengganti isinya pada saat
melakukan pencarian. Kita tidak bisa mengganti isi kepala, karena jika kita ganti, kita
akan kehilangan jejak list yang kita buat. Untuk pindah dari satu simpul ke simpul
berikutnya, kita cukup menggunakan perintah pointer = pointer.berikut;. Kita akan
tahu bahwa kita telah sampai pada akhir list jika pointer bernilai null.
Semuanya bisa kita tuangkan dalam metode instansi cari() pada kelas ListString sebagai
berikut :
public boolean cari(String itemDicari) {
// Kembalikan true jika itemDicari ada di dalam list
// false jika tidak ada dalam list.
Simpul pointer; // Pointer untuk menelusuri list
pointer = kepala;
// Mulai pencarian dari kepala list (kepala adalah variabel instansi)
return false;
} // akhir cari()
Pola di atas akan sering digunakan nanti: Jika kepala adalah variabel yang menunjuk pada
suatu list berantai, maka untuk proses semua simpul dalam list, kita bisa lakukan dengan :
pointer = kepala;
while ( pointer != null ) {
.
. // proses simpul yang ditunjuk oleh pointer
.
pointer = pointer.berikut;
}
Mungkin saja listnya kosong, yaitu apabila isi kepala adalah null. Dalam contoh kode di
atas, jika kepala berisi null, maka badan perulangan while tidak akan dieksekusi sama
sekali, karena kondisi perulangan hanya bisa dijalankan apabila pointer bernilai null.
Menyisipkan item baru ke dalam list sedikit lebih sulit. Dalam kelas ListString, item
pada simpul dibuat dalam urutan menaik. Jika suatu item ditambahkan ke dalam list, item
tersebut harus ditempatkan pada posisi yang tepat sesuai urutannya. Artinya, kita harus
menyisipkan item baru ditengah-tengah list, di antara dua simpul yang sudah ada.
Supaya mudah, kita membutuhkan dua variabel dengan tipe Simpul, di mana masing-
masing menunjuk pada 2 posisi di mana item baru akan disisipkan di antaranya. Pada
ilustrasi berikut, variabel ini adalah sebelum dan pointer. Variabel lain, yaitu
simpulBaru menyimpan referensi ke simpul baru yang akan disisipkan. Untuk melakukan
penyisipan, hubungan antara sebelum dan pointer harus diputus, dan kait baru dari
sebelum ke simpulBaru dan dari simpulBaru ke pointer harus dibuat.
if ( kepala == null ) {
// List masih kosong
// Buat kepala menunjuk ke simpulBaru
kepala = simpulBaru;
}
else if ( kepala.item.compareTo(sisipItem) >= 0 ) {
// Item baru kurang dari item pertama list
// Jadi item baru harus disisipkan sebelum kepala list
simpulBaru.berikut = kepala;
kepala = simpulBaru;
}
else {
// Item baru akan disisipkan di tengah-tengah list setelah
// item pertama. Cari posisi yang tepat dan sisipkan di sana
Simpul pointer; // Simpul untuk menelusuri list
Simpul sebelum; // Simpul yang menunjuk pada posisi sebelum
pointer
sebelum = kepala; // Set sebelum ke kepala list dan pointer ke
posisi setelahnya
pointer = kepala.berikut;
while (pointer != null && pointer.item.compareTo(sisipItem) < 0)
{
// Pindahkan sebelum dan pointer ke posisi berikutnya hingga
pointer
// sampai di akhir list atau sampai pada item yang isinya
lebih besar
// dari sisipItem. Setelah perulangan selesai, pointer
menunjuk
// pada posisi di mana sisipItem akan disisipkan
sebelum = pointer;
pointer = pointer.berikut;
}
simpulBaru.berikut = pointer; // Sisipkan simpulBaru setelah
"sebelum"
sebelum.berikut = simpulBaru;
}
} // akhir sisip()
Jika Anda memperhatikan dengan seksama diskusi di atas, mungkin Anda akan ingat
bahwa ada satu kasus lagi yang tidak pernah disebutkan. Apa yang terjadi jika simpul baru
harus disisipkan di akhir list? Ini terjadi jika semua item di dalam list lebih kecil daripada
item baru.
Sebenarnya, kasus ini sudah ditangani dengan benar oleh subrutin, yaitu di bagian akhir
pernyataan if . Jika sisipItem lebih besar dari semua item di dalam list, maka perulangan
while akan berhenti ketika pointer selesai menelusuri sampai pada akhir list hingga
pointer bernilai null. Akan tetapi, ketika ini terjadi, sebelum masih tetap menunjuk pada
item terakhir pada list. Perintah sebelum.berikut = simpulBaru menambahkan simpul
baru di akhir list. Karena isi pointer adalah null, maka perintah
[code]simpulBaru.berikut = pointer akan mengisi simpulBaru.berikut dengan
null. null adalah nilai yang tepat untuk menandakan akhir list.
Operasi untuk menghapus mirip dengan operasi menyisipkan item, meskupun sedikit lebih
mudah. Masih ada beberapa kasus khusus yang harus dipikirkan. Ketika simpul pertama
akan dihapus, maka isi kepala harus diubah ke simpul kedua. Karena kepala.berikut
adalah simpul berikutnya, maka ini bisa dilakukan dengan perintah kepala =
kepala.berikut. (Sekali lagi, perhatikan bahwa perintah ini juga berlaku jika
kepala.berkut berisi null[code], yaitu ketika hanya ada satu item di dalam
list. Ketika satu-satunya item ini dihapus, maka list bernilai [code]null
yang artinya list sudah kosong.)
Jika simpul yang akan dihapus ada di tengah-tengah list, maka kita bisa membuat variabel
sebelum dan pointer di mana pointer menunjuk pada simpul yang akan dihapus, dan
sebelum menunjuk pada simpul sebelumnya. Setelah diposisikan dengan benar, perintah
"sebelum.berikut = pointer.berikut" akan menghapus simpul tersebut. Simpul yang
dihapus akan diambil oleh pemulung memori.
Berikut ini adalah kode lengkap dari metode hapus() :
public boolean hapus(String hapusItem) {
// Jika hapusItem ada dalam list, hapus.
// Kembalikan true jika string ditemukan dan dihapus.
// Jika string tidak ditemukan kembalikan false.
// (Jika ada beberapa item dengan isi yang sama, hanya
// hapus yang pertama)
if ( kepala == null ) {
// Jika list kosong, sudah pasti tidak ada string hapusItem
return false;
}
else if ( kepala.item.equals(hapusItem) ) {
// Jika hapusItem ada pada simpul pertama, hapus.
kepala = kepala.berikut;
return true;
}
else {
// Di sini, maka ada kemungkinan string terdapat
// di tengah-tengah list. Cari itemnya di dalam list.
Simpul pointer; // Simpul untuk menelusuri list
Simpul sebelum; // Selalu menunjuk pada simpul sebelum pointer
sebelum = kepala; // Mulai dari awal list
pointer = kepala.berikut;
while (pointer != null && pointer.item.compareTo(hapusItem) < 0)
{
// Pindahkan sebelum dan pointer di dalam list hingga
// pointer sampai pada akhir list atau sampai pada item yang
// lebih besar atau sama dengan hapusItem. Ketika perulangan
// selesai, pointer menunjuk pada posisi di mana hapusItem
// seharusnya berada (jika ada)
sebelum = pointer;
pointer = pointer.berikut;
}
if ( pointer != null && pointer.item.equals(hapusItem) ) {
// Pointer menunjuk pada simpul yang akan dihapus
// Hapus dengan mengubah simpul sebelumnya
sebelum.berikut= pointer.berikut;
return true;
}
else {
// Item yang dicari tidak ada
return false;
}
}
} // akhir hapus()
Tumpukan
List berantai adalah salah satu jenis struktur data, yang tersusun dari objek yang terkait
satu sama lain oleh pointer. Pada bagian sebelumnya, kita menggunakan list berantai untuk
menyimpan String terurut, dan kita juga mengimplementasikan operasi sisip, hapus dan
cari pada list tersebut.
Akan tetapi kita juga bisa menyimpan list String pada array atau ArrayList. Kita bisa juga
mengimplementasikan operasi sisip, hapus, dan cari. Implementasi operasi tersebut
akan berbeda, akan tetapi antar muka dan perilakunya akan tetap sama.
Istilah tipe data abstrak (abstract data type, atau ADT) adalah kumpulan nilai dan operasi
yang bisa dilakukan pada nilai tersebut, tanpa perlu mengetahui bagaimana nilai tersebut
disimpan dan bagaimana operasi tersebut diimplementasikan.
Suatu "list terurut yang berisi string" adalah contoh tipe data abstrak. Ada banyak cara
untuk mengimplementasikan tipe data abstrak yang sama, misalnya seperti disebut di atas,
list terurut berisi string bisa diimplementasikan dalam bentuk list berantai atau array.
Program yang menggunakan tipe data ini bisa menggunakannya tanpa mengetahui dengan
detail tentang implementasinya. Lebih jauh, implementasi TDA bisa diganti tanpa
mempengaruhi jalannya program secara keseluruhan. Dengan cara ini, program bisa lebih
mudah untuk dipelihara dan didebug. TDA adalah alat penting dalam rekayasa perancang
lunak.
Pada bagian ini dan yang akan datang, kita akan lihat TDA lain, yaitu tumpukan dan
antrian. Tumpukan dan antrian sering diimplementasikan dalam bentuk list berantai, akan
tetapi ini bukan satu-satunya cara implementasi. Mari kita anggap bagian ini sebagai studi
kasus dari TDA.
Tumpukan (stack) terdiri dari kumpulan item yang disusun sedemikian rupa sehingga satu
item ditumpuk di atas item lainnya, mirip seperti tumpukan boks. Hanya item paling atas
yang bisa diakses pada suatu saat.
Item tersebut bisa diambil dari tumpukan dengan operasi yang disebut "ambil" (atau dalam
bahasa Inggris, istilah untuk mengeluarkan item dari tumpukan disebut "pop"). Item di
bawah hanya bisa diambil jika semua item di atasnya telah dikeluarkan dari tumpukan.
Suatu item hanya bisa ditambahkan di atas tumpukan dengan perintah "dorong" (atau
"push").
Kita bisa membuat tumpukan dari berbagai macam data. Misalnya, jika itemnya bertipe
int, maka operasi dorong dan keluar bisa diimplementasikan dalam metode instansi
void dorong(int itemBaru) // Tambah itemBaru di atas tumpukan
Mungkin akan lebih baik untuk mendefinisikan jenis pengecualian baru, misalnya
EmptyStackException di kedua versi. Dan sebetulnya TDA harusnya memberikan
spesifikasi apa yang harus dilakukan jika program mencoba mengambil item dari
tumpukan kosong. Ini yang sering dilupakan ketika seseorang membuat spesifikasi untuk
interface, yang akhirnya masalah lain akan muncul di kemudian hari.
Apa contoh kegunaan tumpukan dalam keadaan sehari-hari? Misalnya, lihat apa yang
terjadi jika suatu rutin memanggil subrutin. Rutin pertama akan dihentikan sementara
ketika subrutin yang dipanggil dieksekusi, dan akan diteruskan apabila subrutin yang
dipanggil selesai dijalankan.
Sekarang, misalnya subrutin tersebut memanggil subrutin kedua, dan subrutin kedua
memanggil subrutin ketiga, dan seterusnya. Setiap subrutin akan berhenti untuk sementara
ketika subrutin berikutnya dipanggil. Komputer harus bisa melacak subrutin yang
dihentikan. Bagaimana caranya? Yaitu dengan menggunakan tumpukan.
Ketika subrutin dipanggil, catatan aktivasi (activation record) dibuat untuk subrutin
tersebut. Catatan aktivasi ini berisi informasi yang relevan tentang eksekusi dari subruti
tersebut, misalnya parameter dan variabel lokalnya. Catatan aktivasi ini ditempatkan dalam
tunpukan. Catatan ini akan dibuang ketika subrutin selesai dipanggil.
Jika subrutin ini memanggil subrutin lain, maka catatan aktivasi dari subrutin kedua akan
didorong ke dalam tumpukan, di atas catatan aktivasi subrutin pertama. Tumpukan akan
semakin besar jika semakin banyak subrutin yang dipanggil, dan semakin kecil jika
subrutin-subrutin itu selesai dijalankan.
Contoh lainnya, tumpukan digunakan untuk mengevaluasi ekspresi postfix (postfix
expresssion). Ekspresi matematika biasa seperti 2+(15-12)*17 disebut ekspresi infix.
Dalam ekspresi infix, operator berada di tengah nilai yang akan dihitung, misalnya "2 +
2". Dalam ekspresi postfix, operator ada di akhir nilai, misalnya "2 2 +".
Ekspresi 2+(15-12)*17" dapat ditulis dalam bentuk postfix menjadi "2 15 12 - 17 *
+". Di sini operator "-" dilakukan pada dua nilai sebelumnya, yaitu "15" dan "12". Tanda *
dilakukan pada dua nilai sebelumnya, yaitu "15 12 -" dan "17". Dan operator "+" dilakukan
pada 2 dan "15 12 - 17 *". Hasilnya akan persis sama dengan ekspresi infix awalnya.
Meskipun lebih mudah bagi manusia untuk melakukan perhitungan dengan ekspresi infix,
ekspresi postfix memiliki beberapa keuntungan. Satu hal, ekspresi postfix tidak
memerlukan tanda kurung atau aturan perhitungan (tanda kali harus dilakukan lebih dulu
sebelum tambah misalnya). Aturan penghitungan hanya ditentukan berdasarkan urutannya
saja. Sehingga algoritma yang menghitung ekspresi postfix dapat menjalankannya dengan
lebih cepat dan tepat.
Sekarang misalnya kita akan menghitung ekspresi "2 15 12 - 17 * +" dari kiri ke kanan.
Nilai yang kita temui pertama adalah 2, tapi apa yang bisa kita lakukan dengan 2? Di sini
kita belum tahu operatornya, dan selain itu kita juga belum tahu nilai lainnya. Kita akan
ingat nilai 2 ini untuk sementara, yaitu dengan mendorongnya ke dalam tumpukan.
Berikutnya, kita akan melihat nilai 15, yang kita juga masukkan ke dalam tumpukan di atas
nilai 2. Kemudian nilai 12 juga dimasukkan ke dalam tumpukan di atas 15.
Sekarang kita sampai pada operator "-". Operasi ini dilakukan pada dua nilai sebelumnya.
Kita telah menyimpan 2 nilai sebelumnya ke dalam tumpukan, yaitu 15 dan 12. Sekarang
kita ambil 2 nilai tersebut dari dalam tumpukan, dan kita lakukan perhitungan 15 - 12
yaitu 3.
Nilai 3 ini kita simpan lagi ke dalam tumpukan. Ingat bahwa 15 dan 12 sudah diambil dari
dalam tumpukan, sehingga hanya nilai 2 yang ada di dalam tumpukan. Setelah nilai 3
dimasukkan ke dalam tumpukan, maka 3 ada di atas 2 di dalam tumpukan.
Item berikutnya adalah 17, yang juga dimasukkan ke dalam tumpukan di atas nilai 3.
Untuk menghitung item berikutnya "*", kita ambil 2 nilai dari dalam tumpukan, yaitu 3
dan 17. Hasil dari 3 * 17, yaitu 51 dimasukkan kembali ke dalam tumpukan (di atas 2
yang masih ada di dalam tumpukan).
Item berikutnya adalah "+", yang akan mengambil 51 dan 2 dari dalam tumpukan,
menghitung hasilnya, yaitu 53, kemudian menyimpannya lagi ke dalam tumpukan.
Sekarang kita sampai pada akhir ekspresi. Nilai pada tumpukan itu adalah hasil
perhitungan keseluruhan ekspresi. Kita cukup mengambil nilainya dan melaporkan
hasilnya, yaitu 53.
Algoritma untuk melakukan perhitungan ekspresi postfix adalah sebagai berikut :
Mulai dengan tumpukan kosong
untuk setiap item di dalam ekspresi:
jika item berupa bilangan:
Dorong item ke dalam tumpukan
jika item berupa operator
Ambil dua nilai dari tumpukan // bisa terjadi kesalahan
Lakukan perhitungan dua nilai dengan operator tersebut
Dorong hasil perhitungan ke dalam tumpukan
else
Ada kesalahan dalam ekspresi
Ambil nilai dari tumpukan
Jika tumpukan tidak kosong:
Ada kesalahan dalam ekspresi
else:
Nilai terakhir adalah hasil perhitungan
Kesalahan ekspresi dapat dideteksi dengan mudah. Misalnya, dalam ekspresi "2 3 + *",
tidak cukup nilai untuk menghitung operasi "*". Ini akan dideteksi oleh algoritma ketika
mencoba mengambil nilai kedua dari dalam tumpukan, karena pada saat itu tumpukan
sudah kosong.
Kesalahan lain bisa juga muncul ketika menghitung ekspresi " 2 3 4 +", di mana tidak
cukup operator untuk menghitung semua nilai. Ini akan dideteksi ketika 2 masih ada di
dalam tumpukan di akhir algoritma.
Ekspresi postfix sering digunakan secara internal oleh komputer. Dan sebenarnya, mesin
virtual Java adalah "mesin tumpukan", yang menggunakan tumpukan untuk melakukan
perhitungan yang telah kita diskusikan. Algoritma ini bisa diperluas untuk menangani
variabel dan konstanta. Ketika variabel ditemukan di dalam ekspresi, isi variabel ini
didorong ke dalam tumpukan.
Algoritma ini juga bisa dikembangkan untuk operator yang membutuhkan kurang atau
lebih dari dua operator. Banyaknya nilai yang diambil dari dalam tumpukan bisa
disesuaikan dengan berapa banyak nilai yang dibutuhkan. Misalnya, operator "-" sebagai
operator negasi, misalnya "-x" hanya membutuhkan satu nilai.
Antrian
Antrian (queue) adalah struktur data mirip dengan tumpukan, yaitu terdiri dari item dalam
urutan tertentu. Antrian memiliki dua ujung, yang disebut ujung depan dan ujung belakang.
Item selalu dimasukkan dari belakang antrian, dan selalu dikeluarkan dari depan antrian.
Operasi memasukkan dan mengeluarkan item dari dalam antrian disebut "masuk" dan
"keluar" (dalam bahasa Inggris disebut enqueue dan dequeue).
Suatu item yang ditambahkan di belakang antrian tidak bisa dihapus sebelum item di
depannya dihapus. Mirip seperti antrian pada kasir atau pada customer service di bank
misalnya. Customer akan dilayani dalam urutan ketika mereka datang.
Antrian bisa menampung item dengan jenis apa saja. Untuk antrian int, operasi masuk dan
keluar dapat diimplementasikan sebagai metode instansi dalam kelas "AntrianInt". Kita
juga membutuhkan metode instansi untuk menguji apakah antrian kosong atau tidak:
void masul(int N) // Tambah N di belakang antrian
int keluar() {
// Keluarkan item dari kepala antrian
// Bisa melempar NullPointerException.
int itemPertama = kepala.item;
kepala = kepala.berikut; // Sekarang item kedua menjadi kepala
if (kepala == null) {
// Sekarang antrian kosong. Simpul yang telah dihapus adalah
// kepala sekaligus buntut, karena simpul ini adalah satu-
satunya
// yang ada di dalam antrian. Isi buntut dengan null.
buntut = null;
}
return itemPertama;
}
boolean isKosong() {
// Kembalikan true jika antrian kosong
return (kepala == null);
}
Pohon Biner
Kita telah melihat di beberapa bagian sebelumnya bagaimana objek bisa dikaitkan satu
sama lain menjadi list berantai. Ketika suatu objek memiliki 2 pointer ke objek dengan tipe
yang sama, kita bisa membuat struktur data yang lebih kompleks dari list berantai. Dalam
bagian ini kita akan melihat salah satu struktur dasar dan paling berguna: pohon biner
(binary tree).
Setiap objek dalam pohon biner memiliki dua pointer, yang biasa disebut kiri dan kanan.
Selain itu, tentunya simpul pada pohon memiliki data yang bisa bertipe apa saja. Misalnya,
pohon biner integer bisa dibuat dalam bentuk :
class SimpulPohon {
int item; // Data pada simpul ini
SimpulPohon kiri; // Pointer ke cabang sebelah kiri
SimpulPohon kanan; // Pointer ke cabang sebelah kanan
}
Pointer kiri dan kanan dalam SimpulPohon bisa beriis null atau menunjuk pada objek
lain dengan tipe SimpulPohon. Simpul yang menunjuk pada simul lain disebut induk
(parent), sedangkan yang ditunjuk disebut anak (child). Dalam gambar di atas, simpul 3
adalah induk dari simpul 6, dan simpul 4 dan 5 adalah anak dari simpul 2.
Tidak semua struktur yang terdiri dari simpul pohon merupakan pohon biner. Pohon biner
harus memiliki sifat :
Harus ada satu simpul di dalam pohon yang tidak memiliki induk. Simpul ini
disebut simpul akar (root).
Simpul lain harus memiliki hanya satu induk
Tidak boleh ada perulangan dalam pohon biner, artinya tidak boleh ada rantai
pointer yang dimulai dari satu simpul dan berakhir di simpul yang sama.
Simpul yang tidak memiliki anak disebut simpul daun (leaf). Simpul daun dapat dikenali
karena kedua pointer kiri dan kanannya berisi null. Dalam ilustrasi suatu pohon biner,
biasanya simpul akar terletak di atas dan simpul daun terletak di bawah -- tidak sama
seperti pohon sungguhan. Akan tetapi, kita bisa melihat cabang-cabang, seperti pohon,
yang merupakan cikal bakal nama pohon biner ini.
Misalnya, mari kita lihat suatu simpul pada pohon biner. Lihat simpul tersebut beserta
seluruh simpul turunannya (yaitu anak, anak dari anaknya, dan seterusnya). Simpul-simpul
ini juga membentuk pohon biner, yang disebut pohon cabang (subtree) dari pohon
awalnya. Misalnya, pada gambar di atas, simpul 2, 4, dan 5 membentuk pohon cabang.
Pohon cabang ini disebut pohon cabang sebelah kiri dari simpul akarnya. Hal yang sama,
simpul 3 dan 6 adalah pohon cabang sebelah kanan dari simpul akarnya. Salah satu atau
kedua pohon cabang bisa kosong. Ini adalah definisi rekursif. Jadi tidak heran apabila kita
menerapkan subrutin rekursif untuk mengolah struktur data pohon.
Mari kita lihat bagaimana caranya menghitung banyaknya simpul di dalam pohon biner.
Sebagai latihan, kita mungkin bisa membuat algoritma untuk menghitung simpul. Inti
permasalahannya adalah, bagaimana melacak simpul mana yang belum dihitung. Ini bukan
hal sepele. Dan mungkin kita tidak mungkin menyelesaikannya tanpa menggunakan
tumpukan atau antrian.
Dengan rekursi, algoritmanya akan jauh lebih mudah. Suatu pohon bisa kosong, atau bisa
terdiri dari akar dan dua pohon cabang. Jika pohon kosong, maka banyaknya simpul adalah
nol. (Ini merupakan kasus dasar dari rekursi). Kemudian kita bisa menggunakan rekursi
untuk menghitung jumlah simpul pada masing-masing pohon cabang. Jumlahkan hasil dari
kedua cabang, kemudian tambah satu simpul akarnya. Ini adalah jumlah simpul di dalam
pohon.
Dalam Java bisa kita tuliskan sebagai :
static int hitungSimpul( SimpulPohon akar ) {
// Hitung berapa simpul yang dimiliki suatu pohon
// biner, termasuk akarnya
if ( akar == null )
return 0; // Pohon kosong, tidak ada simpul di dalamnya
else {
int jumlah = 1; // Mulai dengan menghitung akarnya
postorderCetak mencetak: 4 5 2 6 3 1
inorderCetak mencetak: 4 2 5 1 3 6
Dalam preorderCetak misalnya, itam pada akar pohon, yaitu 1, dicetak paling awal. Akan
tetapi urutan preorder juga dilakukan untuk setiap pohon cabangnya. Simpul akar pada
pohon cabang sebelah kiri, yaitu 2, dicetak terlebih dahulu sebelum pohon cabangnya 4
dan 5. Sedangkan untuk cabang sebelah kanan, akarnya 3 akan dicetak sebelum 6.
Penelusuran preorder dilakukan pada semua cabang pohon. Urutan penelusuran yang
lainnya bisa dianalisis dengan cara yang sama.
Pohon Pengurutan Biner
Pada bagian sebelumnya kita membahas tentang list berantai dari string, di mana string
dijaga agar tetap dalam urutan menaik. Akan tetapi list berantai seperti ini bisa bekerja
untuk jumlah yang tidak terlalu banyak. Untuk jumlah item yang sangat banyak, list
berantai kurang efisien. Ketika kita menambahkan item ke dalam list, kita harus mencari
dahulu posisi yang tepat di mana item akan disisipkan. Untuk melakukan pencarian, kita
harus melihat paling tidak separuh dari seluruh list.
Jika string list diimplentasikan dalam bentuk array terurut, maka pencariannya bisa
dilakukan lebih cepat, karena pencarian biner bisa digunakan. Akan tetapi untuk
menyisipkan item ke dalam array terurut juga tidak efisien, karena kita harus
memindahkan paling tidak setengah isi array untuk memberi tempat untuk item baru yang
akan disisipkan.
Pohon biner bisa digunakan untuk menyimpan string list terurut (atau item jenis lain),
sehingga pencarian dan penyisipan bisa dilakukan dengan mudah. Pohon biner ini disebut
pohon pencarian biner.
Pohon pencarian biner adalah pohon biner yang memiliki sifat :
Untuk setiap simpul pada pohon, item pada simpul tersebut lebih besar dari semua
item di cabang kiri pohon
Dan simpul tersebut, lebih besar atau sama dengan semua item di cabang kanan
pohon
Berikut ini adalah contoh pohon pengurutan biner yang memiliki item bertipe String.
(Dalam gambar ini pointer berisi null tidak digambarkan, sedangkan pointer yang tidak
null dilambangkan dengan tanda panah)
Pohon pencarian biner memiliki sifat penting berikut : Penelusuran inorder akan mengolah
item dalam urutan menaik. Misalnya, penelusuran inorder digunakan untuk mencetak isi
pohon di atas dalam urutan alfabet. Penelusuran inorder menjamin bahwa semua item di
cabang kiri pohon dari elemen "judy" akan dicetak sebelum "judy", dan semua item di
cabang kanan pohon akan dicetak setelah "judy". Karena sifat pohon biner yang
mengharuskan item di cabang kiri lebih kecil dari item pada simpul, maka keluarannya
akan sama dengan mengurutkan isi pohon secara alfabet dalam urutan menaik.
Misalnya kita ingin mencari item di dalam pohon pencari biner. Pertama, bandingkan item
dengan isi simpul akarnya. Jika isinya sama, maka kita selesai. Jika item yang kita cari
kurang dari item pada simpul akar, kita harus mencari ke sebelah kiri pohon -- pohon
sebelah kanan bisa diabaikan karena isinya hanya item yang lebih besar dari simpul
akarnya. Demikian juga jika item yang kita cari lebih besar dari item pada simpul akar,
maka kita akan mencari di sebelah kanan pohon. Untuk semua kasus, kita bisa
menggunakan prosedur yang sama di setiap cabang pohon.
Bagaimana dengan memasukkan item baru ke dalam pohon. Mula-mula cari di posisi mana
item tersebut akan dimasukkan. Jika posisinya ditemukan, buat simpul baru dan tempelkan
simpul baru di tempat tersebut.
Pencarian dan penyisipan item adalah operasi yang efisien pada pohon pencarian biner,
asalkan pohon tersebut berada dalam kondisi seimbang (balanced). Suatu pohon biner
berada dalam kondisi seimbang jika jumlah simpul pada cabang kanan sama dengan
jumlah simpul pada cabang kiri. Tidak semua pohon biner akan menjadi pohon seimbang,
akan tetapi jika pohon ini dibuat secara acak, besar kemungkinan pohon tersebut akan
menjadi seimbang.
Dalam pencarian dalam pohon pencarian biner, setiap pengujian yang kita lakukan akan
membuang sebagian cabang pohon. Jika pohon tersebut dalam keadaan seimbang, maka
akan semakin banyak elemen yang kita buang, dan dengan demikian pencarian akan
dilakukan dengan lebih cepat. Ini mirip sekali dengan algoritma pencarian biner pada
bagian sebelumnya.
Mari kita lihat bagaimana mengimplementasikan pohon pencarian biner.
Simpul pada pohon biner diekspresikan dalam kelas SimpulPohon berikut ini, beserta
konstruktor untuk membuat simpul baru lebih mudah.
class SimpulPohon {
// Objek SimpulPohon adalah satu simpul pada
// pohon biner string
SimpulPohon(String str) {
// Konstruktor. Membuat simpul berisi str
item = str;
}
} // akhir kelas SimpulPohon
Variabel statik dengan tipe SimpulPohon menunjuk pada pohon pencarian biner:
// Pointer ke simpul akar pohon
// Ketika pohon kosong, akar berisi null
static SimpulPohon akar;
Subrutin rekursif bernama pohonBerisi digunakan untuk mencari item di dalam pohon.
Subruin berikut melakukan pencarian pada pohon biner seperti didiskusikan di atas.
static boolean pohonBerisi( SimpulPohon simpul, String item ) {
// Kembalikan true jika item ditemukan dalam pohon
if ( simpul == null ) {
// Pohon kosong, jadi sudah pasti tidak ada item ini
// di dalamnya
return false;
}
else if ( item.equals(simpul.item) ) {
// Item ini ditemukan di simpul akar
return true;
}
else if ( item.compareTo(simpul.item) < 0 ) {
// Jika item lebih kecil dari simpul, maka mungkin ada di
// cabang kiri pohon. Kembalikan hasil pencarian di
// cabang kiri pohon
return pohonBerisi( simpul.kiri, item );
}
else {
// Jika item sama atau lebih besar dari simpul, maka
// mungkin ada di cabang kanan pohon. Kembalikan hasil
// pencarian di cabang kanan pohon
return pohonBerisi( simpul.kanan, item );
}
} // akhir pohonBerisi()
Ketika subrutin ini dipanggil, parameter pertama adalah variabel anggota statik akar yang
menunjuk pada akar seluruh pohonh pencarian biner.
Perlu dicatat bahwa rekursi bukan sesuatu yang penting dalam mengimplementasikan
subrutin ini. Algoritma pohon pencarian biner yang tidak rekursif mengikuti aturan berikut
: Turun ke bawah hingga kita menemukan item atau hingga mencapai null. Kita bisa
menggunakan perulangan while, sehingga implementasinya menjadi :
static boolean pohonBerisiNR( SimpulPohon akar, String item ) {
// Kembalikan true jika item ada di dalam pohon biner.
SimpulPohon pointer; // Pointer untuk menelusuri pohon
pointer = akar; // Mulai di akar simpul
while (true) {
if (pointer == null) {
// Kita sudah sampai pada akhir pohon, dan item belum
// ditemukan
return false;
}
else if ( item.equals(pointer.item) ) {
// Kita sudah menemukan item
return true;
}
else if ( item.compareTo(pointer.item) < 0 ) {
// Jika item lebih kecil, kemungkinan ada di cabang kiri
// Teruskan penelusuran di cabang kiri pohon
pointer = pointer.kiri;
}
else {
// Jika item lebih besar, kemungkinan ada di cabang kanan
// Teruskan penelusuran di cabang kanan pohon
pointer = pointer.kanan;
}
} // akhir while
} // akhir pohonBerisiNR();
Subrutin untuk menyisipkan item baru ke dalam pohon mirip dengan rutin pencari non-
rekursif di atas. Selain itu rutin penyisipan harus bisa menguji apakah pohon kosong atau
tidak. Jika pohon kosong, maka akar menunjuk pada simpul baru.
akar = new simpulPohon( itemBaru );
Akan tetapi, berarti akar tidak bisa diberikan sebagai parameter subrutin, karena kita tidak
bisa mengubah nilai yang disimpan dalam parameter aktual. (Ini bisa dilakukan dalam
bahasa pemrograman lain). Ada cari lainnya, akan tetapi cara lebih mudah adalah
menggunakan rutin penyisipan non-rekursif yang mengakses variabel anggota akar secara
langsung.
Perbedaan antara mencari dan menyisipkan item adalah kita harus berhati-hati untuk tidak
jatuh dari pohon. ARtinya, kita harus selesai melakukan pencarian sebelum pointer
bernilai null karena mencapai akhir pohon. Jika kita sampai pada tempat kosong, di situlah
kita menempatkan simpul baru kita.
static void sisipPohon(String itemBaru) {
// Tambahkan item ke dalam pohon pencarian biner, di mana
// variabel "akar" berisi. (Catatan kita tidak bisa menggunakan
// akar sebagai parameter, karena kita harus mengubah isi
// variabel ini.)
if ( akar == null ) {
// Jika pohon kosong, set akar ke simpul baru
// yang berisi itemBaru
akar = new SimpulPohon( itemBaru );
return;
}
SimpulPohon pointer; // Untuk menelusuri pohon
pointer = akar; // Mulai dari akar
while (true) {
if ( newItem.compareTo(pointer.item) < 0 ) {
// Karena itemBaru kurang dari item dalam pohon
// maka ia harus berada di cabang kiri pohon.
// Jika ada ruang kosong di pointer.kiri maka simpul baru
// bisa ditambah di sini. Jika tidak turun satu tingkat lagi
ke kiri
if ( pointer.kiri == null ) {
pointer.kiri = new SimpulPohon( itemBaru );
return; // itemBaru sudah ditambahkan ke dalam pohon
}
else
pointer = pointer.kiri;
}
else {
// Karena itemBaru lebih besar dari item dalam pohon
// maka ia harus berada di cabang kanan pohon.
// Jika ada ruang kosong di pointer.kanan maka simpul baru
// bisa ditambah di sini. Jika tidak turun satu tingkat lagi
ke kanan
if ( pointer.kanan == null ) {
pointer.kanan = new SimpulPohon( itemBaru );
return; // itemBaru sudah ditambahkan ke dalam pohon
}
else
pointer = pointer.kanan;
}
} // akhir while
} // akhir sisipPohon()
Bab IX - Pemrograman Generik dan Kelas Koleksi
Bagaimana caranya menghindari "reinventing the wheel", atau membuat kembali sesuatu
yang sudah ditemukan? Banyak struktur data dan algoritma, seperti yang sudah dibahas
pada bab sebelumnya telah dipelajari, diprogram, dan diprogram ulang oleh mahasiswa
ilmu komputer dalam beberapa generasi. Ini adalah kesempatan belajar yang sangat baik.
Sayangnya, algoritma dan data stuktur ini juga diprogram dan diprogram ulang oleh
profesional komputer. Mereka menghabiskan waktu yang tidak perlu untuk memrogram
ulang sesuatu yang sudah ada, bukannya membuat sesuatu yang lebih inovatif dan kreatif.
Programmer yang perlu menggunakan list atau pohon biner, seharusnya tidak perlu lagi
membuat data struktur ini dari awal. Struktur data ini sudah banyak dimengerti dan
diprogram ribuan kali sebelumnya. Masalahnya adalah bagaimana agar struktur data yang
tangguh tersedia dan siap digunakan oleh programmer. Di bagian ini kita akan melihat
bagaimana Java menyelesaikan masalah ini
Pemrograman generik adalah penulisan kode yang bisa digunakan oleh berbagai macam
tipe data. Kita telah menemukan istilahnya pada bagian sebelumnya tentang array dinamis
integer. Kode yang ditulis di sana untuk array dinamis integer hanya bisa bekerja untuk
tipe data int. Akan tetapi kode array dinamis untuk double, String, atau tipe data lainnya
hampir sama. Akan sangat tidak nyaman apabila kita harus mengkopi kodenya berulang-
ulang untuk tipe data yang berbeda-beda.
Seperti disebutkan sebelumnya, Java mencoba menyelesaikan masalah ini dengan
membuat kelas ArrayList. Kelas ini pada dasarnya merupakan array dinamis dengan tipe
Object. Karena semua kelas merupakan kelas turunan dari Object, maka objek yang
bertipe kelas apapun bisa disimpan dalam ArrayList.
Ini adalah contoh pemrograman generik : kode untuk kelas ArrayList cukup ditulis satu
kali, tetapi bisa digunakan untuk objek dengan tipe data yang berbeda-beda (akan tetapi,
tidak bisa digunakan untuk tipe data primitif, seperti int atau double.)
Kelas ArrayList hanyalah satu dari beberapa kelas dan interface yang merupakan
pemrograman generik pada Java. Kita akan lihat beberapa kelas lain dan bagaimana kelas-
kelas ini digunakan. Semua kelas yang didiskusikan pada bagian ini merupakan bagian dari
paket java.util dan kita perlu menambahkan pernyataan import di awal program untuk
bisa menggunakannya. (Sebelum kita menggunakan perintah import java.util.*; di
semua program, kita harus tahu bahwa beberapa kelas di dalam java.util memiliki nama
yang sama pada pake lain. Misalnya, java.util.List dan java.awt.List adalah kelas
yang bernama sama dengan paket yang berbeda).
Adalah sesuatu hal yang tidak mudah untuk mendesain pustaka untuk pemrograman
generik. Solusi yang disediakan Java memiliki banyak fitur bagus, akan tetapi bukan
berarti cara ini adalah cara satu-satunya. Sudah pasti bukan yang terbaik, akan tetapi dalam
konteks desain Java secara keseluruhan, mungkin lebih cocok disebut optimal. Untuk dapat
memberikan gambaran seperti apa pemrograman generik secara umum, mungkin akan
lebih baik untuk melihat sekilas pemrograman generik di bahasa pemrograman lain.
Pemrograman Generik pada Bahasa Pemrograman Lain
Pemrograman Generik pada Smalltalk
Smalltalk adalah salah satu bahasa pemrograman berorientasi objek pertama. Bahasa ini
masih digunakan hingga kini. Meskipun tidak menjadi sepopuler Java atau C++, bahasa ini
adalah sumber ide yang diadopsi banyak bahasa pemrograman. Pada Smalltalk, pada
dasarnya semua pemorgraman adalah generik, karena dua sifat bahasa ini.
Pertama, variabel pada Smalltalk tidak memiliki tipe. Suatu nilai memiliki tipe, seperti
integer atau string, tetapi variabel tidak memiliki nilai. Suatu variabel bisa menampung
jenis data apa saja. Parameter juga tidak memiliki tipe, sehingga subrutin bisa digunakan
pada parameter apa saja. Demikian juga dengan struktur data bisa menampung data apa
saja. Misalnya, sekali kita mendefinisikan struktur data pohon biner pada Smalltalk, kita
bisa menggunakannya untuk integer, string, tanggal, atau data apa saja. Kita tidak perlu
menulis kode baru untuk masing-masing tipe data.
Kedua, semua nilai adalah objek, dan semua operasi pada objek dilakukan dengan metode
dalam kelas. Hal ini juga berlaku bahkan untuk tipe data primitif seperti integer. Ketika
operator "+" digunakan untuk menjumlah integer, operasi ini dilakukan dengan memanggil
metode pada kelas integer. Ketika kita membuat kelas baru, kita bisa membuat operator
"+" sendiri kemudian kita bisa menjumlahkan dua objek dengan tipe kelas tersebut dengan
menggunakan "a + b" seperti kita menjumlahkan angka seperti biasa.
Sekarang misalnya kita membuat subrutin baru yang menggunakan operator "+" untuk
menjumlahkan masing-masing item di dalam list. Subtuin ini bisa digunakan untuk list
integer, tapi juga bisa digunakan ke tipe data apapun yang mendefinisikan "+". Demikian
juga dengan subrutin yang menggunakan operator "<" untuk mengurutkan list juga bisa
digunakan untuk list yang mengandung tipe data apapun yang memiliki definisi "<". Kita
tidak perlu menulis subrutin pengurutan untuk masing-masing tipe data.
Jika kedua fitur ini kita gabungkan, kita bisa memiliki bahasa di mana struktur data dan
algoritmanya akan bekerja untuk jenis tipe data apapun (yang masuk akal), yaitu jika
operasi yang sesuai telah didefinisikan. Inilah yang merupakan pemrograman generik yang
sesungguhnya.
Mungkin ini terdengar sangat baik, dan Anda mungkin bertanya-tanya kenapa tidak semua
bahasa pemrograman bekerja seperti ini. Kebebasan pemrograman seperti ini akan
memudahkan kita membuat program, akan tetapi akan lebih sulit untuk membuat program
yang benar dan tangguh.
Sekali kita membuat struktur data yang bisa menampung tipe data apa saja, akan sulit
untuk menjamin ia bisa menampung tipe data yang kita inginkan. Jika kita ingin suatu
subrutin mengurutkan tipe data apapun, maka akan sangat sulit untuk menjamin bahwa
subrutin ini hanya digunakan untuk data di mana operator "<" telah didefinisikan. Lebih
khusus, kompiler tidak bisa memastikannya. Masalah ini akan muncul di saat program
dijalankan ketika kita mencoba untuk menjalankan operasi tertentu pada suatu tipe data
yang belum ada, kemudian program akan crash.
Pemrograman Generik pada C++
Tidak seperti Smalltalk, C++ adalah bahasa pemrograman dengan tipe kuat, bahkan lebih
kuat dari Java. Setiap variabel memiliki tipe, dan hanya bisa menampung tipe data itu saja.
Artinya pemrograman generik seperti pada Smalltalk tidak mungkin diterapkan.
Lebih Jauh, C++ tidak memiliki sesuatu yang mirip dengan kelas Object pada Java.
Artinya, tidak ada kelas yang merupakan kelas super dari semua kelas. Artinya C++ tidak
bisa menggunakan pemrograman generik seperti Java.
Akan tetapi, C++ memiliki sistem pemrograman geneik yang canggih dan fleksibel, yaitu
yang disebut template. Dalam C++, kita tidak membuat subrutin pengurutan yang berbeda
untuk setiap tipe data. Akan tetapi kita bisa mmebuat template subrutin. Template ini
bukan subrutin; akan tetapi mirip seperti pabrik pembuat subrutin. Kita lihat contoh
berikutnya, karena sintaks C++ mirip dengan Java :
template<class TipeItem>
void urut( TipeItem A[], int banyak ) {
// Urut banyak item dalam array A, ke dalam urutan menaik
// Algoritma yang digunakan adalah pengurutan pilihan
for (int i = banyak-1; i > 0; i--) {
int posisi_maks = 0;
for (int j = 1; j <= banyak; j++)
if ( A[j] > A[posisi_maks] )
posisi_maks = j;
TipeItem temp = A[banyak];
A[banyak] = A[posisi_maks];
A[posisi_maks] = temp;
}
}
Dalam kode di atas, kita mendefinisikan template subrutin. Jika kita menghapus baris
pertama, yaitu template<class TipeItem>", dan mengganti kata "TipeItem" dengan
"int", pada semua isi template, maka kita bisa mendapatkan subrutin untuk mengurut array
int. (Meskipun kita tulis seperti "class TipeItem", kita bisa menggantinya dengan tipe
apapun, termasuk tipe primitif). Jika kita mengganti "TipeItem" dengan string, maka kita
bisa mendapatkan subrutin untuk mengurut array string. Ini yang sebenarnya dilakukan
oleh compiler.
Jika program kita menyebut "urut(list,10)" di mana list adalah array int, maka
kompiler menggunakan template ini untuk membuat subrutin untuk mengurut array int.
Jika kita sebut "urut(kartu,10)" di mana kartu adalah array objek bertipe Kartu, maka
kompiler akan membuat subrutin untuk mengurutkan array Kartu.
Template di atas menggunakan operator ">" untuk membandingkan nilai. Jika operator ini
didefinisikan untuk nilai bertipe Kartu, maka kompiler akan menggunakan template
dengan sukses. Jika ">" tidak didefinisikan untuk kelas Kartu, maka kompiler akan gagal,
akan tetapi ini akan terjadi pada saat program dikompilasi bukan seperi Smalltalk di mana
program akan crash pada saat dijalankan.
C++ juga memiliki template untuk membuat kelas. Jika kita menulis template untuk pohon
biner, kita bisa menggunakan template itu untuk membuat kelas pohon biner int, pohon
biner string, pohon biner tanggal, dan seterusnya -- semua dari satu template. Versi paling
baru C++ memiliki template bawaan yang cukup komplit yang disebut dengan Pustaka
Template Standar (Standard Template Library atau STL). STL sendiri cukup kompleks,
dan bahkan beberapa orang mengatakan sangat amat kompleks. Akan tetapi ini juga fitur
paling menarik dari C++
Pemrograman Generik pada Java
Seperti pada C++, Java adalah bahasa bertipe kuat. Akan tetapi, pemrograman generik
pada Java lebih dekat dengan Smalltalk daripada C++. Seperti telah dijelaskan
sebelumnya, pemrograman generik pada Java berdasarkan pada kelas Object yang
merupakan kelas super dari semua kelas. Hingga tingkat tertentu, ini membuat Java mirip
dengan Smalltalk : Struktur data yang didesain untuk menampung Object bisa digunakan
untuk menyimpan data kelas apapun. Kita tidak perlu membuat template atau fitur pada
bahasa pemrograman lain untuk mendukung pemrograman generik.
Tentunya, tipe primitif, seperti integer, bukan objek pada Java, dan karenanya tidak bisa
disimpan dalam tipe data generik. Dan sebenarnya, tidak ada cara untuk melakukan
pemrograman generik dengan tipe data primitif pada Java. Pendekatan Smalltalk tidak bisa
diterapkan pada Java kecuali untuk objek, dan pendekatan C++ tidak tersedia pada Java.
Lebih jauh, subrutin generik lebih bermasalah pada Java daripada Smalltalk atau C++.
Pada Smalltak, subrutin dapat dipanggil dengan parameter bertipe apapun, dan akan
bekerja asalkan operator yang digunakan pada subrutin didefinisikan pada parameternya.
Pada Java, parameter suatu subrutin harus bertipe tertentu. Dan subrutin hanya bisa
menggunakan operasi untuk tipe itu saja. Subrutin dengan parameter Object bisa
digunakan untuk objek tipe apa saja, akan tetapi subrutin hanya bisa menggunakan operasi
pada kelas Object saja, dan sebenarnya hanya sedikit operasi pada kelas Object!
Misalnya tidak ada operasi pembanding pada kelas Object, jadi kita tidak bisa membuat
algoritma pengurutan generik. Kita akan lihat bagaimana Java menyelesaikan masalah ini.
Karena masalah seperti ini, beberapa orang menyatakan bahwa Java tidak mendukung
pemrograman generik secara keseluruhan. Beberapa orang lain tidak setuju. Akan tetapi,
tetap saja ini tidak menghambat Java untuk digunakan secara luas.
Koleksi dan Map
Struktur data generik pada Java dapat dibagi menjadi dua kategori : koleksi dan map.
Koleksi kurang lebih mirip seperti kumpulan objek-objek. Map menghubungkan objek di
satu kumpulan dan objek di kumpulan lain seperti kamus yang menghubungkan definisi
dengan kata atau buku telepon menghubungkan nama dan nomor telepon. Map mirip
dengan apa yang kita sebut "list asosiasi" pada bagian sebelumnya.
Ada dua jenis koleksi : list dan himpunan (set). List adalah kumpulan objek di mana item-
itemnya diatur secara berurutan. List memiliki item pertama, item kedua, dan seterusnya.
Untuk item di dalam list, kecuali item terakhir, akan ada item yang ada di belakangnya.
Himpunan (set) adalah kumpulan objek di mana hanya ada satu objek yang bisa ada di
dalam suatu himpunan.
Lihat bahwa istilah "koleksi", "list", "himpunan", dan "map" tidak menyatakan bagaimana
data disimpan. List bisa diimplementasikan dalam bentuk array, list berantai, atau map
yang menghubungkan elemen list dengan angka 0, 1, 2, ....
Sebetulnya istilah-istilah ini dibuat pada Java bukan dalam bentuk kelas tapi dalam bentuk
interface. Interface Collection, List, Set dan Map mendefinisikan operasi dasar dari
struktur data tersebut, tapi tidak menjelaskan bagaimana struktur data dan operasinya
diimplementasikan.
Struktur data dan operasinya akan ditulis pada kelas yang mengimplementasikan interface
tersebut. Bahkan ketika kita menggunakan kelas tersebut, kita mungkin tidak tahu
bagaimana kelas tersebut diimplementasikan, kecuali kita melihat langsung pada kode
sumbernya. Struktur data generik pada Java adalah tipe data abstrak (abstract data type).
Mereka memiliki definisi operasi yang bisa dilakukan, dan bukan bagaimana data diatur
dalam memori komputer.
Kita akan lihat kelas list, set dan map pada bagian berikut. Tapi sebelum kita sampai pada
bagian itu, kita akan melihat dahulu beberapa konsep tentang operasi umum yang tersedia
pada semua koleksi.
Algoritma Generik
Interface Collection memiliki metode untuk melakukan beberapa operasi dasar pada
koleksi. Karena "koleksi" adalah konsep yang sangat umum, maka operasi yang bisa
dilakukan oleh semua koleksi juga sesuatu yang sangat umum. Misalnyakol adalah objek
yang mengimplementasi interface Collection. Berikut ini adalah operasi yang bisa
dilakukan.
kol.size() -- mengembalikan int yang berisi banyaknya objek dalam suatu koleksi
kol.isEmpty() -- mengembalikan boolean true jika koleksi kosong, atau ukurannya
sama dengan 0
kol.clear() -- menghapus semua objek dalam koleksi
kol.contains(objek) -- mengembalikan nilai boolean jika objek ada dalam koleksi
kol.add(objek) -- menambah objek ke dalam koleksi. Parameternya bisa berupa
Object apa saja. Beberapa koleksi bisa berisi nilai null, sementara yang lain tidak.
Misalnya menambahkan objek ke dalam Set tidak berpengaruh apa-apa jika objek
tersebut sudah ada di dalamnya.
kol.remove(objek) -- membuang objek dari dalam koleksi, jika ada, dan
mengembalikan nilai boolean yang menyatakan apakah objek tersebut ada atau
tidak di dalam koleksi
kol.containsAll(kol2) -- mengembalikan nilai boolean jika semua objek dalam kol2
ada di dalam koleksi kol. Parameternya bisa berupa Collection apa saja.
kol.addAll(col2) -- menambahkan semua objek yang ada dalam koleksi kol2 ke
dalam kol
kol.removeAll(kol2) -- menghapus semua objek di kol yang ada pada kol2
kol.retainAll(kol2) -- menghapus semua objek pada kol yang tidak ada pada kol2.
Hanya akan mempertahankan objek yang ada pada kol2
kol.toArray() -- mengembalikan array bertipe Object[] yang isinya semua item di
dalam koleksi. Nilai yang dikembalikan bisa di-tipe cast ke dalam tipe array lain,
jika perlu. Misalnya, jika kita tahu bahwa semua item di dalam kol bertipe String,
maka (String[])kol.toArray() akan mengembalikan array String yang berisi
semua string di dalam koleksi.
Karena semua metode adalah turunan dari interface Collection, maka metode ini harus
didefinisikan pada semua objek yang mengimplementasikan interface ini. Akan tetapi ada
masalah yang timbul. Misalnya ukuran suatu Collection tidak bisa diganti setelah dibuat.
Metode yang menambah atau membuang objek tidak bisa digunakan untuk koleksi jenis
ini. Meskipun mungkin legal untuk memanggil metode ini, pengecualian akan dilemparkan
ketika program dijalankan. Jenis pengecualiannya bernama
UnsupportedOperationException.
Ada juga masalah efisiensi. Meskipun suatu operasi didefinisikan untuk beberapa jenis
koleksi, tentunya unjuk kerjanya tidak sama untuk semua kelas. Bahkan untuk metode
sesederhana size() bisa sangat berbeda dari satu jenis koleksi ke jenis koleksi lainnya.
Untuk beberapa jenis koleksi, waktu pengolahan sebanding dengan jumlah item di dalam
koleksi. Koleksi lain mungkin memiliki variabel instansi yang melacak jumlah item di
dalam koleksi, sehingga menjalankan size() sama dengan mengambil nilai variabel
instansi ini. Artinya operasinya hanya dilakukan satu langkah saja, tidak peduli berapa
banyak item yang ada di dalam koleksi.
Ketika kita menggunakan koleksi, alangkah lebih baiknya untuk mengetahui seberapa
efisien suatu operasi dilakukan untuk jenis koleksi tertentu. Kita harus bisa memilih
koleksi yang cocok untuk masalah yang kita hadapi. Kita akan lihat lebih detail di bagian
berikutnya.
Iterator
Interface Collection mendefinisikan beberapa algoritma generik, akan tetapi mungkin
kita ingin membuat algoritma generik kita sendiri. Misalnya kita ingin membuat suatu
subrutin yang mencetak setiap item di dalam koleksi. Untuk bisa melakukan ini secara
generik, kita harus mencari akal bagaimana caranya untuk mengakses setiap item satu per
satu.
Kita telah lihat sebelumnya bagaimana caranya untuk data struktur tertentu : Misalnya
untuk array, kita bisa menggunakan perulangan for dari item pertama hingga terakhir.
Untuk list berantai, kita bisa menggunakan perulangan while dari item pertama hingga
kita menemukan null. Untuk pohon biner kita bisa menggunakan subrutin rekursif dengan
penelusuran infix.
Koleksi bisa dibuat dalam berbagai bentuk. Dengan keragaman bentuk ini dan bermacam-
macam cara untuk menelusurinya, bagaimana kita bisa membuat metode generik yang
berlaku untuk semua kenis koleksi? Masalah ini bisa diselesaikan dengan iterator.
Iterator adalah objek yang digunakan untuk menelusuri koleksi. Koleksi dengan jenis yang
berbeda memiliki jenis iterator yang berbeda pula, akan tetapi semua iterator digunakan
dengan cara yang sama. Algoritma yang menggunakan iterator untuk menelusuri koleksi
bisa dibuat generik, karena teknik yang sama bisa digunakan untuk beragam jenis koleksi.
Konsep iterator mungkin agak aneh untuk seseorang yang baru menemui konsep
pemrgoraman generik, akan tetapi Anda harus mengerti bahwa konsep ini bisa digunakan
untuk menyelesaikan masalah sulit dengan cara yang anggun.
Interface Collection memiliki metode yang bisa digunakan untuk mengambil iterator
dalam koleksi apapun. Jika kol adalah suatu koleksi, maka kol.iterator()
mengembalikan iterator yang bisa digunakan untuk menelusuri koleksi tersebut.
Bayangkan iterator seperti pointer generik yang dimulai dari awal koleksi dan bisa
berpindah dari satu item ke item lain. Iterator didefinisikan oleh interface yang bernama
Iterator. Interface ini hanya memiliki 3 metode. Jika iter merupakan variabel bertipe
Iterator, maka :
iter.next() -- mengembalikan item berikutnya, dan memindahkan iterator ke
item berikutnya. Nilai keluarannya bertipe Object. Ingat bahwa kita tidak bisa
melihat suatu item tanpa memindahkan iterator ke item berikutnya. Jika metode ini
dipanggil apabila tidak ada item lagi yang tersisa, ia akan melempar
NoSuchElementException.
ArrayList adalah urutan objek yang disimpan dalam bentuk array yang ukurannya bisa
membesar jika item baru ditambahkan. Sedangkan LinkedList adalah urutan objek yang
disimpan dalam simpul yang terhubung dengan pointer seperti rantai. Kedua kelas ini
mengimplementasikan interface java.util.List, yang memiliki operasi yang sama untuk
semua jenis list.
Kedua list tersebut mendukung operasi list dasar. Tipe data abstrak didefinisikan dalam
bentuk operasi yang bisa dilakukannya, bukan bagaimana data disusun. Lalu kenapa kita
memiliki 2 kelas? Mengapa bukan satu kelas List saja dengan satu jenis struktur data?
Masalahnya, tidak ada satupun struktur data list yang bisa melakukan semua operasi list
secara efisien. Untuk operasi tertentu, list berantai lebih efisien daripada array. Untuk
operasi lainnya, array lebih efisien. Tergantung daripada bagaimana list tersebut
digunakan. Kita harus bisa memilih bentuk mana yang paling cocok dengan melihat
operasi apa yang sering digunakan.
Secara garis besar, kelas LinkedList lebih efisien untuk aplikasi di mana item-itemnya
lebih sering ditambah atau dibuang dari dalam list, terutama dari depan atau dari tengah
list. Dalam array, operasi tersebut memerlukan waktu yang cukup besar, karena untuk
setiap penyisipan atau pengurangan item, item harus digeser untuk membuat tempat
kosong baru ditengah list. Pada list berantai, simpul bisa diputus di tengah-tengah untuk
menambahkan item baru, yaitu dengan mengubah pointernya saja.
Kelas ArrayList lebih efisien digunakan jika akses random (random access) dibutuhkan
oleh aplikasi. Akses random maksudnya mengakses item ke-n di tengah-tengah list dan
pindah dari satu item ke item lain yang letaknya mungkin berjauhan.
Operasi yang bisa dilakukan dengan efisien oleh kedua kelas tersebut adalah mengurutkan
dan menambah item di akhir list.
Semua list mengimplementasikan metode dalam Collection, termasuk size(), isEmpty,
add(Object), remove(Object), dan clear().
Metode add(Object) digunakan untuk menambah objek di akhir list. Metode
remove(Object) dilakukan dengan mencari itemnya dahulu, yang tidak efisien dilakukan
pada list berantai karena pada list berantai item dibandingkan satu per satu dari awal list
hingga item ditemukan.
Interface List menambah beberapa metode untuk mengakses item pada list tergantung
dari urutannya dalam list. Untuk objek list bertipe List, metode-metode yang tersedia
adalah :
list.get(indeks) -- mengembalikan Object di posisi indeks dalam list, di mana
indeks dimulai dari 0, 1, 2, .... hingga list.size() - 1. Parameter indeks harus
ada dalam rentang ini, jika tidak maka pengecualian IndexOutOfBoundsException
akan dilemparkan.
list.set(indeks, obj) -- menyimpan obj di dalam list pada posisi indeks, dan
mengganti isi objek yang sudah ada di posisi tersebut. Metode ini tidak mengubah
jumlah elemen di dalam list atau memindahkan elemen lain.
list.add(indeks, obj) -- menyisipkan objek obj di dalam list pada posisi
indeks. Jumlah item di dalam list akan bertambah satu, dan item setelah posisi
indeks akan digeser ke belakang. Nilai indeks harus berada pada rentang 0 hingga
list.size().
while (iter.hasNext()) {
Object item = iter.next();
if (itemBaru.compareTo(item) <= 0) {
// itemBaru harus ada SEBELUM item pada iter
// Pindahkan iterator satu tempat sehingga
// menunjuk pada posisi penyisipan yang benar,
// dan pada akhir perulangan.
iter.previous();
break;
}
}
iter.add(itemBaru);
} // akhir sisipTerurut()
Karena parameter pada metode ini bertipe List, maka metode ini bisa digunakan pada
ArrayList maupun LinkedList, dan kira-kira akan berjalan dengan tingkat efisiensi yang
sama untuk kedua jenis list. Mungkin akan lebih mudah untuk menulis metode di atas
dengan menggunakan indeks seperti pada array, dan dengan menggunakan metode
get(indeks) dan add(indeks,obj). Akan tetapi metode ini akan berjalan sangat lambat
untuk LinkedList karena get(indeks) pada LinkedList berjalan tidak efisien.
Pengurutan List
Mengurutkan list adalah operasi yang cukup umum dilakukan, dan seharusnya ada metode
untuk mengurut list di dalam interface List. Kenyataannya tidak ada.
Akan tetapi metode untuk mengurut List tersedia sebagai metode statik dalam kelas
java.util.Collections. Kelas ini memiliki berbagai metode statik yang digunakan
untuk membantu kelas-kelas koleksi :
Collections.sort(list) digunakan untuk mengurut list dalam urutan menaik.
Item di dalam list harus mengimplementasikan interface Comparable, misalnya
pada String.
Collections.sort(list,komparator), digunakan untuk mengurut list
menggunakan komparator (pembanding) sendiri yang diberikan di dalam metode.
Komparator adalah objek yang memiliki metode compare() yang digunakan untuk
membandingkan dua objek. Kita akan lihat contohnya nanti.
Collections.shuffle(list) digunakan untuk mengacak list.
Collections.reverse(list) digunakan untuk membalik urutan elemen di dalam
list, sehingga elemen terakhir menjadi elemen pertama dan sebaliknya.
Karena metode pengurutan yang cukup efisien sudah diberikan untuk List, kita tidak perlu
membuat metode pengurutan sendiri. Tentunya kita akan bertanya apakah ada metode
pengurutan untuk array biasa. Jawabannya adalah ya.
Pengurutan array juga tersedia dalam metode statik dalam kelas java.util.Arrays.
Arrays.sort(A) digunakan untuk mengurut array A, asalkan tipe dasar A adalah tipe
dasar primitif (kecuali boolean) atau jika A adalah array Object yang
mengimplementasikan interface Comparable.
Arrays.sort(A, indeksAwal, indeksAkhir) digunakan untuk mengurut sebagian
array saja. Ini penting karena array kadang hanya terisi sebagian. Perintah ini
mengurut array dari elemen A[indeksAwal], A[indeksAwal + 1], ... hingga
A[indeksAkhir - 1] dalam urutan menaik. Array.sort(A, 0, N) bisa
digunakan untuk mengurut N elemen pertama di array A.
Java tidak mendukung pemrograman generik untuk tipe primitif. Untuk
mengimplementasikan perintah Arrays.sort(A), kelas Arrays memiliki 8 metode : satu
metode untuk array Object dan masing-masing satu metode untuk tipe primitif byte,
short, int, long, float, double, dan char.
Set (Himpunan)
Himpunan (set) adalah kumpulan Object yang mana tidak boleh ada dua dari objek yang
sama di dalam satu himpunan. Objek obj1 dan obj2 adalah objek yang sama jika
obj1.equals(obj2) menghasilkan nilai true (lihat bagian sebelumnya untuk penjelasan
tentang ini).
Set mengimplementasikan metode umum pada Collection dengan sedikit modifikasi
sehingga tidak memiliki objek yang sama di dalamnya. Misalnya, jika set adalah objek
bertipe Set maka set.add(obj) tidak melakukan apa-apa jika obj sudah ada di dalam
set.
Dalam TreeMap, pasangan kunci/nilai disimpan secara berurutan dalam pohon terurut,
yaitu diurut berdasarkan kuncinya. Supaya bisa bekerja dengan benar, maka hanya objek
yang bisa dibandingkan saja yang bisa digunakan sebagai kunci. Artinya kelas kunci harus
berupa kelas yang mengimplementasikan interface Comparable, atau Comparator harus
diberikan pada konstruktornya pada saat TreeMap dibuat.
HashMap tidak menyimpan pasangan kunci/nilai dalam urutan tertentu, sehingga tidak ada
batasan objek apa yang bisa disimpan di dalamnya. Hampir semua operasi dapat berjalan
lebih cepat pada HashMap dibandingkan dengan TreeMap.
Secara umum, lebih baik menggunakan HashMap kecuali kita butuh struktur data dalam
urutan tertentu yang hanya bisa dilakukan dengan TreeMap. Atau dengan kata lain, jika
kita hanya menggunakan perintah put dan get, gunakan HashMap.
Misalnya progrma direktori telefon, yaitu pada kelas BukuTelepon yang memiliki
pasangan nama/nomor telepon. Kelas ini memiliki operasi tambahEntri(nama, nomor)
dan ambilNomor(nama), di mana nama dan nomor bertipe String.
Dalam aplikasi pemrograman sebenarnya, kita tidak perlu lagi membuat kelas baru untuk
mengimplementasikan BukuTelepon tersebut, artinya kita bisa langsung menggunakan
Map. Akan tetapi menggunakan Map mungkin memiliki sedikit kerugian, karena kita
dipaksa harus menggunakan Object bukan String.
Jika ini masalahnya, maka kita bisa membuat kelas baru yang menggunakan Map dalam
implementasinya, seperti berikut :
import java.util.HashMap;
mengembalikan sebagian list yang terdiri dari elemen pada posisi antara indeksAwal
hingga indeksAkhir (termasuk indeksAwal tapi tidak termasuk indeksAkhir.) Tampilan
ini memungkinkan kita untuk melakukan operasi apapun seperti pada list biasa, akan tetapi
sublist bukan list terpisah. Perubahan yang dilakukan pada sublist akan juga
mempengaruhi list aslinya.
Begitu juga dengan set, kita bisa membuat tampilan yang merupakan subset dari suatu set.
Jika set adalah suatu TreeSet, maka set.subSet(dariElemen, hinggaElemen)
mengembalikan [code]Set yang berisi elemen-elemen set antara dariElemen hingga
hinggaElemen (termasuk dariElemen tapi tidak termasuk hinggaElemen).
Misalnya, jika kata adalah suatu TreeSet di mana semua elemennya adalah String berisi
huruf kecil, maka kata.subSet("m", "n") akan berisi semua elemen pada kata yang
dimulai dari huruf m. Subset ini adalah tampilan yang merupakan bagian dari set aslinya.
Artinya membuat subset tidak mengkopi elemen, dan perubahan pada subset (misalnya
penambahan dan pengurangan elemen) juga mempengaruhi set aslinya.
Tampilan set.headSet(hinggaElemen) berisi semua elemen dari set yang
kurang dari [code]hinggaElemen, dan set.tailSet(dariElemen) adalah tampilan
yang berisi semua elemen yang lebih besar atau sama dengan dariElemen.
Kelas TreeMap memiliki tiga tampilan submap. Submap mirip dengan subset. Submat
adalah Map yang berisi subset dari kunci dan nilai pada Map aslinya. Jika map suatu variabel
bertipe TreeMap, maka map.subMap(dariKunci, hinggaKunci) mengembalikan
tampilan yang berisi semua pasangan kunci/nilai dari map yang kuncinya ada di antara
dariKunci dan hinggaKunci (termasuk dariKunci tapi tidak termasuk hinggaKunci).
Ada juga tampilan map.headMap(hinggaKunci) dan map.tailMap(dariKunci) seperti
aturan pada subset.
Misalnya, bukuHitam adalah suatu TreeMap di mana kuncinya adalah nama dan nilainya
adalah nomor telepon. Kita bisa mencetak semua entri pada bukuHitam yang namanya
dimulai dengan "M" seperti berikut :
Map ems = bukuHitam.subMap("M","N");
Pada gambar di atas, hanya ada satu item dengan kode hash 0, tidak ada item dengan kode
hash 1, dua item dengan kode hash 2, dan seterusnya. Pada tabel hash yang dirancang
dengan benar, hampir semua list berantai berisi nol atau satu elemen saja, dengan rata-rata
panjang list kurang dari 1. Meskipun kode hash dari suatu kunci mungkin tidak membawa
kita langsung pada kunci yang kita mau, akan tetapi tidak akan lebih dari satu atau dua
item yang harus kita cari sebelum kita sampai pada item yang kita inginkan.
Agar bekerja dengan benar, jumlah item dalam tabel hash harus kurang dari besarnya
array. Pada Java, katika jumlah item melebihi 75% ukuran array, maka array tersebut akan
diganti dengan array yang lebih besar dan semua item pada array yang lama dipindahkan
ke array baru.
Kelas Object memiliki metode bernama hashCode() yang mengembalikan nilai bertipe
int. Ketika objek obj disimpan dalam tabel yang berukuran N, maka kode hash antara 0
hingga N-1 diperlukan. Kode hash ini bisa dihitung dengan menggunakan
Math.abs(obj.hashCode()) % N, yaitu sisa pembagian dari nilai mutlak
obj.hashCode() dengan N. (Nilai mutlak diperlukan karena obj.hashCode() bisa
bernilai negatif, dan kita tidak ingin nilai negatif sebagai indeks array kita).
Supaya hash bisa bekerja dengan benar, dua objek yang sama menurut metode equals()
seharusnya memiliki kode hash yang sama. Dalam kelas Object metode equals() dan
hashCode() dihitung berdasarkan lokasi memori di mana objek tersebut disimpan. Akan
tetapi seperti disebutkan sebelumnya, banyak kelas yang memiliki metode equals()
sendiri.
Jika suatu kelas memiliki metode equals() sendiri, dan jika objek tersebut akan
digunakan sebagai kunci pada tabel hash, maka kelas tersebut juga harus mendefinisikan
metode hashCode().
Misalnya dalam kelas String, metode equals() didefinisi ulang sehingga dua objek
String dianggap sama jika urutan karakter dalam String tersebut sama. Metode
hashCOde() pada kelas String itu juga didefinisi ulang sehingga kode hash dari string
dihitung berdasarkan karakter di dalam String, bukan lokasi memorinya.
Untuk kelas standar Java, kita bisa berharap bahwa metode equals() dan hashCode sudah
dibuat dengan benar, akan tetapi untuk kelas yang kita buat sendiri kita mungkin harus
membuat fungsi hash sendiri.
Bab X - Pengenalan Input/Output (I/O)
Program komputer bisa berguna jika ia bisa berinteraksi dengan dunia lain. Interaksi di sini
maksudnya input/output atau I/O. Pada bab ini, kita akan melihat input output pada file dan
koneksi jaringan (network). Pada Java, input/output pada file dan jaringan dilakukan
berdasarkan aliran (stream), di mana semua objek dapat melakukan perintah I/O yang
sama. Standar output (System.out) dan standar input (System.in) adalah contoh aliran.
Untuk bekerja dengan file dan jaringan, kita membutuhkan pengetahuan tentang
pengecualian, yang telah dibahas sebelumnya. Banyak subrutin yang digunakan untuk
bekerja dengan I/O melemparkan pengecualian yang wajib ditangani. Artinya subrutin
tersebut harus dipanggil di dalam pernyataan try ... catch sehingga pengecualian yang
terjadi bisa ditangani dengan baik.
Stream, Reader, dan Writer
Tanpa bisa berinteraksi dengan dunia lain, suatu program tidak ada gunanya. Interaksi
suatu program dengan dunia lain sering disebut input/output atau I/O. Sejak dulu, salah
satu tantangan terbesar untuk mendesain bahasa pemrograman baru adalah mempersiapkan
fasilitas untuk melakukan input dan output. Komputer bisa terhubung dengan beragam
jenis input dan output dari berbagai perangkat. Jika bahasa pemrograman harus dibuat
secara khusus untuk setiap jenis perangkat, maka kompleksitasnya akan tak lagi bisa
ditangani.
Salah satu kemajuan terbesar dalam sejarah pemrograman adalah adanya konsep (atau
abstraksi) untuk memodelkan perangkat I/O. Dalam Java, abstraksi ini disebut dengan
aliran (stream). Bagian ini akan memperkenalkan tentang aliran, akan tetapi tidak
menjelaskan dengan komplit. Untuk lebih lengkapnya, silakan lihat dokumen resmi Java.
Ketika berhubungan dengan input/output, kita harus ingat bahwa ada dua kategori data
secara umum : data yang dibuat oleh mesin, dan data yang bisa dibaca manusia. Data yang
dibuat mesin ditulis dengan model yang sama dengan bagaimana data tersebut disimpan di
dalam komputer, yaitu rangkaian nol dan satu. Data yang bisa dibaca manusia adalah data
dalam bentuk rangkaian huruf. Ketika kita membaca suatu bilangan 3.13159, kita
membacanya sebagai rangkaian huruf yang kita terjemahkan sebagai angka. Angka ini
akan ditulis dalam komputer sebagai rangkaian bit yang kita tidak mengerti.
Untuk menghadapi kedua jenis data ini, Java memiliki dua kategori besar untuk aliran :
aliran byte untuk data mesin (byte stream), dan aliran karakter (character stream) untuk
data yang bisa dibaca manusia. Ada banyak kelas yang diturunkan dari kedua kategori ini.
Setiap objek yang mengeluarkan data ke aliran byte masuk sebagai kelas turunan dari
kelas abstrak OutputStream. Objek yang membaca data dari aliran byte diturunkan dari
kelas abstrak InputStream. Jika kita menulis angka ke suatu OutputStream, kita tidak
akan bisa membaca data tersebut karena ditulis dalam bahasa mesin. Akan tetapi data
tersebut bisa dibaca kembali oleh InputStream. Proses baca tulis data akan menjadi sangat
efisien, karena tidak ada penerjemahan yang harus dilakukan : bit yang digunakan untuk
menyimpan data di dalam memori komputer hanya dikopi dari dan ke aliran tersebut.
Untuk membaca dan menulis data karakter yang bisa dimengerti manusia, kelas utamanya
adalah Reader dan Writer. Semua kelas aliran karakter merupakan kelas turunan dari
salah satu dari kelas abstrak ini. Jika suatu angka akan ditulis dalam aliran Writer,
komputer harus bisa menerjemahkannya ke dalam rangkaian karakter yang bisa dibaca
maunsia.
Membaca angka dari aliran Reader menjadi variabel numerik juga harus diterjemahkan,
dari deretan karakter menjadi rangkaian bit yang dimengerti komputer. (Meskipun untuk
data yang terdiri dari karakter, seperti dari editor teks, masih akan ada beberapa terjemahan
yang dilakukan. Karakter disimpan dalam komputer dalam nilai Unicode 16-bit. Bagi
orang yang menggunakan alfabet biasa, data karakter biasanya disimpan dalam file dalam
kode ASCII, yang hanya menggunakan 8-bit. Kelas Reader dan Writer akan menangani
perubahan dari 16-bit ke 8-bit dan sebaliknya, dan juga menangani alfabet lain yang
digunakan negara lain.)
Adalah hal yang mudah untuk menentukan apakah kita harus menggunakan aliran byte
atau aliran karakter. Jika kita ingin data yang kita baca/tulis untuk bisa dibaca manusia,
maka kita gunakan aliran karakter. Jika tidak, gunakan aliran byte. System.in dan
System.out sebenarnya adalah aliran byte dan bukan aliran karakter, karenanya bisa
menangani input selain alfabet, misalnya tombol enter, tanda panah, escape, dsb.
Kelas aliran standar yang akan dibahas berikutnya didefinisikan dalam paket java.io
beserta beberapa kelas bantu lainnya. Kita harus mengimpor kelas-kelas tersebut dari paket
ini jika kita ingin menggunakannya dalam program kita. Artinya dengan menggunakan
"import java.io.*" di awal kode sumber kita.
Aliran tidak digunakan dalam GUI, karena GUI memiliki aliran I/O tersendiri. Akan tetapi
kelas-kelas ini digunakan juga untuk file atau komunikasi dalam jaringan. Atau bisa juga
digunakan untuk komunikasi antar thread yang sedang bekerja secara bersamaan. Dan juga
ada kelas aliran yang digunakan untuk membaca dan menulis data dari dan ke memori
komputer.
Operasi pada Aliran (Stream)
Kelas dasar I/O Reader, Writer, InputStream dan OutputStream hanya menyediakan
operasi I/O sangat dasar. Misalnya, kelas InputStream memiliki metode instansi
public int read() throws IOException
untuk membaca satu byte data dari aliran input. Jika sampai pada akhir dari aliran input ,
metode read() akan mengembalikan nilai -1. Jika ada kesalahan yang terjadi pada saat
pengambilan input, maka pengecualian IOException akan dilemparkan. Karena
IOException adalah kelas pengecualian yang harus ditangani, artinya kita harus
menggunakan metode read() di dalam penyataan try atau mengeset subrutin untuk
throws IOException. (Lihat kembali pembahasan tentang pengecualian di bab
sebelumnya)
Kelas InputStream juga memiliki metode untuk membaca beberapa byte data dalam satu
langkah ke dalam array byte. Akan tetapi InputStream tidak memiliki metode untuk
membaca jenis data lain, seperti int atau double dari aliran. Ini bukan masalah karena
dalam prakteknya kita tidak akan menggunakan objek bertipe InputStream secara
langsung. Yang akan kita gunakan adalah kelas turunan dari InputStream yang memiliki
beberapa metode input yang lebih beragam daripada InputStream itu sendiri.
Begitu juga dengan kelas OutputStream memiliki metode output primitif untuk menulis
satu byte data ke aliran output, yaitu metode
public void write(int b) throws IOException
Tapi, kita hampir pasti akan menggunakan kelas turunannya yang mampu menangani
operasi yang lebih kompleks.
Kelas Reader dan Writer memiliki operasi dasar yang hampir sama, yaitu read dan
write, akan tetapi kelas ini berorientasi karakter (karena digunakan untuk membaca dan
menulis data yang bisa dibaca manusia). Artinya operasi baca tulis akan mengambil dan
menulis nilai char bukan byte. Dalam prakteknya kita akan menggunakan kelas turunan
dari kelas-kelas dasar ini.
Salah satu hal menarik dari paket I/O pada Java adalah kemungkinan untuk menambah
kompleksitas suatu aliran dengan membungkus aliran tersebut dalam objek aliran lain.
Objek pembungkus ini juga berupa aliran, sehingga kita juga bisa melakukan baca tulis
dari objek yang sama dengan tambahan kemampuan dalam objek pembungkusnya.
Misalnya, PrintWriter adalah kelas turunan dari Writer yang memiliki metode
tambahan untuk menulis tipe data Java dalam karakter yang bisa dibaca manusial. Jika kita
memiliki objek bertipe Writer atau turunannya, dan kita ingin menggunakan metode pada
PrintWriter untuk menulis data, maka kita bisa membungkus objek Writer dalam objek
PrintWriter.
Artinya, OutputStream hanya berisi metode dasar untuk menulis byte, sedangkan
DataOutputStream memiliki metode writeDouble(double x) untuk menulis nilai
double, writeInt(int x) untuk menulis nilai int, dan seterusnya. Dan juga kita bisa
membungkus objek bertipe OutputStream atau turunannya ke dalam aliran
DataOutputStream sehingga kita bisa menggunakan metode yang lebih kompleks.
Misalnya, jika baskomByte adalah variabel bertipe OutputStream, maka
DataOutputStream baskomData = new DataOutputStream(baskomByte);
OutputStream
Beberapa kelas turunan dari OutputStream dapat dirangkum dalam tabel di bawah ini :
Argumen yang
Kelas Kegunaan dibutuhkan untuk
membuat objek
Kelas FilterOutputStream sendiri terdiri dari beberapa jenis, yang bisa dirangkum dalam
tabel berikut ini :
Argumen yang
Kelas Kegunaan dibutuhkan untuk
membuat objek
File
Data dan program pada memori komputer hanya bisa bertahan selama komputer itu nyala.
Untuk tempat penyimpanan yang lebih lama, komputer menggunakan file, yaitu kumpulan
data yang disimpan dalam hard disk, disket atau CD-ROM, USB stick, dan lain-lain. File
disusun dalam direktori (atau sering juga disebut folder). Direktori bisa terdiri dari
direktori lain atau file lain. Nama direktori dan file digunakan untuk mencari suatu file
dalam komputer.
Program dapat membaca data dari file yang sudah ada. Program juga bisa membuat file
baru atau menulis data ke dalam file yang sudah ada. Dalam Java, input dan output seperti
ini bisa menggunakan aliran (stream). Data karakter yang bisa dibaca manusial dapat
dibaca dari file dengan menggunakan objek dari kelas FileReader yang merupakan kelas
turunan Reader. Data bisa ditulis dalam bentuk yang bisa dibaca manusia dengan
menggunakan FileWriter yang merupakan kelas turunan dari Writer.
Untuk membaca atau menyimpan suatu file dalam format mesin, kelas I/O-nya adalah
FileInputStream dan FileOutputStream. Semua kelas ini didefinisikan dalam paket
java.io.
Perlu dicatat bahwa applet yang didownload dari suatu jaringan pada umumnya tidak bisa
mengakses file karena pertimbangan keamanan. Kita bisa mendownload dan menjalankan
applet, yaitu dengan mengunjungi halaman web pada browser kita. Jika applet tersebut bisa
digunakan untuk mengakses file pada komputer kita, maka orang bisa membuat applet
untuk menghapus semua file dalam komputer yang mendownloadnya.
Untuk mencegah hal seperti itu, ada beberapa hal di mana applet yang didownload tidak
bisa lakukan. Mengakses file adalah salah satu hal yang dilarang. Akan tetapi program
desktop bisa memiliki akses ke file kita seperti program-program lainnya. Program desktop
bisa melakukan akses file yang dijelaskan pada bagian ini.
Kelas FileReader memiliki konstruktor yang mengambil nama file sebagai parameternya,
kemudian membuat aliran input yang bisa digunakan untuk membaca file tersebut.
Konstruktor ini akan melemparkan pengecualian bertipe FileNotFoundException jika file
tersebut tidak ditemukan.
Jenis pengecualian seperti ini membutuhkan penanganan wajib, sehingga kita harus
memanggil konstruktor di dalam pernyataan try atau menambahkan pernyataan throw di
kepala subrutin yang menjalankan konstruktor tersebut. Milsanya, anggap kita memiliki
file bernama "data.txt", dan kita ingin membuat program untuk membaca data pada file
tersebut. Kita bisa menggunakan pernyataan berikut untuk membaca aliran input dari file
tersebut :
// (Mendeklarasikan variabel sebelum pernyataan try
// jika tidak, maka variabel tersebut hanya bisa
// dilihat di dalam blok try, dan kita tidak bisa
// menggunakannya lagi di bagian program lain
FileReader data;
try {
// buat aliran input
data = new FileReader("data.txt");
}
catch (FileNotFoundException e) {
... // lakukan sesuatu untuk menangani kesalahan
}
Kelas FileNotFoundException merupakan kelas turunan dari IOException, sehingga
kita bisa menangkap IOException pada pernyataan try...catch di atas. Secara umum,
hampir semua kesalahan yang terjadi pada saat operasi input/output dapat ditangkap
dengan pernyataan catch yang menangani IOException.
Begitu kita berhasil membuat FileReader, kita bisa mulai membacanya. Tapi karena
FileReader hanya memiliki metode input primitif dari standar kelas Reader, kita mungkin
akan perlu membungkusnya dalam objek lain, misalnya BufferedReader atau kelas
pembungkus lain. Untuk membuat BufferedReader untuk membaca file bernama
"data.dat", kita bisa gunakan :
TextReader data;
try {
data = new BufferedReader(new FileReader("data.dat"));
}
catch (FileNotFoundException e) {
... // tangani pengecualian
}
BufferedReader memiliki metode bantu untuk mengambil data per baris dengan perintah
readline(). Sehingga apabila satu data ditulis dalam urutan per baris, kita bisa gunakan
perintah Double.parseDouble(string) atau Integer.parseInt(string) untuk mengubahnya
menjadi double atau int.
Untuk menyimpan data tidaklah lebih sulit dari ini. Kita bisa membuat objek bertipe
FileWriter. Dan kemudian kita mungkin ingin membungkus aliran output ini dalam
objek PrintWriter. Misalnya, kita ingin menyimpan data ke file yang bernama
"hasil.dat", kita bisa menggunakan :
PrintWriter result;
try {
keluaran = new PrintWriter(new FileWriter("hasil.dat"));
}
catch (IOException e) {
... // tangani pengecualian
}
Jika tidak ada file bernama "hasil.dat", maka file baru akan dibuat. Jika file sudah ada,
maka isinya akan dihapus dan diganti dengan data yang ditulis oleh program kita.
Pengecualian IOException bisa terjadi jika, misalnya, file tersebut sedang dibaca oleh
program lain, sehingga sistem operasi menolak program kita untuk menulisnya pada saat
yang sama.
Setelah kita selesai menggunakan file, sebaiknya anda menutup file tersebut, atau
mengatakan kepada sistem operasi bahwa kita telah selesai menggunakan file itu (Jika kita
lupa, sistem operasi akan menutup file secara otomatis setelah program selesai dijalankan
atau objek aliran file diambil oleh pemulung memori, akan tetapi akan sangat baik jika kita
menutup file secara manual untuk menghindari kemungkinan lainnya).
Kita bisa menutup file dengan menggunakan metode close() pada aliran tersebut. Setelah
file telah ditutup, maka kita tidak mungkin lagi membaca atau menulis data dari atau ke
file tersebut. Kita harus membukanya kembali. (Perlu dicatat bahwa penutupan file juga
bisa melemparkan pengecualian IOException yang wajib ditangani, akan tetapi
PrintWriter menangani pengecualian tersebut secara otomatis sehingga kita tidak perlu
menanganinya lagi).
Sebagai contoh komplit, berikut ini adalah program yang akan membaca angka dari file
bernama "data.dat", dan kemudian menuliskannya kembali dalam urutan terbalik ke dalam
file yang bernama "hasil.dat". Dalam file tersebut hanya akan ada satu angka untuk setiap
barisnya dan diasumsikan tidak ada lebih dari 1000 angka sekaligus. Penanganan
pengecualian digunakan untuk mengecek apakah ada masalah di tengah operasi. Meskipun
mungkin tidak begitu berguna untuk aplikasi sungguhan, akan tetapi program ini
mendemonstrasikan bagaimana menggunakan operasi baca tulis sederhana pada file.
package balikfile;
import java.io.*;
/**
* @param args
*/
public static void main(String[] args) {
BufferedReader data; // Aliran input karakter untuk membaca data
PrintWriter hasil; // Aliran output karakter untuk menulis data
try {
// Baca data dari file input
banyakAngka = 0;
while ((baris = data.readLine()) != null) { // baca hingga
habis
angka[banyakAngka] = Double.parseDouble(baris);
banyakAngka++;
}
System.out.println("Selesai!");
}
catch (IOException e) {
// Ada masalah dengan pembacaan/penulisan file
System.out.println("Kesalahan baca/tulis");
}
catch (NumberFormatException e) {
// Ada masalah dengan format angka dalam file
System.out.println("Kesalahan format: " + e.getMessage());
}
catch (IndexOutOfBoundsException e) {
// Tidak boleh meletakkan 1000 angka dalam file
System.out.println("Terlalu banyak angka.");
System.out.println("Penulisan dihentikan.");
}
finally {
// Akhiri dengan menutup semua file apapun yang terjadi
try {
data.close(); // Tutup file input
}
catch (IOException e) {
System.out.println("Tidak bisa menutup data.dat");
}
hasil.close(); // Tutup file output
}
}
}
Berikut ini adalah program lengkapnya yang bisa diimport ke dalam Eclipse beserta contoh
file data.dat.
Setelah selesai dijalankan file baru akan dibuat hasil.dat yang bisa Anda double-click
untuk melihat hasilnya
import java.io.*;
if (direktori.isDirectory() == false) {
if (direktori.exists() == false)
System.out.println("Tidak ada direktori ini!");
else
System.out.println("Ini bukan direktori.");
}
else {
isiFile = direktori.list();
System.out.println("Files dalam direktori \"" + direktori +
"\":");
for (int i = 0; i < isiFile.length; i++)
System.out.println(" " + isiFile[i]);
}
}
Berikut ini adalah program lengkapnya yang bisa diimport ke dalam Eclipse. Ini adalah
hasil keluarannya :
Semua kelas yang digunakan untuk memaca dan menulis data dari dan ke dalam file
memiliki konstruktor yang bisa mengambil objek File sebagai parameternya. Misalnya,
jika file adalah variabel bertipe File, dan kita ingin mengambil karakter dari file
tersebut, maka kita bisa membuat FileReader untuk melakukannya dengan menggunakan
new FileReader(file).
Mengkopi File
Mengkopi suatu file adalah operasi biasa, dan sistem operasi manapun memiliki perintah
atau cara untuk melakukannya. Akan tetapi kita juga bisa membuat program Java untuk
melakukannya.
Karena program harus bisa mengkopi file jenis apapun, kita tidak bisa menganggap data di
dalam file adalah data yang bisa dibaca manusia. File lagu atau video misalnya berisi
deretan byte yang merupakan representasi digital dari lagu atau video tersebut.
Oleh karena itu kita harus menggunakan InputStream dan OutputStream untuk
melakukan operasi baca tulis yang bisa menangani data biner, bukan Reader dan Writer
yang hanya bisa menangani data yang bisa dibaca manusia.
Program yang kita buat akan mengkopi beberapa byte sekaligus dari InputStream ke
OutputStream, akan tetapi kita membutuhkan tempat sementara di mana data tersebut
akan ditempatkan sebelum data tersebut ditulis kembali pada OutputStream. Tempat
sementara tersebut disebut buffer yang merupakan array berukuran tertentu, misalnya 4096
byte (atau 4 kilo byte).
Jika sumber adalah variabel bertipe InputStream, maka byteTerbaca =
sumber.read(buffer) akan mengisi penuh buffer. Metode ini mengembalikan int yang
merupakan berapa byte yang efektif diambil oleh sumber, kemudian diletakkan dalam
variabel byteTerbaca. Jika hasilnya -1, berarti tidak ada lagi data yang bisa diambil dari
dalam sumber.
Begitu juga jika kopi adalah keluaran yang bertipe OutputStream maka
kopi.write(buffer, 0, byteTerbaca) menulis deretan byte dari buffer dari posisi 0
hingga byteTerbaca ke aliran keluaran kopi.
Sehingga secara umum perintah-perintah di atas dapat dirangkum menjadi :
byte[] buffer = new byte[4096];
int byteTerbaca;
Program yang akan kita buat menerima input dari baris perintah. Kemudian program akan
mengecek apakah kedua parameter tersebut berisi nama file dengan benar. Jika salah satu
parameternya kosong, maka program akan menampilkan pesan kesalahan. Program juga
akan mengecek apakah akhir.dat merupakan file yang sudah ada sebelumnya, kemudian
memberi pertanyaan kepada user apakah isi file ini ingin ditindih dengan isi file awal.dat.
Jika ya, maka operasi akan diteruskan, jika tidak maka program akan dihentikan.
Berikut ini adalah listing lengkap program KopiFile, yang bisa diunduh di sini dan
diimport ke dalam Eclipse.
import java.io.*;
/**
* @param args
*/
public static void main(String[] args) {
// Mengecek apakah argumen program cukup untuk meneruskan program
// Dibutuhkan dua argumen, yaitu sumberFile dan tujuanFile
if (args.length < 2) {
System.out.println("Cara menjalankan program : " +
"java KopiFile sumberFile tujuanFile");
return;
}
// Jika kopi file sudah ada, kita akan tanyakan apakah file
tujuan
// akan ditimpa
if (kopiFile.exists()) {
// buat objek baru untuk mengambil input
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String timpaFile = null;
}
Perlu diingat bahwa program ini tidak bisa dijalankan lewat Eclipse. Jika Anda mencoba
menjalankan lewat Eclipse, maka tampilan kesalahan akan muncul, karena tidak ada
parameter yang diberikan.
Untuk menjalankan program, Anda harus membuka konsol pada Windows dengan Start ->
Run -> kemudian ketik cmd dan enter. Setelah itu pergi ke direktori tempat proyek Anda
berada pada Eclipse. Misalnya pada komputer saya, saya meletakkan semua proyek
Eclipse pada c:\belajarjava.lyracc.com\KopiFile. Di dalamnya seharusnya Anda
akan menemui 2 direktori, yaitu src dan bin. src adalah tempat di mana kode sumber
berada, sedangkan bin adalah tempat dimana hasil kompilasi berada. Eclipse akan
melakukan kompilasi secara otomatis.
Berikut screenshot hasil jalannya program. Di sini saya mengkopi file dari
c:\belajarjava.lyracc.com\KopiFile\src\KopiFile.java ke
c:\belajarjava.lyracc.com\Kopi123.java.
Jaringan (network)
Dalam pemrograman, jaringan (network) hanyalah salah satu jenis dari input di mana data
bisa diambil, dan output di mana data bisa dikirim. Konsep ini mempermudah pemahaman
kita tentang pemrograman dalam jaringan, akan tetapi ada beberapa hal lain yang harus
diperhatikan sehingga pemrograman pada jaringan dapat berhasil dengan baik.
Pada Java, kita bisa menggunakan aliran input dan output untuk melakukan komunikasi
pada network, seperti halnya pada file. Akan tetapi membuat koneksi jaringan antara dua
komputer sedikit lebih rumit, karena ada dua komputer yang berbeda, yang keduanya harus
setuju membuka koneksi. Dan ketika data dikirimkan dari satu komputer ke komputer lain,
komunikasi harus dilakukan seirama sehingga data yang dikirimkan akan sampai ke
komputer yang lain.
Salah satu paket Java standar adalah java.net. Paket ini memiliki beberapa kelas yang
bisa digunakan untuk berkomunikasi melalui jaringan. Dua jenis I/O network disediakan
dalam paket ini. Yang pertama, yang lebih tinggi tingkatannya, berdasarkan pada Web dan
memberikan fasilitas komunikasi seperti halnya web browser ketika mendownload suatu
halaman web untuk kemudian ditampilkan. Kelas utama dalam jenis network seperti ini
adalah java.net.URL dan java.net.URLConnection. Suatu objek bertipe URL adalah
lambang abstrak dari sebuah URL (Universal Resource Locator), yaitu alamat web di mana
dokumen HTML atau lainnya bisa ditemukan pada web. Sedangkan URLConnection
adalah koneksi network ke dokumen tadi.
Jenis I/O kedua adalah melihat jaringan pada tingkatan yang lebih rendah, yaitu
berdasarkan ide suatu soket (socket). Soket digunakan oleh program untuk melakukan
koneksi dengan program lain pada suatu jaringan. Komunikasi melalui network melibatkan
dua soket, yaitu masing-masing pada setiap komputer. Java memiliki kelas
java.net.Socket untuk merepresentasikan suatu soket yang digunakan dalam
komunikasi network.
Istilah "soket" mungkin mirip dengan colokan kabel data (misalnya) modem, akan tetapi
penting untuk diingat bahwa soket adalah objek bertipe Socket. Artinya program bisa
memiliki beberapa soket dalam waktu yang sama, yang masing-masing terhubung ke
program yang dijalankan pada komputer lain. Semuanya menggunakan koneksi network
yang sama dari satu kabel.
Bagian ini akan memberikan pengenalan tentang kelas-kelas dasar jaringan, dan
bagaimana hubungannya dengan aliran input dan ouput serta pengecualian.
URL dan URLConnection
Kelas URL digunakan untuk merepresentasikan suatu sumber pada Web. Setiap sumber
memiliki alamat, yang unik (tidak bisa sama), dan memiliki informasi yang cukup
sehingga web browser bisa mencari sumber tersebut dan mengambilnya. Alamat ini
disebut "url" atau "universal resource locator".
Suatu objek beritpe kelas URL melambangkan alamat tersebut. Jika kita sudah memiliki
objek bertipe URL, maka kita bisa membuka URLConnection ke alamat tersebut. Suatu url
biasanya berupa string, misalnya "http://java.lyracc.com/belajar/java-untuk-
pemula/bab-i-pendahuluan". Ada juga yang disebut url relatif. URL relatif adalah lokasi
suatu sumber relatif terhadap url lain, yang biasanya disebut landasan (base) atau konteks
(context) dari url relatif tersebut. Misalnya jika konteksnya adalah
http://java.lyracc.com/belajar/java-untuk-pemula/ maka url relatif dari "bab-i-
pendahuluan" akan menunjuk pada http://java.lyracc.com/belajar/java-untuk-
pemula/bab-i-pendahuluan.
Suatu objek bertipe URL bukan string sederhana, akan tetapi dibangun dari kumpulan string
yang membentuk suatu url. Objek URL juga bisa dibuat dari objek URL lain, yang
merupakan konteksnya, dan string lain yang berisi relatif urlnya. Konstruktornya memiliki
bentuk seperti :
public URL(String alamatURL) throws MalformedURLException
dan
public URL(URL konteks, String alamatRelatif) throws
MalformedURLException
Lihat bahwa kedua konstruktor akan melempar pengecualian bertipe
MalformedURLException jika string yang diberikan bukan nama url legal. Kelas
MalformedURLException merupakan kelas turunan dari IOException yang wajib
ditangani, sehingga konstruktor di atas harus dipanggil dalam pernyataan try ... catch
atau ditulis di dalam subrutin yang melempar pengecualian ini.
Konstruktur jenis kedua akan lebih nyaman digunakan untuk applet. Dalam applet, tersedia
dua metode yang bisa digunakan untuk mengambil konteks URL. Metode
getDocumentBase() pada kelas Applet mengembalikan objek bertipe URL. Objek URL ini
adalah lokasi tempat halaman HTML yang berisi applet tersebut berada. Dengan ini, kita
bisa memerintahkan applet untuk kembali dan mengambil file lain yang disimpan di
tempat yang sama. Misalnya,
URL url = new URL(getDocumentBase(), "data.txt");
membuat URL baru yang merujuk pada file bernama data.txt pada komputer yang sama
dan pada direktori yang sama pada halaman web di mana applet tersebut sedang berjalan.
Metode lainnya, yaitu getCodeBase(), mengembalikan URL yang merupakan lokasi di
mana applet tersebut berada (belum tentu sama dengan lokasi HTML-nya).
Setelah kita memiliki objek URL yang benar, kita bisa memanggil openConnection()
untuk membuka koneksi pada URL tersebut. Metode ini mengembalikan objek bertipe
URLConnection. Objek URLConnection bisa digunakan untuk membuka InputStream
untuk membaca halaman atau file pada alamat URL tersebut, yaitu dengan menggunakan
metode getInputStream(). Misalnya :
URL url = new URL(alamatURL);
URLConnection koneksi = url.openConnection();
InputStream dataURL = connection.getInputStream();
Metode openConnection() dan getInputStream dapat melempar pengecualian
IOException. Jika InputStream berhasil dibuka, kita bisa menggunakannya dengan cara
biasa, termasuk membungkusnya dalam aliran input jenis lain, misalnya BufferedReader.
Membaca dari aliran ini tentunya juga bisa melemparkan pengecualian.
Salah satu metode instansi yang berguna dalam kelas URLConnection adalah
getContentType(), yang mengembalikan String yang menjelaskan jenis informasi pada
URL yang ditunjuk. Hasilnya bisa bernilai null jika jenisnya belum diketahui, atau tidak
bisa ditentukan. Jenis dokumen bisa saja belum tersedia hingga aliran input berhasil dibuat,
sehingga lebih baik menggunakan getContentType() setelah getInputStream() berhasil
dilakukan.
String yang dikembalikan oleh getContentType() ditulis dalam format yang disebut
MIME, misalnya "text/plain", "text/html", "image/jpeg", "image/gif", dan banyak lagi
lainnya. Semua jenis MIME terdiri dari dua bagian, yaitu bagian umum, seperti "text" atau
"image", dan bagian khususnya, misalnya "html" atau "gif". Jika kita hanya tertarik pada
data teks misalnya, kita hanya perlu menguji apakah hasil keluaran getContentType()
dimulai dengan "text". (Jenis MIME pertama kali dimaksudkan untuk menjelaskan isi
email. Namanya adalah singkatan dari "Multipurpose Internet Mail Extensions". Kini,
MIME digunakan secara umum untuk menjelaskan jenis suatu informasi atau file pada
suatu sumber).
Mari kita lihat contoh singkat bagaimana membaca data dari suatu URL. Subrutin berikut
akan membuka koneksi ke URL tertentu, mengecek apakah jenisnya berupa teks,
kemudian mengkopi hasilnya ke layar. Beberapa operasi dalam subrutin ini mungkin
melempar pengecualian. Kita akan menambahkan "throws Exception" di kepala subrutin
untuk meneruskan penanganan pengecualian ini kepada program utama yang memanggil
subrutin ini.
static void bacaTeksDariURL( String alamatURL ) throws Exception {
// Subrutin ini mencetak isi dari alamat URL yang
// diberikan ke layar. Semua kesalahan akan ditangani
// oleh program yang memanggil subrutin ini
while (true) {
int data = dataURL.read();
if (data < 0)
break;
System.out.print((char)data);
}
} // akhir bacaTeksDariURL()
Soket, Klien, dan Server
Komunikasi melalui internet dilakukan berdasarkan sepasang protokol yang dinamakan
Internet Protocol dan Transmission Control Protocol, yang digabungkan menjadi TCP/IP.
(Sebenarnya, ada lagi protokol komunikasi yang lebih sederhana yang disebut dengan UDP
yang bisa digunakan menggantikan TCP pada beberapa aplikasi. UDP juga didukung Java,
akan tetapi kita akan membahas TCP/IP saja yang merupakan komunikasi dua arah yang
handal digunakan pada beberapa komputer melalui jaringan).
Agar dua program dapat berkomunikasi menggunakan TCP/IP, masing-masing program
harus membuat soket, yang kemudian soket-soket tersebut harus terhubung satu sama lain.
Setelah terhubung, komunikasi dapat dilakukan dengan menggunakan aliran input dan
output seperti biasa. Setiap program harus memiliki aliran input dan outputnya masing-
masing. Data yang ditulis oleh suatu program di aliran outputnya akan dikirim ke komputer
lain. Di sana, data tersebut akan diisi pada aliran input program tersebut. Ketika program
tadi membaca aliran inputnya, maka pada dasarnya program tersebut membaca data yang
dikirim oleh program lain.
Bagian tersulitnya adalah bagaimana membuat koneksi antar komputer tersebut. Dalam hal
ini, dua soket akan digunakan. Pertama-tama, suatu program harus membuat soket yang
menunggu secara pasif hingga koneksi lain dari soket lain di komputer lain datang. Soket
yang sedang menunggu ini disebut sedang "mendengar" (listening) suatu koneksi.
Di sisi lain di komputer lain, program lain membuat soket yang mengirim permintaan
sambungan ke soket pendengar tadi. Ketika soket pendengar menerima permintaan
sambungan dari soket lain, soket ini akan merespon, sehingga komunikasi akan terjadi.
Begitu komunikasi terjadi, maka masing-masing program akan bisa membuat aliran input
dan aliran output untuk koneksi ini. Komunikasi akan terus terjadi hingga salah satu
program menutup (close) koneksi.
Program yang membuat soket pendengar, juga sering disebut server, dan soketnya disebut
soket server. Program yang menghubungi server disebut klien (client), dan soket yang
digunakan disebut soket klien.
Idenya adalah suatu server di suatu tempat pada network sedang menunggu permintaan
sambungan dari suatu klien. Server dianggap sebagai sesuatu yang memberikan layanan,
dan klien mendapatkan layanan dengan cara menyambungkannya pada server. Pada
komunikasi jaringan, ini disebut model klien/server.
Dalam aplikasi dunia nyata, program server dapat memberikan koneksi kepada beberapa
klien pada waktu yang sama. Ketika suatu klien terhubung pada soket pendengar, maka
soket tersebut tidak berhenti mendengar. Akan tetapi, soket tersebut akan terus mendengar
jika ada koneksi klien lain pada saat yang sama.
Kelas URL yang telah didiskusikan sebelumnya menggunakan soket klien di belakang layar
untuk melakukan komunikasi jaringan yang dibutuhkan. Di sisi lainnya adalah program
server yang menerima permintaan sambungan dari objek URL, membaca permintaan objek
tersebut, misalnya permintaan file di alamat tertentu, dan meresponnya dengan
mengirimkan isi file tersebut melalui network ke objek URL tadi. Setelah mengirimkan
data, server akan memutuskan koneksi ini.
Ketika kita memanggil metode accept(), ia tidak akan mengembalikan hasilnya sebelum
permintaan sambungan diterima (atau suatu kesalahan terjadi). Metode ini disebut
"diblokade" ketika menunggu koneksi. (Ketika suatu metode diblokade, maka thread yang
memanggil metode tersebut tidak bisa berbuat apa-apa. Akan tetapi thread lain di program
yang sama masih bisa berjalan). ServerSocket tersebut akan terus mendengar koneksi
hingga ia ditutup menggunakan metode close() atau hingga terjadi kesalahan.
Misalnya kita akan membuat server yang akan mendengarkan port 1728, dan misalnya kita
telah menulis metode baru beriLayanan(Socket) untuk menangani komunikasi dengan
suatu klien. Maka bentuk sederhana dari program server adalah sebagai berikut :
try {
ServerSocket server = new ServerSocket(1728);
while (true) {
Socket koneksi = server.accept();
beriLayanan(koneksi);
}
}
catch (IOException e) {
System.out.println("Server dimatikan dengan pesan kesalahan:
" + e);
}
Di sisi klien, soket klien dibuat dengan menggunakan konstruktor pada kelas Socket.
Untuk melakukan koneksi ke server pada suatu alamat dan port tertentu, kita bisa
menggunakan konstruktor
public Socket(String komputer, int port) throws IOException
Parameter pertama bisa berupa alamat IP atau nama domain. Konstruktor akan
memblokadi dirinya hingga koneksi tersambung atau hingga terjadi kesalahan. Setelah
koneksi tersambung, kita bisa menggunakan metode getInputStream() dan
getOutputStream() pada Socket untuk mengambil aliran input dan output yang bisa
digunakan untuk komunikasi antara dua komputer.
Berikut ini adalah kerangka untuk melakukan koneksi klien :
void koneksiKlien(String namaKomputer, int port) {
// namaKomputer bisa berupa alamat IP atau nama domain
// dari komputer yang bertindak sebagai server.
// port adalah port dimana server mendengarkan koneksi,
// misalnya 1728.
Socket koneksi;
InputStream in;
OutputStream out;
try {
koneksi = new Socket(namaKomputer,port);
in = koneksi.getInputStream();
out = koneksi.getOutputStream();
}
catch (IOException e) {
System.out.println(
"Usah melakukan sambungan gagal, dengan kesalahan : " +
e);
return;
}
.
. // Gunakan aliran in dan out untuk berkomunikasi dengan server
.
try {
koneksi.close();
// (Atau, bisa juga bergantung pada server untuk
// memutuskan sambungan)
}
catch (IOException e) {
}
} // akhir koneksiKlien()
Membuat komukasi melalui jaringan terlihat lebih mudah dari yang sebenarnya. Jika
jaringan yang kita gunakan benar-benar handal, mungkin perintah di atas cukup untuk
digunakan. Akan tetapi, untuk membuat program tangguh yang bisa menangani segala
permasalahan dalam jaringan yang kurang handal atau karena kesalahan manusia misalnya,
adalah hal yang tidak mudah. Pengalaman yang bisa membawa kita menjadi programmer
jaringan yang lebih baik dan lebih komplet. Yang kita bahas di sini semoga berguna
sebagai pengantar untuk membawa Anda lebih jauh mencari tahu tentang pemrograman
dengan jaringan.
Contoh Pemrograman pada Jaringan
Contoh ini melibatkan dua program, yaitu klien sederhana dan servernya. Klien melakukan
koneksi dengan server, membaca satu baris teks dari server, kemudian menampilkan teks
ini pada layar. Teks yang dikirim oleh server adalah tanggal dan waktu saat ini di komputer
di mana server dijalankan.
Untuk membuka koneksi, klien harus tahu di komputer mana server dijalankan dan di port
mana server tersebut mendengarkan permintaan sambungan. Server akan mendengarkan
pada port bernomor 32007. Nomor port ini bisa berapapun di antara 1025 hingga 65535,
asalkan klien dan servernya menggunakan port yang sama. Nomor port antara 1 hingga
1024 hanya digunakan oleh layanan standar dan seharusnya tidak digunakan untuk server
lainnya.
Nama komputer atau alamat IP di mana server dijalankan harus diberikan pada paramater
baris perintah. Misalnya jika server dijalankan pada komputer kita sendiri, kita bisa
memanggilnya dengan "java KlienTanggal localhost". Berikut ini adalah program klien
lengkapnya.
import java.net.*;
import java.io.*;
if (args.length > 0)
komputer = args[0];
else {
// Tidak ada nama komputer yang diberikan
// Beri pesan kesalahan dan program selesai
System.out.println("Cara menggunakan : java KlienTanggal
<server>");
return;
}
try {
koneksi = new Socket( komputer, PORT_PENDENGAR );
masuk = new InputStreamReader( koneksi.getInputStream() );
while (true) {
int ch = masuk.read();
if (ch == -1 || ch == '\n' || ch == '\r')
break;
System.out.print( (char)ch );
}
System.out.println();
masuk.close();
}
catch (IOException e) {
System.out.println("Kesalahan : " + e);
}
}
}
Perhatikan bahwa semua komunikasi dengan server dilakukan dalam pernyataan try ...
catch. Ini akan menangkap pengecualian IOException yang mungkin terjadi ketika
koneksi sedang dibuka atau ditutup atau sedang membaca karakter dari aliran input.
Aliran yang digunakan adalah aliran sederhana Reader yang memiliki operasi input
masuk.read(). Fungsi ini membaca satu per satu karakter dari aliran, kemudian
mengembalikan nomor kode Unicodenya. Jika akhir aliran telah dicapai, maka nilai -1
akan dikembalikan. Perulangan while membaca karakter ini satu per satu hingga akhir
aliran ditemui atau akhir baris ditemui. Akhir baris ditandai dengan salah satu dari '\n' atau
'\r' atau keduanya, tergantung dari jenis komputer di mana server tersebut berjalan.
Agar program ini dapat berjalan tanpa kesalahan, maka program server harus dijalankan
terlebih dahulu. Kita bisa membuat program klien dan server pada komputer yang sama.
Misalnya kita bisa membuat dua jendela konsol pada windows, kemudian menjalankan
server di konsol yang satu dan menjalankan klien di server yang lain. Agar ini bisa
berjalan, komputer lokal kita memiliki alamat 127.0.0.1, sehingga perintah "java
KlienTanggal 127.0.0.1" artinya sama dengan memerintahkan program KlienTanggal
untuk melakukan sambungan dengan server yang berjalan di komputer yang sama. Atau
bisa juga menggunakan alamat "localhost" sebagai pengganti "127.0.0.1".
Program servernya kita namakan ServerTanggal. Program ServerTanggal membuat
ServerSocket untuk mendengarkan permintaan sambungan pada port 32007. Setelah
soket pendengar kita buat, maka server akan masuk pada perulangan tak hingga di mana ia
menerima dan mengolah permintaan sambungan. Program ini akan berjalan terus menerus
tanpa henti kecuali kita hentikan dengan paksa -- misalnya dengan menekan tombol Ctrl-C
di jendela konsol di mana server dijalankan.
Ketika koneksi diterima dari klien, server akan memanggil subrutin lain untuk menangani
koneksi tersebut. Dalam subrutin itu, pengecualian apapun yang terjadi akan ditangkap
sehingga server tidak akan mati. Subrutin akan membuat aliran PrintWriter untuk
mengirim data melalui koneksi yang terjadi.
Server akan menulis tanggal dan waktu sekarang pada aliran output ini, kemudian menutup
koneksi. (Kelas standar java.util.Date akan digunakan untuk mengambil tanggal saat
ini. Objek bertipe Date melambangkan tanggal dan waktu. Konstruktor standarnya, "new
Date()" membuat objek yang melambangkan tanggal dan waktu ketika objek tersebut
dibuat.)
Berikut ini adalah program server lengkapnya :
import java.net.*;
import java.io.*;
import java.util.Date;
/**
* @param args
*/
public static void main(String[] args) {
ServerSocket pendengar; // Mendengarkan sambungan yang masuk
Socket koneksi; // Untuk berkomunikasi dengan sambungan yang
masuk
/*
* Menerima dan mengolah sambungan selamanya, atau hingga
kesalahan
* terjadi. (Kesalahan yang terjadi ketika sedang berkomunikasi
atau
* mengirimkan tanggal akan ditangkap untuk mencegah server
crash)
*/
try {
pendengar = new ServerSocket(PORT_PENDENGAR);
System.out.println("Mendengarkan pada port " +
PORT_PENDENGAR);
while (true) {
koneksi = pendengar.accept();
kirimTanggal(koneksi);
}
} catch (Exception e) {
System.out.println("Maaf, server telah mati.");
System.out.println("Kesalahan : " + e);
return;
}
}
Dan program di atas dapat diunduh pada daftar sisipan di bawah, dan diimpor ke dalam
Eclipse dengan menggunakan instruksi pada halaman berikut.
Untuk menjalankan program di atas, jalankan program server terlebih dahulu, dari dalam
konsol ketik "cd <nama_direktori_tempat_proyek_server_berada>\bin" (di screen shot di
atas direktorinya berada di c:\belajarjava.lyracc.com\servertanggal\bin) kemudian ketik
"java ServerTanggal".
Kemudian untuk menjalankan program klien, lakukan dengan cara yang serupa, yaitu buka
konsol baru, kemudian ketik "cd <nama_direktori_tempat_proyek_klien_berada>\bin" (di
screen shot di atas direktornya berada di c:\belajarjava.lyracc.com\klientanggal\bin)
kemudian ketik "java KlienTanggal localhost".
Untuk mengetahui di direktori mana proyek ini berada pada Eclpse Anda, klik kanan
proyek tersebut dari dalam Eclipse -> Properties, seperti pada screen shot berikut ini :
Pemrograman Serentak (Concurrency)
Java adalah bahasa pemrograman banyak thread, yang artinya beberapa hal bisa dilakukan
bersama-sama. Thread adalah unit terkecil dari eksekusi suatu program. Thread
mengeksekusi rangkaian instruksi satu demi satu. Ketika sistem menjalankan program,
komputer akan membuat thread baru. (Thread dalam konteks ini disebut proses, akan tetapi
perbedaanya tidank penting di sini). Instruksi-instruksi dalam program akan dieksekusi
oleh thread ini secara berantai, satu demi satu dari awal hingga akhir. Thread disebut
"mati" jika program selesai dieksekusi.
Dalam sistem komputer modern, beberapa thread bisa tercipta dalam satu waktu. Pada satu
saat tertentu, hanya ada satu thread yang bisa dijalankan, karena CPI hanya bisa melakukan
satu hal dalam satu waktu. (Pada komputer dengan multiprosesor, multicore, dan hyper-
threading, masing-masing prosesor atau core melakukan thread yang berbeda-beda). Akan
tetapi sebenarnya komputer membagi waktu menjadi bagian-bagian kecil sehingga seolah-
olah seluruh thread dijalankan secara bersama-sama. Pembagian waktu berarti CPU
mengeksekusi suatu thread dalam kurun waktu tertentu, setelah itu beralih mengeksekusi
thread yang lain, kemudian thread lain, dan seterusnya dan kemudian kembali ke thread
pertama -- kira-kira 100 kali per detik. Di mata user, semua thread berjalan pada saat yang
sama.
Java adalah bahasa pemrograman banyak thread. Artinya Java bisa membuat satu atau
lebih thread yang bisa dijalankan secara paralel. Hal ini adalah bagian mendasar, yang
dibuat di dalam core bahasa, bukan merupakan tambahan (add-on) seperti bahasa
pemrograman lain. Tetap saja pemrogaman dengan banyak thread adalah sesuatu yang
tidak mudah.
Penggunaan thread yang banyak digunakan adalah untuk membuat GUI (graphical user
interface) yang responsif. Pada dasarnya suatu program harus dapat terus bejalan dan pada
saat yang sama tetap bisa menerima input dari user, menanggapi klik mouse, dan
sebagainya.
Thread juga digunakan untuk mempercepat suatu proses, misalnya kita ingin membuat
program yang menunggu suatu input I/O dari network, dan pada saat yang sama
mengolahnya sehingga proses pengolahan berjalan serentak. Jika program harus menunggu
seluruh input datang baru kemudian melakukan pengolahan, tentunya akan memakan
waktu yang lebih lama, terutama apabila aliran network lambat atau pengolahannya
memakan waktu lama.
Jika kita memiliki CPU multiprocessor atau multicore, maka menggunakan banyak thread
akan mempercepat eksekusi program, karena masing-masing thread dijalankan secara
terpisah. Misalnya untuk melakukan video encoding dengan jumlah data besar, jika kita
menggunakan seluruh core yang tersedia maka prosesnya akan dapat diselesaikan dengan
cepat.
Dasar-dasar Thread
Cara termudah untuk membuat thread adalah membuat kelas turunan dari
java.lang.Thread, yang memiliki semua metode untuk membuat dan menjalankan
thread. Metode paling penting adalah run(), yang bisa kita beban-lebihkan untuk
melakukan tugas yang kita butuhkan. Atau dengan kata lain run() adalah metode yang
akan dijalankan bersamaan dengan thread lain.
Contoh berikut membuat 5 thread, masing-masing memiliki nomor identifikasi unik yang
dibuat dengan menggunakan variabel statik. Metode run() dibebanlebihkan untuk
menghitung mundur hingga hitungMundur bernilai nol. Setelah metode run() selesai
dijalankan, thread akan mati secara otomatis.
(Contoh-contoh pada bagian ini bisa diunduh untuk diimport ke dalam Eclipse. Lihat akhir
halaman ini untuk tautannya)
package com.lyracc.threaddasar1;
public ThreadDasar() {
super("Thread ke-" + ++jumlahThread);
start();
}
/**
* @param args
*/
public static void main(String[] args) {
for(int i = 0; i < 5; i++)
new ThreadDasar();
}
}
Pada contoh program di atas, objek thread diberi nama melalui argumen pada
konstruktornya. Nama ini dipanggil ketika metode run() melakukan penghitungan
mundur, yaitu dengan menggunakan metode getName().
Metode run() pada thread biasanya memiliki perulangan internal yang akan terus menerus
dipanggil hingga tidak lagi digunakan. Kita harus membuat suatu kondisi sehingga bisa
keluar dari perulangan tersebut (misalnya pada contoh di atas, perulangan akan selesai jika
hitungMundur bernilai 0). Seringkali, run() dijalankan di dalam perulangan yang tak
pernah berhenti (kita akan lihat nanti bagaimana menghentikan suatu thread dengan aman).
Pada metode main(), thread dibuat beberapa kali kemudian dijalankan. Metode start()
pada kelas Thread digunakan untuk melakukan tugas tertentu sebelum metode run()
dijalankan. Jadi, langkah-langkahnya adalah : konstruktor dipanggil untuk membuat objek,
kemudian memanggil start() untuk melakukan konfigurasi thread, dan kemudian metode
run() dijalankan. Jika kita tidak memanggil start() maka metode run() tidak akan
pernah dijalankan.
Keluaran dari program ini akan berbeda setiap kali dijalankan, karena penjadwalan thread
tidak dapat ditentukan dengan pasti (non-deterministik). Bahkan, kita bisa melihat
perbedaan yang sangat jelas ketika kita menggunakan versi JDK yang berbeda. Misalnya,
JDK lama tidak melakukan pembagian waktu lebih cepat, artinya, 1 thread mungkin bisa
melakukan tugasnya dengan cepat hingga selesai sebelum thread lain dijalankan. Pada JDK
lain kita akan melihat program akan mencetak 5 untuk seluruh thread hingga 1 untuk
seluruh thread. Artinya pembagian waktunya lebih baik, karena setiap thread memiliki
kesempatan yang sama untuk menjalankan program. Karenanya, untuk membuat suatu
program multi-threading, kita tidak boleh terpaku pada keluaran suatu kompiler. Program
kita harus dibuat seaman mungkin.
Ketika objek Thread dibuat pada metode main(), kita lihat bahwa kita tidak menyimpan
referensi ke objek tersebut. Pada objek biasa, tentunya objek ini akan langsung ditangkap
oleh pemulung memori karena objek ini tidak direferensikan di manapun. Akan tetapi pada
thread, objek hanya bisa diambil oleh pemulung memori jika metode run() selesai
dijalankan. Pada contoh di atas, program masih bisa berjalan seperti biasa, dan objek
Thread akan diberikan kepada pemulung memori setelah mencetak angka 1.
Yielding (menghasilkan)
Jika kita tahu bahwa kita telah mendapatkan hasil yang kita inginkan pada metode run(),
kita bisa memberi tahu penjadwal thread bahwa kita telah selesai dan memberi jalan
kepada thread lain untuk mendapatkan kesempatan pada CPU. Akan tetapi ini hanya
sebagai petunjuk, yang artinya belum tentu dijalankan oleh penjadwal thread.
Misalnya pada contoh di atas, kita bisa mengganti isi metode run() dengan
public void run() {
while (true) {
System.out.println( getName() + " : " + hitungMundur );
if (--hitungMundur == 0)
return;
yield();
}
}
Secara umum, yield mungkin berguna untuk situasi yang agak langka, dan kita tidak bisa
menggunakannya secara serius untuk memperbaiki kinerja aplikasi kita.
Tidur (sleeping)
Cara lain untuk mengatur perilaku thread kita adalah dengan memanggil sleep untuk
menunda eksekusi thread selama waktu tertentu (dalam mili detik). Misalnya pada kode
berikut, kita ubah metode run() menjadi seperti :
public void run() {
while (true) {
System.out.println( getName() + " : " + hitungMundur );
if (--hitungMundur == 0)
return;
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Ketika kita memanggil sleep(), metode ini harus diletakkan di dalam blok try karena
sleep() bisa melemparkan pengecualian, yaitu jika tidurnya diganggu sebelum waktunya
selesai. Hal ini terhadi misalnya apabila thread lain yang memiliki referensi ke thread ini
memanggil interrupt() pada thread ini. Pada contoh di atas, kita lemparkan lagi
pengecualian yang terjadi dengan pengecualian lain bertipe RuntimeException, karena
kita tidak tahu bagaimana pengecualian ini harus ditangani, dan membiarkan metode yang
memanggilnya menangkap pengecualian baru ini.
Metode sleep() tidak digunakan untuk mengatur bagaimana thread akan berjalan menurut
urutan tertentu. Metode ini hanya menghentikan eksekusi suatu thread sementara. Yang
dijamin adalah bahwa thread akan tidur selama paling sedikit 100 mili detik (atau mungkin
sedikit lebih lama hingga thread jalan kembali). Urutan thread diatur oleh penjadwal thread
yang memiliki mekanisme sendiri tergantung dari keadaan thread lain atau bahkan aplikasi
lain di luar Java, oleh karena itu sifatnya disebut non-deterministik.
Jika kita harus mengatur thread mana dahulu yang harus dijalankan, cara terbaik mungkin
tidak menggunakan thread sama sekali, atau mendesain agar suatu thread memanggil
thread lain dengan suatu urutan tertentu. Tentunya cara terakhir lebih rumit dari yang
dibayangkan.
Prioritas
Prioritas suatu thread digunakan untuk memberi tahu penjadwal thread tentang prioritas
thread tersebut. Tetap saja urutannya tidak bisa ditentukan karena sifatnya yang non-
deterministik. Jika ada beberapa thread yang sedang diblok dan menunggu giliran untuk
dijalankan, penjadwal thread akan cenderung menjalankan thread dengan prioritas tertinggi
terlebih dahulu. Akan tetapi, tidak berarti thread dengan prioritas rendah tidak akan pernah
dijalankan, hanya lebih jarang dijalankan ketimbang thread dengan prioritas tinggi.
Perhatikan contoh berikut :
package com.lyracc.prioritasthread;
/**
* @param args
*/
public static void main(String[] args) {
new PrioritasThread(Thread.MAX_PRIORITY);
for(int i = 0; i < 5; i++)
new PrioritasThread(Thread.MIN_PRIORITY);
}
}
Pada contoh di atas, kita ubah konstruktornya untuk mengeset prioritas kemudian
menjalankan thread. Pada metode main() kita buat 6 thread, yang pertama dengan prioritas
maximum, dan yang lain dengan prioritas minimum. Perhatikan keluarannya, bagaimana
thread pertama dijalankan lebih dulu sedangkan thread-thread lain berjalan seperti biasa
dalam kondisi acak karena memiliki prioritas yang sama.
Di dalam metode run() kita lakukan perhitungan matematika selama 100.000 kali.
Tentunya ini perhitungan yang memakan waktu sehingga setiap thread harus menunggu
giliran di saat thread lain sedang dijalankan. Tanpa perhitungan ini, thread akan
dilaksanakan sangat cepat dan kita tidak bisa melihat efek dari prioritas thread.
Prioritas suatu thread bisa kita set kapan saja (tidak harus pada konstruktor) dengan metode
setPriority(int prioritas) dan kita bisa membaca prioritas suatu thread dengan
menggunakan metode getPriority().
Meskipun JDK memiliki 10 tingkat prioritas, akan tetapi sistem operasi memiliki tingkat
prioritas yang berbeda-beda. Windows misalnya memiliki 7 tingkat dan Solaris memiliki
231 tingkat prioritas. Yang lebih pasti adalah menggunakan konstanta MAX_PRIORITY,
NORM_PRIORITY, dan MIN_PRIORITY pada kelas thread.
Thread Daemon
Thread daemon adalah thread yang bekerja di belakang layar yang memberikan layanan
umum kepada thread-thread lain selama program berjalan, akan tetapi thread ini bukan
bagian penting dari suatu program. Artinya ketika semua thread yang bukan daemon
selesai dijalankan, program akan berhenti, dan jika masih ada thread non-daemon yang
masih dieksekusi, program tidak akan berhenti.
Perhatikan contoh program berikut ini.
package com.lyracc.threaddaemon;
/**
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 5; i++)
new ThreadDaemon();
}
}
Perintah setDaemon() sebelum metode start() dipanggil. Pada metode run(), thread
diperintahkan untuk tidur selama 100 mili detik. Ketika semua thread dimulai, program
langsung berhenti sebelum thread bisa mencetak dirinya. Ini karena semua thread kecuali
main() adalah thread daemon. Hanya thread non-daemon saja yang bisa mencegah
program untuk terus berjalan.
Untuk mengetahui suatu thread adalah thread daemon atau bukan, kita bisa menggunakan
perintah isDaemon(). Suatu thread daemon akan membuat thread yang juga merupakan
thread daemon.
Menggabungkan thread
Perintah join() bisa digunakan pada thread lain untuk menunda eksekusi hingga thread
lain tersebut selesai dijalankan. Misalnya, jika thread a memanggil t.join() pada thread
t, maka eksekusi thread a akan terhenti sementara hingga thread t selesai dijalankan (atau
ketika t.isAlive() bernilai false).
Kita bisa juga memanggil join() dengan argumen waktu (baik dalam mili detik, ataupun
milidetik dan nanodetik), yaitu jika thread target tidak selesai dalam kurun waktu tersebut,
eksekusi pada thread induk akan kembali dilakukan.
Panggilan join() bisa dibatalkan dengan memanggil interrupt() pada thread induk,
sehingga klausa try ... catch diperlukan pada metode join().
Mari kita lihat contoh berikut ini.
package com.lyracc.joindemo;
grr.interrupt();
}
}
Hasil keluarannya adalah seperti pada gambar berikut.
ThreadPemalas adalah thread yang akan ditidurkan sepanjang waktu yang diberikan pada
konstruktornya. Metode run() bisa berhenti jika waktu tidur sudah habis atau ada interupsi
yang terjadi. Di dalam klausa catch, interupsi akan dilaporkan. Fungsi isInterrupted()
melaporkan apakah thread ini diinterupsi atau tidak. Akan tetapi ketika thread ini
diinterupsi, kemudian pengecualiannya ditangkap oleh klausa catch, misalnya, maka tanda
interupsi akan segera dihapus. Oleh karenanya isInterrupted() akan selalu bernilai
false pada program di atas. Tanda interupsi akan digunakan pada situasi lain yang
mungkin berada di luar pengecualian.
ThreadPenggabung adalah thread yang menunggu hingga ThreadPemalas selesai dengan
tugasnya, yaitu dengan memanggil join() ke objek ThreadPemalas pada metode run()-
nya.
Pada metode utama main(), setiap ThreadPemalas tersambung pada ThreadPenggabung.
Dan kita lihat pada keluarannya, jika ThreadPemalas selesai bekerja, baik karena
dibangunkan melalui interupsi atau karena waktu sudah selesai, ThreadPenggabung yang
tersambung juga akan menyelesaikan tugasnya.
Variasi Kode
Pada contoh-contoh di atas, semua objek thread yang kita buat diturunkan dari kelas
Thread. Kita hanya membuat objek yang berfungsi sebagai thread dan tidak memiliki
tugas dan fungsi lain. Akan tetapi, kelas kita mungkin saja merupakan kelas turunan dari
kelas lain. Karena Java tidak mendukung pewarisan berganda, kita tidak bisa menurunkan
kelas tersebut bersamaan dengan kelas Thread.
Dalam hal ini, kita bisa menggunakan cara alternatif yaitu dengan mengimplementasi
interface Runnable. Runnable hanya memiliki satu metode untuk diimplementasi, yaitu
metode run().
Contoh berikut mendemonstrasikan contoh penggunaannya :
package com.lyracc.runnablesederhana;
Ketika suatu kelas mengimplementasikan interface Runnable, artinya kelas ini memiliki
metode bernama run(), akan tetapi tidak berarti bahwa kelas ini bisa melakukan sesuatu
seperti kelas Thread atau kelas-kelas turunan yang kita buat dari kelas ini. Kita harus
membuat objek Thread sendiri seperti ditunjukkan dalam metode main() di atas,
kemudian menjalankan start() sendiri.
Kemudahan yang ditawarkan oleh interface Runnable adalah kemungkinan untuk
menggabungkannya dengan kelas dan interface lain. Misalnya kita ingin membuat kelas
baru yang merupakan kelas turunan dari suatu kelas lain. Kita cukup menambahkan
impement Runnable pada definisi kelasnya untuk membuat kelas yang bisa kita jadikan
thread. Dengan cara ini, kita masih bisa mengakses anggota kelas induk secara langsung,
tanpa melalui objek lain. Akan tetapi, kelas dalam (inner class) juga bisa mengakses
anggota kelas luar (outer class). Kadang-kadang kita ingin juga membuat kelas dalam yang
merupakan turunan dari kelas Thread.
Perhatikan beberapa variasi untuk mendeklarasikan dan menggunakan thread pada contoh
berikut ini.
package com.lyracc.variasithread;
// Konstruktor KelasDalamBernama
// Membuat objek baru yang merupakan instansi kelas Dalam
public KelasDalamBernama(String nama) {
dalam = new Dalam(nama);
}
} // akhir KelasDalamBernama
// Konstruktor KelasDalamAnonim
public KelasDalamAnonim(String nama) {
// Kelas anonim turunan Thread
t = new Thread(nama) {
public void run() {
while (true) {
System.out.println(getName() + " : " + hitungMundur);
if (--hitungMundur == 0)
return;
try {
sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}; // akhir kelas anonim
t.start();
}
} // akhir KelasDalamAnonim
// Konstruktor KelasRunnableBernama
// Membuat objek baru yang merupakan instansi kelas Dalam
public KelasRunnableBernama(String nama) {
dalam = new Dalam(nama);
}
} // akhir KelasRunnableBernama
new Thread("Hansip") {
public void run() {
while (true) {
int nilai = genap.ambilNilai();
// Jika ganjil, keluar dan cetak nilainya
if (nilai % 2 != 0) {
System.out.println(nilai);
System.exit(0);
}
}
}
}.start();
while (true)
genap.berikut();
}
}
Pada metode main(), objek SelaluGenap akan dibuat -- sifatnya harus final karena
objek ini harus bisa diakses oleh kelas anonim yang berupa Thread. Jika nilai yang dibaca
oleh thread berupa bilangan ganjil, maka bilangan tersebut akan dicetak di layar kemudian
keluar dari program.
Apa yang terjadi adalah program pasti akan keluar dengan mencetak nilai ganjil. Ini berarti
ada ketidakstabilan dalam program tersebut. Ini adalah contoh masalah mendasar dengan
pemrograman banyak thread. Kita tidak pernah tahu kapan suatu thread akan jalan. Thread
kedua bisa jalan ketika thread pertama baru selesai menjalankan i++; yang pertama di
dalam metode berikut(). Di sini thread kedua menganggap ada kesalahan perhitungan,
padahal proses belum selesai.
Kadang-kadang kita memang tidak peduli ketika suatu sumber daya (dalam contoh di atas,
variabel i) sedang diakses apakah sedang digunakan atau tidak. Akan tetapi supaya
program banyak thread bisa bekerja dengan baik, kita harus mencegah supaya dua thread
tidak mengakses sumber daya yang sama, terutama di saat-saat kritis.
Mencegah tabrakan seperti ini bisa dicegah dengan meletakkan kunci pada sumber daya
ketika sedang digunakan. Thread pertama yang sedang mengubah variabel i seharusnya
mengunci variabel ini sehingga thread kedua yang ingin mengambil nilainya harus
menunggu hingga proses penambahan selesai.
Pemecahan Masalah Tabrakan Sumber Daya Bersama
Untuk memecahkan masalah tabrakan pada thread, hampir semua metode serentak
melakukan akses serial ke suatu sumber daya yang digunakan bersama. Artinya hanya satu
thread yang bisa mengakses suatu sumber daya pada suatu waktu. Biasanya hal ini
dilakukan dengan membuat kunci sehingga satu thread saja yang bisa mengakses kunci
tersebut. Kunci ini sering disebut mutex atau mutual exclusion.
Mari kita ambil contoh di rumah kita hanya ada satu kamar mandi. Beberapa orang
(thread) ingin masuk ke kamar mandi (sumber daya bersama), dan mereka ingin masuk
sendirian. Untuk masuk ke dalam kamar mandi, seseorang harus mengetok pintu untuk
mengetahui apakah ada orang di dalamnya. Jika tidak ada, maka mereka bisa masuk dan
mengunci pintunya. Thread lain yang mau menggunakan kamar mandi "diblok" sehingga
tidak bisa masuk, sehingga thread harus menunggu hingga seseorang keluar dari kamar
mandi.
Analogi di atas sedikit berbeda jika ketika seseorang keluar dari kamar mandi dan ada
beberapa orang yang ingin mengakses kamar mandi secara bersamaan. Karena tidak ada
"antrian" maka kita tidak tahu siapa yang harus masuk berikutnya, artinya penjadwal
thread bersifat non-deterministik. Yang terjadi adalah, jika banyak orang menunggu di
depan kamar mandi, maka siapa yang paling dekat dengan kamar mandi akan masuk
terlebih dahulu. Seperti telah diulas sebelumnya, kita bisa memberi tahu penjadwal thread
dengan perintah yield dan setPriority() akan tetapi tetap saja masih sangat bergantung
kepada JVM dan implementasi pada suatu platform dan tidak bisa ditentukan dengan pasti
siapa yang berhak masuk terlebih dahulu.
Java memiliki fitur untuk mencegah terjadinya tabrakan sumber daya, yaitu dengan
menggunakan kata kunci synchronized. Ketika suatu thread berusaha untuk
mengeksekusi suatu perintah yang diberi kata kunci synchronized, Java akan mengecek
apakah sumber daya tersebut tersedia. Jika ya, maka kunci ke sumber daya tersebut akan
diambil, kemudian perintah dijalankan, dan setelah selesai melepaskannya kembali. Akan
tetapi synchronized tidak selalu berhasil.
Sumber daya bersama bisa berbentuk lokasi memori (dalam bentuk objek), atau bisa juga
berupa file, I/O atau bahkan printer. Untuk mengontrol akses ke sumber daya bersama, kita
biasanya membungkusnya dalam bentuk objek. Metode lain yang mencoba untuk
mengakses sumber daya tersebut bisa diberi kata kunci synchronized. Artinya jika thread
sedang mengeksekusi salah satu metode synchronized, thread lain diblok untuk
mengeksekusi metode synchronized lain dalam kelas itu hingga thread pertama selesai.
Karena biasanya data dari suatu kelas kita buat private dan akses ke memori hanya bisa
dilakukan dengan menggunakan metode, maka kita bisa mencegah tabrakan dengan
membuat metode menjadi synchronized. Berikut ini adalah contoh pendeklarasian
synchronized.
synchronized void a() { /* perintah Anda di sini */ }
synchronized void b() { /* perintah Anda di sini */ }
Setiap objek memiliki kunci masing-masing yang otomatis dibuat ketka objek tersebut
dibuat (kita tidak perlu membuat kode spesial). Ketika kita memanggil metode yang diberi
tanda synchronized, objek tersebut dikunci dan tidak boleh ada lagi metode
synchronized yang bisa dieksekusi hingga metode sebelumnya selesai dijalankan dan
kunci dilepas. Karena hanya ada satu kunci untuk setiap objek, maka kita tidak mungkin
menyimpan 2 data pada satu tempat pada saat yang bersamaan.
Satu thread bisa mengunci objek beberapa kali. Ini terjadi jika satu metode memanggil
metode lain di kelas yang sama, kemudian metode tersebut memanggil metode lain lagi di
kelas yang sama dan seterusnya. JVM akan melacak berapa kali objek tersebut terkunci.
Setiap kali suatu metode selesai, kunci akan dilepas. Ketika objek tidak terkunci lagi, maka
kuncinya bernilai 0, yang artinya thread lain bisa mulai menggunakan metode pada objek
ini.
Ada juga kunci per kelas, yang artinya kunci ini berlaku untuk suatu kelas. Otomatis semua
objek yang diciptakan dari kelas yang sama memiliki kunci bersama. Caranya yaitu dengan
menggunakan synchronized static metode sehingga suatu objek bisa juga mengunci
kelas sehingga objek lain yang menggunakan metode ini tidak bisa jalan apabila sedang
digunakan oleh objek lain.
Memperbaiki SelaluGenap
Kita akan ubah sedikit program SelaluGenap di awal bagian ini untuk memberikan kata
kunci synchronized pada metode berikut() dan ambilNilai(). Jika kita hanya
meletakkan kunci pada salah satu metode, maka metode yang tidak diberi kunci akan tetap
bebas untuk dieksekusi mengabaikan ada atau tidaknya kunci. Di sini lah kunci
pemrograman serentak, di mana kita harus memberi kunci di setiap akses ke sumber daya
bersama.
Metode ini akan berjalan terus menerus, oleh karena itu kita akan gunakan waktuMulai
untuk menyimpan waktu ketika thread mulai berjalan, kemudian secara periodik mengecek
waktu saat ini. Jika proses sudah berjalan lebih dari 4 detik, kita hentikan proses kemudian
mencetak hasilnya.
package com.lyracc.selalugenapsynchronized;
new Thread("Hansip") {
// mencatat waktu ketika thread dimulai
private long waktuMulai = System.currentTimeMillis();
public void run() {
while (true) {
int nilai = genap.ambilNilai();
// Jika ganjil, keluar dan cetak nilainya
if (nilai % 2 != 0) {
System.out.println(nilai);
System.exit(0);
}
// Selesaikan program jika sudah melewati 4 detik
if (System.currentTimeMillis() - waktuMulai > 4000) {
System.out.println(nilai);
System.exit(0);
}
}
}
}.start();
while (true)
genap.berikut();
}
}
Bagian Kritis
Kadang-kadang kita hanya ingin mencegah beberapa thread untuk mengakses sebagian
kode saja di dalam suatu metode, bukan keseluruhan metode. Bagian kode yang kita ingin
lindungi ini disebut bagian kritis (critical section) dan juga bisa dibuat dengan kata kunci
synchronized. Akan tetapi, kata kunci ini digunakan dengan menyatakan objek mana
yang memiliki kunci yang harus dicek sebelum bagian ini dijalankan.
Berikut ini adalah bentuk umum dari pernyataan synchronized untuk melindung bagian
kritis :
synchronized(objekKunci) {
// Kode di bagian ini hanya bisa diakses
// Jika objekKunci sedang tidak diakses oleh thread lain
}
Bentuk umum di atas juga disebut blok tersinkron (synchronized block); sebelum blok ini
bisa dieksekusi, kunci pada objek objekKunci harus dicek terlebih dahulu. Jika thread lain
telah mengunci ojek ini, maka bagian kritis tidak bisa dimasuki hingga thread lain selesai
dan melepas kuncinya.
Siklus Hidup Thread
Suatu thread bisa berada dalam salah satu kondisi berikut :
1. Baru : Objek thread baru saja dibuat, akan tetapi belum mulai dijalankan, sehingga
belum bisa berbuat apa-apa.
2. Bisa-jalan : Artinya objek ini sudah dimulai dan sudah bisa dijalankan oleh mekanisme
pembagian waktu oleh CPU. Sehingga thread ini bisa jalan kapan saja, selama
diperintahkan oleh penjadwal thread.
3. Mati : suatu thread biasanya mati ketika selesai menjalankan metode run().
Sebelumnya, kita bisa memanggi metode stop(), akan tetapi program bisa berada dalam
kondisi tidak stabil jika metode ini dipanggil. Kita akan lihat beberapa metode lain untuk
menghentikan thread di bagian berikutnya.
4. Diblok : Thread seharusnya bisa berjalan, akan tetapi ada yang menghalanginya. Salah
satunya adalah jika thread menunggu di bagian kritis sementara ada thread lain yang
sedang menjalankan bagian kritis tersebut. Ketika suatu thread berada dalam kondisi
diblok, penjadwal thread akan mengabaikannya dan tidak memberikan waktu CPU.
Bagaimana Suatu Thread Berada dalam Kondisi Diblok
Ketika suatu thread diblok, ada suatu alasan kenapa thread tersebut tidak bisa terus
berjalan. Suatu thread dapat diblok karena beberapa alasan sebagai berikut :
Kita memberi perintah thread untuk tidur dengan sleep(milidetik) sehingga
thread tidak akan jalan dalam waktu yang sudah disebutkan
Kita memerintahkan thread untuk menunggu dengan perintah wait(). Thread tidak
akan dijalankan kembali hingga diberikan pesan notify() atau notifyAll().
Thread sedang menunggu selesainya operasi I/O
Thread mencoba memanggil metode dengan kata kunci synchronized, akan tetapi
thread lain sedang memegang kuncinya.
Kerjasama Antar Thread
Setelah kita mengerti bagaimana thread bisa bertabrakan satu sama lain, dan bagaimana
caranya mencegah tabrakan antar thread, langkah berikutnya adalah belajar bagaimana
membuat thread dapat bekerja sama satu sama lain. Kuncinya adalah komunikias antar
thread yang diimplementasi dengan aman dalam metode-metode pada kelas Object, yaitu
wait() dan notify().
wait() dan notify()
Pertama-tama penting untuk mengerti bahwa sleep() tidak melepas kunci thread ketika
dipanggil. Artinya jika sleep() dipanggil dari dalam bagian kritis, maka thread lain tidak
bisa masuk hingga thread yang memanggil sleep() bangun, meneruskan eksekusi, hingga
keluar dari bagian kritis. Sedangkan wait() melepas kunci ketika dipanggil, sehingga
thread lain bisa masuk ke dalam bagian kritis.
Ada dua bentuk wait(). Yang pertama memiliki argumen waktu dalam bentuk mili detik
(mirip dengan sleep(). Perbedaannya dengan sleep() adalah :
wait() melepaskan kunci
Kita bisa membatalkan wait() dengan menggunakan notify() atau
notifyAll(), atau hingga waktu tunggu berlalu.
Bentuk kedua dari wait() adalah wait() yang tidak memiliki argumen. Jenis wait() ini
akan terus berlangsung hingga dibatalkan dengan notify atau notifyAll().
Aspek penting dari wait(), notify() dan notifyAll() adalah metode ini merupakan
bagian dari kelas dasar Obejct dan bukan bagian dari kelas Thread seperti sleep().
Meskipun kelihatan janggal, hal ini sangat penting karena semua objek memiliki kunci.
Artinya kita bisa memanggil wait() dari dalam metode synchronized, tidak peduli
apakah kelas tersebut merupakan kelas turunan dari Thread atau bukan.
Sebetulnya satu-satunya tempat kita bisa memanggil wait(), notify() dan notifyAll()
adalah dari dalam blok atau metode synchronized. (sleep() bisa dipanggil dari manapun
karena ia tidak berhubungan dengan kunci suatu objek). Jika kita memanggil wait(),
notify() atau notifyAll() dari luar metode atau blok synchronized, compiler tidak
akan memperingatkan Anda, akan tetapi ketika program dijalankan, kita akan mendapatkan
pengecualian IllegalMonitorStateException dengan pesan kesalahan yang tidak
dimengerti, seprti "thread ini bukan pemiliknya". Pesan ini berarti bahwa thread yang
memanggil wait(), notify() atau notifyAll() harus memiliki kunci objek sebelum bisa
memanggil salah satu metode ini.
Kita juga bisa meminta suatu objek untuk memanipulasi kuncinya sendiri. Caranya,
pertama-tama kita harus mengambil kuncinya. Misalnya, jika kita ingin memanggil
notify() ke suatu objek x, kita harus melakukannya di dalam blok synchronized untuk
mengambil kunci x, seperti :
synchronized(x) {
x.notify();
}
Biasanya, wait() digunakan jika kita menunggu sesuatu yang dikontrol oleh sesuatu di
luar kontrol metode kita (di mana sesuatu ini hanya bisa diubah oleh thread lain). Kita
tidak ingin menunggu dan berulang-ulang menguji apakah sesuatu itu sudah tersedia,
karena cara ini akan memboroskan penggunaan CPU. Kita bisa menggunakan wait()
untuk memerintahkan suatu thread untuk menunggu hingga sesuatu tersebut berubah, dan
hanya ketika notify() dipanggil, maka thread tersebut akan bangun dan mengeceknya.
Dengan kata lain wait() digunakan melakukan aktifitas tak-sinkron antara beberapa
thread.
Sebagai contoh, anggap suatu restoran memiliki satu orang koki dan satu orang pelayan.
Pelayan harus menunggu hingga si koki selesai memasak makanan. Ketika koki selesai, ia
akan memberi tahu pelayan, kemudian membawa makanan ini ke customer, kemudian
menunggu kembali. Koki di sini kita sebut sebagai produsen, dan pelayan disebut sebagai
konsumen.
package com.lyracc.rumahmakan;
class Pesanan {
private int i = 0;
public Pesanan(int i) {
this.i = i;
}
public Pelayan(RumahMakan r) {
rumahMakan = r;
start();
}
import java.util.*;
class Garpu {
private static int hitung = 0;
private int nomor = hitung++;
// Ilmuwan makan
public void makan() {
// cek apakah garpu kirinya tersedia
synchronized (garpuKiri) {
System.out.println(this + " punya " + this.garpuKiri
+ ". Menunggu " + this.garpuKanan);
// kemudian cek apakah garpu kanannya tersedia
synchronized (garpuKanan) {
System.out.println(this + " makan");
}
}
}
// Kelas utama
public class IlmuwanMakan {
final static int JUMLAH_ILMUWAN = 3; // bisa diganti
final static int WAKTU_FIKIR_MAKS = 10; // mili detik, bisa diganti
final static boolean DEADLOCK = true; // ubah ini menjadi false
untuk mencegah deadlock
final static int WAKTU_TIMEOUT = 10000; // mili detik atau buat 0
jika tidak ingin timeout
int i = 0;
Untuk memecahkan masalah ini, kita harus mengerti bahwa deadlock bisa terjadi jika
keempat kondisi berikut ini terjadi pada saat yang sama :
1. Saling melarang (mutual exclusion): Paling sedikit salah satu sumber daya yang
digunakan objek tidak boleh digunakan bersama. Dalam hal ini, satu garpu bisa
digunakan oleh dua orang ilmuwan
2. Paling sedikit salah satu proses sedang memegang suatu sumber daya, dan di saat
yang sama menunggu sumber daya lain yang dipegang oleh proses lain. Dalam hal
ini, agar deadlock terjadi, seorang ilmuwan pasti sedang memegang satu garpu dan
menunggu garpu lain yang dipegang oleh ilmuwan lain.
3. Suatu sumber daya tidak bisa diambil secara paksa. Proses hanya bisa melepas
sumber daya dalam kondisi normal. Ilmuwan-ilmuwan kita adalah orang yang
beradab, sehingga tidak bisa merebut garpu yang sedang dipegang oleh ilmuwan
lain.
4. Lingkaran menunggu sedang terjadi, di mana proses pertama sedang menunggu
satu sumber daya yang dipegang oleh proses kedua, yang juga sedang menunggu
sumber daya yang dipegang oleh proses ketiga, dan seterusnya hingga proses
terakhir menunggu sumber daya yang dipegang oleh proses pertama, sehingga
semua proses saling menunggu satu sama lain. Pada contoh ini, lingkaran
menunggu terjadi karena semua ilmuwan mengambil garpu kiri terlebih dahulu
baru kemudian garpu kanan. Kita bisa memecahkan deadlock dengan membalik
garpu kiri dan garpu kanan pada ilmuwan terakhir, sehingga ilmuwan terakhir akan
mengambil garpu kanan terlebih dahulu, baru kemudian garpu kiri.
Karena semua kondisi di atas harus terjadi secara bersama-sama agar deadlock bisa terjadi,
maka untuk mencegah terjadinya deadlock, kita harus memecah salah satu kondisi saja.
Pada program ini, cara termudah adalah dengan memecah kondisi keempat. Akan tetapi ini
bukan satu-satunya pemecahan, kita bisa memecahkannya dengan teknik yang lebih
canggih. Untuk ini saya mereferensikan Anda pada buku-buku teknik threading tingkat
lanjut untuk lebih detailnya.
Kesimpulannya, Java tidak menyediakan bantuan secara alami untuk mencegah deadlock:
Anda harus menghindarinya sendiri dengan membuat program multi threading dengan
lebih hati-hati.
Menghentikan Thread
Salah satu perubahan pada Java 2 untuk mengurangi kemungkinan terjadinya deadlock
adalah dengan dideprekasi (artinya pengembangannya dihentikan, dan user disarankan
untuk menghindari penggunaannya) metode stop(), suspend(), dan resume() pada kelas
Thread.
Alasan mengapa metode stop() dideprekasi adalah karena metode ini tidak melepas kunci
yang sudah dimilikinya, dan jika objek tersebut berada dalam kondisi "cacat" seperti ini,
thread lain bisa melihat dan mengubah objek cacat ini. Hasilnya akan muncul masalah
yang tersembunyi yang akan sangat sulit dideteksi.
Java menyediakan cara lain untuk menghentikan thread, yaitu dengan mengeset suatu
variabel untuk memberi tahu thread tersebut agar menghentikan dirinya sendiri yaitu
dengan keluar dari metode run()-nya. Variabel ini akan dicek pada metode run() yang
jika bernilai true, maka metode run() akan berhenti. Berikut ini adalah contohnya :
package com.lyracc.hentikanthread;
import java.util.*;
/**
* @param args
*/
public static void main(String[] args) {
import java.util.*;
/**
* @param args
*/
public static void main(String[] args) {
Lakukan hal yang sama pada semua kesalahan, yaitu Shell, Label, RowLayout dan
SWT.NONE, sehingga tidak ada lagi kesalahan yang dilaporkan oleh Eclipse.
Untuk menjalankan program Anda, klik tombol Run seperti pada gambar berikut.
CATATAN : Ingat bahwa SWT bergantung penuh pada sistem operasi yang Anda
gunakan, karena SWT menggunakan widget bawaan sistem operasi seperti
dijelaskan pada bagian sebelumnya. Ketika Anda membuat program ini pertama
kali, Anda menambahkan pustaka SWT yang spesifik terhadap sistem operasi di
mana Eclipse berjalan. Artinya, jika Anda menambahkan pustaka
org.eclipse.swt.win32.win32.x86_3.4.1.v3449c, maka program JAR Anda hanya
akan bisa dijalankan pada sistem operasi Windows. Untuk bisa menjalankannya
pada sistem operasi lain, misalnya Linux, maka Anda harus menambahkan pustaka
SWT khusus Linux yang bisa diunduh secara terpisah atau menggunakan Eclipse
pada Linux.
Berikut ini adalah screen shot hasil jalannya program
Melihat Lebih Dekat HelloSWT
Secara umum aplikasi SWT membutuhkan beberapa langkah sebagai berikut :
1. Buat Display baru
2. Buat satu atau lebih Shell
3. Buat manager layout untuk Shell baru
4. Buat widget di dalam shell
5. Buka jendela shell
6. Buat perulangan pengirim event
7. Buang (dispose) display dan widget-widget lainnya
8. Tentunya import berbagai paket yang diperlukan oleh program
package helloswt;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
/**
* @param args
*/
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout( new RowLayout());
Label label = new Label(shell, SWT.NONE);
label.setText("Hello, World!");
shell.pack();
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
display.dispose();
label.dispose();
}
}
Mari kita bahas satu per satu
1. Membuat Display baru
Membuat Display dapat dilakukan dengan membuat instansi dari objek bertipe Display,
misalnya seperti perintah berikut
Display display = new Display();
Display (tampilan) adalah objek maya yang merupakan kontainer induk semua komponen
GUI. Suatu tampilan sebetulnya tidak terlihat, akan tetapi komponen yang ditambahkan ke
dalam tampilan dapat dilihat. Biasanya, suatu aplikasi hanya membutuhkan satu tampilan.
2. Membuat Shell baru
Membuat Shell dapat dilakukan dengan membuat instansi dari objek bertipe Shell,
misalnya seperti perintah berikut
Shell shell = new Shell(display);
Pada pernyataan di atas, konstruktor Shell mengambil parameter yang merupakan induk
di mana shell ini akan diletakkan. Pada pernyataan di atas, kita membuat objek shell
bertipe Shell yang memiliki induk objek display.
Shell adalah jendela suatu aplikasi. Shell tertinggi adalah shell yang berada langsung di
bawah objek bertipe Display, dan merupakan jendela utama suatu aplikasi. Shell
sebenarnya adalah widget komposit, yaitu suatu widget yang terdiri dari beberapa widget
lain. Oleh karenanya shell juga bisa berisi shell lain. Untuk aplikasi sederhana yang
menampilkan kata "Hello World!", kita hanya membutuhkan satu shell saja yang kita
letakkan pada objek display.
3. Membuat manager layout untuk Shell baru
Untuk membuat manager layout, kita menggunakan metode instansi dari objek shell, yaitu
setLayout() yang mengambil parameter jenis layout yang akan disusun pada shell.
Misalnya,
shell.setLayout( new RowLayout());
Pada contoh di atas, kita memerintahkan objek shell untuk mengatur widget-widget di
dalamnya dalam urutan seperti baris. RowLayout adalah kelas layout yang digunakan objek
shell untuk mengatur objek widget di dalamnya.
Manager layout digunakan untuk mengatur secara otomatis peletakkan setiap widget. Pada
SWT, objek komposit seperti Shell tidak mengetahui bagaimana widget harus ditampilkan.
Oleh karenanya jika kita tidak memberikan manager layout kepada objek bertipe Shell,
maka tidak ada widget yang akan ditampilkan.
Bahasan khusus tentang layout cukup kompleks, dan karenanya kita akan bahas tersendiri
pada bagian selanjutnya.
4. Membuat widget di dalam shell
Widget adalah komponen pembangun GUI. Menu, tombol, teks, kotak cek, kotak input
teks, hingga kanvas, adalah contoh-contoh widget. Setiap widget memiliki sifat dan cara
kerja yang berbeda-beda. Pada contoh ini, kita menggunakan widget yang sangat
sederhana yang dinamakan Label. Widget ini digunakan untuk menampilkan teks,
misalnya digunakan sebagai label atau pertanyaan pada suatu input teks.
Pada contoh HelloSWT, kita gunakan konstruktor yang mengambil 2 parameter. Yang
pertama, adalah kontainer induk di mana Label ini akan ditampilkan, dan parameter kedua
adalah bit gaya.
Label label = new Label(shell, SWT.NONE);
Label ini kemudian kita isi dengan kata "Hello Word!" untuk menampilkan kata ini pada
jendela utama.
label.setText("Hello, World!");
Widget buatan sendiri dan kelas lain yang dibuat khusus untuk
org.eclipse.swt.custom Eclipse; tidak bergantung pada platform apapun (implementasinya
sama pada semua sistem operasi)
Informasi Penjelasan
Jenis event yang terjadi beserta penjelasannya dirangkum dalam tabel berikut ini :
Suatu event juga bisa memiliki tipe. Artinya event yang dihasilkan merupakan
objek bertipe suatu kelas, misalnya MouseEvent, bukan hanya bertipe kelas Event
yang jenisnya disimpan dalam variabel tertentu. Event bertipe mengikuti pola
JavaBeans standar. Kelas-kelas ini terdapat dalam paket
org.eclipse.swt.events
Tabel berikut menggambarkan perbandingan antara event bertipe dan jenis event
dari event tanpa tipe.
ArmEvent SWT.Arm
SWT.Move
ControlEvent
SWT.Resize
DisposeEvent SWT.Dispose
SWT.FocusIn
FocusEvent
SWT.FocusOut
HelpEvent SWT.Help
SWT.KeyDown
KeyEvent
SWT.KeyUp
SWT.Hide
MenuEvent
SWT.Show
ModifyEvent SWT.Modify
SWT.MouseDoubleClick
MouseEvent SWT.MouseDown
SWT.MouseUp
MouseEvent SWT.MouseMove
SWT.MouseEnter
MouseEvent SWT.MouseExit
SWT.MouseHover
PaintEvent SWT.Paint
SWT.DefaultSelection
SelectionEvent
SWT.Selection
SWT.Activate
ShellEvent SWT.Close
SWT.Deactivate
SWT.Iconify
SWT.Deiconify
TraverseEvent SWT.Traverse
SWT.Collapse
TreeEvent
SWT.Expand
VerifyEvent SWT.Verify
ControlListener
ControlEven controlMoved(ControlEvent) SWT.Move
(dan
t controlResized(ControlEvent) SWT.Resize
ControlAdapter)
DisposeEven
DisposeListener widgetDisposed(DisposeEvent) SWT.Dispose
t
FocusListener
focusGained(FocusEvent) SWT.FocusIn
FocusEvent (dan
focusLost(FocusEvent) SWT.FocusOut
FocusAdapter)
MenuListener
menuHidden(MenuEvent) SWT.Hide
MenuEvent (dan
menuShown(MenuEvent) SWT.Show
MenuAdapter)
ModifyEven
ModifyListener modifyText(ModifyEvent) SWT.Modify
t
SWT.MouseDoubleC
MouseListener mouseDoubleClick(MouseEvent)
lick
MouseEvent (dan mouseDown(MouseEvent)
SWT.MouseDown
MouseAdapter) mouseUp(MouseEvent)
SWT.MouseUp
MouseMoveListe
MouseEvent mouseMove(MouseEvent) SWT.MouseMove
ner
MouseTrackListe
mouseEnter(MouseEvent) SWT.MouseEnter
ner (dan
MouseEvent mouseExit(MouseEvent) SWT.MouseExit
MouseTrackAdapt
mouseHover(MouseEvent) SWT.MouseHover
er)
shellActivated(ShellEvent) SWT.Activate
shellClosed(ShellEvent) SWT.Close
ShallListener (dan
ShellEvent shellDeactivated(ShellEvent) SWT.Deactivate
ShellAdapter)
shellIconified(ShellEvent) SWT.Iconify
shellDeiconified(ShellEvent) SWT.Deiconify
TraverseEve
TraverseListener keyTraversed(TraverseEvent) SWT.Traverse
nt
Penanganan Mouse
Semua sistem operasi di mana SWT diimplementasikan mendukung perangkat tunjuk.
Biasanya berbentuk mouse, akan tetapi bisa jadi berupa trackball, trackpad, atau jenis
perangkat keras lainnya. Pada komputer genggam, perangkat tunjuk bisa jadi berupa stylus.
Untuk mempermudah pembahasan, kita akan gunakan mouse sebagai perangkat tunjuk,
tidak peduli bagaimana perangkat aslinya.
Posisi suatu mouse biasanya digambarkan dalam bentuk ikon kecil pada layar yang disebut
kursor. Hal ini berlaku untuk semua platform, kecuali pada Windows CE, karena perangkat
Windows CE biasanya berupa perangkat tunjuk "langsung", seperti stylys, yang tidak
membutuhkan kursor.
Mouse biasanya memiliki tiga tombol (kecuali pada Macintosh yang hanya memiliki satu
tombol, walaupun sebenarnya mouse lebih dari 1 tombol pun bisa digunakan). Mouse
digunakan untuk menunjuk, klik, geser (drag) dan memilih komponen kontrol GUI. Bisa
juga digunakan untuk menampilkan menu konteks yang biasanya ditampilkan dengan
mengklik kanan suatu mouse. Perilaku "drag-and-drop" mouse kurang lebih sangat
bergantung pada platformnya.
Ketika kita menggeser mouse, kursor akan berbah bentuk, tergantung dari kontrol apa di
bawahnya. Misalnya, widget teks akan mengubah tampilan kursor seperti huruf I untuk
menunjukkan bahwa user bisa mengetikkan sesuatu pada widget tersebut. Di dalam kursor,
ada titik pusat yang menunjukkan koordinat x dan y suatu mouse ketika event pada mouse
terjadi.
Event pada Mouse
Ketika tombol mouse ditekan atau mouse digerakkan, event mouse dibuat dan akan
diberikan kepada widget yang ada di bawahnya. Akan tetapi ketika tombol mouse ditekan
dan ditahan (terus ditekan), dan mouse berada di luar widget (mungkin ada di widget lain
atau pada aplikasi lain di desktop), eventnya akan diberikan kepada widget awal di mana
mouse tersebut ditekan. Pengalihan event sementara ini disebut pengambilan mouse.
Widget yang menerima event disebut widget pengambil. Pengambilan mouse terhadi
secara otomatis pada SWT. (Ini mungkin bukan sesuatu masalah, akan tetapi sebagai
informasi saja kepada Anda).
Tabel - Isi Event Mouse ketika suatu tombol mouse ditekan, dilepaskan atau mouse digeser
Nama
Penjelasan
Field
Bit mask yang menyatakan kondisi keyboard dan mouse sebelum event
stateMask
terjadi
Ketika mouse ditekan atau dilepas, field bernama "button" akan diisi oleh tombol mana
yang ditekan. Tombol mouse diberi nomor dari kiri ke kanan yang dimulai dari 1. Untuk
user kidal (dan mengkonfigurasi sistem operasi untuk orang kidal), penomoran tombol
tetap sama, akan tetapi dimulai dari kanan ke kiri. Pemetaan tombol untuk orang kidal ini
tidak tampak oleh SWT dan aplikasi kita, karena dilakukan secara otomatis oleh sistem
operasi.
Ketika terjadi event pada mouse, koordinat x dan y-nya juga dilaporkan dalam event.
Koordinat yang dilaporkan adalah koordinat relatif widget ketika event tersebut dibuat
(bukan koordinat global layar atau aplikasi kita). Karena user mungkin telah memindahkan
mouse setelah menekan tombol, maka lokasi sebenarnya ketika event ini ditangani
mungkin berbeda dengan ketika event dibuat. Hal ini untuk menghindari program kita
untuk bertindak terlalu sensitif terhadap pergerakan mouse. (Jika kita membutuhkan lokasi
yang aktual pada saat-saat tertentu, kita bisa menggunakan metode getCursorLocation()
yang dimiliki oleh kelas Display.)
Event pada mouse juga menggunakan field lain yang dinamakan stateMask untuk
menunjukkan keadaan mouse. Seperti pada penanda tombol, stateMask berisi keadaan
mouse sebelum terjadinya suatu event. Misalnya, jika tidak ada tombol mouse yang
ditekan atau tombol keyboard lain yang ditekan ketika tombol kiri mouse ditekan, maka
event mouse akan diisi dengan button bernilai 1 dan stateMask bernilai 0. stateMask
tidak berisi "tombol 1". Akan tetapi ketika terjadi event lain ketika mouse kiri sedang
ditekan, maka stateMask akan berisi 1.
Keadaan suatu mouse dilambangkan oleh konstanta pada kelas SWT, seperti pada tabel
berikut :
stateMask Penjelasan
Berikut ini adalah event pada mouse yang disediakan oleh SWT. Seperti disebutkan pada
bagian sebelumnya, event dan listener SWT terdiri dari event/listener tanpa tipe dan
event/listener bertipe. Keduanya disarikan dalam tabel berikut :
Kelas Interface/Kelas
Jenis event (event Penjelas
Event Listener (listener Metode (listener bertipe)
tanpa tipe) an
(event bertipe)
bertipe)
Mouse
mouseDoubleClick(Mouse SWT.MouseDouble
di-double
Event) Click
click
Tombol
MouseListener
MouseEv mouseDown(MouseEvent) SWT.MouseDown mouse
(dan
ent ditekan
MouseAdapter)
Tombol
mouse
mouseUp(MouseEvent) SWT.MouseUp
dilepaska
n
Mouse
MouseEv MouseMoveListe
mouseMove(MouseEvent) SWT.MouseMove berpinda
ent ner
h posisi
Mouse
masuk ke
mouseEnter(MouseEvent) SWT.MouseEnter
wilayah
klien
Mouse
MouseTrackListe
berada di
MouseEv ner (dan mouseExit(MouseEvent) SWT.MouseExit
sekitar
ent MouseTrackAda
klien
pter)
Mouse
keluar
mouseHover(MouseEvent) SWT.MouseHover dari
wilayah
klien
Mari kita lihat contoh penggunaan mouse event pada program berikut. Anda bisa
mengunduh program ini dan mengimportnya pada Eclipse di sini.
package net.lyracc.pelacakmouse;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
/**
* @param args
*/
public static void main(String[] args) {
// Membuat display dan shell baru
Display display = new Display();
Shell shell = new Shell(display);
};
Berikut ini adalah daftar event dan listener yang berkaitan dengan fokus.
Widget
menerima
focusGained(FocusEvent) SWT.FocusIn
fokus dari
FocusListener
keyboard
FocusEvent (dan
Widget
FocusAdapter)
kehilangan
focusLost(FocusEvent) SWT.FocusOut
fokus dari
keyboard
Mari kita lihat contoh berikut ini untuk memahami lebih lanjut tentang fokus input. Kita
akan buat 2 widget yang berupa input teks. Ketika input teks atas menerima fokus, maka
judul aplikasi kita ganti dengan "Fokus pada input teks atas". Ketika input teks bawah
menerima fokus, maka judul aplikasi kita ganti dengan "Fokus pada input teks bawah".
Jika input teks atas kehilangan fokus, kita isi input teks yang kehilangan fokus dengan
"Saya kehilangan fokus", begitu input teks atas menerima fokus kembali, kita akan hapus
kalimat tersebut.
Contoh program ini dapat Anda unduh di sini, untuk diimport pada Eclipse. Pembahasan
detail tentang contoh program ini akan dibahas kemudian.
Event pada Tombol
Ketika suatu tombol pada keyboard ditekan, event tombol akan dibuat dan diberikan
kepada aplikasi kita. Akan tetapi, tergantung pada platformnya, kedaerahan (locale), dan
kombinasi tombol, ada kalanya event tidak terjadi. Misalnya, pada karakter Eropa yang
memiliki aksen, mesin pengolah karakter dari sistem operasi akan mengambil tombol
tersebut untuk diolah. Misalnya pada kedaerahan Jerman jika karakter ^ ditekan kemudian
diikuti dengan tombol e, maka karakter ê akan ditampilkan. Demikian juga jika tombol
bantu ditekan untuk mengolah bahasa Jepang, IME akan mengolah urutan karakter menjadi
karakter Kanji.
Dengan kata lain, event tingkat rendah seperti ini sangat bergantung pada platform dan
sistem operasi, sehingga tidak terlalu berguna untuk kebanyakan program. SWT
menyembunyikan event sistem operasi dan hanya menampilkan satu event tombol saja
setelah sistem operasi selesai mengolah tombol tersebut.
Berikut ini adalah daftar event dan listener yang berkaitan dengan tombol.
Kelas
Interface/Kelas Jenis event
Event Metode (listener
Listener (listener (event tanpa Penjelasan
(event bertipe)
bertipe) tipe)
bertipe)
Tombol
keyPressed(KeyEvent) SWT.KeyDown
KeyListener (dan ditekan
KeyEvent
KeyAdapter) Tombol
keyReleased(keyEvent) SWT.KeyUp
dilepaskan
Event SWT.KeyDown dan SWT.KeyUp merupakan representasi tingkat tinggi dari tombol
yang ditekan dan dilepaskan. Event-event ini berguna jika kita ingin mencegat suatu
tombol tertentu dan melakukan aksi khusus ketika tombol itu ditekan.
Berikut ini adalah isi event keyboard ketika ditekan/dilepaskan.
Nama
Penjelasan
Field
character berisi karakter yang kita masukkan lewat keyboard setelah diolah oleh sistem
operasi. Misalnya jika kita menekan tombol <a> maka character berisi 'a'. Jika tombol
<Shift> dan <a> ditekan, maka character berisi 'A'. Jika tombol <Ctrl> dan <a> ditekan,
maka character akan diisi karakter yang bersesuaian dengan Ctrl+a, yaitu karakter
dengan kode Unicode '\u0001' (atau SOH). Beberapa tombol seperti Enter, Backspace,
Tab, memiliki kode karakter Unicode tersendiri. SWT juga memiliki konstanta untuk
mewakili tombol-tombol ini, yaitu
SWT.LF Tombol LF
keyCode berisi karakter yang tidak bisa diwakilkan dengan karakter Unicode, misalnya
tombol <F1>, tombol <PgUp>, tombol <Panah Atas>, dan lain-lain termasuk tombol
angka pada keypad dan tombol <+> <-> <*> pada keypad. Beberapa tombol tersebut
dilambangkan dalam konstanta sebagai berikut. Khusus untuk keypad, apabila tombol <+>
ditekan, maka selain keyCode berisi SWT.KEYPADD_ADD, character juga berisi '+'.
SWT.F SWT.KEYPA
SWT.F12 SWT.HOME SWT.KEYPAD_CR
2 D_1
SWT.F SWT.KEYPA
SWT.F13 SWT.END SWT.HELP
3 D_2
SWT.F SWT.KEYPA
SWT.F14 SWT.INSERT SWT.CAPS_LOCK
4 D_3
SWT.F SWT.KEYPA
SWT.PAGE_UP SWT.KEYPAD_0
10 D_9
stateMask berisi tombol sebelum tombol ditekan, yang biasanya <Ctrl>, <Shift>, <Alt>,
<Command>. Pada kebanyakan keyboard hanya ada 3 tombol pertama, akan tetapi ada
juga yang memiliki lebih dari 3 tombol. Tombol-tombol ini disebut tombol pengubah.
SWT membuat tombol-tombol ini menjadi kode seperti
stateMask Penjelasan
Dengan representasi seperti ini, maka SWT bisa dijalankan pada beberapa platform, dan
tidak bergantung dengan tombol apa yang ada pada suatu sistem operasi. Bayangkan jika
Anda ingin menggunakan <Control> + <b> untuk membuat karakter menjadi tebal, akan
tetapi tombol <Control> tidak tersedia pada Macintosh.
Untuk menguji tombol pengubah mana yang ditekan, kita bisa menggunakan bitwise AND,
misalnya (e.stateMask & SWT.SHIFT).
Berikut ini adalah contoh program pelacak keyboard yang akan melaporkan tombol apa
yang Anda tekan dan lepaskan. Contoh program ini dapat Anda unduh di sini untuk
mengimportnya ke dalam Eclipse. Jalankan program ini pada Eclipse, kemudian ketik apa
saja di program Anda, perhatikan "Console" di Eclipse akan penuh dengan berbagai
laporan tentang tombol yang ditekan dan dilepaskan.
Kadang kala dalam kondisi yang sangat langka, kita harus mengolah sendiri tombol kita
sebelum diolah oleh suatu wudget. Karena SWT menggunakan widget bawaan sistem
operasi, pengolahan tombol terjadi di level sistem operasi. Misalnya, ketika user mengetik
pada widget teks, listener SWT.KeyDown akan dijalankan, kemudian sistem operasi akan
memasukkan karakter dan menggambarnya kembali dijalankan oleh sistem operasi.
Dengan menggunakan doit, kita bisa membuang karakter tersebut untuk tidak
meneruskannya ke sistem operasi.
Contoh berikut akan menghalangi user untuk memasukkan karakter pada widget teks
dengan mengeset doit menjadi false setiap kali event SWT.KeyDown terjadi.
package com.lyracc.penghalangtombol;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
/**
* @param args
*/
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
Text text = new Text(shell, SWT.SINGLE | SWT.BORDER);
text.pack();
shell.pack();
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
display.dispose();
}
}
Traversal
Traversal atau penelusuran berarti memindahkan fokus dari satu kontrol ke kontrol lain.
Tombol traversal berbeda dengan tombol akselerasi dan tombol sistem jendela (yang akan
dibahas nanti), yaitu : Suatu kontrol bebas memilih apakah akan menjalankan operasi
traversal atau mengolah tombol yang ditekan. Jika kontrol memilih untuk menjalankan
operasi traversal maka tombol tersebut tidak akan diproses lebih lanjut.
Ada dua jenis traversal, yaitu mnemonik dan tab.
Traversal Mnemonik
Mnemonik biasanya tertulis sebagai karakter yang diberi garis bawah pada label suatu
widget. Suatu aksi akan dilakukan jika user menekan kombinasi tombol yang cocok
dengan mnemonik tersebut, biasanya dengan menekan tombol <Alt> bersamaan dengan
karakter yang digaris bawah tersebut.
Gambar berikut adalah contoh mnemonik pada program OpenOffice ketika kita membuka
kotak dialog Format Font
Jika kita menekan <Alt> + H maka tampilan Help akan ditampilkan.
Menekan tombol kombinasi untuk memanggil mnemonik sama dengan mengaktifkan
widget tersebut. Pada contoh di atas, mnemonik <Alt> + H diterapkan pada tombol, yang
artinya ketika kita menekan <Alt> + H, sama dengan kita menekan tombol Help. Beberapa
widget yang tidak bisa menerima pilihan, seperti label dan kotak grup masih bisa
menerima mnemonik, akan tetapi fungsinya hanya untuk memindahkan fokus ke widget
tersebut.
Bagaimana caranya menambahkan mnemonik? Mudah saja. Cukup tambahkan '&' di
depan karakter yang akan kita tandai sebagai mnemonik, kemudian masukkan string ini
sebagai argumen pada metode setText() suatu widget. Misalnya pada perintah berikut :
Button tombol1 = new Button(shell, SWT.PUSH);
tombol1.setText("&Klik saya");
akan membuat tombol dengan mnemonik <Alt> + K, seperti pada gambar berikut ini :
Untuk membuat mnemonik pada karakter '&' sendiri, gunakan '&&', misalnya "Ini && Itu"
akan menghasilkan "Ini & Itu"
Traversal Tab
Traversal tab didukung pada semua platform. Tidak seperti pada mnemonik, pada traversal
tab, kita tidak perlu mendefinisikan apa-apa, karena setiap platform memiliki cara sendiri
bagaimana memindahkan fokus dari satu widget ke widget lain. Misalnya, ketika kita
menekan tombol <Tab>, maka fokus akan otomatis pindah ke widget berikutnya. Ketika
sampai pada widget terakhir, maka fokus akan diulang dari widget pertama.
Nama traversal tab mungkin agak sedikit salah sasaran, karena sepertinya hanya tombol
<Tab> saja yang bisa digunakan untuk memindahkan fokus dari satu widget ke widget
lain. Pada beberapa platform, menekan tombol panah juga memindahkan fokus.
Beberapa tombol lain seperti <Esc> digunakan untuk menutup kotak dialog, dan
sebenarnya termasuk dalam tombol traversal tab juga.
Berikut ini adalah daftar event dan listener yang berkaitan dengan traversal.
Interface/Kelas
Kelas Event Jenis event
Listener
(event Metode (listener bertipe) (event tanpa Penjelasan
(listener
bertipe) tipe)
bertipe)
Navigasi
pada
TraverseEvent TraverseListener keyTraversed(TraverseEvent) SWT.Traverse
keyboard
dideteksi
doit Suatu boolean yang bisa digunakan untuk membatalkan aksi traversal
Variabel detail bukan hanya untuk dibaca akan tetapi kita juga bisa mengisinya apabila
kita ingin mengubah jenis traversal. Misalnya kita ingin mengubah tombol <Enter> bukan
untuk menutup dialog akan tetapi untuk memindahkan fokus ke widget lain, kita bisa
mengisi variabel detail dengan SWT.TRAVERSE_ARROW_PREVIOUS.
doit digunakan untuk membatalkan traversal jika variabel ini diisi false. Akan tetapi lihat
bahwa pada variabel detail juga bisa SWT.TRAVERSE_NONE. Apa perbedaannya? Ingat
bahwa tombol yang tidak digunakan untuk traversal akan diberikan kepada widget yang
menerima traversal untuk diolah lebih lanjut.
Artinya jika event doit kita isi dengan true, traversal akan dilakukan dan tombol akan
"dikonsumsi" (tidak diberikan kepada widget untuk diproses kembali). Jika doit kita isi
denga false, traversal tidak dilakukan dan tombol akan diberikan kepada widget untuk
diproses.
Apa yang terjadi jika detail juga diisi dengan SWT.TRAVERSE_NONE? Jika detail diisi
dengan SWT.TRAVERSE_NONE maka widget tidak akan melakukan traversal, tidak peduli
apakah isi doit berisi true atau false. Akan tetapi, variabel doit menentukan apakah
tombol akan diberikan kepada widget untuk diproses.
Jadi jika doit berisi false dan detail berisi SWT.TRAVERSE_NONE, maka tombol akan
diberikan kepada widget, dan traversal tidak dilakukan. Akan tetapi jika doit berisi true
dan detail berisi SWT.TRAVERSE_NONE, maka traversal tidak dilakukan, dan tombol akan
dikonsumsi dan tidak akan diberikan kepada widget.
Berikut ini adalah contoh penggunaan traversal.
Program ini akan membuat 6 tombol. Coba tekan tombol <Tab>. Fokus akan pindah ke
tombol berikutnya setiap kali Anda menekan tombol <Tab>. Ketika Anda melewati tombol
4 atau tombol 6, akan tercetak "Button {4} ditelusuri". Jika Anda menekan tombol <Shift>
+ <Tab> fokus akan berpindah ke tombol sebelumnya. Akan tetapi jika Anda sampai pada
tombol 4 atau tombol 6, event traversal yang terjadi akan ditangkap dan traversal akan
diabaikan. Akibatnya Anda tidak akan bisa pindah dari tombol 4 ke tombol 3 atau tombol 6
ke tombol 5. Pada saat yang sama di konsol akan tercetak "Anda tidak bisa kembali!".
Program lengkapnya adalah sebagai berikut, yang bisa diunduh di sini.
package com.lyracc.traversalkustom;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;
public class TraversalKustom {
/**
* @param args
*/
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setSize(300, 200);
SWT.SHIFT +
<Shift> + tombol panah ke atas
SWT.ARROW_UP
Metode Penjelasan
isDisposed() Mengembalikan true jika widget ini sudah dihapus dari memori
Event ini terdapat pada semua kelas turunan dari kelas abstrak Widget
Interface/Kelas
Kelas Event Jenis event
Listener Penjelasa
(event Metode (listener bertipe) (event
(listener n
bertipe) tanpa tipe)
bertipe)
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
/**
* @param args
*/
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display, SWT.CLOSE | SWT.BORDER);
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
display.dispose();
}
}
Anda bisa mengganti-ganti kontruktor shell dengan beberapa kombinasi berikut dan lihat
hasilnya sendiri :
Shell shell = new Shell(display, SWT.SHELL_TRIM)
Shell shell = new Shell(display, SWT.DIALOG_TRIM)
Shell shell = new Shell(display, SWT.TOOL)
Shell shell = new Shell(display, SWT.CLOSE | SWT.BORDER)
Shell shell = new Shell(display, SWT.NO_TRIM)
Beberapa contoh di atas tidak akan menampilkan tombol close, sehingga untuk
mematikannya Anda harus menghentikannya dari dalam Eclipse dengan menekan tombol
Contoh berikut (kode lengkapnya untuk diimport ke dalam Eclipse bisa diunduh di sini) ini
membuat shell kosong, jika mouse kita berhenti di suatu tempat (hover), shell baru yang
berisi tool tip akan ditampilkan, seperti pada gambar berikut. Tool tip ini akan hilang jika
mouse berpindah tempat.
Untuk membuatnya, kita akan membuat satu kelas yang khusus menangani event, yang
kita namakan ToolTipListener. Kelas ini mengimplementasikan MouseMoveListener
dan MouseTrackListener (lihat bagian penanganan mouse untuk apa apa listener
digunakan). MouseTrackListener digunakan untuk menangkap event hover yang akan
membuat shell baru, kemudian menambahkan label untuk menampilkan tulisan ToolTip.
Shell baru ini kita buat dengan bit gaya SWT.TOOL. Berikut ini adalah listing dari kelas
ToolTipListener.
package com.lyracc.tooltipdemo;
import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.graphics.*;
// Buat tool tip baru sebagai shell, dengan bit gaya SWT.ON_TOP
tip = new Shell(shellUtama, SWT.ON_TOP);
tip.setLayout(new FillLayout());
// Tambahkan label
label = new Label(tip, SWT.NONE);
label.setText("ToolTip");
@Override
public void mouseEnter(MouseEvent e) {
}
@Override
public void mouseExit(MouseEvent e) {
// Jika mouse keluar dari shell, tutup tool tip
tutupToolTip();
}
@Override
public void mouseHover(MouseEvent e) {
// Jika tip sudah ada, keluar dan tidak lakukan apa-apa
if (tip != null) return;
@Override
public void mouseMove(MouseEvent e) {
// Jika mouse pindah lokasi, tutup tool tip
tutupToolTip();
}
}
Kelas utamanya (yang memiliki metode main()) akan menginstansiasi kelas
ToolTipListener dan menambahkan listener ke shell utamanya. Berikut ini adalah listing
kelasnya.
package com.lyracc.tooltipdemo;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
/**
* @param args
*/
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
shell.pack();
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
display.dispose();
}
}
Metode pada Shell
Mengeset judul jendela dan icon jendela
Metode Penjelasan
setImage(Image Mengeset gambar icon pada jendela, atau gambar icon yang
gambar) akan digunakan ketika jendela diminimasi
Metode Penjelasan
Metode Penjelasan
Metode Penjelasan
Pada contoh berikut ini kita akan membuat shell berbentuk oval dengan latar belakang
merah. Kita bisa memindahkan shell tersebut dengan menggesernya ke posisi lain (tekan
mouse di dalam oval, kemudian gerakkan mouse). Karena tidak ada tombol (X) di sudut
kanan atas jendela, kita tambahkan event yang akan menutup jendela ketika sembarang
tombol keyboard ditekan.
Program lengkapnya dapat diunduh di sini dan dapat Anda import ke dalam Eclipse.
Berikut program lengkapnya.
package com.lyracc.shellberbentuklingkaran;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
/**
* @param args
*/
public static void main(String[] args) {
Display display = new Display();
// Membuat shell baru tanpa garis tepi
Shell shell = new Shell(display, SWT.NO_TRIM);
int offsetX = 0;
int offsetY = 0;
switch (e.type) {
// Jika mouse ditekan ambil koordinat saat itu
case SWT.MouseDown:
if (e.button == 1) {
offsetX = e.x;
offsetY = e.y;
}
break;
case SWT.MouseMove:
// Jika mouse pindah sambil ditekan dengan tombol
kiri
if ((e.stateMask & SWT.BUTTON1) != 0) {
case SWT.KeyDown :
// Jika tombol keyboard ditekan tutup jendela
shellUtama.dispose();
break;
}
}
};
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
// Jangan lupa untuk mendispose region juga
region.dispose();
display.dispose();
}
}
Event pada Shell
Event berikut hanya terdapat pada widget Shell.
Kelas Interface/Kelas
Jenis event
Event Listener
Metode (listener bertipe) (event tanpa Penjelasan
(event (listener
tipe)
bertipe) bertipe)
Jendela
shellActivated(ShellEvent) SWT.Activate
diaktifkan
Jendela Shell
ditutup
shellClosed(ShellEvent) SWT.Close
(dengan
ShellListener tombol X)
ShellEvent (dan Jendela
shellDeactivated(ShellEvent) SWT.Deactivate
ShellAdapter) dinonaktifkan
Jendela Shell
shellIconified(ShellEvent) SWT.Iconify
diminimasi
Jendela Shell
shellDeiconified(ShellEvent) SWT.Deiconify dibuka
(restore)
Berikut ini adalah contoh program untuk memberikan kotak dialog yang menanyakan
apakah kotak dialog akan ditutup, kemudian menampilkan tombol "Yes/No/Cancel".
Program ini menangkap event SWT.Close yang dilempar ketika program hendak ditutup.
Jika jawaban user "Cancel", maka variabel doit di dalam event akan diisi dengan false,
yang berarti membatalkan event menutup program.
Berikut ini adalah listing program lengkap, yang juga dapat diunduh di sini untuk diimport
ke dalam Eclipse.
package com.lyracc.tutupshell;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;
/**
* @param args
*/
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
display.dispose();
}
}
Kotak Dialog
Kelas Dialog adalah kelas super abstrak dari beberapa jenis kotak dialog yang merupakan
kotak dialog bawaan dari sistem operasi. Kotak dialog adalah shell tingkat tinggi yang
digunakan oleh sistem operasi untuk memasukkan suatu nilai atau memberi tahu user
tentang suatu suatu pesan atau kesalahan. Misalnya, kotak dialog file akan meminta user
memilih suatu file yang kemudian digunakan untuk programmer untuk membuka dan
membaca isi file tersebut.
Kotak dialog sangat berbeda dari satu platform ke platofrm yang lain. Pada beberapa
platform, programmer bisa mengakses widget-widget di dalamnya, sedangkan pada
platform lain hal ini tidak dimungkinkan. Dengan alasan ini, SWT dianggap sebagai kotak
hitam. Lebih penting lagi, kotak dialog bukan widget. Dialog bukan kelas turunan dari
kelas Widget dan tidak memiliki metode yang dimiliki oleh widget-widget lain.
Ada beberapa jenis kotak dialog yang disediakan SWT :
Jenis kotak
Penjelasan
dialog
Ada 3 jenis modalitas yang didukung oleh suatu kotak dialog, yang bisa diset sebagai bit
gaya ketika membuat kotak dialog :
SWT.PRIMARY_MODAL, suatu kotak dialog memblok input induknya, default untuk
kotak dialog
SWT.APPLICATION_MODAL, suatu kotak dialog memblok input jendela-jendela lain
pada aplikasi yang sama
SWT.SYSTEM_MODAL, suatu kotak dialog memblok seluruh jendela bahkan jendela
aplikasi lain.
Untuk membuat kotak dialog, sama seperti pada jendela, yaitu dengan menggunakan
konstruktor
new Dialog(Shell induk);
atau
new Dialog(Shell induk, int gaya);
Gaya yang bisa digunakan berbeda-beda tergantung dari jenis kotak dialognya.
Beberapa metode umum yang tersedia untuk semua jenis kotak dialog adalah sebagai
berikut
Metode Penjelasan
Gaya Gambar
SWT.ICON_ERROR
SWT.ICON_INFORMATION
SWT.ICON_QUESTION
SWT.ICON_WARNING
SWT.ICON_WORKING
Bit gaya yang merupakan pertanyaan yang diberikan kepada user. Ada beberapa
kombinasi yang bisa digunakan, seperti pada tabel berikut, beserta contohnya pada
Windows Vista.
Gaya Tampilan
SWT.OK
SWT.OK | SWT.CANCEL
SWT.YES | SWT.NO
SWT.RETRY | SWT.CANCEL
Metode Penjelasan
Untuk melihat contoh penggunaan MessageBox lihat contoh sebelumnya pada bahasan
tentang event pada shell.
Kotak Dialog FileDialog
Kelas FileDialog melambangkan kotak dialog untuk memilih file. Pada dasarnya ada dua
jenis kotak dialog pemilihan file, yaitu untuk menyimpan dan membaca file, seperti
diimplementasikan pada kebanyakan program File -> Open dan File -> Save. Keduanya
hampir sama dalam tampilan, kecuali beberapa detail lain yang berbeda, misalnya tombol
"Open" pada kotak dialog untuk membaca file, dan "Save" untuk kotak dialog untuk
menyimpan file.
Kotak dialog pemilihan file bisa diset untuk memilih hanya beberapa file saja dengan
bentuk tertentu, misalnya hanya file berekstensi *.html saja yang ditampilkan dalam daftar
file. Ini disebut file filter. Kotak dialog pemiihan file juga bisa diset untuk memilih satu
file saja atau beberapa file sekaligus.
Mirip dengan kotak dialog lain, kotak dialog file tidak akan ditutup jika user sebelum user
menekan tombol "Open" atau "Save" atau "Cancel".
Berikut ini adalah beberapa bit gaya yang bisa diberikan ketika kotak dialog pemilihan file
dibuat dengan konstruktornya.
Bit gaya Penjelasan
Membuat kotak dialog pemilihan file yang hanya bisa memilih satu file saja
SWT.SINGLE
(perilaku standar jika tidak disebutkan)
Membuat kotak dialog pemilihan file yang bisa memilih beberapa file
SWT.MULTI
sekaligus
Metode Penjelasan
Berikut ini (dapat diunduh di sini) adalah contoh untuk menampilkan kotak dialog untuk
menyimpan suatu file. Ketika Anda menjalankan program ini, shell kosong akan
ditampilkan. Jika Anda menekan tombol mouse di dalam shell kosong ini, kotak dialog
untuk menyimpan file akan ditampilkan. Hanya ada dua ekstensi yang diperbolehkan, yaitu
"*.java" dan "*.class". Jika nama file sudah ada, program akan menanyakan apakah Anda
akan menimpa isi file. Jika tidak, maka kotak dialog kembali ditampilkan. Jika ya, kotak
dialog akan ditutup, dan hasil pilihan Anda akan ditampilkan pada MessageBox yang
berisi nama file pilihan Anda.
package com.lyracc.kotakdialogfile;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;
/**
* @param args
*/
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
display.dispose();
}
}
Kotak Dialog DirectoryDialog
Kelas DirectoryDialog melambangkan kotak dialog untuk memilih direktori. Kelas ini
lebih sederhana dibandingkan dengan FileDialog. Kelas ini tidak memiliki bit gaya
apapun.
Untuk membuat kotak dialog untuk direktori, cara yang sama bisa dilakukan seperti pada
FileDialog. Metode-metode yang dimiliki juga mirip dengan FileDialog dengan lebih
sedikit variasi, yaitu :
Metode Penjelasan
Kotak dialog ini tidak memiliki bit gaya atau event. Keluarannya bertipe RGB yang
merupakan representasi dari gabungan warna Red Green Blue yang tidak bergantung pada
suatu device. Kelas RGB tersedia dalam paket org.eclipse.swt.graphics
Untuk menggunakannya sama seperti pada kotak-kotak dialog lainnya.
Berikut ini adalah beberapa metode yang terdapat pada kelas ColorDialog
Metode Penjelasan
Metode Penjelasan
Berikut ini adalah contoh menggunakan FontDialog. Shell yang berisi "contoh teks" akan
berubah warna dan penampilannya apabila kita mengubah jenis hurufnya dengan menekan
tombol "Ubah huruf".
Listing program lengkapnya adalah sebagai berikut, atau bisa juga diunduh di sini untuk
diimport ke dalam Eclipse.
package com.lyracc.kotakdialogfont;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
// Referensi ke display
Display displayUtama = e.display;
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
display.dispose();
}
}
Dasar-dasar Kontrol dan Label
Kelas Control merepresentasikan elemen user interface yang berada di dalam suatu
jendela. Kelas ini merupakan kelas abstrak yang tidak bisa digunakan secara langsung.
Sedangkan Label, Button, Text, dan kontrol-kontrol lain yang biasa kita temui pada suatu
jendela merupakan kelas turunan dari kelas Control yang bisa kita gunakan. Kelas Shell
pun adalah salah satu kelas turunan tidak langsung dari kelas Control, sehingga beberapa
metode dan event pada kelas Control tersedia pada kelas Shell.
Kelas Control memiliki beberapa bit gaya, yaitu
Bit gaya Keterangan
Membuat label yang diberi garis tepi (border). Bit gaya SWT.BORDER
SWT.BORDER
dimiliki oleh semua Widget, bukan hanya Label
Teks akan ditampilkan dari kanan ke kiri seperti bahasa Arab dan
SWT.RIGHT_TO_LEFT
Hebrew
Untuk lebih menjelaskan tentang kelas abstrak Control dan berbagai fiturnya mari kita
lihat kelas turunan yang paling sederhana, yaitu Label.
Label
Kelas Label adalah teks atau gambar yang tidak bisa dipilih oleh user dan tidak
menghasilkan event apa-apa. Label biasanya digunakan untuk menampilkan teks pasif,
misalnya judul suatu input teks atau keterangan lainnya.
Kelas Label memiliki beberapa bit gaya, yaitu
SWT.LEFT
Hanya salah satu dari gaya ini yang boleh digunakan, hanya berguna
SWT.RIGHT
untuk teks atau gambar
SWT.CENTER
Mari kita buat contoh sederhana, yaitu menggambar satu jendela (shell) dan satu label yang
bertuliskan "Selamat Datang!". Kita bisa buat programnya seperti ini.
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
// Inisialisasi label
label1 = new Label(shell, SWT.LEFT);
label1.setText("Selamat Datang!");
}
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
HelloSWT2 hello = new HelloSWT2();
hello.run();
}
}
Apa yang Anda dapatkan? Anda terkejut karena label tidak ditampilkan? Apa yang kurang
dari program kita di atas?
Ketika suatu kontrol pertama kali dibuat (label adalah kelas turunan dari kontrol, jadi label
juga berperilaku seperti kontrol), ukuran dan lokasinya adalah 0. Tugas kita sebagai
programmer untuk menentukan ukuran kontrol dan lokasi di mana ia akan diletakkan.
Bound (Tepi)
Suatu persegi panjang yang menjelaskan lokasi dan ukuran suatu kontrol pada induknya
disebut tepi suatu kontrol. Tepi suatu kontrol memiliki satuan piksel. Sedangkan unitnya
relatif terhadap induknya. Untuk shell utama, koordinatnya mengikuti koordinat layar.
Untuk kontrol yang berada di dalam shell, koordinat (0,0) adalah koordinat pojok kiri atas
dari shell. Jika suatu widget berada dalam widget lain, maka koordinat (0,0)-nya adalah
koordinat pojok kiri atas dari widget induknya.
Untuk mengeset dan mengambil lokasi dan ukuran suatu widget, kita bisa gunakan
beberapa metode berikut ini :
Metode Penjelasan
setBounds(int x, int y, int
Mengeset lokasi dan ukuran suatu kontrol
lebar, int tinggi)
Perhatikan bahwa beberapa metode menerima dan mengembalikan Rectangle dan Point
sebagai parameter atau tipe keluarannya. Kelas Rectangle dan Point adalah kelas generik
yang dimiliki SWT untuk melambangkan persegi panjang dan suatu titik. Kelas-kelas ini
didefinisikan dalam paket org.eclipse.swt.graphics.
Kelas Rectangle memiliki variabel instansi x, y, height, dan width yang semuanya
bertipe int. Sedangkan kelas Point memiliki variabel instansi x dan y yang bertipe int.
Mari kita tengok kembali program di atas. Kita akan ubah sedikit yaitu memberi ukuran
dan lokasi label sehingga bisa ditampilkan dengan benar.
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
// Inisialisasi label
label1 = new Label(shell, SWT.LEFT);
label1.setText("Selamat Datang!");
label1.setBounds(10, 10, 150, 25); // set lokasi & ukuran kontrol
agar bisa ditampilkan
}
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
HelloSWT2 hello = new HelloSWT2();
hello.run();
}
}
Berikut ini adalah hasilnya.
Metode Penjelasan
computeSize(int wHint,
Menghitung ukuran kontrol yang dibutuhkan, dimana wHint
int hHint, adalah lebar kira-kira, dan hHint adalah tinggi kira-kira. Jika
boolean berubah diisi true maka ukuran kontrol akan dihitung
berubah)
kembali, bukan diambil dari cache.
Metode pack() digunakan untuk mengeset suatu kontrol secar otomatis berdasarkan
ukuran minimum yang dibutuhkan oleh kontrol tersebut. Pada contoh label di atas, kita
bisa mengganti instruksi setBounds() dengan rangkaian perintah pack() dan
setLocation() untuk mengeset ukuran dan lokasinya secara terpisah. Misalnya,
...
// Inisialisasi label
label1 = new Label(shell, SWT.LEFT);
label1.setText("Selamat Datang!");
label1.pack(); // set ukuran kontrol otomatis
label1.setLocation(10, 10); // set lokasi kontrol
...
Metode computeSize() digunakan untuk menghitung ukuran yang dibutuhkan oleh suatu
kontrol sehingga kontrol tersebut bisa digambar dengan utuh. Dengan kata lain kita ingin
menjawab pertanyaan ini : berapa tinggi yang dibutuhkan jika lebarnya adalah x, atau
berapa lebar yang dibutuhkan jika tingginya adalah y, atau berapa lebar dan tinggi yang
dibutuhkan secara umum.
Untuk menjawab pertanyaan tersebut, kita membutuhkan dua variabel, yaitu salah satu
nilai "saran", yaitu nilai yang kita sudah ketahui, dan nilai SWT.DEFAULT untuk menandai
variabel yang ingin kita cari. Misalnya
Untuk menghitung tinggi widget jika lebarnya kira-kira 10 piksel, kita bisa
menggunakan label1.computeSize(10, SWT.DEFAULT)
Untuk menghitung lebar widget jika tingginya kira-kira 10 piksel, kita bisa
menggunakan label1.computeSize(SWT.DEFAULT, 10)
Untuk menghitung lebar dan tinggi widget yang dibutuhkan secara bersamaan, kita
bisa menggunakan label1.computeSize(SWT.DEFAULT,SWT.DEFAULT)
Event Ketika Kontrol Berpindah Tempat
Ketika suatu kontrol berpindah tempat, misalnya karena digeser atau ukurannya diubah
oleh user, event tertentu akan dilepaskan. Kita bisa memasang listener pada kontrol
tersebut untuk menangani event yang terjadi. Berikut ini adalah event yang terkait dengan
perubahan lokasi dan ukuran suatu kontrol.
Interface/Kelas
Kelas Event Jenis event
Listener
(event Metode (listener bertipe) (event tanpa Penjelasan
(listener
bertipe) tipe)
bertipe)
Posisi suatu
controlMoved(ControlEvent) SWT.Move kontrol
ControlListener
berubah
ControlEvent (dan
Ukuran
ControlAdapter)
controlResized(ControlEvent) SWT.Resize kontrol
berubah
Contoh berikut membuat dua buah shell, yaitu shell utama dan shell kedua. Shell kedua
adalah shell kosong tanpa tepi mirip seperti tool tip. Jika shell utama berubah posisi atau
ukuran, shell kedua akan tergeser secara otomatis sehingga jaraknya tetap sama.
import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
@Override
public void controlMoved(ControlEvent e) {
pindah();
}
@Override
public void controlResized(ControlEvent e) {
pindah();
}
});
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
ShellPindah shellPindah = new ShellPindah();
shellPindah.run();
}
}
Mengeset Teks pada Label
Suatu label bisa diisi teks atau gambar, tapi tidak keduanya secara bersamaan. Berikut ini
adalah beberapa metode yang digunakan untuk mengeset dan mengambil teks dan gambar
pada label.
Metode Penjelasan
Berikut ini adalah contoh mengeset teks dan gambar pada suatu label
Label label1 = new Label(shell, SWT.LEFT);
label1.setText("Selamat Datang!");
SWT.BORDER
SWT.BORDER adalah bit gaya yang tersedia pada Control. Semua kontrol, termasuk Label
bisa menggunakan bit gaya ini. Bit gaya ini memberikan garis tepi di sekitar kontrol.
Misalnya, dari contoh pada bagian sebelumnya, kita tambahkan SWT.BORDER sebagai bit
gaya pada label1, maka keluarannya ada seperti gambar berikut ini.
...
// Inisialisasi label
label1 = new Label(shell, SWT.LEFT | SWT.BORDER);
label1.setText("Selamat Datang!");
label1.setBounds(10, 10, 150, 25); // set lokasi & ukuran kontrol
agar bisa ditampilkan
...
Kerapatan Teks
Kita juga bisa mengatur kerapatan (alignment) suatu teks atau gambar. Selain
menggunakan bit gaya SWT.LEFT, SWT.RIGHT, atau SWT.CENTER pada konstruktor. Atau
kita juga bisa menggunakan metode instansi pada kelas tombol berikut :
Metode Penjelasan
Misalnya,
Label labelTeks = new Label(shell, SWT.NONE); // Tanpa bit gaya
labelTeks.setText("Tombolku");
labelTeks.setAlignment(SWT.LEFT);
Ilustrasi berikut menunjukkan label dengan tiga kerapatan berbeda, menggunakan bit gaya
SWT.LEFT, SWT.RIGHT, dan SWT.CENTER.
Memangkas Teks
Jika kita ingin agar label yang kita tampilkan hanya terbatas pada ukuran tertentu saja, bit
gaya SWT.WRAP bisa digunakan untuk ini. SWT.WRAP memberitahukan kontrol untuk
memotong teks sehingga tidak ada teks yang terpotong dengan paksa. Berikut ini adalah
contoh perbedaan label yang menggunakan SWT.WRAP (label atas) dan yang tidak (label
bawah).
// Inisialisasi label
label1 = new Label(shell, SWT.LEFT | SWT.BORDER | SWT.WRAP);
label1.setText("Selamat Datang di sini");
label1.setLocation(10, 10); // set lokasi kontrol
// Inisialisasi label
label2 = new Label(shell, SWT.LEFT | SWT.BORDER);
label2.setText("Selamat Datang di sini");
label2.setLocation(10, 40); // set lokasi kontrol
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
HelloSWT3 hello = new HelloSWT3();
hello.run();
}
}
Menggunakan Label sebagai Garis Pemisah
Kelas Label bisa digunakan untuk membuat garis pemisah, baik horizontal maupun
vertikal, yaitu dengan menggunakan bit gaya SWT.SEPARATOR yang digabungkan dengan
salah satu dari SWT.HORIZONTAL atau SWT.VERTICAL.
Garis pemisah biasanya digunakan untuk memisahkan item pada menu, atau memisahkan
pertanyaan dan tombol pada kotak dialog. Garis pemisah juga memiliki bit gaya lain, yaitu
SWT.SHADOW_IN, SWT.SHADOW_OUT dan SWT.SHADOW_NONE untuk menggambar beberapa
jenis garis pemisah, akan tetapi bit gaya ini tidak tersedia pada semua platform. Untuk
platform yang tidak tersedia seperti Linux, bit gaya ini tidak berpengaruh apa-apa.
Contoh kode berikut menggambar kedua jenis garis pemisah.
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
// Inisialisasi label
label1 = new Label(shell, SWT.CENTER);
label1.setText("Vertikal");
label1.setBounds(10, 10, 100, 15);
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
HelloSWT4 hello = new HelloSWT4();
hello.run();
}
}
Mengeset Warna
Kelas Control memiliki metode untuk mengeset latar belakang dan latar depan suatu
kontrol. Pada Label, latar belakang Label adalah ruangan di sekitar garis tepi atau wilayah
yang ditentukan dengan metode setBounds(), sedangkan latar depan Label adalah warna
teksnya.
Karena kelas Control adalah kelas super dari kontrol-kontrol lain, bukan hanya Label,
tetapi juga Button, Text dan Shell misalnya, maka metode-metode ini juga bisa
digunakan oleh kontrol-kontrol lain yang merupakan kelas turunan dari Control. Latar
belakang dan latar depan tiap-tiap kontrol berbeda-beda tergantung dari kontrolnya.
Berikut ini adalah beberapa metode pada kelas Control untuk mengatur warna.
Metode Penjelasan
setForeground(Color
Mengeset warna latar depan suatu kontrol
warna)
setBackground(Color
Mengeset warna latar belakang suatu kontrol
warna)
Perhatikan bahwa metode-metode di atas bekerja dengan menggunakan input dan output
warna bertipe Color. Kelas Color terdapat pada paket org.eclipse.swt.graphics
Kelas Color
Instansi kelas Color digunakan untuk melambangkan warna yang sebenarnya yang telah
diberikan oleh sistem operasi.
Kelas ini bisa dibuat dengan dua cara : mengambil instansi warna dari sistem, dan
membuat objek sendiri dari paduan warna merah, hijau dan biru (RGB). Khusus untuk cara
yang kedua, programmer bertanggung jawab untuk menghapus objek ini dari memori
sistem dengan menggunakan dispose() setelah kita selesai menggunakannya.
Mengambil warna sistem
Cara termudah untuk mengisi kelas Color untuk digunakan dalam mengeset latar belakang
dan latar depan suatu kontrol adalah dengan mengambil warna sistem. Warna sistem terdiri
dari dua jenis, yaitu warna deskriptif dan warna sistem.
Warna deskriptif adalah warna seperti yang kita kenal, seperti merah, hijau, biru. Akan
tetapi setiap platform memiliki daftar warna tersendiri. Oleh karena itu, SWT menyimpan
warna-warna ini dalam konstanta pada kelas SWT, misalnya SWT.COLOR_RED. Warna
ini kita ubah menjadi objek bertipe Color dengan menggunakan perintah
getSystemColor(int konstantaWarna) yang terdapat pada objek berkelas Display,
misalnya
Color warna = display.getSystemColor(SWT.COLOR_RED);
Warna-warna deskriptif yang disimpan pada kelas SWT adalah sebagai berikut :
Warna
SWT.COLOR_WHITE
SWT.COLOR_BLACK
SWT.COLOR_RED
SWT.COLOR_DARK_RED
SWT.COLOR_GREEN
SWT.COLOR_DARK_GREEN
SWT.COLOR_YELLOW
SWT.COLOR_DARK_YELLOW
SWT.COLOR_BLUE
SWT.COLOR_DARK_BLUE
SWT.COLOR_MAGENTA
SWT.COLOR_DARK_MAGENTA
SWT.COLOR_CYAN
SWT.COLOR_DARK_CYAN
SWT.COLOR_GRAY
SWT.COLOR_DARK_GRAY
Warna sistem adalah warna yang dimiliki oleh sistem sebagai warna default suatu kontrol.
Misalnya, warna default latar belakang tombol selalu sama pada Windows, demikian juga
pada Linux. Jika warna ini diubah oleh user melalui Control Panel Windows, misalnya,
maka seluruh aplikasi akan mengikuti warna ini. Keuntungannya, warna aplikasi yang kita
buat akan selalu konsisten dengan warna sistem operasi. Warna sistem ini juga disimpan
sebagai konstanta pada kelas SWT, dan digunakan dengan cara yang sama dengan warna
deskriptif di atas.
Perlu diingat bahwa tidak semua sistem operasi memiliki kesemua warna ini. Untuk
mengetahui warna aktual suatu widget kita bisa menggunakan perintah getBackground()
dan getForeground seperti disebutkan di awal bagian ini.
Warna sistem yang disediakan oleh SWT adalah sebagai berikut
Warna Penjelasan
Warna bayangan
SWT.COLOR_WIDGET_DARK_SHADOW
gelap suatu widget
Warna bayangan
SWT.COLOR_WIDGET_NORMAL_SHADOW
normal suatu widget
Warna bayangan
SWT.COLOR_WIDGET_LIGHT_SHADOW
terang suatu widget
Warna bayangan
SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW highlight suatu
widget
Kelas RGB
Kelas ini merupakan representasi dari warna merah, hijau dan biru (red, green, blue =
RGB), yang merupakan komposisi suatu warna di layar. Kelas RGB berbeda dengan kelas
Color di mana RGB hanya berisi representasi dari warna tersebut, bukan merupakan warna
yang sesungguhnya yang bisa diolah atau diberikan kepada suatu kontrol.
Instasiasi dari kelas RGB bisa dibuat dengan menggunakan konstruktor dengan bentuk
RGB(int merah, int hijau, int biru).
Karena tidak menggunakan memori dari sistem operasi, maka seringkali instansi kelas ini
digunakan dalam transaksi data dengan widget lain, misalnya pada ColorDialog, warna
yang dipilih oleh user akan disimpan dalam bentuk RGB.
Seperti disebutkan sebelumnya, instansi kelas ini tidak bisa digunakan langsung untuk
mengeset latar belakang atau latar depan suatu kontrol. Untuk bisa digunakan, kita harus
meminta alokasi dari sistem operasi, yaitu dalam bentuk objek bertipe Color. Caranya
yaitu dengan menggunakan salah satu konstruktor kelas Color yang berbentuk seperti
Color(Device perangkat, RGB rgb)
Di sini perangkat bisa berupa display atau printer. Setelah kita berhasil meminta warna
dari sistem operasi, kita bisa mengecek apakah warna yang dihasilkan sesuai dengan yang
kita berikan dengan menggunakan metode pada kelas Color, yaitu
getRed() untuk mendapatkan warna merah
getGreen untuk mendapatkan warna hijau
getBlue() untuk mendapatkan warna biru
getRGB() untuk mendapatkan objek bertipe RGB yang merupakan gabungan merah,
hijau dan biru.
Apakah ini berarti bahwa warna yang kita minta belum tentu sama dengan warna yang
diberikan suatu perangkat? Jawabannya benar. Untuk perangkat yang memiliki warna tak
terbatas, warna yang kita set akan sama dengan warna yang kita dapat. Ada beberapa
perangkat yang hanya memiliki sejumlah warna tertentu. Artinya, warna-warna yang
didukung oleh perangkat ini terbatas jumlahnya. Ketika sistem operasi meminta warna dari
perangkat ini, warna yang terdekat yang akan diberikan kembali kepada kita. Ini sangat
berguna untuk menjamin portabilitas SWT di berbagai platform dan perangkat.
Mari kita lihat contoh berikut, di mana label akan kita berikan warna latar depan dan warna
latar belakang yang berbeda dari defaultnya. Program lengkapnya bisa Anda unduh di sini.
package com.lyracc.warnalabel;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.graphics.*;
// Inisialisasi label
label1 = new Label(shell, SWT.CENTER | SWT.BORDER);
label1.setText("Hello dari SWT");
label1.setBounds(10, 10, 200, 25);
// Set warna, cara kedua dengan membuat objek Color dan RGB
warna1 = new Color(display, new RGB(255,255,0)); // menggunakan
objek RGB
warna2 = new Color(display, 0, 0, 255); // konstruktor Color
jenis lain
label2.setBackground(warna1);
label2.setForeground(warna2);
warna1.dispose(); // objek ini harus dihapus manual dari memori
warna2.dispose(); // objek ini harus dihapus manual dari memori
}
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
WarnaLabel jendela = new WarnaLabel();
jendela.run();
}
}
Mengeset Huruf
Kelas Control memiliki metode untuk mengeset huruf kontrol. Berikut ini adalah
beberapa metode pada kelas Control untuk mengatur warna.
Metode Penjelasan
setFont(Font huruf) Mengeset huruf suatu kontrol
Perhatikan bahwa metode-metode di atas bekerja dengan menggunakan input dan output
huruf bertipe Font. Kelas Font terdapat pada paket org.eclipse.swt.graphics.
Seperti pada warna dengan Color dan RGB, huruf memiliki dua kelas sejenis, yaitu Font
dan FontData. Kelas Font, seperti Color adalah huruf yang sudah dialokasikan oleh
sistem operasi, sedangkan FontData seperti RGB adalah representasi huruf yang belum
dialokasi oleh sistem operasi.
Ingat bahwa jenis huruf sangat dipengaruhi oleh sistem operasi. Jenis huruf tertentu hanya
ada pada sistem operasi tertentu, sehingga sangat sulit untuk membuat aplikasi yang
bergantung pada huruf tertentu. Kita bisa juga mengontrol penggunaan huruf hingga detail,
misalnya jeda antar huruf, tinggi huruf dan lainnya, tapi dalam prakteknya, teknik ini
jarang digunakan untuk memodifikasi suatu kontrol. Kecuali jika Anda ingin membuat
widget sendiri, atau ingin menggambar langsung pada kanvas. Kita tidak akan membahas
hingga ke level detail seperti ini.
Kelas Font
Instansi kelas Font digunakan untuk melambangkan huruf yang sebenarnya yang telah
diberikan oleh sistem operasi.
Kelas ini bisa hanya bisa dibuat dengan membuat objek sendiri dengan menggunakan
konstruktornya. Programmer bertanggung jawab untuk menghapus objek ini dari memori
sistem dengan menggunakan dispose() setelah kita selesai menggunakannya. Berikut ini
adalah konstruktor kelas Font yang tersedia :
Konstruktor Penjelasan
Font(Device device, Membuat huruf pada suatu perangkat (baik display atau
FontData fd) printer) dengan input FontData
Font(Device device, Membuat huruf pada suatu perangkat (baik display atau
String name, int printer) dengan input nama, tinggim dan gayanya. Gaya
height, int style) adalah salah satu atau gabungan dari SWT.NORMAL, SWT.BOLD,
dan SWT.ITALIC
Konstruktor Penjelasan
FontData(String nama, Membuat instansi FontData dengan input nama, tinggi huruf
int tinggi, int gaya) dan gaya. Gaya huruf bisa merupakan salah satu atua
kombinasi dari SWT.NORMAL, SWT.BOLD dan SWT.ITALIC
Untuk memanipulasi FontData berikut ini adalah beberapa metode yang bisa digunakan
Metode Penjelasan
Mari kita simak contoh berikut ini. Kita akan membuat label dengan miring dan tebal dari
suatu kontrol. Ukurannya juga akan kita ubah menjadi 2 kali lipatnya. Algoritmanya bisa
dituangkan sebagai berikut :
Ambil Font dari label tersebut
Ambil FontData dari Font
Modifikasi setiap FontData yang dikembalikan
Buat Font baru
Gunakan setFont() untuk mengeset huruf ke label tersebut
Algoritma tersebut dapat kita tuangkan seperti
...
Font huruf = label1.getFont();
FontData[] dataHuruf = huruf.getFontData();
for (int i = 0; i < dataHuruf.length; i++) {
dataHuruf[i].setStyle(SWT.BOLD | SWT.ITALIC);
dataHuruf[i].setHeight(dataHuruf[i].getHeight() * 2);
}
Font hurufBaru = new Font(display, dataHuruf);
label1.setFont(hurufBaru);
hurufBaru.dispose();
...
Kita modifikasi sedikit program kita sebelumnya untuk menampilkan label dengan huruf
dan warna berbeda. Program lengkapnya bisa diunduh di sini.
package com.lyracc.hurufdanwarnalabel;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.graphics.*;
// Inisialisasi label
label1 = new Label(shell, SWT.CENTER | SWT.BORDER);
label1.setText("Hello dari SWT");
label1.setBounds(10, 10, 400, 50);
// Modifikasi huruf
Font huruf = label1.getFont();
FontData[] dataHuruf = huruf.getFontData();
for (int i = 0; i < dataHuruf.length; i++) {
dataHuruf[i].setStyle(SWT.BOLD | SWT.ITALIC);
dataHuruf[i].setHeight(dataHuruf[i].getHeight() * 2);
}
Font hurufBaru = new Font(display, dataHuruf);
label1.setFont(hurufBaru);
hurufBaru.dispose();
// Set warna, cara kedua dengan membuat objek Color dan RGB
warna1 = new Color(display, new RGB(255,255,0)); // menggunakan
objek RGB
warna2 = new Color(display, 0, 0, 255); // konstruktor Color
jenis lain
label2.setBackground(warna1);
label2.setForeground(warna2);
warna1.dispose(); // objek ini harus dihapus manual dari memori
warna2.dispose(); // objek ini harus dihapus manual dari memori
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
HurufDanWarnaLabel jendela = new HurufDanWarnaLabel();
jendela.run();
}
}
Menampilkan gambar
Seperti halnya warna dan huruf pada SWT, ada dua kelas yang digunakan untuk
menampilkan gambar pada SWT : ImageData untuk melambangkan gambar dalam format
yang tidak tergantung pada suatu perangkat, dan Image yang merupakan representasi
gambar pada suatu perangkat.
Suatu kontrol memiliki beberapa metode untuk menampilkan gambar di latar depan
ataupun latar belakangnya. Tentunya, ini tergantung dari kontrol tersebut bagaimana hal ini
akan diimplementasikan. Pada Label misalnya, latar belakang bisa diset berbentuk gambar
menggantikan warna latar belakangnya. Atau bisa juga label diisi dengan gambar sebagai
pengganti teksnya.
Berikut ini adalah beberapa metode untuk mengeset suatu gambar pada kontrol.
Metode Penjelasan
Seperti warna dan huruf suatu kontrol yang memiliki pasangan Color dan RGB atau Font
dan FontData, gambar juga memiliki pasangan Image dan ImageData. ImageData bisa
juga dipergunakan untuk melakukan pengolahan gambar sederhana, misalnya mengganti
kontras melakukan clipping, dan sebagainya, akan tetapi kita tidak akan membahas terlalu
detail tentang gambar pada bagian ini. Kita akan bahas lebih lanjut bagaimana untuk
mengeset gambar latar depan dan latar belakang suatu kontrol.
Kelas Image
Kelas Image bisa diinstansiasi ke dalam bentuk objek dengan menggunakan beberapa
konstruktor. Salah satunya yang paling mudah digunakan adalah bentuk konstruktor seperti
Image(Device device, String namaFileGambar), di mana device adalah perangkat
yang akan digunakan untuk menampilkan gambar, bisa display atau printer, dan
namaFileGambar adalah path di mana file gambar yang akan kita tampilkan berada.
Misalnya, pada perintah berikut
Image gambar = new Image(display, "contoh.png");
digunakan untuk mengimport gambar ke dalam objek bernama gambar. Objek ini bisa kita
gunakan untuk mengeset gambar latar belakang atau pengganti teks pada suatu kontrol.
Akan tetapi ingat objek ini juga harus dihapus dari sistem operasi dengan memanggil
gambar.dispose() secara eksplisit setelah tidak lagi digunakan.
Jika ukuran suatu kontrol lebih besar dari pada gambar yang akan kita tampilkan di latar
belakangnya, maka gambar tersebut akan diulang-ulang untuk memenuhi tampilan suatu
kontrol. Kita bisa menggunakan metode instansi getBounds() dari suatu Image untuk
mendapatkan ukuran gambar yang diperlukan. Hasilnya bisa kita gunakan untuk mengeset
kontrol yang akan kita gunakan untuk menampilkan gambar.
Contoh berikut menampilkan dua jenis label, di mana yang pertama menampilkan gambar
sebagai warna latar belakangnya, dan yang satu menampilkan gambar sebagai pengganti
teks. Program lengkapnya dapat diunduh di sini.
package com.lyracc.gambarlabel;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.graphics.*;
public class GambarLabel {
// Inisialisasi label
label1 = new Label(shell, SWT.CENTER | SWT.BORDER);
label1.setText("Hello dari SWT");
label1.setBounds(10, 10, 200, 50);
label1.setForeground(display.getSystemColor(SWT.COLOR_RED));
// Inisialisasi gambar
Image gambar = new Image(display,"contoh.png");
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
GambarLabel jendela = new GambarLabel();
jendela.run();
}
}
Button (Tombol)
Kelas Button melambangkan suatu tombol yang jika ditekan atau dilepaskan akan
melepaskan event tertentu.
Kelas Button memiliki beberapa bit gaya :
Tampilan (pada
Bit gaya Keterangan
Linux)
SWT.ARROW
SWT.CHECK
SWT.PUSH Hanya salah satu dari gaya ini yang boleh digunakan
SWT.RADIO
SWT.TOGGLE
SWT.LEFT Hanya salah satu dari gaya ini yang boleh digunakan
SWT.RIGHT
SWT.CENTER
SWT.UP
SWT.DOWN
Hanya boleh digunakan bersama dengan SWT.ARROW
SWT.LEFT
SWT.RIGHT
Suatu tombol bisa diisi teks atau gambar, tapi tidak keduanya secara bersamaan. Teks yang
diisi pada suatu tombol tidak dapat berisi baris baru '\n' atau '\r'. Berikut ini adalah
beberapa metode yang digunakan untuk mengeset dan mengambil teks dan gambar pada
tombol.
Metode Penjelasan
Berikut ini adalah contoh mengeset teks dan gambar pada suatu tombol :
Button tombolTeks = new Button(shell, SWT.PUSH);
tombolTeks.setText("Tombolku");
Metode Penjelasan
setAlignment(int Mengeset kerapatan suatu teks atau gambar, rapatan hanya bisa
rapatan) berisi SWT.LEFT, SWT.RIGHT, atau SWT.CENTER
Mengambil int yang merupakan rapatan suatu teks atau gambar
getAlignment()
pada tombol.
Misalnya,
Button tombolTeks = new Button(shell, SWT.PUSH);
tombolTeks.setText("Tombolku");
tombolTeks.setAlignment(SWT.LEFT);
Tombol SWT.PUSH
Tombol ini memiliki sifat tombol biasa, yaitu ketika di klik tombol akan tampil seperti
ditekan, dan ketika dilepas maka tombol akan kembali seperti biasa. Tombol ini memiliki
event berikut :
Kelas Interface/Kel
Jenis event
Event as Listener Penjelas
Metode (listener bertipe) (event tanpa
(event (listener an
tipe)
bertipe) bertipe)
Suatu
aksi
pemiliha
n
dilakuka
n pada
widgetDefaultSelected(Selectio SWT.DefaultSele
pilihan
nEvent) ction
default
SelectionListe
(misalny
SelectionEv ner (dan
a ketika
ent SelectionAdap
tombol
ter)
Enter
ditekan)
Suatu
aksi
pemiliha
widgetSelected(SelectionEvent) SWT.Selection
n
dilakuka
n dalam
widget
(misalny
a
memilih
item
pada
drop
down list
Misalnya untuk menambahkan listener untuk event yang terjadi ketika tombol ditekan, kita
bisa menggunakan SWT.Selection, misalnya pada contoh berikut :
Button tombolTeks = new Button(shell, SWT.PUSH);
tombolTeks.setText("Tombolku");
tombolTeks.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
// Perintah-perintah lainnya
}
});
Tombol SWT.ARROW
Tombol ini secara perilaku mirip dengan SWT.PUSH, akan tetapi perbedaannya tombol ini
tidak bisa diisi teks atau gambar. Tombol ini akan menggambar tanda panah, yang arahnya
tergantung dari bit gaya yang diberikan, bisa ke kiri, kanan, atas atau bawah.
Tombol SWT.TOGGLE
Berbeda dengan tombol SWT.PUSH, tombol ini ketika diklik akan berada dalam posisi
tertekan terus walaupun mouse sudah dilepaskan. Untuk mengembalikannya, tombol ini
harus diklik kembali. Karena tombol ini bisa memiliki dua keadaan, yaitu keadaan tertekan
dan keadaan terlepas, ada dua metode yang kita bisa gunakan untuk mengeset keadaan
tombol atau untuk mengambil keadaan tombol saat itu, yaitu
Metode Penjelasan
Tombol SWT.CHECK
Tombol SWT.CHECK tidak berbentuk seperti tombol, akan tetapi perilakunya mirip
dengan SWT.TOGGLE. Secara tampilan, SWT.CHECK ditampilkan seperti tombol kotak
tik, yang apabila diklik kotak tik akan tercontreng. Untuk menghilangkan contrengan, klik
kembali kotak cek tersebut.
Karenanya, mirip seperti SWT.TOGGLE, tombol ini memiliki dua keadaan tetap, yaitu
kondisi tercontreng dan tidak tercontreng. Untuk mengeset keadaan ini, gunakan metode
yang sama dengan SWT.TOGGLE, yaitu setSelection() dan getSelection().
Tombol SWT.RADIO
Tombol ini mirip seperti SWT.CHECK, di mana apabila kita mengklik tombol radio, maka
tombol tersebut berada dalam keadaan terpilih. Perbedaannya, jika beberapa tombol
SWT.RADIO berada dalam satu induk, maka hanya satu tombol radio saja yang bisa aktif.
Artinya jika kita mengklik tombol radio kedua, maka tombol ini akan terpilih dan yang
lainnya akan mati. Untuk mengesetnya, gunakan metode yang sama seperti pada
SWT.CHECK dan SWT.TOGGLE.
Biasanya, hanya satu tombol radio pada suatu kotak dialog atau jendela tidak masuk akal.
Karena hanya ada tombol radio yang bisa aktif, maka satu tombol radio ini akan selalu
aktif, sehingga percuma untuk diletakkan dalam GUI. Jika ada dua tombol radio,
perilakunya persis sama dengan satu tombol SWT.CHECK (jika satu aktif, maka yang
lainnya pasti tidak aktif). Untuk itu tombol radio biasanya digunakan untuk meminta user
memilih satu dari sekian banyak pilihan.
Berikut ini adalah contoh program yang bisa Anda unduh di sini. Program ini akan
menggambar beberapa jenis tombol. Jika salah satu tombol ditekan, maka warna latar
belakang jendela akan berubah-ubah. Warna-warna ini disimpan dalam palette warna
dalam bentuk array. Jika warna terakhir sudah ditampilkan, kemudian tombol ditekan
kembali, maka warna pertama akan ditampilkan kembali.
package com.lyracc.tombolwarna;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;
// Inisialisasi tombol-tombol
Button tombol = new Button(shell,SWT.PUSH);
tombol.setText("Tombolku SWT.PUSH");
// Mengeset warna-warna
warna = new Color[JUMLAHWARNA];
warna[0] = display.getSystemColor(SWT.COLOR_BLACK);
warna[1] = display.getSystemColor(SWT.COLOR_BLUE);
warna[2] = display.getSystemColor(SWT.COLOR_CYAN);
warna[3] = display.getSystemColor(SWT.COLOR_GRAY);
warna[4] = display.getSystemColor(SWT.COLOR_GREEN);
warna[5] = display.getSystemColor(SWT.COLOR_MAGENTA);
warna[6] = display.getSystemColor(SWT.COLOR_RED);
warna[7] = display.getSystemColor(SWT.COLOR_WHITE);
warna[8] = display.getSystemColor(SWT.COLOR_YELLOW);
warna[9] =
display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
}
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
TombolWarna tombolWarna = new TombolWarna();
tombolWarna.run();
}
}
Text
Kelas Text digunakan sehingga user bisa mengetikkan atau mengedit suatu teks. Kelas ini
juga merupakan turunan dari kelas Control sehingga memiliki metode, bit gaya, dan event
seperti pada kelas Control lainnya. Berikut ini adalah metode, bit gaya, dan event khusus
untuk kelas Text
Tampilan (pada
Bit Gaya Keterangan Aturan
Linux)
Variabel Keterangan
Kontrol Text adalah elemen user interface yang digunakan sehingga user bisa
memasukkan atau mengedit string. Kontrol ini bisa dipilih. Pilhannya merupakan tempat di
mana karakter akan dimasukkan. Jika pilihannya lebih dari satu karakter, kontrol Text
akan menggambar karakter yang dipilih berbeda dengan karakter yang tidak dipilih
(misalnya diberi warna latar belakang biru atau hitam, sedangkan teks yang dipilih
berwarna terang). Jika pilihannya tidak ada, maka akan digambar seperti caret yang
bentuknya seperti huruf I. Caret adalah garis vertikal yang muncul di antara karakter. Caret
ini memberi petunjuk kepada user dimana karakter yang diketik akan dimasukkan.
Gambar berikut adalah caret yang diletakkan di akhir teks. Di bawahnya, ketika salah satu
karakter dipilih.
Kontrol Text hanya mendukung teks "biasa". Artinya semua karakter pada kontrol ini
memiliki jenis huruf dan warna yang sama. Jika kita membutuhkan kontrol untuk mengedit
teks yang lebih fleksibel, kita bisa menggunakan org.eclipse.swt.custom.StyledText
yang didesain untuk Eclipse. Perhatikan bahwa StyledText bukan widget alami bawaan
sistem operasi.
Suatu program aplikasi bisa mendeteksi bahwa sesuatu dalam kontrol teks telah berubah,
atau untuk memfilter huruf/angka ketika diketik oleh user. Misalnya, suatu aplikasi
memberi teks peringatan bahwa hanya angka dari 0-9 saja yang bisa dimasukkan. Untuk
itu, aplikasi akan mendengarkan event SWT.Modify dan SWT.Verify yang akan dijelaskan
kemudian.
Ada dua jenis kontrol teks : yang memiliki satu baris teks dan yang memiliki beberapa
baris teks.
Kontrol Text Satu Baris dan Multi Baris
Bit gaya SWT.SINGLE digunakan untuk membuat kontrol teks satu baris. Potongan kode
berikut membuat kontrol teks satu baris yang dilengkapi dengan garis tepi, kemudian
mengisi teksnya dengan "asd".
Text teks1 = new Text(shell, SWT.BORDER | SWT.SINGLE);
teks1.setText("asd");
Kontrol teks banyak baris dibuat dengan menggunakan bit gaya SWT.MULTI. Tidak seperti
kontrol teks satu baris, kontrol teks banyak baris bisa kita lengkapi dengan SWT.H_SCROLL
atau SWT.V_SCROLL pada bit gayanya untuk membuat scroll bar horizontal dan vertikal.
Berikut ini adalah potongan kontrol banyak baris dengan garis tepi dan scroll bar, dengan
teks yang cukup panjang.
teks2 = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.H_SCROLL |
SWT.V_SCROLL);
teks2.setText("Hallo SWT, saya sedang belajar " + Text.DELIMITER +
"membuat program dengan SWT");
teks2.setSize(100, 70);
Operasi String
Kontrol Text memiliki beberapa metode untuk mengisi dan mengambil string dari widget
mirip seperti Label dan Button.
Metode Penjelasan
Sebagian string dalam kontrol teks dapat dapat dipilih dengan menggunakan metode
setSelection(). Metode ini mengambil argumen berupa posisi caret awal dan posisi
caret akhir pilihan. Berikut ini adalah ilustrasi posisi caret di dalam suatu kontrol Text
Metode Penjelasan
Program berikut membuat input teks banyak baris, yang kemudian memilih kata
"Indonesia", seperti pada gambar berikut.
package com.lyracc.pilihteks;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
// Inisialisasi label
teks1 = new Text(shell, SWT.BORDER | SWT.MULTI |
SWT.WRAP | SWT.V_SCROLL | SWT.H_SCROLL);
teks1.setText("Kita bersama-sama di sini, untuk menegaskan " +
"kembali Indonesia tempat kita berdiri.");
teks1.setSelection(52, 61);
}
// Perintah "standar" SWT, harus ada pada setiap aplikasi SWT
public void run() {
shell.open();
while (!shell.isDisposed())
if (!display.readAndDispatch())
display.sleep();
display.dispose();
}
/**
* @param args
*/
public static void main(String[] args) {
// Instansiasi kelas ini, kemudian jalankan run
PilihTeks jendela = new PilihTeks();
jendela.run();
}
}
Menyisipkan dan Menambah Teks
Karakter bisa disisipkan ke dalam kontrol teks dengan metode insert() atau bisa
ditambahkan dengan metode append(). Penyisipan dilakukan pada posisi kursor,
sedangkan penambahan teks selalu dilakukan di akhir teks. Untuk menyisipkan teks pada
posisi tertentu, pindahkan dahulu posisi caret ke posisi yang diinginkan dengan
setSelection(), baru lakukan penyisipan.
Metode Penjelasan
Jika sebagian teks sudah dipilih, maka teks yang dipilih akan dihapus, kemudian teks baru
disisipkan di posisi tersebut.
Operasi Clipboard
Kontrol Text menyediakan fasilitas copy, cut, dan paste ke clipboard. Kontrol ini juga
mendukung tombol shortcut (misalnya Ctrl-C, Ctrl-X, dan Ctrl-V) untuk melakukan
operasi clipboard dari dan ke kontrol ini. Beberapa platform juga menyediakan menu
konteks (yang keluar ketika kita mengklik kanan di kontrol tersebut) yang berisi operasi
clipboard ini.
Metode Penjelasan
Menyisipkan karakter dari dalam clipboard di posisi caret. Jika caret sudah
paste() memilih sebagian teks, teks dipilih akan dihapus dan diganti dengan teks dari
clipboard
Metode Penjelasan
Metode Penjelasan
setEchoCharacter(char
Mengeset karakter echo dengan karakter tertentu
c)
Metode Penjelasan
Jika input teks berupa input teks banyak baris, dan pilihan tidak
ditunjukkan, misalnya karena tersembunyi di bagian bawah teks yang
showSelection() panjang, setelah memanggil showSelection(), maka teks yang
dipilih akan otomatis ditunjukkan, yaitu dengan menggeser scroll bar
secara otomatis hingga pilihan terlihat