BAGAIMANA CARA BERPIKIR SEBAGAI ILMUWAN KOMPUTER Versi Java
Pengarang Allen B. Downey
Penerjemah Wim Permana,
[email protected] (Bab 617) Muhammad Fuad Dwi Rizki,
[email protected] (Bab 5, 18, 19, 20) (Lampiran Foreach Dan Generik) (Jawaban Latihan) Agus Juliardi (Bab 14)
1
Copyright 2003 Allen Downey. Permission is granted to copy, distribute, and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later ver sion published by the Free Software Foundation; with Invariant Sections being “Preface”, with no FrontCover Texts, and with no BackCover Texts. A copy of the license is included in the appendix entitled “GNU Free Documentation License.” The GNU Free Documentation License is available from www.gnu.org or by writing to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 021111307, USA.
Copyright (c)
Terjemahan, Lampiran Foreach dan Generik, dan
Jawaban Latihan © 2006 oleh Wim Permana, Muhammad Fuad Dwi Rizki, dan Agus Juliardi. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNUFree Documentation License".
2
Daftar Isi 1. Alur Program..............................................................................................................14 1.1Apa itu Bahasa Pemrograman?.............................................................................14 1.2 Apa itu program ?.................................................................................................17 1.4 Bahasa Formal dan Natural...................................................................................21 1.5 Program Pertama...................................................................................................24 1.6 Daftar Kata............................................................................................................27 1.7. Latihan.................................................................................................................28 2. Variabel dan Tipenya..................................................................................................31 2.1 Mencetak lebih banyak.........................................................................................31 2.2 Variabel.................................................................................................................33 2.3 Assignment...........................................................................................................34 2.4 Mencetak Variabel................................................................................................36 2.5 Keywords..............................................................................................................38 2.6 Operator................................................................................................................39 2.7 Operasi..................................................................................................................41 2.8 Operator pada String.............................................................................................42 2.9 Komposisi.............................................................................................................43 2.10 Daftar kata...........................................................................................................44 2.11 Latihanlatihan....................................................................................................46 3. Method........................................................................................................................49 3.1 Floatingpoint........................................................................................................49 3.2 Mengkonversi double ke int..................................................................................51 3.3 Method Matematika..............................................................................................52 3.4 Composition..........................................................................................................54
3
3.5 Menambahkan metode baru..................................................................................55 3.6 Class dan method..................................................................................................59 3.7 Program dengan banyak method...........................................................................61 3.8 Parameter dan argumen........................................................................................62 3.9 Stack diagram.......................................................................................................65 3.10 Metode Dengan Banyak Parameter....................................................................66 3.11 Method dan hasilnya...........................................................................................67 3.12 Daftar kata...........................................................................................................68 3.13 Latihan................................................................................................................69 4. Kondisional dan rekursi..............................................................................................74 4.1 Operator modulus.................................................................................................74 4.2 Percabangan..........................................................................................................74 4.3 Eksekusi Alternatif...............................................................................................76 4.4 Percabangan Berantai...........................................................................................77 4.5 Percabangan Bersarang.........................................................................................78 4.6 The return statement.............................................................................................79 4.7 Konversi Tipe........................................................................................................80 4.8 Recursion..............................................................................................................81 4.9 Diagram Stack Untuk Metode Rekursif................................................................84 4.10 Konvensi dan Divine Law...................................................................................85 4.11 Daftar Kata .........................................................................................................86 4.12 Latihan................................................................................................................87 5. Fruitful methods..........................................................................................................93 5.1 Nilai Kembalian....................................................................................................93 5.2 Pengembangan Program.......................................................................................96 5.3 Komposisi...........................................................................................................100 4
5.4 Overloading........................................................................................................101 5.5 Ekspresi Boolean................................................................................................103 5.6 Operator Logika .................................................................................................104 5.7 Metode Boolean..................................................................................................105 5.8 Lagi tentang Rekursi ..........................................................................................106 5.9 Leap of faith........................................................................................................109 5.10 Satu Contoh Lagi..............................................................................................111 5.11 Daftar Kata........................................................................................................112 5.12 Latihan .............................................................................................................113 6. Iterasi.........................................................................................................................121 6.1 Multiple Assignment...........................................................................................121 6.2 Iterasi ..................................................................................................................123 6.3 Pernyataan While ...............................................................................................123 6.4 Tabel...................................................................................................................126 6.5 Tabel Dua Dimensi.............................................................................................129 6.6 Enkapsulasi dan Generalisasi..............................................................................131 6.7 Metode................................................................................................................133 6.8 Enkapsulasi .... sekali lagi!..................................................................................133 6.9 Variabel Lokal....................................................................................................134 6.10 Generalisasi. Sekali lagi dengannya ................................................................135 6.11 Daftar KataKata...............................................................................................139 6.12 Latihan..............................................................................................................140 7. String.........................................................................................................................145 7.1 Memanggil metode dalam objek.........................................................................145 7.2 Length.................................................................................................................147 7.3 Traversal.............................................................................................................148 5
7.4 RunTime errors.................................................................................................149 7.5 Membaca Dokumentasi......................................................................................149 7.6 Metode indexOf..................................................................................................150 7.7 Looping dan Counting........................................................................................153 7.8 Operator Increment dan Decrement....................................................................154 7.9 Strings are Immutable.........................................................................................155 7.10 Strings are Incomparable..................................................................................156 7.11 Daftar KataKata...............................................................................................158 7.12 Latihan..............................................................................................................159 8. Interesting Objects....................................................................................................168 8.1 Apanya yang menarik?.......................................................................................168 8.2 Paketpaket..........................................................................................................168 8.3 Objek Titik (Point)..............................................................................................169 8.4 Variabel Instan...................................................................................................170 8.5 Objek Sebagai Parameter....................................................................................171 8.6 Rectangle.............................................................................................................172 8.7 Objek sebagai tipe kembalian (Return Type).....................................................173 8.8 Objek dapat diubahubah (Mutable)...................................................................173 8.9 Aliasing...............................................................................................................175 8.10 Null...................................................................................................................176 8.11 Garbage Collection...........................................................................................177 8.12 Objek dan Primitif.............................................................................................178 8.13 Daftar KataKata...............................................................................................180 8.14 Latihan..............................................................................................................181 9. Membuat Objek Anda Sendiri..................................................................................188 9.1 Definisidefinisi kelas dan tipetipe objek..........................................................188 6
9.2 Time ...................................................................................................................189 9.4 Konstruktor Lagi.................................................................................................192 9.5 Membuat Sebuah Objek yang Baru....................................................................193 9.6 Mencetak Sebuah Objek.....................................................................................195 9.7 Operasi Pada Objek.............................................................................................196 9.8 Fungsifungsi murni............................................................................................197 9.9 Modifier..............................................................................................................200 9.10 Fillin Methods..................................................................................................202 9.11 Mana yang terbaik?...........................................................................................203 9.12 Pengembangan secara bertahap vs perencanaan...............................................204 9.13 Generalisasi.......................................................................................................206 9.14 Algoritma..........................................................................................................207 9.15 Daftar KataKata...............................................................................................208 9.16 Latihan .............................................................................................................209 10.Array.........................................................................................................................215 10.1 Mengakses Elemen...........................................................................................216 10.2 Menyalin Array.................................................................................................217 10.3 Bentuk Perulangan for .....................................................................................218 10.4 Array dan Objek................................................................................................220 10.5 Panjang Array...................................................................................................220 10.6 AngkaAngka Acak (Random) ........................................................................221 10.7 Array dari Angkaangka Random (Acak) ........................................................223 10.8 Pencacahan........................................................................................................225 10.9 Histogram..........................................................................................................227 10.10 Solusi SinglePass...........................................................................................228 10.11 Daftar KataKata.............................................................................................229 7
10.12 Latihan............................................................................................................230 11.Array berisi Objek....................................................................................................238 11.1 Komposisi.........................................................................................................238 11.2 Objek Card........................................................................................................238 11.3 Metode printCard..............................................................................................241 11.4 Metode sameCard.............................................................................................243 11.5 Metode compareCard........................................................................................245 11.6 Array berisi Card..............................................................................................248 11.7 Metode printDeck.............................................................................................250 11.8 Searching..........................................................................................................251 11.9 Tumpukan dan subtumpukan............................................................................257 11.10 Daftar KataKata.............................................................................................258 11.11 Latihan............................................................................................................259 12.Objek berisi Array....................................................................................................261 12.1 Kelas Deck........................................................................................................261 12.2 Shuffling...........................................................................................................264 12.3 Pengurutan (Sorting).........................................................................................265 12.4 Subtumpukan (Subdecks).................................................................................266 12.5 Pengacakan dan Pembagian Kartu (Shuffling and Dealing).............................268 12.6 Mergesort..........................................................................................................270 12.7 Daftar KataKata...............................................................................................274 12.8 Latihan..............................................................................................................274 13.Pemrograman Berorientasi Objek............................................................................280 13.1 Bahasa Pemrograman dan Tekniktekniknya...................................................280 12.3 Metode Objek dan Metode Kelas......................................................................281 13.3 Objek Terkini....................................................................................................281 8
13.4 Bilangan Kompleks...........................................................................................282 13.5 Fungsifungsi dalam Bilangan Kompleks.........................................................283 13.6 Fungsifungsi lainnya dalam Bilangan Kompleks............................................285 13.7 Modifier............................................................................................................286 13.8 Metode toString................................................................................................287 13.9 Metode equals...................................................................................................288 13.10 Memanggil Satu Metode Objek dari Metode Objek Lain..............................289 13.11 Keganjilan dan Error.......................................................................................290 13.12 Inheritance.......................................................................................................291 13.13 Lingkaran yang bisa digambar .......................................................................292 13.14 Hierarki Kelas.................................................................................................293 13.15 Rancangan Berorientasi Objek.......................................................................294 13.16 Daftar KataKata.............................................................................................295 13.17 Latihan............................................................................................................296 14.Linked List................................................................................................................298 14.1 Acuanacuan dalam Objek................................................................................298 14.2 Kelas Node........................................................................................................298 14.3 List sebagai Collection......................................................................................301 14.4 List dan Rekursi................................................................................................302 14.5 Infinite List........................................................................................................304 14.6 Teorema Ambiguitas Fundamental...................................................................305 14.7 Metode Objek Untuk NodeNode.....................................................................306 14.8 Memodifikasi List.............................................................................................307 14.9 Wrappers dan Helpers.......................................................................................308 14.10 Kelas intList....................................................................................................310 14.11 Invariants.........................................................................................................312 9
14.12 Daftar KataKata.............................................................................................313 14.13 Latihan............................................................................................................313 15.Stacks........................................................................................................................316 15.1 Tipe Data Abstrak ............................................................................................316 15.2 The Stack ADT.................................................................................................317 15.3 Objek Stack dalam Java....................................................................................318 15.4 Kelas Pembungkus............................................................................................320 15.5 Membuat Kelas Pembungkus............................................................................321 15.6 Membuat Kelas Pembungkus Lagi...................................................................321 15.7 Mengeluarkan NilaiNilai.................................................................................322 15.8 MetodeMetode Bermanfaat dalam Kelas Pembungkus...................................323 15.9 Ekspresi Postfix................................................................................................324 15.10 Parsing............................................................................................................325 15.11 Mengimplementasikan ADT...........................................................................327 15.12 Implementasi Array untuk Stack ADT...........................................................327 15.13 Mengubah Ukuran Array................................................................................329 15.14 Daftar KataKata.............................................................................................332 15.15 Latihan............................................................................................................333 16.Antrian dan Antrian Prioritas...................................................................................337 16.1 ADT Antrian.....................................................................................................338 16.2 Veneer...............................................................................................................340 16.3 Linked Queue....................................................................................................343 16.4 Circular Buffer..................................................................................................345 16.5 Priority Queue...................................................................................................351 16.6 Metaclass...........................................................................................................352 16.7 Implementasi Array untuk Antrian Prioritas....................................................353 10
16.8 Klien Antrian Prioritas......................................................................................355 16.9 Kelas Golfer......................................................................................................357 16.10 Daftar KataKata.............................................................................................360 16.11 Latihan............................................................................................................361 17.Pohon (Trees)...........................................................................................................364 17.1 Simpul Pohon....................................................................................................364 17.2 Membuat Pohon................................................................................................365 17.3 Menelusuri Pohon.............................................................................................366 17.4 Pohon Ekspresi.................................................................................................367 17.5 Penelusuran.......................................................................................................368 17.6 Enkapsulasi.......................................................................................................370 17.7 Mendefinisikan metaclass.................................................................................372 17.8 Mengimplementasikan Metaclass.....................................................................373 17.9 Kelas Vector......................................................................................................374 17.10 Kelas Iterator...................................................................................................377 17.11 Daftar KataKata.............................................................................................378 17.12 Latihan............................................................................................................380 18.Heap..........................................................................................................................384 18.1 Implementasi Pohon menggunakan Array........................................................384 8.2 Analisis Kinerja .................................................................................................389 18.3 Analisis Merge Sort..........................................................................................392 18.4 Overhead...........................................................................................................394 18.5 Implementasi PriorityQueue.............................................................................395 18.6 Definisi Heap ...................................................................................................397 18.7 Penghapusan pada heap....................................................................................398 18.8 Penambahan Heap ............................................................................................400 11
18.9 Kinerja Heap ....................................................................................................401 18.10 Heapsort..........................................................................................................403 18.11 Daftar Kata Kata.............................................................................................404 18.12 Latihan............................................................................................................405 19.Maps.........................................................................................................................407 19.1 Array, Vector, Map...........................................................................................407 19.2 Map ADT..........................................................................................................408 19.3 HashMap builtin..............................................................................................408 19.4 Implementasi Dengan Vector...........................................................................414 19.5 Kelas Abstrak List.............................................................................................422 19.6 Implementasi dengan HashMap........................................................................423 19.7 Fungsi Hash......................................................................................................424 19.8 Mengubah Ukuran Hash Map...........................................................................427 19.9 Kinerja dari pengubahan ukuran.......................................................................428 19.10 Daftar Kata......................................................................................................429 19.11 Latihan............................................................................................................430 20.Kode Huffman..........................................................................................................434 20.1 Variabellength codes.......................................................................................434 20.2 Tabel frekuensi ................................................................................................435 20.3 Pohon Huffman.................................................................................................437 20.4 Metode super.....................................................................................................440 20.5 Pemecahan Kode...............................................................................................443 20.6 Pengkodean.......................................................................................................444 20.7 Daftar Kata........................................................................................................445 21.Lampiran Foreach dan Generik................................................................................446 Generik......................................................................................................................446 12
Foreach......................................................................................................................448 Daftar Kata ...............................................................................................................451 22.Jawaban Latihan.......................................................................................................452 Bab 2.........................................................................................................................452 Bab 3.........................................................................................................................453 Bab 4.........................................................................................................................456 Bab 6.........................................................................................................................457 Bab 7.........................................................................................................................465 Bab 8.........................................................................................................................471 Bab 9.........................................................................................................................473 Bab 10.......................................................................................................................478 Bab 11.......................................................................................................................488 Bab 12.......................................................................................................................492 Bab 13.......................................................................................................................504 Bab 14 ......................................................................................................................507 Bab 15.......................................................................................................................520 Bab 16.......................................................................................................................537 Bab 17.......................................................................................................................546 Bab 18.......................................................................................................................556 Bab 19.......................................................................................................................566 BAB 20.....................................................................................................................588 23.History .....................................................................................................................606 24.GNU Free Documentation License..........................................................................606
13
BAB 1 Alur Program Tujuan dari buku ini dan kelas ini adalah untuk mengajarkan anda untuk berfikir seperti ahli komputer. Saya menyukai cara berfikir ahli komputer karena mereka menggabungkan beberapa fitur matematika, engineering, dan ilmu alam. Seperti ahli matematika, ilmu komputer menggunakan bahasa formal untuk mengutarakan ide (secara spesifik komputasi). Seperti seorang engineer, komputer mendesain sesuatu, mengumpulkan komponen kedalam sistem dan mengevaluasi alternatif antar pertukaran data. Seperti seorang ilmuwan, komputer meneliti sifat kompleksitas sistem, hipotesa form dan prediksi tes.
Salah satu dari kemampuan penting dari komputer adalah pemecahan masalah (ProblemSolving). Dengan pengertian kemampuan untuk memformulasikan masalah, berfikir secara kreatif pemecahannya dan mengekspresikan solusi secara jelas dan sayarat. Dengan demikian, proses dari pembelajaran ke program merupakan kesempatan sempurna untuk mempraktekkan kemampuan pemecahan masalah. Oleh karena itu bab ini dinamakan “Alur Program”.
Di level pertama, Anda akan belajar program yang merupakan kemampuan berguna untuk diri sendiri. Di level lainnya Anda akan menggunakan pemrograman yang berarti suatu berakhir. Jika kita pelan maka akhirnya akan menjadi jelas.
1.1Apa itu Bahasa Pemrograman? Bahasa Pemrograman yang akan Anda pelajari adalah Java, yang relatif masih baru
14
(Sun merilis versi pertama pada bulan mei 1995). Java merupakan contoh bahasa tingkat tinggi; bahasa tingkat tinggi lainnya yang mungkin pernah Anda dengar adalah pascal, C, C++, dan FORTRAN.
Seperti yang Anda duga selain Bahasa Tingkat Tinggi ada juga Bahasa Tingkat Rendah. Kadangkadang dikenal juga dengan bahasa mesin atau bahasa asembly. Dengan kata lain komputer hanya dapat mengeksekusi program dalam bahasa tingkat rendah. Sehingga program yang ditulis menggunakan bahasa tingkat harus diterjemahkan terlebih dahulu sebelum dapat di Jalankan. Terjemahan ini akan memakan waktu dan itulah sedikit kekurangan dari bahasa tingkat tinggi.
Akan tetapi keuntungannya sangat banyak, pertama, sangatlah mudah membuat program dengan bahasa tingkat tinggi, mudah dalam arti program dapat dibuat dalam waktu singkat, cepat dan mudah dibaca dan mudah untuk dikoreksi. Kedua, bahasa tingkat tinggi bersifat portable, yang berarti bahwa bahasa tingkat tinggi dapat berjalan diberbagai komputer yang berbeda dengan sedikit ataupun tanpa modifikasi. Bahasa tingkat rendah hanya dapat berjalan pada satu macam komputer saja dan harus ditulis ulang jika dijalankan pada komputer lainnya.
Dengan beberapa keuntungan ini, hampir semua program yang ditulis dengan menggunakan bahasa tingkat tinggi. Bahasa tingkat rendah hanya digunakan untuk aplikasi tertentu saja. Ada dua cara dalam menterjemahkan program; diterjemahkan (intrepeter) atau di compile. Penterjemah (intrepeter) adalah sebuah program yang membaca program tingkat tinggi dan menerjemahkan perintahnya. Sebagai efeknya, program diterjemahkan per baris. Secara berurutan membaca baris dan melaksanakan perintahnya. 15
Compiler adalah sebuah program yang membaca suatu bahasa tingkat tinggi dan menterjemahkan semua sekaligus sebelum mengeksekusi perintah. Biasanya Anda sering membuat program secara terpisah lalu mengeksekusi kodenya diakhir. Dalam kasus ini program tingkat tinggi dinamakan source code dan terjemahan program dinamakan object code atau executable. Sebagai contoh sekiranya Anda menulis program dalam bahasa C. Anda mungkin menggunakan teks editor untuk menulis program (teks editor adalah editor sederhana). Ketika program selesai, Anda mungkin menyimpannya dalam sebuah file dengan nama program.c, dimana “program” adalah nama file. Dan akhiran .c adalah convensi yang menandai kode program dalam bahasa C. Lalu, berdasarkan programming environment yang Anda sukai, Anda bisa meninggalkan text editor dan menjalankan compilernya. Compiler akan membaca source code anda, menterjemahkannya, dan membuat file baru yang bernama program.o yang berisi kode objek atau program.exe yang executable.
16
Pada bahasa Java tidak seperti biasa karena langsung dikompilasi dan di interpreted. Sebagai ganti terjemahan bahasa Java ke bahasa mesin, Java Compiler men generate java byte code. Byte code sangat mudah diinterpretasikan seperti bahasa tingkat tinggi. Seperti itu, dimungkinkan untuk mengkompile program java pada satu mesin, lalu mentransfer byte code ke mesin lain dengan jaringan. lalu menginterpretasikan byte code ke mesin lainnya. Kemampuan ini adalah salah satu keunggulan Java diantara banyak keunggulan lainnya sebagai bahasa tingkat tinggi.
Walaupun proses ini tampak rumit, pada kebanyakan lingkungan pemrograman (kadang disebut development environment), langkah ini langsung secara otomatis. Biasanya Anda hanya menulis program dan menekan tombol compile untuk mengcompile dan untuk menjalankannya. Disisi lain hal ini berguna untuk mengetahui langkahnya dan apa yang terjadi pada layar. Sehingga jika terjadi kesalahan Anda dapat langsung memperbaikinya.
1.2 Apa itu program ? Program adalah urutan dari perintahperintah tertentu untuk melaksanakan proses komputasi. Komputasi dapat berupa beberapa perhitungan matematika, seperti halnya menyelesaikan suatu sistem persamaan untuk menemukan akarakar polinomial. Akan 17
tetapi juga bisa berupa sebuah simbol komputasi seperti searching, dan replacing teks pada sebuah dokumen atau cara lain (mengcompile sebuah program).
Perintah yang kita sebut sebagai suatu pernyataan, kelihatan berbeda pada setiap bahasa pemrograman yang berbeda tetapi terdapat beberapa operasi dasar yang kebanyakan bahasa menyediakan: Input
:memasukkan data dari keyboard, atau sebuah file, atau beberapa perangkat lain.
Output
:menampilkan data dari layar dan membuat sebuah file atau perangkat lain.
Math
:menyelesaikan operasi dasar matematika seperti penambahan, dan perkalian.
Testing
:mencek kondisi tertentu dan mengeksekusi urutan dari pernyataan yang sesuai.
Repetition
:melakukan suatu aksi secara berulang biasanya dengan beberapa variasi.
Semua hal diatas sangatlah baik. Setiap program yang pernah Anda gunakan, tak masalah itu rumit, terdiri dari pernyataanpernyataan yang melaksanakan beberapa operasi. Lalu satu cara untuk menggambarkan pemrograman yaitu sebuah proses dari Task yang besar dan kompleks menjadi subtask yang kecil bahkan sampai pada subtask yang cukup sederhana yang dilakukankan dengan satu operasioperasi dasar.
Sebagai contoh, dalam bahasa inggris, sebuah kalimat harus dimulai dengan huruf besar dan diakhiri dengan periode. Kalimat mengandung syntax error. Begitupun dengan yang satu ini. Untuk kebanyakan pembaca , beberapa syntax yang error bukanlah masalah yang signifikan. Yang mana sama halnya kenapa kita membaca puisi yang mengabaikan apabila ada kesalahan. 18
Compiler tidak memperbolehkan satu kesalahan.Jika ada satu kesalahan pada program Anda , compiler akan menampilkan pesan kesalahan dan lalu keluar. Sehingga Anda tidak bisa menjalankan program Anda. Untuk mempersulit persoalan, ada beberapa peraturan syntax pada Java yang dalam bahasa inggris, dan pesan kesalahan yang anda terima dari compiler sering tidak banyak membantu. Selama beberapa minggu pertama pembuatan program anda, kemungkinan Anda akan menghabiskan waktu untuk mencari kesalahan syntax. Semakin anda mendapatkan pengalaman maka anda semakin sedikit melakukan kesalahan dan semakin cepat menemukan error.
1.3.2 RUNTIME ERROR Tipe kedua dari error adalah Runtime error. Disebut demikian karena error yang terjadi tidak muncul sampai program dijalankan. Pada Java runtime errror terjadi ketika interpereter menjalankan byte code dan terjadi suatu kesalahan.
Untungnya, Java cenderung merupakan bahasa yang aman. Yang berarti runtime errornya jarang, terutama pada program singkat yang sederhana yang akan kita tulis untuk beberapa minggu selanjutnya kedepan.
Kemudian dalam semester nanti, Anda kemungkinan akan melihat lebih jelas tentang run time error. Terutam ketika kita membicarakan tentang objek dan reference. (Bab 8).
Pada Java runtime error dinamakan exceptions, dan pada kebanyakan environment itu muncul pada windows dengan kotak dialog yang mengandung informasi tentang apa yang terjadi dan apa yang program kan ketika itu terjadi. Informasi ini berguna untuk 19
proses Debugging.
1.3.3 Logics error dan semantics Tipe ketiga dari error adalah logical dan semantics errror. Jika ada logical error pada program Anda, maka akan di compile dan berhasil di run dengan pengertian komputer tidak akan mengenerate setiap pesan kesalahan. Akan tetapi hal itu bukan hal yang benar. Ada hal lain yang akan dilakukan yaitu program melakukan apa yang anda suruh.
Masalahnya program yang anda tulis bukanlah program yang anda inginkan. Artinya programnya (semantiknya) salah. Mengindentifikasi logical error sangat sulit. Karena itu mengharuskan anda untuk kembali melihat output program dan mencoba untuk memperbaiki apa yang telah dilakukan.
1.3.4 Experimental debugging Salah satu dari kemampuan penting yang akan Anda dapatkan dikelas ini adalah debugging. Walaupun itu dapat menimbulkan frustasi , debugging adalah salah satu dari hal yang beralasan, menantang dan menarik dalam sebuah pemrograman.
Dengan kata lain debugging seperti pekerjaan detektif. Anda dihadapkan pada petunjuk, dan anda harus menyimpulkan proses dan kejadian yang mengarah pada hasil yang anda lihat.
Debugging juga seperti ilmu percobaan. Sesekali Anda memiliki ide apa yang akan salah, Anda memperbaiki program dan mencobanya lagi. Jika hipotesis Anda benar,
20
lalu Anda dapat memprediksi hasil dari modifikasi, lalu Anda mengambil langkah yang terdekat untuk mengerjakan program. Jika hipotesis Anda salah Anda harus mengulang lagi membuat program yang baru. Sherlock Holmes mengatakan: ”jika Anda telah mengeliminasi ketidakmungkinan, maka apa saja yang tersisa haruslah benar” (dari Conan Doyle's The Sign of Four).
Kebanyakan orang mengatakan, programming dan debugging adalah sama. Bahwa programming merupakan suatu proses yang berangsurangsur dari debugging sebuah program sampai akhirnya menjadi program yang diinginkan. Artinya bahwa Anda memulai program kerja dengan melakukan sesuatu, membuat modifikasi kecil lalu melakukan debugging sehingga Anda selalu memiliki program kerja.
Sebagai contoh, Linux adalah sistem operasi yang terdiri dari beribu baris kode akan tetapi itu awalnya berasal dari program sederhana Linus Tovards yang digunakan untuk mengeksplorasi Intel 3086 Chips. Menurut Larry Grandfield ” Satu dari Linus project adalah sebuah program yang berasal dari cetakan AAAA dan BBBBB. Ini nantinya akan menjadi Linux”.(Panduan Pemakai Linux Beta versi 1).
Di bab selanjutnya Saya akan lebih menyarankan untuk mempelajari tentang debugging dan latihan program lainnya.
1.4 Bahasa Formal dan Natural Bahasa Natural adalah bahasa yang diucapkan oleh orang, seperti bahasa Inggris, Spanyol, dan perancis. Bahasa Natural tidak dibuat oleh manusia (walaupun orang mencoba mengatakan demikian), bahasa natural tercipta secara alami.
21
Bahasa Formal adalah bahasa yang di desain orang untuk aplikasi tertentu. Sebagai contoh, notasi matematika menggunakan bahasa formal yang umumnya baik untuk menjelaskan hubungan antara angka dan simbol. Ahli kimia menggunakan bahasa formal untuk merepresentasikan struktur molekul kimia dan banyak lagi.
Bahasa pemrograman adalah bahasa formal yang di desain untuk mengekpresikan suatu komputasi Seperti yang disebutkan diawal, bahasa formal mempunyai aturan yang tegas pada syntax. Sebagai contoh, 3+3= 6 adalah pernyataan matematika yang syntaxnya benar. Tetapi 3= 6$ bukan. Juga H2O adalah syntax yang benar secara kimia. Tapi 2Zz tidak benar.
Peranan syntax ada dua, sebagai tanda dan struktur. Tanda adalah elemen dasar dari suatu bahasa. Seperti kata dan angka dan unsurunsur kimia. Satu masalah dengan 3=+6$ bahwa $ bukanlah tanda yang benar secara matematika (sejauh yang saya tahu). 2Zz salah karena tidak ada unsur dengan tanda Zz.
Yang kedua jenis aturan sintaksis menyinggung kepada struktur suatu statemen, maksudnya suatu tanda diatur. Statemen 3=+6$ dari sudut bangunannya tidak benar, sebab Anda tidak bisa mempunyai suatu tanda pertambahan setelah suatu tanda sama dengan. Dengan cara yang sama, rumusan molekular harus mempunyai tulisan di bawah baris setelah nama unsur, yang sebelumnya tidak.
Ketika Anda membaca suatu kalimat di dalam Bahasa Inggris atau suatu statemen di dalam suatu bahasa formal, Anda harus menggambarkan ke luar apa struktur kalimat 22
tersebut (walaupun di dalam suatu bahasa alami yang Anda lakukan ini tanpa disadari). Proses ini disebut parsing.
Sebagai contoh, ketika Anda mendengar kalimat, “Sepatu lain jatuh," Anda memahami bahwa “sepatu lain" adalah subjek dan “jatuh" adalah kata kerja. Sekali anda sudah menguraikan suatu kalimat, anda dapat menggambarkan apa maknanya, itu adalah makna semantik dari sebuah kalimat. Asumsikan bahwa Anda mengetahui sepatu apa itu, dan apa makna jatuh, anda akan memahami implikasi yang umum dari kalimat ini.
Bagaimanapun bahasa alami dan formal mempunyai banyak fitur pada tanda, struktur, sintaksis dan semantik ada banyak perbedaan.
Ambiguitas: Bahasa alami adalah penuh dengan kerancuan, yang mana orang berhadapan dengan dengan penggunaan tanda dan informasi lainnya. Bahasa formal dirancang dengan hampir atau sama sekali tidak bersifat ambigu, yang berarti bahwa semua statemen persis mempunyai satu arti dengan mengabaikan konteks. Redudancy: Dalam rangka mengejar kerancuan dan mengurangi salah paham, bahasa alami banyak mengandung redudancy. Sebagai hasilnya, bahasa alami sering bertele tele. Bahasa formal adalah lebih sedikit lebih ringkas dan tidak berlebih lebihan.
Harfiah: Bahasa alami penuh dengan idiom dan kiasan. Jika Saya mengatakan,sepatu lain jatuh," kemungkinan tidak ada sepatu dan tidak ada apapun yang jatuh.Bahasa formal persis seperti apa yang dikatakan. Orang berbicara tentang suatu bahasa alami sering mempunyai kesulitan pada waktu menyesuaikan ke bahasa formal. Dalam beberapa hal perbedaan antara bahasa formal dan bahasa alami seperti perbedaan antara
23
sebuah puisi dan prosa, tetapi:
Puisi: Katakata digunakan untuk bunyinya juga untuk maksud/artinya , dan syair/puisi yang utuh bersamasama menciptakan suatu efek atau tanggapan emosional. Kerancuan sering merupakan kesengajaan.
Prosa:Arti katakata secara harfiah lebih penting dan pemberdayaan struktur lebih berarti. Prosa jadilah lebih bisa dianalisa dibanding puisi, namun sering rancu.
Program: Arti dari suatu program komputer tidak ambigu dan jelas, dan dapat dipahami seluruhnya oleh analisa tanda dan struktur.
Di sini adalah beberapa usul untuk membaca program ( dan bahasa formal lain). Pertama, ingat bahwa bahasa formal jauh lebih tebal/padat dibanding bahasa alami, sehingga memakan waktu lebih untuk memahaminya. Strukturnya juga sangat penting, jadi bukanlah gagasan yang baik jika membaca dari atas sampai ke bawah, dari kiri ke kanan. Sebagai gantinya belajar menguraikan kalimat program dapat mengidentifikasikan tanda dan menginterpretasikan struktur. Yang akhirnya, ingat bahwa perihal detil. HalHal kecil seperti mengeja kesalahan dan tanda baca tidak baik,dapat Anda hindari dari bahasa alami, dapat membuat suatu perbedaan besar di dalam suatu bahasa formal.
1.5 Program Pertama Biasanya program pertama orangorang pada suatu bahasa baru dinamakan dengan “Hello World." sebab semuanya itu menampilkan “Hello world" Di dalam bahasa Java, programnya seperti : 24
class Hello {
// main: generate some simple output public static void main (String[] args) { System.out.println ("Hello, world."); } }
Sebagian orang menilai mutu suatu bahasa program terlihat dari kesederhanaannya “Hello World" Program. Dengan standard ini, bahasa Java tidak melakukan seluruhnya dengan baik. Bahkan program yang paling sederhana berisi sejumlah code yang sulit dijelaskan pada para programmer pemula. Mari kita abaikan semuanya terlebih dahulu. Sekarang, saya akan menjelaskan beberapa.
Semua program terdiri dari definisi kelas, yang mempunyai format:
class CLASSNAME { public static void main (String[] args) { STATEMENTS } }
Di sini CLASSNAME menunjukkan bahwa suatu nama dapat anda tentukan sendiri. Nama kelas pada contoh ini adalah Hello. 25
Pada baris yang kedua, kata public static void anda abaikan dulu, tetapi perhatikan kata main. main adalah suatu nama khusus yang menandai tempat dimana program akan mulai. Ketika program berjalan, program akan mulai dengan mengeksekusi statemen di dalam main dan itu berlanjut sampai sampai akhir statemen, dan kemudian keluar.
Tidak ada batas jumlah dari statemen yang terdapat di dalam main, pada contoh hanya berisi satu. Ini merupakan suatu statemen cetakan, maksudnya bahwa itu mencetak suatu pesan pada layar, dengan kata lain berarti mengirimkan sesuatu ke printer. Dalam buku ini saya tidak akan banyak mengatakan tentang mengirimkan berbagai hal kepada printer. Tetapi akan mencetaknya pada layar.
Statemen yang mencetak berbagai hal pada layar adalah System.Out.Println, Dan diantara tanda kurung adalah hal yang akan dicetak. Pada bagian akhir statemen ada suatu titik koma (;) , yang mana diperlukan pada ujung tiaptiap statemen.
Ada beberapa lain yang anda perlu perhatikan tentang sintaksis dari program ini. Pertama, bahasa Java menggunakan kurung kurawal ( { dan }) untuk mengolongkan sesuatu secara bersama. Kurung kurawal yang paling jauh ( baris 1 dan 8) berisi definisi kelas , dan bagian dalam berisi definisi dari main.
Perhatikan baris 3 mulai dengan //. Ini menandakan pada baris ini berisi komentar pada teks Bahasa Inggris anda dapat menaruhnya pada pertengahan program, biasanya menjelaskan apa yang program kerjakan. Ketika compiler melihat tanda //, maka akan mengabaikan segalanya dari sana sampai akhir baris.
26
1.6 Daftar Kata Problemsolving: Proses merumuskan suatu masalah,menemukan suatu solusi, dan menyatakan solusi itu. Highlevel language: Suatu bahasa program seperti bahasa Java yang dirancang untuk memudahkan manusia untuk membaca dan menulis. Lowlevel language: Suatu bahasa program yang dirancang untuk memudahkan komputer untuk melaksanakannya. Juga disebut ”bahasa mesin" atau ”bahasa assembly”. Bahasa formal: Bahasa yang sudah dirancang untuk mempresentasikan suatu gagasan matematika atau program komputer. Semua bahasa program adalah bahasa formal. Bahasa alami: Bahasa orang yang manapun itu sudah terbentuk secra alami. Portabilitas: Suatu properti dari suatu program yang dapat berjalan lebih dari satu macam komputer. Interpreted: Untuk mengeksekusi suatu program di dalam suatu highlevel language menterjemahkannya perbaris pada waktu yang sama. Compile: Untuk menterjemahkan suatu program di dalam suatu highlevel language ke dalam suatu lowlevellanguage, untuk kemudian di eksekusi. Source program: Suatu program di dalam suatu highlevel language sebelum di compile. Object Code: Keluaran compiler, setelah menterjemahkan program itu. Executable: Nama lain untuk object code yang siap untuk dieksekusi. ByteCode: Semacam object code khusus digunakan untuk Program Java. Byte Code serupa dengan suatu lowlevel language, tetapi portable sama seperti highlevel language.
27
Statement: Bagian dari suatu program yang merupakan sesuatu yang akan dikan ketika program berjalan. Suatu cetakan statemen menyebabkan keluaran pada layar. Comment: Bagian dari suatu program yang berisi informasi tentang program, tetapi itu tidak mempunyai efek ketika program berjalan. Algorithm: Suatu proses umum untuk memecahkan suatu kategori permasalahan. Bug: Suatu kesalahan di dalam suatu program. Syntax: Struktur suatu program. Semantic: Arti dari suatu program. Parse: Untuk menguji dan menganalisis suatu struktur syntax sebuah program. Syntax error: Suatu kesalahan di dalam suatu program yang membuatnya mustahil untuk di parsing. Exception: Suatu kesalahan di dalam suatu program yang membuatnya gagal pada waktu dijalankan. Biasa disebut runtime error. Debugging: Proses untuk menemukan dan membuang ke tiga macam kesalahan.
1.7. Latihan Latihan 1.1 Ilmuwan Komputer mempunyai kebiasaan menggunakan katakata Bahasa Inggris untuk mengartikan sesuatu yang berbeda dengan maksud Bahasa Inggris secara umum. Sebagai contoh, didalam Bahasa Inggris, suatu pernyataan dan suatu komentar adalah hal hampir sama bagusnya, tetapi ketika kita sedang berbicara sekitar suatu program, mereka umumnya berbedabeda. Daftar kata pada akhir masingmasing bab dimaksudkan untuk menyoroti katakata dan ungkapan yang mempunyai arti khusus didalam ilmu pengetahuan komputer. Ketika anda melihat katakata yang telah dikenal, jangan berasumsi bahwa anda mengetahui 28
apa artinya!
a. Didalam komputer jargon, apa perbedaan antara suatu statemen dan suatu komentar? b. Apa artinya bahwa suatu program dikatakan portable ? c. Apa yang dimaksud dengan executable?
Latihan 1.2 Sebelum anda melakukan yang lain, cari tahu bagaimana cara menyusun dan menjalankan Program Java didalam environtment yang anda gunakan. Beberapa environtment menyediakan contoh program yang serupa dengan contoh pada bagian 1.5. a. Ketik program ”Hello World”, kemudian compile dan Run. b.Tambahkan suatu statemen yang dapat mencetak pesan kedua setelah “Hello World". Sesuatu yang lucu seperti, Anda apa kabar? Compile dan Run lagi. c.Tambahkan suatu baris komentar pada program dimana saja dan Compile. Lalu Run lagi. Komentar yang baru mestinya tidak mempunyai effect pada program setelah di Run. Latihan ini mungkin tampak sepele, tetapi itu adalah awal dari banyak program yang akan kita kerjakan nanti. Dalam mendebug dengan perceaa diri, anda harus mempunyai percaya diri pada environment pemrograman anda. Dalam beberapa environment, sangat mudah untuk kehilangan jejak program yang mana yang sedang dieksekusi, dan anda mungkin menemukannya diri berusaha mendebug suatu program ketika anda menjalankan program yang lain. Menambahkan (dan mengubah) suatu cetakan statemen merupakan hal yang sederhana untuk menetapkan hubungan antara program yang sedang anda perhatikan dan keluarannya ketika program diRun.
29
Latihan 1.3 Adalah suatu hal baik jika anda melakukan banyak kesalahan, sehingga anda dapat melihat apa yang salah dengan cara melihat pemberi tahu kesalahan pada compiler. Kadangkadang compiler akan memberi tahu anda tepatnya apa yang salah, dan anda harus membereskan kesalah itu. Kadangkadang, walaupun demikian, compiler juga akan memberi tahu banyak kesalahan yang tidak semestinya. Anda akan sedikit memakai nalar ketika anda mempercayai compiler dan anda harus hatihati. a. Hapus salah satu dari kurung kurawal pembuka. b. Hapus salah satu dari kurung kurawal yang terdekat. c. Sebagai ganti main, tulis mian. d. Hapus kata static. e. Hapus kata public. f. Hapus kata System. g. Gantikan println dengan pintln. h.Gantikan println dengan print. Yang ini adalah suatu hal ketat sebab merupakan suatu kesalahan logika, bukan kesalahan sintaksis. Statemen ”System.Out.Print” adalah legal tetapi mungkin iya atau mungkin bukan apa yang anda harapkan. i. Hapus salah satu dari tanda kurung. Tambahkan lagi lebih dari itu.
30
Bab II Variabel dan Tipenya 2.1 Mencetak lebih banyak Seperti yang telah saya sebutkan pada bab yang lalu, Anda dapat meletakkan sebanyak apapun statemen pada main. Sebagai contoh, untuk mencetak lebih dari satu baris :
class Hello { // main: generate some simple output public static void main (String[] args) { System.out.println ("Hello, world."); System.out.println ("How are you?");
// print one line // print another
} }
Juga, seperti yang anda lihat, adalah legal untuk menaruh komentar pada akhir suatu baris, seperti halnya sejajar dengan baris itu. Ungkapan yang nampak tanda kutipnya disebut “string”, sebab karena terdiri dari suatu urutan string huruf. sebetulnya, string bisa berisi kombinasi huruf, angkaangka, tanda baca, dan karekter khusus lainnya.
println adalah singkatan dari “print line" sebab setelah masingmasing baris itu ditambahkan suatu khusus khusu yang dinamakan newline, yang menyebabkan cursor untuk bergerak ke baris yang berikutnya. Setelah itu Println dilibatkan, teks yang baru muncul pada baris berikutnya. Menampilkan output dari banyak cetakan statemen dalam satu baris sangatlah 31
bermanfaat. satu baris. Anda dapat mekannya command print berikut:
// main: generate some simple output public static void main (String[] args) { System.out.print ("Goodbye, "); System.out.println ("cruel world!"); } }
Dalam hal ini keluaran akan nampak pada satu baris seperti Goodbye, cruel world!. Perhatikan bahwa ada suatu spasi antara kata “Goodbye” dan tanda kutip yang kedua. Spasi ini muncul pada Output, dan mempunyai efek pada program. Spasi yang muncul diluar tanda kutip biasanya tidak mempunyai efek pada program. Sebagai contoh, saya bisa sudah menulis :
class Hello { public static void main (String[] args) { System.out.print ("Goodbye, "); System.out.println ("cruel world!"); } }
Program ini akan dicompile dan diRun sama halnya sebelumnya. Tulisan pada bagian akhir ( newlines) juga tidak mempunyai efek pada program, maka saya bisa sudah menulis :
32
class Hello { public static void main (String[] args) { System.out.print ("Goodbye, "); System.out.println ("cruel world!");}}
Ini juga bisa, walaupun anda mungkin melihat bahwa program menjadi semakin sulit dan lebih sulit untuk membaca. Baris baru dan spasi bermanfaat untuk mengorganisir program anda secara visual, membuat program lebih mudah untuk membaca program itu dan menempatkan kesalahan sintax.
2.2 Variabel Salah satu keunggulan fitur suatu bahasa pemrograman adalah kemampuan untuk memanipulasi variabel. Suatu variabel adalah lokasi yang menyimpan suatu nilai. Nilai adalah berbagai hal yang dapat dicetak dan disimpan (kita akan lihat kemudian) yang dioperasikan.String yang telah kita cetak (" Hello, World.", " Goodbye", dll.) adalah nilai.
Dalam menyimpan suatu nilai, anda harus menciptakan suatu variabel. Karena nilai yang ingin kita simpan adalah string, maka kita akan mendeklarasikan variabel baru sebagai suatu string :
String fred;
Statemen ini merupakan suatu deklarasi, karena mendklarasikan variabel bernama fred yang bertipe string. Masingmasing variabel mempunyai suatu tipe yang menentukan tipe nilai apa yang akan disimpan. Sebagai contoh, tipe int dapat menyimpan bilangan bulat, dan tidak mungkin akan menyimpan tipe string. 33
Anda akan melihat bahwa beberapa tipe dimulai dengan huruf besar dan beberapa dengan huruf kecil.. Kita akan belajar arti dari perbedaan ini nanti, tetapi untuk sekarang anda benarbenar perlu untuk berhatihati. Ada tipe selain Int atau string, dan compiler akan menolak jika anda mencoba membuat sendiri tipe data.
Untuk membuat suatu variabel bilangan bulat, sintaknya seperti int bob; , dimana bob adalah nama yang dapat anda tulis sembarang. Secara umum, anda akan menulis suatu nama variabel yang mengindikasikan variabel yang ingin anda bentuk. Sebagai contoh, jika anda melihat deklarasi variabel ini:
String firstName; String lastName; int hour, minute;
Anda mungkin bisa menerkanerka tentang nilai apa yang akan disimpan oleh tipe tersebut. Contoh ini memperlihatkan sintak untuk medemonstrasikan berbagai variabel dengan tipe yang sama: hour dan minute keduaduanya merupakan bilangan bulat ( tipe int).
2.3 Assignment Sejak kita sudah menciptakan beberapa variabel, kita ingin menyimpan suatu nilai pada variabel tersebut. Kita akan mekannya dengan Assignmen Statement.
fred = "Hello.";
// give fred the value "Hello."
34
hour = 11;
// assign the value 11 to hour
minute = 59;
// set minute to 59
Contoh ini menunjukkan tiga assignment, dan komentar menunjukkan tiga perbedaan cara orang menyatakan tentang assignment statement. Kosa katanya jadi meragukan di sini, tetapi gagasan secara langsung:
ketika anda mendeklarasikan suatu variabel, anda menciptakan suatu nama untuk lokasi penyimpanan.
Ketika anda mekan assignment terhadap suatu variabel, anda memberinya suatu nilai.
Suatu cara umum untuk mempresentasikan suatu variabel secara tertulis adalah dengan menggambarkan suatu kotak dengan nama variabel diluarnya dan nilai variabel pada didalamnya. Gambar ini menunjukkan efek dari ke tiga assignment statemen :
Untuk masingmasing variabel, nama variabel nampak di luar kotak itu dan nilai nampak di dalam.
Pada umumnya, suatu variabel harus mempunyai tipe yang sama seperti meng assign nilai. Anda tidak bisa menyimpan suatu string didalam minute atau suatu bilangan bulat didalam fred.
Di sisi lain, aturan itu bisa meragukan, karena ada banyak cara yang dapat anda kan
35
dalam mengkonversi nilainilai dari satu tipe ke tipe lain, dan Java kadangkadang mengkonversi berbagai hal secara otomatis. Maka sekarang anda perlu ingat aturan yang umum, dan kita akan memperbicangkannya secara khusus nanti.
Hal lain yang meragukan adalah bahwa beberapa string kelihatan seperti bilangan bulat, tetapi sesungguhnya bukan. Sebagai contoh, fred mengandung string " 123", yang mana terdiri dari karakter 1, 2 dan 3, tapi itu tidak sama halnya seperti nomor 123.
fred = "123"; // legal fred = 123;
// not legal
2.4 Mencetak Variabel Anda dapat mencetak nilai suatu variabel menggunakan perintah yang sama seperti yang kita gunakan untuk mencetak Strings.
class Hello { public static void main (String[] args) { String firstLine; firstLine = "Hello, again!"; System.out.println (firstLine); } }
Program ini menciptakan suatu variabel yang bernama firstLine, meng assign nya ke 36
dalam nilai " Hello, Again!" dan kemudian mencetak nilainya. Ketika kita memperbicangkan tentang “mencetak suatu variabel," kita berarti mencetak nilai variabel itu. Untuk mencetak nama suatu variabel, anda harus menaruhnya didalam tanda kutip. Sebagai contoh: System.Out.Println (" Firstline");
Jika anda ingin lebih detail , anda bisa menulis :
String firstLine; firstLine = "Hello, again!"; System.out.print ("The value of firstLine is "); System.out.println (firstLine);
Keluaran dari program ini adalah :
The value of firstLine is Hello, again!
Saya senang untuk mengatakan bahwa sintak untuk mencetak suatu variabel adalah sama halnya dengan mengabaikan jenis variabel itu.
int hour, minute; hour = 11; minute = 59; System.out.print ("The current time is "); System.out.print (hour); System.out.print (":"); System.out.print (minute);
37
System.out.println (".");
Keluaran dari program ini adalah : The current time is 11:59.
PERINGATAN: Ini adalah praktek umum untuk menggunakan beberapa perintah print yang diikuti oleh println, untuk meletakkan berbagai nilai pada Baris yang sama. Tetapi anda harus hatihati perintah println pada bagian akhir. Pada banyak environment keluaran dari print disimpan tanpa dipertunjukkan sampai perintah println dilibatkan, di mana keseluruhan baris dipertunjukkan dengan segera. Jika anda menghilangkan println, program bisa berakhir tanpa pernah mempertunjukkan keluaran yang disimpan!
2.5 Keywords Beberapa bagian yang lalu, saya berkata bahwa anda dapat membuat nama apapun yang anda butuhkan untuk variable, tetapi itu tidak selalu benar. Ada katakata tertentu yang tersedia pada Java sebab itu digunakan oleh compiler untuk menguraikan struktur dari program anda, dan jika anda menggunakannya sebagai nama variabel, akan menjadi kacau. Katakata ini disebut keywords, meliputi public, class, void, int, dan banyak lagi yang lain.
Daftar yang lengkap ada tersedia pada :
http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html
Lokasi ini, yang disediakan oleh Sun, meliputi Dokumentasi Java yang akan mengacu
38
pada keseluruhan isi buku.
Dengan menghafal daftar itu, saya jamin anda mendapat keuntungan dari fitur yang disediakan pada banyak Java Development environment : Kode yang di tampilkan. Dalam mengetik, bagianbagian berbeda dari program anda perlu tampak berbeda warnanya. Sebagai contoh, kata kunci boleh jadi biru, string merah, dan lain kode hitam. Jika anda mengetik suatu nama variabel dan warnya biru, awas! Anda mungkin menemukan hal yang aneh dari compiler itu.
2.6 Operator Operator adalah lambang khusus yang digunakan untuk mempresentasikan perhitungan sederhana seperti penambahan dan perkalian. Kebanyakan dari operator didalam Java persisnya seperti apa yang anda inginkan, sebab itu adalah lambang matematika secara umum. Sebagai contoh, operator untuk menambahkan dua bilangan bulat adalah +. Yang berikut adalah semua ekpresi yang legal menurut Java yang mana artinya kelihatan lebih jelas:
1+1
hour1
hour*60 + minute
minute/60
Ungkapan dapat berisi nama dan nomor variabel. Pada setiap kasus nama variabel digantikan dengan nilai sebelum perhitungan nya dikan. Penambahan, pengurangan dan perkalian semua yang anda inginkan, tetapi anda mungkin dikagetkan oleh pembagian. Sebagai contoh, program yang berikut:
int hour, minute; hour = 11; minute = 59;
39
System.out.print ("Number of minutes since midnight: "); System.out.println (hour*60 + minute); System.out.print ("Fraction of the hour that has passed: "); System.out.println (minute/60);
akan menghasilkan keluaran yang berikut:
Number of minutes since midnight: 719 Fraction of the hour that has passed: 0
Baris yang pertama adalah apa yang kita harapkan, tetapi baris yang kedua adalah aneh. Nilai menit variabel adalah 59, dan 59 yang dibagi oleh 60 adalah 0.98333, bukan 0. Pertentangan ini disebabkan bahwa Java mekan pembagian dalam bilangan bulat.
Ketika kedua operand adalah bilangan bulat (operand adalah suatu operasi pada operator), hasilnya harus jadi suatu bilangan bulat, dan bilangan bulat bersifat selalu membulatkan kebawah, bahkan pada kasus seperti ini dimana bilangan bulat yang berikutnya menjadi sangat dekat.
Suatu alternatif yang mungkin dalam hal ini adalah untuk mengkalkulasi suatu persentase dibanding pecahan:
System.out.print ("Percentage of the hour that has passed: "); System.out.println (minute*100/60);
Hasilnya : 40
Percentage of the hour that has passed: 98
Lagilagi hasil dibulatkan bawah, tetapi setidaknya sekarang jawabannya sudah benar. Dalam memperoleh jawaban yang lebih sayarat lagi, kita bisa menggunakan suatu tipe variable berbeda, yang disebut floatingpoint, yang mampu untuk menyimpan nilai yang kecil.Kita akan mempelajari itu pada bab berikutnya.
2.7 Operasi Ketika lebih dari satu operator muncul pada suatu ekspresi, evaluasi bergantung pada nilai precedence. Suatu penjelasan lengkap tentang nilai precedence sangat rumit, namun untuk mengetahuinya anda dapat memulai:
•
Perkalian dan pembagian mempunyai kedudukan lebih tinggi ( dikan sebelum) penambahan dan pengurangan. Maka 2*31 hasilnya 5, bukan 4, dan 2/31 hasilnya 1, bukan 1 ( ingat bahwa pembagian bilangan bulat 2/3 adalah 0).
•
Jika operator mempunyai kedudukan yang sama maka akan dievaluasi dari kiri ke kanan. Maka didalam ungkapan ini menit*100/60, perkalian dikan terlebih dulu, hasil dari 5900/60 adalah 98. Jika operasi dikan dari kanan ke kiri, hasil 59*1 adalah 59, dan itu salah.
•
Kapanpun Jika anda ingin mengesampingkan ketentuanketentuan nilai precende ( atau anda tidak tahu pasti apa hasilnya) anda dapat menggunakan tanda kurung. Ungkapan didalam tanda kurung dievaluasi terlebih dahulu, maka 2*
41
( 31) 4. Anda dapat juga menggunakan tanda kurung untuk membuat suatu ungkapan lebih mudah untuk membaca, seperti di ( menit* 100) / 60,walaupun itu tidak mengubah hasil.
2.8 O perator pada String
Secara umum anda tidak bisa melaksanakan operasi matematika pada String, sekalipun string kelihatan seperti angkaangka. Yang berikut adalah tidak benar (jika kita mengetahui bahwa fred mempunyai tipe string)
fred 1
"Hello"/123
fred * "Hello"
Ngomongngomong, dengan memperhatikan ekspresi tersebut dapatkan anda menunjukkan bahwa fred adalah suatu integer atau string? Tidak. SatuSatunya cara mengetahui jenis suatu variabel adalah dengan melihat tempat dimana variabel itu dideklarasikan.
Menariknya, operator + dapat dikan pada string, walaupun itu tidak kan persis dengan apa yang anda harapkan. Untuk string, operator + menghasilkan penggabungan, yang berarti menggabungkan dua operand. Sehingga " Hello,"+ " World." menghasilkan string Hello,World. dan fred+ " ism" berarti menambahkan akhiran ism itu kepada ujung kata fred apapun tipe fred ini dipakai sebagai format baru.
42
2.9 Ko m p osisi
Sejauh ini kita sudah melihat di unsurunsur suatu variabel bahasa program, ungkapan, dan statements tertutup, tanpa membicarakan tentang bagaimana cara menggabungkannya.
Salah satu cirri dari bahasa program paling bermanfaat adalah kemampuan mereka untuk mengambil satu bagian blok kecil dan menyusunnya. Sebagai contoh, kita mengetahui bagaimana cara mengalikan angkaangka dan kita mengetahui bagaimana cara mencetaknya, itu dapat kita sayakan keduaduanya pada waktu yang sama:
System.out.println (17 * 3);
Sebenarnya, saya seharusnya tidak mengatakan “pada waktu yang sama," karena pada kenyataannya perkalian harus terjadi sebelum pencetakan, tetapi intinya adalah bahwa ungkapan manapun menyertakan angkaangka, string, dan variabel, dapat digunakan di dalam suatu statemen cetakan. Kita telah melihat satu contoh:
System.out.println (hour*60 + minute);
Tetapi anda dapat juga menaruh ungkapan pada sembarang tempat pada sisi kanan dari suatu statemen tugas:
int percentage; percentage = (minute * 100) / 60;
43
Kemampuan ini tidak lagi nampak sangat mengesankan sekarang, tetapi kita akan lihat contoh lain dimana komposisi memungkinkan untuk menyatakan perhitungan kompleks dengan rapi dan dengan singkat.
PERINGATAN: Ada batas dimana anda dapat menggunakan ungkapan tertentu; khususnya, dari sisi kiri dari suatu tugas statemen haruslah nama suatu variabel, bukan suatu ekspresi. Itu disebabkan pada sisi yang kiri menandai adanya tempat penyimpanan hasil nantinya. Ungkapan tidak mempresentasikan tempat penyimpanan, hanya nilai. Sehingga yang berikut adalah tidak benar: minute+1= hour; 2.10 Daftar kata
Variabel: Suatu nama tempat dimana suatu nilai disimpan. Semua variabel mempunyai suatu tipe, yang dideklarasikan ketika suatu variable dibuat.
Value: Suatu angka atau string (atau hal alin yang dinamai kemudian) yang dapat disimpan pada suatu variabel. TiapTiap nilai kepunyaan satu tipe.
type: Suatu set nilai. Tipe suatu variabel menentukan dalam penyimpanan nilainya. Sejauh ini, tipe yang sudah kita lihat adalah integer ( int didalam Java) dan ( string didalam Java.
Keyword: Suatu kata digunakan oleh compiler untuk menguraikan (kalimat) program. anda tidak bisa menggunakan kata kunci sepert public, class dan void sebagai nama variabel.
44
Statement: Satu baris kode yang mempresentasikan suatu perintah atau tindakan. Sejauh ini, statemen yang sudah kita lihat adalah deklarasi, tugas, dan statemen cetakan.
Deklarasi: Suatu statemen yang menciptakan suatu variabel baru dan menentukan tipenya
Assigment: Suatu statemen yang menugaskan suatu nilai kepada suatu variabel.
Expression: Suatu kombinasi variabel, operator dan nilainilai yang mempresentasikan hasil nilai tunggal. Ungkapan juga mempunyai tipe yang ditentukan oleh operator dan operand.
Operator: Suatu lambang khusus yang mempresentasikan suatu perhitungan sederhana seperti addition,multiplication atau penggabungan string.
Operands: Salah satu dari nilainilai dimana diatasnya suatu operator beroperasi.
Precedence: Kedudukan suatu operator ketika suatu operasi dievaluasi
Concatenate: Untuk menggabungkan ujungujung dua operand.
Komposisi: Kemampuan untuk mengkombinasikan statemen dan ekspresi sederhana ke dalam statemen campuran dan ekspresiekspresi untuk mempresentasikan perhitungan kompleks dengan singkat. 45
2.11 Latihanlatihan
Latihan 2.1 a. Ciptakan suatu program baru nama Date.Java. Copy atau ketik sesuatu seperti Hello, World" yakinkan anda dapat meng compile dan Run nya.
b. Ikuti contoh didalam Bagian 2.4, tulis suatu program yang menciptakan variabel nama hari, tanggal/date, bulan dan tahun. hari akan berisi hari minggu dan tanggal/date akan berisi hari pada bulan itu. Apa tipe masingmasing variabel? Berikan nilai untuk variabel yang mempresentasikan tanggal/date sekarang.
c. Cetak nilai dari tiap variabel sejajar dengan dirinya. Ini adalah suatu langkah intermediate yang bermanfaat untuk mengecek yang segalanya yang telah dikan sejauh ini.
d. Modifikasi program itu sedemikian rupa sehingga dapat mencetak tanggal/date itu didalam Format standar Amerika: Wednesday, February 17, 1999.
e. Modifikasi lagi program itu sedemikian rupa sehingga total keluarannya adalah:
American format: Wednesday, February 17, 1999
46
European format: Wednesday 17 February, 1999
Inti dari latihan ini adalah untuk menggunakan penggabungan string untuk menampilkan nilainilai dengan tipe berbeda ( int dan string), dan untuk praktek mengembangkan program secara berangsurangsur dengan menambahkan beberapa statemen pada waktu yang sama.
Latihan 2.2
a. Ciptakan suatu program baru yang dinamakan Time.Java. Mulai sekarang, saya tidak akan mengingatkan anda untuk mulai dengan suatu yang kecil, bekerja dengan program, tetapi anda sendiri yang mekannya.
b. Ikuti contoh didalam Bagian 2.6, untuk menciptakan variabel nama jam, menit dan detik/second, dan meng assign nilainya dengan waktu yang sekarang. Gunakan format 24 jam, sedemikian rupa sehingga pada 2pm nilai jam adalah 14.
c. Buat program untuk mengkalkulasi dan mencetak banyaknya detik mulai dari tengah malam.
d. Buat program untuk mengkalkulasi dan mencetak banyaknya detik yang tersisa pada hari itu.
e. Buat program untuk mengkalkulasi dan mencetak persentase dari hari yang telah lewat. 47
f. Ubah nilainilai jam, menit dan detik untuk mencerminkan waktu yang sekarang ( saya berasumsi bahwa waktu telah berlalu), dan periksa untuk meyakinkan bahwa program dapat bekerja dengan tepat dengan nilainilai yang berbeda.
Inti dari latihan ini adalah untuk menggunakan sebagian dari operasi perhitungan, dan untuk memulai berpikir tentang campuran variabel seperti jam yang dipresentasikan dengan banyak nilai. Juga, anda mungkin menemukan permasalahan dalam menghitung persentase dengan ints, yang mana adalah motivasi untuk belajar floating point pada yang berikutnya.
SYARAT: anda boleh menggunakan variabel tambahan untuk menyimpan nilainilai yang untuk sementara sepanjang perhitungan itu. Variabel seperti ini, digunakan pada komputasi tetapi tidak pernah dicetak yang terkadang disebut dengan intermediate atau variabel temporer.
48
BAB III Method 3.1 Floatingpoint
Di dalam bab yang terakhir kita mempunyai beberapa permasalahan dengan angka angka yang bukanlah bilangan bulat. Kita disibukkan dengan masalah untuk mengukur persentase sebagai pecahan, tetapi suatu solusi yang lebih umum adalah untuk menggunakan angkaangka floatingpoint, yang dapat mempresentasikan pecahan seperti halnya bilangan bulat. Didalam Java, tipe floatingpoint disebut double.
Anda dapat menciptakan variabel floating dan memberikan nilai dengan menggunakan Sintak yang sama yang]kita gunakan untuk tipe lain . Sebagai contoh:
double pi; pi = 3.14159;
Ini juga benar untuk mempresentasikankan suatu variabel dan menugaskan suatu nilai kepadanya pada waktu yang sama: int x = 1; String empty = ""; double pi = 3.14159;
Sebenarnya, sintak ini adalah secara umum. Suatu kombanisi assigment dan deklarasi terkadang disebut suatu initialisasi. 49
Walaupun angkaangka floatingpoint bermanfaat, namunn sering suatu source membingungkan karena sepertinya ada tumpangtindih antara bilangan bulat dan angka angka floatingpoint. Sebagai contoh, jika anda mempunyai nilai 1, yang merupakan suatu bilangan bulat, bilangan floatingpoint, atau keduaduanya?
Pada dasarnya, Java menilai integer itu 1 dan floatingpoint 1.0, sungguhpun sepertinya adalah jumlahnya sama. Tetapi tipenya berbeda, dan pada dasarnya, anda tidaklah diijinkan untuk meng assign antara tipe. Sebagai contoh, yang berikut adalah tidak benar:
int x = 1.1;
Sebab variabel pada sisi kiri adalah suatu int dan nilai pada sisi kanan adalah suatu double. maka mudah untuk melupakan aturan ini, kususnya karena ada tempat dimana java akan secara otomatis mengkonversi dari satu tipe ke lain. Sebagai contoh:
double y = 1;
seharusnya secara teknis tidak benar, tetapi Java mengijinkan dengan mengubah int itu kepada suatu double secara otomatis. Kemudahan ini sangat menyenangkan, tetapi itu dapat menyebabkan permasalahan; sebagai contoh:
double y = 1 / 3;
50
Anda mungkin mengharapkan variabel y diberi nilai 0.333333, benar, tetapi sesungguhnya akan menghasilkan nilai 0.0. Alasan adalah bahwa ekspresi pada sisi kanan nampak seperti perbandingan dua bilangan bulat, maka Java mengerjakan pembagian bilangan bulat, yang menghasilkan bilangan bulat 0. yang dikonversi Untuk floatingpoint hasilnya 0.0. Satu cara untuk memecahkan masalah ini ( saat anda menggambarkan apa artinya) adalah untuk ekspresi floatingpoint dari kanan.
double y = 1.0 / 3.0;
disini y diset menjadi 0.333333, yang diharapkan.
Semua operasi yang telah kita lihat sejauh ini penambahan, pengurangan, perkalian,dan pembagian juga ada pada nilai floatingpoint, walaupun anda mungkin tertarik untuk mengetahui bahwa mekanisme dasarnya berbeda sepenuhnya. Pada kenyataanya, prosesor mempunyai perangkat keras khusus hanya untuk mekan operasi floatingpoint.
3.2 Mengkonversi double ke int Seperti saya sebutkan, Java mengkonversi int ke double secara otomatis jika perlu, sebab tidak ada informasi hilang dari konversi itu. Pada sisi lain, mengubah suatu double ke suatu int memerlukan penyelesaian. Java tidak melaksanakan operasi ini secara otomatis, sebagai programmer, anda menyadari hilangnya sebagian jumlah dari angka itu.
Cara yang paling sederhana untuk mengkonversi suatu floatingpoint ke suatu bilangan 51
bulat adalah menggunakan typecast. Dinamakan typecasting karena mengijinkan anda untuk mengambil suatu tipe nilai dan mengubahnya kedalam tipe lain (dalam pengertian mengubah tidak melempar).
Sayangnya, sintaks untuk typecasting jelek: anda menaruh nama tipe didalam tanda kurung dan menggunakannya sebagai suatu operator. Sebagai contoh,
int x = (int) Math.PI;
Operator int mempunyai efek mengikuti bilangan bulat, maka x menghasilkan nilai 3.
Typecasting harus didahulukan sebelum operasi perhitungan, maka didalam contoh yang berikut, nilai PI (22:7) menghasilkan pertama dikonversi ke suatu bilangan bulat , dan hasilnya adalah 60, bukan 62.
int x = (int) Math.PI * 20.0;
Mengubah ke suatu bilangan bulat selalu dibulatkan kebawah, sekalipun pecahannya 0.99999999.
Dua properties ini (precedence dan pembulatan) dapat membuat typecast kurang bagus.
3.3 Method Matematika
Di dalam matematika, anda mungkin sudah melihat fungsi seperti sin dan log, dan anda 52
sudah mempelajari ekspresi seperti sin(∏/2) dan log(1/x). Pertama, anda mengevaluasi ekspresi didalam tanda kurung, yang mana disebut argumentasi fungsi. Sebagai contoh, ∏/2 kirakira 1.571, dan 1/x adalah 0.1 ( diasumsikan bahwa x 10).
Kemudian anda dapat mengevaluasi fungsi itu dengan menggunakan tabel atau dengan mekan berbagai perhitungan. Sin 1.571 adalah 1, dan log 0.1 adalah 1 ( diasumsikan bahwa log mengindikasikan bilangan pokok 10). Proses ini dapat diterapkan berulangkali untuk mengevaluasi ekspresi yang lebih rumit seperti log(1/ sin(∏/2)). pertama kita mengevaluasi argumentasi function, lalu mengevaluasi fungsi yang paling dalam, dan seterusnya.
Java menyediakan satu set builtin fungsi yang mengandung kebanyakan operasi matematikan yang anda tahu. Fungsi ini disebut methods. Kebanyakan method matematika beroperasi pada double.
Method matemmatika dilibatkan menggunakan suatu sintaksis yang serupa dengan perintah print yang kita lihat:
double root = Math.sqrt (17.0); double angle = 1.5; double height = Math.sin (angle);
Contoh yang pertama akar 17. Contoh yang kedua sinus 1.5, yang mana adalah nilai sudut variabel. Java berasumsi bahwa nilainilai yang anda gunakan dengan sin dan
53
fungsi trigonometric lain ( cos, Tan) adalah didalam radian. Untuk mengkonversi dari derajat ke radian, anda dapat membagi dengan 360 dan mengalikan dengan 2. Untungnya, Java menyediakan ∏ sebagai nilai yang builtin:
PI (22:7) adalah dalam huruf besar. Java tidak mengenali Pi (22:7), Pi (22:7), atau pie.
Metoda bermanfaat lain didalam Claas adalah putaran, yang mengelilingi suatu nilai floatingpoint kepada bilangan bulat yang paling dekat dan kembalikan ke suatu int. double degrees = 90; double angle = degrees * 2 * Math.PI / 360.0;
int x = Math.round (Math.PI * 20.0);
Dalam hal ini perkalian dikan terlebih dulu, sebelum method dilibatkan. hasilnya adalah 63 ( yang dibulatkan dari 62.8319).
3.4 Composition Sama halnya dengan fungsi matematika, Method Java dapat composed, maksudnya anda menggunakan satu ekspresi sebagai bagian dari yang lain. Sebagai contoh, anda dapat menggunakan ekspresi manapun sebagai suatu argumentasi ke suatu metoda:
double x = Math.cos (angle + Math.PI/2);
Statemen ini mengambil nilai Math.Pi, membaginya dengan dua dan menambahkan hasilnya kepada nilai variabel angle. Penjumlahan kemudian melewatkan suatu 54
argumentasi untuk method cos.(ingat PI (22:7) itu adalah nama suatu variabel, bukan method , maka tidak ada argumentasi, bukan pula argumentasi yang kosong()).
Anda dapat juga mengambil satu method dan melewatkannya pada argumentasi ke lain:
double x = Math.exp (Math.log (10.0));
Didalam Java, log berfungsi selalu menggunakan dasar e, maka statemen ini mengartikan dasar log e 10 dan kemudian menaikkan e. Hasilnya di assign ke x; saya berharap anda mengetahui apa artinya.
3.5 Menambahkan metode baru
Sejauh ini kita hanya menggunakan metoda yang dibangun dalam Java, tetapi memungkinkan untuk menambahkan metoda baru. Sebenarnya kita melihat satu metoda definisi: main. Method dinamakan main khusus menandai di mana pelaksanaan program dimulai, tetapi sintak untuk yang main adalah sama dengan definisi metoda lain:
public static void NAME ( LIST OF PARAMETERS ) { STATEMENTS }
Anda dapat membuat nama apapun yang anda butuhkan untuk metoda anda, kecuali nama main atau keyword java lainnya. Daftar yang menginformasikan tipe parameter, 55
bila ada, anda harus menyediakan untuk penggunaan fungsi yang baru.
Parameter tunggal untuk main adalah String[] args, yang menunjukkan bahwa siapapun menggunakan main harus menyediakan suatu array String ( akan dipelajari pada Bab 10). Sepasang metoda yang pertama yang akan kita tulis tidak punya parameters,sehingga sintaknya kelihatan seperti ini:
public static void newLine () { System.out.println (""); }
Method ini dinamai newLine, dan tanda kurung yang kosong menunjukkan bahwa ia tidak mengambil parameter apapun. Method itu hanya berisi statemen tunggal, yang mencetak suatu string kosong yang ditandai oleh"". Pencetakan suatu string dengan tidak ada huruf didalamnya tidak akan nampak bermanfaat, kecuali ingat bahwa println melompati baris yang berikutnya setelah itu mencetak, maka statemen ini mempunyai efek melompati baris yang berikutnya.
Didalam main kita dapat membuat method yang baru ini menggunakan sintak yang serupa dengan memakai perintah Java yang sudah builtin :
public static void main (String[] args) { System.out.println ("First line."); newLine (); System.out.println ("Second line."); }
56
Keluaran dari program ini adalah
First line. Second line.
Perhatikan spasi antara kedua bentuk. Akibatnya bagaimana jika kita ingin menambah spasi antara dua baris? Kita bisa memanggil method yang sama berulangkali:
public static void main (String[] args) { System.out.println ("First line."); newLine (); newLine (); newLine (); System.out.println ("Second line."); }
Atau kita bisa menulis suatu metoda baru yang dinamakan threeLine yang mencetak tiga bentuk baru:
public static void threeLine () { newLine (); newLine (); newLine (); } public static void main (String[] args) { System.out.println ("First line."); threeLine ();
57
System.out.println ("Second line."); }
Anda perlu memperhatikan beberapa hal sekitar program ini:
Anda dapat memanggil prosedur yang sama berulangkali. Sebenarnya, itu umumnya berguna bagi mekannya.
Anda bisa mempunyai satu method memanggil metoda lain. Dalam hal ini, main meminta threeLine dan threeLine meminta newLine. Lagilagi ini umumnya bermanfaat.
Didalam threeLine saya menulis tiga statemen semua pada baris yang sama, yang mana secara yg sintak benar (ingat bahwa spasi dan baris baru pada umumnya tidak mengubah arti dari suatu program). Di sisi lain, itu suatu gagasan yang baik untuk menaruh statemen masingmasing sejajar dengan barisnya, untuk membuat program anda mudah dibaca. Saya terkadang menggunakan aturan dalam buku ini untuk menghemat ruang.
Sejauh ini, mungkin belum begitu jelas kenapa begitu berharga ini untuk menciptakan semua metoda baru ini . sebenarnya, ada banyak pertimbangan, tetapi contoh ini hanya mempertunjukkan dua hal:
1. Menciptakan suatu method baru memberi anda suatu kesempatan untuk memberi suatu nama kepada satu kelompok statemen. Method dapat menyederhanakan suatu program dengan menyembunyikan suatu perhitungan kompleks di belakang perintah 58
tunggal, dan dengan penggunaan kata Bahasa Inggris sebagai pengganti kode. Lebih jelasnya , newLine atau System.Out.Println("")?
2. Menciptakan suatu method baru yang dapat membuat suatu program yang lebih kecil dengan menghilangkan kode berulang. Sebagai contoh, bagaimana cara anda mencetak sembilan baris baru berurutan? Anda bisa memanggil method threeLine tiga kali.
3.6 Class dan method Dari pengelompokkan kodekode dari bagian yang sebelumnya, maka keseluruhan definisi kelas kelihatan seperti ini:
class NewLine { public static void newLine () { System.out.println (""); }
public static void threeLine () { newLine (); newLine (); newLine (); }
public static void main (String[] args) { System.out.println ("First line."); threeLine (); System.out.println ("Second line."); } }
59
Baris yang pertama menunjukkan bahwa ini adalah definisi untuk suatu kelas yang baru yang dinamakan Newline. Suatu Class adalah suatu kumpulan dari suatu method yang berelasi. Dalam hal ini, kelas yang bernama Newline berisi tiga metoda, yang dinamakan newLine, threeLine, dan main.
Class lain yang telah kita lihat adalah Math Kelas. Class itu berisi method yang bernama sqrt, sin, dan banyak yang lain. Ketika kita memanggil suatu fungsi matematika, kita harus menetapkan nama Class (Math) dan nama fungsinya. Itulah mengapa sintak berbeda untuk masingmasing method builtin dan metoda yang kita tulis:
Math.pow (2.0, 10.0); newLine ();
Statemen yang pertama memanggil method pow didalam Class Math (Yang menaikkan kekuatan argumentasi yang pertama ke argumentasi yang kedua ). Statemen yang kedua meminta method newLine metoda, Java mengasumsikan (dengan tepat) didalam Class Newlines, yang mana sedang kita tulis.
Jika anda mencoba untuk memanggil suatu method dari Class yang salah, compiler akan menghasilkan suatu kesalahan. Sebagai contoh, jika anda ketik:
pow (2.0, 10.0);
Compiler kirakira akan mengatakan, Tidak bisa menemukan suatu method bernama pow didalam Class Newline." Jika anda sudah melihat pesan ini, anda mungkin heran
60
mengapa compiler mencari pow didalam Class yang didefinisikan. Sekarang anda sudah tahu.
3.7 Program dengan banyak method Ketika anda memperhatikan suatu Class yang didefinisikan yang berisi beberapa method, ketika dicoba untuk membacanya dari atas sampai ke bawah, namun nampaknya membingungkan, sebab itu bukanlah Order Of Execution dari program. Eksekusi selalu dimulai pada awal dari statemen dari main, dengan mengabaikan dimana programnya (dalam hal ini saya dengan bebas menaruhnya pada bagian bawah). Statemen dieksekusi satu per satu secara berurutan, sampai anda menjangkau suatu method yang dituju. Method seperti suatu belokan didalam alur eksekusi. Setelah itu akan berlanjut ke statemen yang berikutnya, anda pertama akan kebaris yang pertama dari metoda yang dilibatkan, mengeksekusi semua statemen disana, dan kemudian kembali dan mengambil lagi dimana anda berhenti.
Kelihatannya cukup sederhana, namun anda harus ingat bahwa satu method dapat memanggil method yang lain. Seperti itu, selagi masih pada main, kita mungkin harus berhenti dan melaksanakan statemen pada threeLine. Tetapi ketika kita sedang melaksanakan threeLine, kita mendapatkan interupsi tiga kali untuk berhenti dan melaksanakan newLine.
Pada bagian ini, newLine memanggil method println yang sudah builtin, yang berbelok ke hal lain. Kebetulan, Java sungguh ahli pada mengawasi dimana itu, maka ketika println selesai, lalu mengambil kembali eksekusi dimana newLine berhenti, dan
61
kemudian kembali lagi kepada threeLine, dan kemudian akhirnya kembali lagi kepada main sehingga program dapat berakhir. Sebenarnya, secara teknis program tidak berakhir pada ujung main. Sebagai gantinya, eksekusi mengambil dimana program itu berhenti mengeksekusi yang melibatkan main, yang merupakan Interpreter Java. Interpreter Java mengasuh macam hal suka menghapus window dan cleanup umum, dan kemudian program berakhir.
Apa hal yang tidak baik untuk dikan dalam mempelajari buku ini? Ketika anda membaca suatu program, jangan membaca dari atas sampai ke bawah. Sebaiknya mengikuti alur pelaksanaan.
3.8 Parameter dan argumen
Sebagian dari method builtin yang sudah kita gunakan mempunyai parameter, yang bernilai bahwa anda membiarkan method mekan pekerjaan itu. Sebagai contoh, jika anda ingin menetukan 30 Method sinus suatu angka, anda harus menandai apa angkanya. lalu, sin mengambil suatu nilai double sebagai parameter. Untuk mencetak suatu string, anda harus menyediakan string, yang mana println mengambil suatu String sebagai parameter.
Beberapa method mengambil parameter lebih dari satu, seperti pow, yang mengambil dua double, yaitu base dan eksponen. Perhatikan bahwa pada setiap kasus ini kita harus menetapkan tidak hanya berapa banyak parameter yang ada, tetapi juga tipe paremater. Maka seharusnya itu tidak mengejutkan anda ketika anda menulis suatu Class 62
definition, daftar parameter menandai tipe parameter masingmasing. Sebagai contoh:
public static void printTwice (String phil) { System.out.println (phil); System.out.println (phil); }
Metoda ini mengambil parameter tunggal yang dinamai phil, yang mempunyai tipe String.
Apapun parameternya (dalam posisi ini kita tidak menyatakan apa parameternya), akan dicetak dua kali. Saya memilih nama phil untuk menyatakan pada anda bahwa nama yang anda beri suatu parameter terserah anda, tetapi secara umum anda sebaiknya memilih sesuatu yang lebih ilustratif dibanding phil. Untuk memanggil method ini, kita harus menyediakan suatu String. Sebagai contoh, kita mungkin mempunyai suatu method main seperti ini:
public static void main (String[] args) { printTwice ("Don't make me say this twice!"); }
String yang anda buat disebut suatu argument, dan kita mengatakan bahwa argument diberikan pada method. Dalam hal ini kita akan menciptakan suatu nilai string yang berisi teks "Don't make me say this twice!" dan melewatkan string sebagai suatu
63
argumenti ke printTwice dimana, berlawanan dengan yang diinginkan, dan akan dicetak dua kali.
Sebagai alternatif, jika kita mempunyai suatu Variabel String, kita bisa menggunakannya sebagai ganti suatu argument:
public static void main (String[] args) { String argument = "Never say never."; printTwice (argument); }
Perhatikan sesuatu yang sangat penting disini: nama variabel yang kita lewatkan sebagai suatu argument ( argument) tidak mekan apaapa terhadap parameter ( phil).
Saya katakan lagi bahwa :
Nama variabel yang kita lewatkan sebagai suatu argument tidak mekan apapun terhadap nama parameter.
Itu bisa sama atau bisa berbeda, tetapi penting untuk menyadari bahwa itu bukanlah hal yang sama, kecuali jika mempunyai nilai yang sama ( dalam hal ini string " ("Don't make me say this twice!")
Nilai yang anda nyatakan sebagai suatu argumenti harus mempunyai tipe yang sama dengan parameter method yang anda panggil. Aturan ini sangat penting, tetapi hal ini 64
sering membuat kita bingung pada Java untuk dua pertimbangan:
Ada beberapa metoda yang dapat menerima argumentasi dengan banyak tipe berbeda. Sebagai contoh, anda dapat mengirimkan tipe manapun untuk print dan println, dan itu akan selalu benar. Hal semacam ini merupakan suatu exception,though.
Jika anda membuat kesalahan pada aturan, compiler sering menghasilkan suatu hal yang kacau pada pemberitahu kesalahan. sehingga, Anda sering mendapati bermacam kesalahan argument pada method ini ," itu mungkin karena efek dari tidak ditemukannya suatu metoda dengan nama dan tipe itu. Sekali anda menemukan pemberitahu kesalahan ini maka anda akan menggambarkan tahu bagaimana cara menginterpretasikannya.
3.9 Stack diagram
Parameter dan variabel lain hanya ada di dalam methodnya sendiri. Di dalam membatasi main, tidak ada hal seperti phil. Jika anda mencoba untuk menggunakan itu, compiler akan menganggapnya salah. Dengan cara yang sama, di dalam printTwice tidak ada hal seperti argument.
Satu cara untuk mengetahui dimana masingmasing variabel didefinisikan pada suatu stack diagram.
Stack Diagram untuk contoh yang sebelumnya kelihatan seperti ini:
65
Untuk masingmasing method ada suatu kotak abuabu yang dinamakan frame yang berisi parameter method dan variabel lokal. Nama method nampak diluar frame. Seperti biasanya, nilai dari tiap variabel digambark di dalam suatu kotak dengan nama variabel di sampingnya.
3.10 Metode Dengan Banyak Parameter Sintak untuk mendeklarasikan dan memanggil method dengan berbagai parameter adalah suatu sumber kesalahan pada umumnya. Pertama, ingat bahwa anda harus mendeklarasikan tipe tiaptiap parameter. Sebagai contoh
public static void printTime (int hour, int minute) { System.out.print (hour); System.out.print (":"); System.out.println (minute); }
Itu bisa jadi terkecoh untuk menulis int jam, menit, tetapi format ituhanya benar untuk mendeklarasikan variabel, bukan untuk parameter.
Sumber kesalahan umum yang lain adalah bahwa anda tidak punya sesuatu untuk 66
mendeklarasikan tipe argument. Yang berikut adalah salah!
int hour = 11; int minute = 59; printTime (int hour, int minute); // WRONG!
Dalam hal ini, Java dapat mengatakan bahwa tipe dari hour dan minute dengan memperhatikan deklarasinya. Juga tidak legal dan tidak benar memasukkannya ketika anda melewatkannya sebagi argument. Sintaksis yang benar adalah printTime (hour, minute).
Latihan 3.1 Gambar suatu frame stack yang menunjukkan status program ketika main memanggil printTime dengan argumentasi 11 dan 59.
3.11 Method dan hasilnya Anda mungkin sudah memperhatikan sebagian dari method yang telah kita gunakan, seperti method Math, menghasilkan hasil. Method lain, seperti println dan newLine, meng eksekusi beberapa action tetapi mereka tidak mengembalikan suatu nilai. itu memunculkan beberapa pertanyaan:
•
Apa yang terjadi jika anda meminta suatu method dan yang anda tidak mekan apapun dengan hasil (yaitu.anda tidak meng assignnya pada suatu variabel atau menggunakannya sebagai bagian dari ekspresi yang besar)?
67
•
Apa yang terjadi jika anda menggunakan suatu method print sebagai bagian dari suatu ekspresi seperti System.Out.Println (" boo!")+ 7?
•
Dapatkah kita menulis method yang menghasilkan hasil, atau kita tetap menggunakan newLine dan printTwice?
Jawaban bagi pertanyaan ketiganya adalah ya, anda dapat menulis method yang mengembalikan nilai," dan itu akan kita pelajari dalam 2 bab. Saya akan meninggalkannya untuk anda coba jawab. Sebenarnya, kapan saja anda mempunyai suatu pertanyaan tentang apa yang legal dan illegal lebih baik jika mencobanya pada compiler.
3.12 Daftar kata floatingpoint: Suatu jenis variabel ( atau nilai) yang dapat berisi pecahan seperti halnya bilangan bulat. Didalam Jawa tipe ini disebut double.
Class: Suatu koleksi yang dinamai method. Sejauh ini, kita sudah menggunakan Class Math dan Class System, dan kita sudah menulis Classs dengan nama Hello dan Newline.
method: Penamaan suatu urutan statemen yang melaksanakan beberapa fungsi bermanfaat. Metoda boleh atau boleh tidak mengambil parameter, dan boleh atau boleh tidak menghasilkan suatu hasil.
Parameter: Suatu potongan informasi yang anda menyediakan untuk memanggil suatu
68
metohd. Parameter adalah seperti variabel didalamnya nilainilai dan mempunyai tipe.
Argument: Suatu nilai yang anda sediakan ketika anda memanggil suatu method. Nilai ini harus mempunyai tipe yang sama parameter yang bersangkutan.
invoke: Menyebabkan suatu method untuk dieksekusi
3.13 Latihan
Latihan 3.2
Latihan ini adalah untuk mempraktekkan membaca kode dan untuk meyakinkan bahwa anda memahami alur eksekusi melalui suatu program dengan berbagai method.
a. Apa keluaran program yang berikut? Perhatikan dimana ada spasi dan dimana ada baris baru. SYARAT: mulai dengan menguraikan katakata apa ping dan baffle yang kan ketika itu dilibatkan.
b. Gambar suatu diagram stack yang menunjukkan status program pertama kali ping dilibatkan.
69
public static void zoop () { baffle (); System.out.print ("You wugga "); baffle (); } public static void main (String[] args) { System.out.print ("No, I "); zoop (); System.out.print ("I "); baffle (); } public static void baffle () { System.out.print ("wug"); ping (); } public static void ping () { System.out.println ("."); }
Latihan 3.3 Latihan ini adalah untuk meyakinkan bahwa anda memahami bagaimana cara menulis dan memanggil method yang mengambil parameter.
a. Tulis baris yang pertama suatu metoda nama zool yang mengambil tiga parameter: suatu int dan dua String.
70
b. Tulis satu baris kode yang memanggil zool, ketika melewatkan suatu argument dengan nilai 11, nama binatang kesayangan anda yang pertama, dan nama jalan yang ada ditempat anda ketika anda dibesarkan.
Latihan 3.4
Tujuan latihan ini adalah untuk mengambil kode dari suatu latihan sebelumnya dan mengemasnya didalam suatu method yang mengambil parameter. Anda perlu mulai bekerja menemukan solusi dari Latihan 2.1.
a. Tulis suatu method untukmemanggil printAmerican yang mengambil day, date, month dan years sebagai parameter dan dicetak didalam Format Amerika.
b. Uji method anda dengan memanggilnya dari main dan melewatkan argument yang sesuai. Keluarannya harus terlihat ( kecuali date boleh berbeda):
Wednesday, September 29, 1999
c. Anda sudah mempunyai debug printAmerican, tulis method lain untuk memanggil printEuropean yang dicetak didalam Format Eropa.
Latihan 3.5
Banyak perhitungan dapat dinyatakan dengan singkat menggunakan “multadd" operasi, 71
yang mengambil tiga operand dan menghitung a*b+ c. Beberapa prosessor menyediakan implementasi perangkat keras dari operasi ini untuk angkaangka floatingpoint.
a. Buat suatu program baru dengan nama Multadd.Java.
b. Tulis suatu method dengan nama multadd yang mengambil tiga double sebagai parameter dan cetak multadditionation nya . c. Tulis suatu method main yang menguji multadd dengan memanggilnya dengan beberapa parameter sederhana, seperti 1.0, 2.0, 3.0, dan kemudian cetak hasilnya, dan harus 5.0.
d. Juga didalam main, gunakan multadd untuk menghitung nilainilai yang berikut:
e. Tulis suatu method yang bernama yikes yang mengambil double sebagai parameter dan gunakan multadd untuk mengkalkulasi dan cetak
SYARAT: Method Math untuk menaikkan nilai e adalah Math.Exp.
72
Didalam bagian yang terakhir, anda mendapat kesempatan untuk menulis suatu method yang memanggil suatu method yang anda tulis.Kapan saja anda mekannya, merupakan suatu gagasan baik untuk menguji method yang pertama secara hatihati sebelum anda memulai menulis yang kedua. Selain itu, anda mungkin menemukan cara mekan debugging dua method pada waktu yang sama,yang bisa jadi sangat sulit. Salah satu tujuan dari latihan ini adalah untuk mempraktekkan patternmatching: kemampuan untuk mengenali suatu masalah spesifik sebagai suatu kategori permasalahan umum.
73
BAB 4 Kondisional dan rekursi 4.1 Operator modulus Operator Modulus bekerja pada bilangan bulat dan menghasilkan sisa ketika operan yang pertama dibagi oleh yang kedua. Di (dalam) Java, operator modulus adalah suatu tanda persen,%. Sintaksis persisnya sama halnya untuk lain operator:
int quotient = 7 / 3; int remainder = 7 % 3;
Operator yang pertama, pembagian bilangan bulat, menghasilkan 2. Operator yang kedua menghasilkan 1. Dengan demikian, 7 yang dibagi oleh 3 adalah 2 dengan sisa 1. Operator modulus ternyata adalah anehnya bermanfaat. Sebagai contoh, anda dapat memeriksa apakah satu bilangan; jumlah adalah dapat dibagi oleh lain: jika x% y adalah nol, kemudian x dapat dibagi dengan y. Juga, anda dapat menggunakan operator modulus untuk mengetahui digit paling kanan dari suatu bilangan. Sebagai contoh, x% 10 menghasilkan digit x paling kanan (di (dalam) basis 10). Dengan cara yang sama x% 100 menghasilkan dua digit terakhir. 4.2 Percabangan
Dalam menulis program yang betul betul fungsionalt, kita hampir selalu memerlukan kemampuan untuk memeriksa kondisikondisi tertentu dan merubah aliran program. Pernyataan percabangan memberi [kita/kami] kemampuan ini. Format yang paling
74
sederhana adalah if statemen:
if (x > 0) { System.out.println ("x is positive"); }
Ungkapan di (dalam) tanda kurung disebut kondisi. Jika benar, kemudian statemen di (dalam) tandakurung dieksekusi. Jika kondisi tidaklah benar, tidak dilakukan apapun. Kondisi dapat berisi operator perbandingan apapun, kadangkadang disebut relational operator:
x == y // x equals y x != y // x is not equal to y x > y // x is greater than y x < y // x is less than y x >= y // x is greater than or equal to y x <= y // x is less than or equal to y
Walaupun operasi ini mungkin dikenal baik oleh anda, sintaks Java menggunakannya sedikit berbeda dari lambang matematika seperti=, ≠ dan ≤. Suatu kesalahan umum adalah untuk menggunakan = tunggal sebagai ganti suatu ==. Ingat bahwa = adalah operator tugas, dan == adalah suatu operator perbandingan. Juga, tidak ada hal seperti =< atau =>.
Dua sisi suatu kondisi operator harus memiliki tipe yang sama. Anda hanya dapat membandingkan int ke int dan double dengan double . Sungguh sial, dalam posisi ini 75
anda tidak bisa bandingkan string sama sekali! Ada suatu [jalan/cara] untuk bandingkan string tetapi kita tidak takkan membahasnya pada bab ini.
4.3 Eksekusi Alternatif Bentuk kedua dari percabangan adalah pelaksanaan alternatif, di mana ada dua kemungkinan, dan kondisi menentukan yang mana yang akan dieksekusi. Sintaks nya seperti di bawah ini:
if (x%2 == 0) { System.out.println ("x is even"); } else { System.out.println ("x is odd"); }
Jika sisa ketika x dibagi oleh 2 adalah nol, kita mengetahui bahwa x menjadi genap, dan kode ini mencetak suatu pesan. Jika kondisi adalah salah, statemen cetakan yang kedua akan dieksekusi. Karena kondisi harus salah atau benar, maka salah satu dari alternafif akan dieksekusi.
Jika anda berpikir mungkin akan sering mengecek genap atau ganjil, anda mungkin ingin “membungkus" kode atas dalam suatu method, sebagai berikut:
public static void printParity (int x) { if (x%2 == 0) { System.out.println ("x is even"); } else {
76
System.out.println ("x is odd"); } }
Sekarang anda mempunyai suatu metoda nama printParity yang akan mencetak suatu pesan sesuai untuk bilangan bulat apapun. Di (dalam) metode “main” anda akan memanggil metoda ini sebagai berikut:
printParity (17);
Selalu ingat bahwa ketika anda mengeksekusi suatu metoda, anda tidak harus mendeklarasikan tipe argumen yang anda sediakan. Java dapat mengetahui tipe data tersebut secara otomatis. Anda seharusnya penulisan seperti:
int number = 17; printParity (int number); // WRONG!!!
4.4 Percabangan Berantai Kadangkadang anda ingin melihat kemungkinan sejumlah kondisikondisi terkait dan memilih salah satu dari beberapa tindakan. Satu cara untuk melakukan ini adalah dengan rangkaian if dan else seperti berikut:
if (x > 0) { System.out.println ("x is positive"); } else if (x < 0) { system.out.println ("x is negative");
77
} else { System.out.println ("x is zero"); }
Rantai ini dapat sepanjang yang anda inginkan, walaupun pada akhirnya akan sulit dibaca. Satu cara untuk membuat [mereka/nya] lebih mudah untuk dibaca adalah untuk menggunakan standard indentasi [seperti/ketika] dipertunjukkan contoh ini. Jika anda meletakkan semua statement dan kurung kurawal dalam satu baris, anda memiliki lebih sedikit kemungkinan untuk membuat kesalahan sintaks dan anda dapat menemukan nya dengan lebih cepat.
4.5 Percabangan Bersarang Sebagai tambahan terhadap yang percabangan berantai, anda dapat juga membuat percabangan bersarang di dalam yang lain. Kita bisa menulis contoh yang sebelumnya dengan:
if (x == 0) { System.out.println ("x is zero"); } else { if (x > 0) { System.out.println ("x is positive"); } else { System.out.println ("x is negative"); } }
78
Sekarang ada suatu percabangan luar yang berisi dua cabang. Cabang yang pertama berisi suatu statemen cetakan sederhana, tetapi cabang yang kedua berisi yang pernyataan bersyarat lain, yang mempunyai dua cabang. Kebetulan, dua cabang itu adalah keduaduanya statemen cetakan, walaupun mereka mungkin bisa juga pernyataan bersyarat yang lain.
Perhatikan lagi indentasi membuat struktur lebih enak dibaca, tetapi meskipun demikian, percabangan bersarang lebih susah dibaca dengan cepat. Secara umum, merupakan suatu ide yang baik untuk menghindari nya ketika anda bisa.
Di lain pihak , struktur tersarang macam ini adalah umum, dan kita akan segera melihatnya lagi. 4.6 The return statement
Statemen kembalian mengijinkan anda untuk mengakhiri pelaksanaan suatu metoda sebelum anda menjangkau akhir pernyataan. Satu alasan untuk menggunakan nya adalah jika anda mendeteksi suatu kondisi kesalahan:
public static void printLogarithm (double x) { if (x <= 0.0) { System.out.println ("Positive numbers only, please."); return; } double result = Math.log (x); System.out.println ("The log of x is " + result); }
79
Terdapat suatu metoda bernama printLogarithm yang mengambil suatu tipe data double
bernama x sebagai parameter. Hal yang mulamula ia kerjakan adalah memeriksa apakah x kurang dari atau sama dengan nol, dalam hal ini ia mencetak suatu pemberitahu kesalahan dan kemudian menggunakan pernyataan return untuk keluar dari metode. Alir pelaksanaan dengan seketika kembali ke pemanggil dan sisa metoda tidaklah dieksekusi.
Saya menggunakan suatu nilai floating point pada sisi kanan kondisi sebab ada suatu variabel floating point pada sisi kiri.
4.7 Konversi Tipe Anda mungkin ingin tahu bagaimana anda suatu pernyataan seperti "The log of x is " + result, bisa dieksekusi tanpa kesalahan padahal salah satu operan adalah suatu String dan lain adalah double. Dalam hal ini Java secara cerdas mengubah double menjadi String sebelum mengerjakan penggabungan String.
Hal macam ini adalah contoh suatu masalah umum di (dalam) merancang suatu bahasa program, bahwa ada suatu konflik antar[a] formalisme, yaitu kebutuhan bahasa formal untuk mempunyai aturan sederhana dengan sedikit eksepsi , dan kenyamanan, yang merupakan kebutuhan bahasa program agar mudah digunakan dalam praktek.
Yang lebih sering terjadi adalah kenyamanan lah yang menang, yang biasnaya baik untuk para programmer ahli, tetapi tidak baik untuk para programmer pemula. Dalam buku ini saya sudah mencoba untuk menyederhanakan berbagai hal dengan menekankan aturan dan penghilangan banyak perkecualian.
80
Meskipun demikian, ada baiknya untuk mengetahui bahwa kapan saja anda mencoba menambahkan dua variabel, jika salah satu dari mereka adalah suatu String, maka Java akan mengkonversi yang lain menjadi String dan melakukan penggabungan String. Apa pendapat anda jika anda melakukan suatu operasi antara suatu bilangan bulat dan suatu floating point?
4.8 Recursion Saya menyebutkan pada bab yang terakhir bahwa diperbolehkan satu metoda untuk memanggil metode yang lain, dan kita sudah melihat beberapa contoh menyangkut itu. Saya lupa untuk menyebutkan bahwa juga diperbolehkan suatu metoda untuk memanggil dirinya sendiri. Mungkin tidak jelas mengapa itu suatu hal baik, tetapi ternyata itu adalah salah satu dari hal yang yang menarik dan ajaib yang suatu program dapat lakukan.
Sebagai contoh, lihat di metoda yang berikut:
public static void countdown (int n) { if (n == 0) { System.out.println ("Blastoff!"); } else { System.out.println (n); countdown (n1); } }
81
Nama metoda adalah countdown dan mengambil bilangan bulat tunggal sebagai parameter. Jika parameter adalah nol, ia encetak kata “Blasto." jika tidak ia mencetak bilangan bulat tersebut dan memanggil suatu metoda countdown kembali dengan n1 sebagai argumen.
Apa yang terjadi jika kita memanggil metoda ini, di (dalam) main:
countdown (3);
Eksekusi countdown dimulai dengan n=3, dan karena n bukanlah nol, ia mencetak nilai 3, dan kemudian memanggil dirinya sendiri... Pelaksanaan countdown dimulai dengan n=2, dan karena n bukanlah nol, ia mencetak nilai 2, dan kemudian memanggil dirinya sendiri... Pelaksanaan countdown dimulai dengan n=1, dan karena n bukanlah nol, ia mencetak nilai 1, dan kemudian memanggil [dirinya] sendiri... Pelaksanaan countdown dimulai dengan n=0, dan karena n adalah nol, ia mencetak kata “Blastoff"
kemudian kembali pada metode yang memanggilnya.
Countdown yang mendapat n=1 kembali (return). Countdown yang mendapat n=2 kembali (return). Countdown yang mendapat n=3 kembali (return). Dan kemudian anda adalah ke main. Sehingga total keluaran kelihatan seperti: 3 2
82
1 Blastoff!
Sebagai contoh kedua, mari kita lihat lagi di metoda newLine dan threeLine.
public static void newLine () { System.out.println (""); } public static void threeLine () { newLine (); newLine (); newLine (); }
Walaupun metode di atas dapat bekerja , mereka tidak akan banyak berguna jika saya ingin mencetak 2 baris atau 106. Suatu alternatif lebih baik adalah dengan
public static void nLines (int n) { if (n > 0) { System.out.println (""); nLines (n1); } }
Program ini sangat serupa; sepanjang n adalah lebih besar dari nol, ia mencetak satu newline, dan kemudian memanggil dirinya sendiri untuk mencetak n1 tambahan newlines. Sehingga, total jumlah newlines yang dicetak adalah 1+ ( n1), yang adalah sama dengan n.
83
Proses suatu pemanggilan metoda terhadap dirinya sendiri disebut recursion, dan . Metode seperti itu disebut recursive.
4.9 Diagram Stack Untuk Metode Rekursif Di (dalam) bab yang sebelumnya kita menggunakan suatu diagram stack untuk menampilkan status suatu program selama pemanggilan suatu metoda. Diagram yang sama dapat membuat lebih mudah untuk menginterpretasikan suatu metoda rekursif.
Ingat bahwa setiap kali suatu metoda dipanggil ia menciptakan instance baru yang berisi variabel lokal dan parameter baru. Gambar berikut adalah diagram stack untuk countdown, dengan n= 3:
Terdapat satu instance main dan empat instance countdown, masingmasing dengan suatu nilai berbeda untuk parameter n. Stack terbawah, countdown dengan n=0 adalah kasus dasar. Sehingga ia tidak membuat suatu panggilan rekursif, jadi tidak ada lagi instance countdown. Main adalah kosong sebab main tidak mempunyai 84
parameter atau variabel lokal apapun.
Latihan 4.1 Gambar suatu diagram stack yang menunjukkan status program setelah main memanggil nLines dengan parameter n=4, tepat sebelum instansce nLines terakhir dikembalikan.
4.10 Konvensi dan Divine Law Di dalam bagian terakhir, saya menggunakan ungkapan “by convention“ beberapa kali untuk menandai (adanya) keputusan disain sewenangwenang tanpa pertimbangan penting, tetapi hanya dikatakan berdasarkan konvensi.
Di (dalam) kasus ini, adalah suatu keuntungan untuk terbiasa dengan konvensi dan menggunakannya, sehingga akan membuat program anda lebih mudah untuk dipahami oleh orang lain. Dan penting pula untuk membedakan antara ( sedikitnya) tiga macam aturan:
Divine Law: Ini adalah ungkapan saya untuk menandai aturan yang benar karena beberapa prinsip logika atau matematika, maka itu adalah benar juga untuk bahasa program (atau sistem formal) apapun. Sebagai contoh, tidak ada cara untuk menentukan ukuran dan penempatan suatu kotak menggunakan lebih sedikit dari empat potongan informasi. Contoh lain adalah bahwa menambahkan bilangan bulat adalah komutatif. Hal ini adalah bagian dari definisi penambahan dan sama hal nya dengan Java.
Rules Of Java: Ini adalah peraturan tentang semantik dan yang syntactic Java yang tidak bisa anda langgar, sebab program yang dihasilkan tidak akan bisa dikompilasi atau dijalankan. Sebagai contoh, fakta bahwa lambang “+” menghadirkan penambahan dan 85
penggabungan String.
Gaya Dan Konvensi: Ada banyak aturan yang tidaklah dipaksa oleh compiler, tetapi itu adalah penting untuk penulisan program yang benar, sehingga dapat didebug dan dimodifikasi, dan dapat dibaca orang lain. Contoh nya adalah indentasi dan penempatan kurung kurawal, dan konvensi untuk menamai variabel, metoda dan kelas.
Selagi saya adalah pada topik, anda sudah mungkin mengerti mulai sekarang bahwa nama kelas selalu mulai dengan huruf besar, tetapi variabel dan metoda mulai dengan huruf kecil. Jika nama lebih dari satu kata, maka anda biasanya membuat huruf pertama setiap kata dengan huruf besar, seperti di newLine dan printParity.
4.11 Daftar Kata modulus: Suatu operator yang bekerja pada bilangan bulat dan menghasilkan sisa ketika satu bilangan dibagi oleh lain. Di (dalam) Java hal ini ditandai
dengan
tanda persen(%). conditional: Suatu blok statemen yang boleh atau tidak boleh dieksekusi tergantung pada suatu kondisi. chaining: Suatu cara untuk menggabungkan beberapa pernyataan bersyarat dalam suatu urutan. nesting: Meletakkan pernyataan bersyarat di dalam satu atau kedua cabang dari pernyataan bersyarat lain. coordinate: Suatu variabel atau nilai yang menunjukkan lokasi dari jendela grafis dua dimensi. pixel: satuan koordinat dalam komputer
86
bounding box: cara umum untuk menentukan koordinat suatu area segiempat. typecast : operator yang mengkonversi dari satu tipe data ke tipe data yang lain. Di dalam Java dengan tipe data dalam tanda kurungs seperti (int). interface: Deskripsi tentang parameter yang dibutuhkan oleh suatu metode dan tipe data nya prototype: Suatu cara untuk menggambarkan metode menggunakan sintaks yang mirip Java. recursion: Proses yang memanggil metoda yang sama dengan yang sekarang dieksekusi. infinite recursion: Suatu metoda yang memanggil dirinya sendiri secara berulang tanpa pernah berhenti. Hal seperti ini biasanya menghasilkan StackOver FlowException.
fractal: Semacam gambaran yang adalah didefinisikan secara berulang, sedemikian sehingga masingmasing bagian dari gambar adalah suatu versi yang lebih kecil keseluruhan.
4.12 Latihan Latihan 4.2 Jika anda diberi tiga tongkat, anda mungkin bisa atau tidak bisa menyusun nya dalam suatu segi tiga. Sebagai contoh, jika salah adalah 12 inci dan yang lain adalah satu inci ,jelas bahwa anda tidak akan bisa membuat ketiganya menjadi segitiga. Berapun panjangnya, ada suatu test sederhana untuk melihat apakah mungkin untuk membentuk suatu segi tiga:
“Jika terdapat dari ketiga tongkat tersebut yang panjang nya lebih besar dari jumlah dua
87
yang lain, maka anda tidak dapat membentuknya menjadi segitiga, jika tidak maka mereka dapat dibentuk sebagai segitiga"
Tulis suatu metoda bernama isTriangle yang mengambil tiga bilangan bulat sebagai argumen, dan itu mengembalikan true/false, tergantung apakah anda bisa membentuk nya menjadi segitiga
Tujuan latihan ini adalah untuk menggunakan pernyataan bersyarat untuk menulis suatu metoda yang mengembalikan suatu nilai.
Latihan 4.3 Latihan ini meninjau aliran eksekusi program dengan berbagai metoda. Baca kode yang berikut dan jawab pertanyaan di bawah.
public class Buzz { public static void baffle (String blimp) { System.out.println (blimp); zippo ("ping", 5); } public static void zippo (String quince, int flag) { if (flag < 0) { System.out.println (quince + " zoop"); } else { System.out.println ("ik"); baffle (quince); System.out.println ("boowahaha");
88
} } public static void main (String[] args) { zippo ("rattle", 13); } }
a. Tulis angka 1 di sebelah statement program yang akan pertama kali dieksekusi. Perhatikan untuk membedakan hal yang merupakan statement dari hal yang bukan. b. Tulis nomor 2 disamping statement yang dieksekusi pada kali kedua , dan seterusnya sampai akhir program [itu]. Jika suatu statemen dieksekusi lebih dari sekali, itu bisa berakhir dengan billangan lebih dari satu disampingnya. c. berapa nilai blimp ketika baffle dieksekusi? d. Apa yang keluaran program ini?
Latih 4.4 Sajak pertama nyanyian “ 99 Bottles of Beer” adalah:
99 bottles of beer on the wall, 99 bottles of beer, ya’ take one down, ya’ pass it around, 98 bottles of beer on the wall. Sajak berikutnya serupa kecuali banyak botol lebih sedikit satu setiap sajak, sampai sajak terakhir: No bottles of beer on the wall, no bottles of beer, ya’ can’t take one down, ya’ can’t pass it around, ’cause there are no more bottles of beer on the wall! Dan kemudian nyanyian berakhir. 89
Tulis suatu program yang mencetak keseluruhan lirik lagu “ 99 Bottles of Beer”. Program anda harus berisi metode rekursif yang bukan merupakan hal yang susah, mungkin anda juga ingin menulis metode tambahan untuk memisahkan fungsi fungsi utama program.
Ketika anda menulis kode, anda akan mungkin ingin mengujinya dengan ejumlah bilangan kecil seperti “3 Bottles Of Beer."
Tujuan latihan ini adalah untuk mengambil suatu masalah dan memecahkannya ke dalam permasalahan lebih kecil, dan untuk memecahkan permasalahan kecil dengan menulis metode sederhana yang dapat didebug dengan mudah.
Latihan 4.5 Apa yang program yang berikut?
public class Narf { public static void zoop (String fred, int bob) { System.out.println (fred); if (bob == 5) { ping ("not "); } else { System.out.println ("!"); } } public static void main (String[] args) {
90
int bizz = 5; int buzz = 2; zoop ("just for", bizz); clink (2*buzz); } public static void clink (int fork) { System.out.print ("It's "); zoop ("breakfast ", fork) ; } public static void ping (String strangStrung) { System.out.println ("any " + strangStrung + "more "); } }
Latihab 4.6 Teorema Fermat yang menyatakan bahwa tidak ada bilangan bulat a, b, dan c sehingga
kecuali kasus ketika n= 2.
Tulis suatu metoda bernama checkFermat yang mengambil empat bilangan bulat sebagai parameter a, b, c dan n yang memeriksa untuk melihat apakah teori Fermat benar. Jika n adalah lebih besar dari 2 dan ternyata adalah benar an+ bn= cn, program akan mencetak “Teori, Fermat adalah salah!" Jika tidak program akan mencetak “Tidak ia betul."
91
Anda perlu berasumsi bahwa terdapat suatu metoda bernama raiseToPow yang mengambil dua bilangan bulat sebagai argumen dan argumen kedua sebagai pangkat argumen pertama. Sebagai contoh:
int x = raiseToPow (2, 3);
akan memasukkan nilai 8 ke dalam x, karena 23=8
92
Bab 5 Fruitful methods 5.1 Nilai Kembalian Beberapa metode bawaan yang telah kita gunakan, seperti fungsi Math, telah menghasilkan hasil. Tujuan dari pemanggilan metode adalah untuk menghasilkan sebuah nilai baru, yang biasanya kita masukkan pada sebuah variabel yang merupakan bagian dari sebuah ekspresi. Misalnya double e = Math.exp (1.0); double height = radius * Math.sin (angle);
Tetapi sejauh ini semua metode yang telah kita tulis adalah metode dengan kembalian void; yaitu metode yang tidak mengembalikan nilai. Ketika anda memanggil metode void, ia hanya terdiri atas satu baris, tanpa pemberian nilai ke dalam satu variabel. nLines (3); g.drawOval (0, 0, width, height);
Pada bab ini, kita akan menulis metode yang mengembalikan nilai, yang saya sebut sebagai “fruitful methods”, untuk nama yang lebih baik. Contoh yang pertama adalah area, yang mengambil double sebagai parameter, dan mengembalikan luas lingkaran dengan radius yang diberikan: public static double area (double radius) {
double area = Math.PI * radius * radius; return area;
} Hal pertama yang harus anda perhatikan adalah awal dari definisi metode adalah 93
berbeda. Sebagai ganti public static void, yang menunjukkan metode void, kita menggunakan public static double, yang menunjukkan nilai kembalian dari metode ini adalah double. Saya masih belum menjelaskan apa itu public static tapi tenanglah.
Juga perhatikan bahwa baris terakhir adalah bentuk alternatif dari pernyataan return yang juga terdapat nilai yang akan dikembalikan. Pernyataan tersebut berarti, “kembali secepatnya dari metode ini dan gunakan nilai berikut sebagai nilai kembalian.” Nilai yang disediakan bisa lebih kompleks, jadi kita bisa menulis metode lebih singkat: public static double area (double radius) { return Math.PI * radius * radius; } Di lain pihak, variabel sementara seperti area lebih membuat debugging jadi lebih mudah. Pada kedua kasus, tipe nilai kembalian pada pernyataan return harus sesuai dengan tipe kembalian metode. Dengan kata lain, ketika anda mendeklarasikan bahwa nilai kembalian adalah double, anda membuat janji bahwa metode ini betul betul akan menghasilkan double. Jika anda mencoba return dengan tanpa nilai, atau nilai dengan nilai yang berbeda, kompiler akan memaksa anda mengubahnya (menampilkan kesalahan).
Kadang kadang berguna juga untuk memiliki beberapa statemen return, satu di setiap cabang kondisi: public static double absoluteValue (double x) { if (x < 0) { return x;
94
} else { return x; } Karena statemen return ada pada kondisi alternatif, maka hanya satu yang akan dieksekusi. Meskipun sah untuk memiliki lebih dari satu statemen return dalam satu metode, harus selalu anda ingat bahwa segera setelah dieksekusi, metode akan berhenti tanpa mengeksekusi pernyataan yang ada di bawah nya lagi.
Kode yang ada setelah sebuah pernyataan return, atau dimana pun dimana tidak akan pernah dieksekusi, disebut dead code. Beberapa kompiler akan memperingatkan anda jika ada bagian kode yang mengalami hal ini.
Jika anda meletakkan statemen return di dalam percabangan, anda harus menjamin bahwa setiap jalan yang mungkin pada program akan mencapai statemen return. Misalnya public static double absoluteValue (double x) { if (x < 0) { return x; } else if (x > 0) { return x; } // WRONG!! } progam di atas tidak sah karena jika x sama dengan 0, maka tidak ada kondisi yang benar dan metode akan kembali tanpa mencapai statemen return. Pesan kompiler untuk hal seperti ini adalah “return statement required in absoluteValue,” yang adalah pesan 95
memusingkan mengingat telah ada dua statemen return.
5.2 Pengembangan Program Pada titik ini anda seharusnya telah bisa untuk melihat pada metode Java yang lengkap dan melaporkan apa yang mereka kerjakan. Tetapi mungkin belum jelas bagaimana cara menulis nya. Saya akan menyarankan satu teknik yang saya sebut “incremental development”.
Sebagai contoh, bayangkan anda ingin mencari jarak antara dua titik, dengan koordinat (x1,y1) dan (x2,y2). Dengan rumus berikut
Langkah pertama adalah memikirkan bagaimana metode distance akan terlihat pada Java. Dengan kata lain apa saja input dan apa saja output nya.
Pada kasus ini, dua titik adalah parameter, dan sudah jamak untuk mewakili nya dengan 4 double, meskipun kita akan lihat nanti terdapat objek Point pada Java yang dapat kita gunakan. Nilai kembalian adalah jarak, yang akan berupa double juga.
Selanjutnya kita dapat menulis garis besar metode sebagai berikut: public static double distance (double x1, double y1, double x2, double y2) { return 0.0; }
96
Nilai kembalian return 0.0; dibutuhkan agar program dapat dikompilasi. Pada tahap ini program tidak melakukan apapun yang berguna, tapi akan lebih baik untuk mencoba mengkopilasi sehingga kita bisa mengetahui jika terdapat syntaks yang error sebelum kita membuatnya menjadi lebih rumit.
Untuk menguji metode yang baru, kita harus menggunakannya dengan nilai contoh pada metode main: double dist = distance (1.0, 2.0, 4.0, 6.0); Saya memilih nilai nilai ini sehingga jarak horizontal adalah 3 dan jarak vertikal adalah 4. maka hasilnya akan menjadi 5. Ketika anda menguji sebuah metode akan berguna jika mengetahui jawaban yang benar.
Ketika kita telah mengecek sintaks pada definisi metode, kita bisa memulai menambah baris kode. Setelah setiap tahap perubahan , kita mengkompilasi lagi dan menjalankan program. Dengan cara ini, kita mengetahui dengan tepat dimana masalah yang terjadi (jika terdapat error) yang pasti pada baris terakhir yang kita tambahkan.
Langkah berikut pada komputasi adalah untuk mencari jarak antara x2x1 dan y2y1. Saya akan menyimpan nilai nilai itu pada variabel sementara bernama dx dan dy. public static double distance (double x1, double y1, double x2, double y2) { double dx = x2 x1; double dy = y2 – y1; System.out.println ("dx is " + dx); System.out.println ("dy is " + dy); 97
return 0.0; } Saya menambahkan pernyataan cetak yang membuat saya bisa melihat nilai yang ada sebelum diproses (jika anda menggunakan IDE (Integrated Development Environment) anda bisa melakukan hal ini tanpa System.out.println dengan fitur debugger, catatan penerjemah). Sebagaimana yang telah saya sebutkan, saya telah mengetahui bahwa nilai nilai tersebut adalah 3 dan 4.
Ketika metode telah diselesaikan saya akan menghapus statemen cetak tersebut. Kode seperti ini disebut scaffolding, karena akan membantu dalam membuat program, tapi bukan merupakan bagian produk akhir. Kadang kadang merupakan hal yang baik untuk tidak menghapus kode scaffold, tetapi hanya dibuat komentar, hanya jika kira kira anda akan membutuhkannya lagi.
Langkah berikut pada pengembangan adalah memangkatkan dx dan dy. Kita bisa menggunakan metode Math.pow, tetapi akan lebih mudah dan cepat jika hany mengalikannya dengan dirinya sendiri. public static double distance (double x1, double y1, double x2, double y2) { double dx = x2 x1; double dy = y2 y1; double dsquared = dx*dx + dy*dy; System.out.println ("dsquared is " + dsquared); return 0.0; } 98
Lagi, saya akan mengkompilasi dan menjalankan program pada tahap ini dan mengecek nilai (yang seharusnya 25).
Langkah terakhir adalah menggunakan Math.sqrt yaitu metode untuk menghitung dan mengembalikan hasil. public static double distance (double x1, double y1, double x2, double y2) { double dx = x2 x1; double dy = y2 y1; double dsquared = dx*dx + dy*dy; double result = Math.sqrt (dsquared); return result; } lalu pada metode main, kita lalu akan mengecek nilai dari hasil metode di atas.
Semakin anda berpengalaman dalam pemrograman, anda akan makin mahir dan bisa menulis dan mendebug program lebih dari satu baris setiap kali. Namun demikian proses pengembangan secara bertahap ini bisa menghemat banyak waktu debugging.
Hal kunci pada proses adalah: ●
Mulai dengan program yang bisa bekerja dan buat perubahan bertahap yang kecil. Jika terjadi kesalahan, anda akan mengetahui dengan tepat di aman ia terjadi.
●
Gunakan variabel sementara untuk menyimpan nilai (nilai yang bukan hasil akhir) jadi anda bisa mencetak dan mengeceknya. 99
●
Ketika program bekerja, anda mungin ingin menghapus beberapa baris scaffolding atau menggabungkan beberapa baris/pernyattan menjadi satu tetapi hanya jika itu tidak membuat program sulit untuk dibaca.
5.3 Komposisi Seperti yang telah anda ketahui, ketika anda mendefinisikan sebuah metode, anda bisa menggunakannya sebagai bagian dari ekspresi, dan anda bisa membangun metode baru menggunakan metode yang telah ada. Misalnya, bagaimana jika seseorang memberikan anda dua titik, titik pusat dari lingkaran dan sebuah titik dari keliling, dan bertanya tentang luas lingkaran.
Jika titik tengah disimpan pada variabel xc dan yc, dan titik keliling adalah xp dan yp. Langkah pertama adalah mencari jari jari lingkaran, yang merupakan jarak antara dua titik. Untungnya, kita memiliki sebuah metode, distance yang melakukan: double radius = distance (xc, yc, xp, yp); Langkah kedua adalah mencari luas lingkaran dengan jari jari yang ada, dan mengembalikannya double area = area (radius); return area; Dengan menggabungkan semua nya ke dalam sebuah metode, maka kita dapatkan: public static double fred (double xc, double yc, double xp, double yp) {
double radius = distance (xc, yc, xp, yp);
double area = area (radius);
return area; 100
} Nama metode ini adalah fred, yang mungkin kelihatan ganjil. Saya akan menjelaskan pada bagian berikutnya.
Variabel sementara radius dan area berguna untuk pengembangan dan debugging, tetapi ketika program telah bekerja kita bisa membuat nya lebih singkat dengan menggabungkan pemanggilan metode. public static double fred (double xc, double yc, double xp, double yp) { return area (distance (xc, yc, xp, yp)); }
5.4 Overloading Pada bagian sebelumnya anda mungkin menyadari bahwa fred dan area melakukan hal yang sama, mencari luas lingkaran tetapi dengan dua parameter yang berbeda. Untuk area, kita harus menyediakan radius; untuk fred kita harus menyediakan dua titik.
Jika kedua metode melakukan hal yang sama, merupakan hal yang lazim untuk memberikan nama yang sama. Dengan kata lain, akan lebih baik jika fred bernama area. Memiliki lebih dari satu metode dengan nama yang sama, yang disebut overloading, adalah sah dalam Java. Jadi kita bisa menamai fred dengan: public static double area(double x1, double y1, double x2, double y2) {
return area (distance (xc, yc, xp, yp));
} 101
Ketika anda memanggil metode yang dioverloading, Java mengetahui versi yang mana yang anda inginkan dengan melihat pada argumen yang anda sediakan. Jika anda menulis double x = area (3.0); Java akan mencari metode yang bernama area yang menggunakan satu parameter double sebagai argumen, jadi ia akan menggunakan versi yang pertama. Yang mengartikan argumen sebagai radius. Jika anda menulis: double x = area (1.0, 2.0, 4.0, 6.0); Maka Java akan menggunakan versi yang kedua.
Banyak perintah bawaan Java yang dioverloading, yang berati ada versi yang berbeda beda yang menerima jumlah dan tipe parameter yang berbeda. Misalnya, ada versi print dan println yang menerima satu parameter dengan tipe apapun. Pada kelas Math, ada versi abs yang bekerja pada double dan ada juga yang bekerja untuk int.
Meskipun overloading adalah metode yang berguna, ia seharusnya dipakai dengan hati hati. Anda mungkin menemukan diri anda pusing sendiri jika anda mencoba mendebug satu versi metode padahal sebenarnya anda menggunakan versi yang berbeda.
Sebenarnya. Hal ini mengingatkan saya salah satu aturan dari debugging: yakinkan bahwa versi program yang anda perbaiki adalah versi program yang sedang berjalan! Kadangkadang anda mungkin menemukan diri anda membuat perubahan setelah perubahan yang lain pada suatu program, dan melihat hal yang sama setiap kali anda menjalankannya. Ini adalah tanda peringatan untuk satu alasan atau yang lain bahwa anda tidak menjalankan versi program yang sedang anda perbaiki. Untuk 102
mengeceknya, letakkan print (tidak menjadi masalah apa yang anda cetak) dan yakinkan bahwa program mencetak pernyataan print tersebut.
5.5 Ekspresi Boolean Sebagian besar operasi yang telah kita lihat menghasilkan nilai dengan tipe yang sama dengan operan nya. Misalnya + operator mengambil dua int dan menghasilkan sebuah int atau dua double yang menghasilkan sebuah double.
Pengecualian yang telah kita lihat adalah operator relasional, yang membandingkan int dan float dan mengembalikan apakah true atau false. True dan false adalah nilai khusus dalam Java. Keduanya merupakan tipe data boolean.
Ekspresi boolean dan variabel bekerja seperti hal nya tipe yang lain: boolean fred; fred = true; boolean testResult = false; Contoh pertama adalah deklarasi variabel sederhana; contoh kedua adalah sebuah penugasan, dan contoh ketiga adalah kombinasi dari deklarasi dan penugasan, yang kadang kadang disebut inisialisasi. Nilai true dan false adalah kata kunci dalam Java, jadi mereka mungkin tampil dengan warna yang berbeda, tergantung pada lingkungan pengembangan anda.
Sebagaimana yang telah saya sebutkan, hasil dari operator kondisional adalah booean, jadi anda bisa menyimpan hasil pembandingan pada sebuah variabel: boolean evenFlag = (n%2 == 0); // true if n is even 103
boolean positiveFlag = (x > 0); // true if x is positive dan menggunakannya sebagai bagian dari pernyataan kondisional: if (evenFlag) { System.out.println ("n was even when I checked it"); } Variabel yang digunakan pada cara ini sering disebut sebagai flag, karena ia menunjukkan ada atau tidak adanya suatu kondisi.
5.6 Operator Logika Ada tiga operatot logika pada Java: AND, OR, NOT, yang memiliki simbol &&, ||, dan !. Sematik (arti) dari operator operator ini sama dengan arti nya dalam bahasa Inggris. Misalnya x>0 d && x<10 berarti benar hanya jika x lebih besar dari 0 dan lebih kecil dari 10.
evenFlag || n%3 ==0 benar jika salah satu dari kedua kondisi benar, jika evenFlag bernilai benar atau n habis dibagi 3. NOT operatir memiliki efek untuk menegasikan atau menginvers ekspresi boolean, jadi !evenFlag bernilai benar jika evenFlag bernilai false, atau jika n bernilai ganjil.
Operator logika sering menyediakan cara untuk menyederhanakan pernyataan kondisi bersarang. Misalnya bagaimana anda menulis kode di bawah ini menggunakan satu kondisional. if (x > 0) { if (x < 10) { System.out.println ("x is a positive single digit."); 104
} }
5.7 Metode Boolean Metode dapat mengembalikan nilai boolean seperti hal nya tipe yang lain, yang sering menyembunnyikan pengetesan kompleks di dalam metode. Misalnya public static boolean isSingleDigit (int x) { if (x >= 0 && x < 10) { return true; } else { return false; } } Nama metode adalah isSingleDigit. Cara yang umum untuk memberi nama metode boolean seperti pertanyaan ya/tidak (yes/no question). Tipe kembalian adalah boolean yang berarti setiap pernyataan kembalian harus menyediakan ekspresi boolean.
Kode nya sendiri sama saja, meskipun akan lebih panjang sedikit dari seharusnnya. Ingat bahwa ekspresi x>=0 && x<10 memiliki tipe boolean, jadi tidak ada yang salah dengan mengembalikannya secara langsung, dan menghindari pernyataan menggunakan if. public static boolean isSingleDigit (int x) { return (x >= 0 && x < 10); } Pada metode main anda bisa memanggil metode dengan cara yang biasa: 105
boolean bigFlag = !isSingleDigit (17); System.out.println (isSingleDigit (2)); Baris pertama menugaskan nilai true pada bigFlag dengan alasan 17 bukan angka satu digit. Baris kedua mencetak true karena 2 adalah bilangan satu digit. Benar bahwa println dioverloading untuk juga menangani boolean. Cara yang paling umum untuk menggunakan metode boolean adalah di dalam pernyataan kondisional if (isSingleDigit (x)) { System.out.println ("x is little"); } else { System.out.println ("x is big"); }
5.8 Lagi tentang Rekursi Sekarang kita memiliki metode yang mengembalikan nilai, anda mungkin tertarik untuk mengetahui bahwa kita telah memiliki bahasa pemrograman yang lengkap, yang saya maksudkan bahwa segala hal yang dapat dihitung dapat diekspresikan dalam bahasa ini. Semua program yang telah ditulis dapat ditulis kembali menggunakan fitur fitur bahasa yang telah kita gunakan sejauh ini (sebenarnya, kita juga membutuhkan beberapa perintah untuk mengontrol alat seperti keyboard, mouse, dan diks).
Membuktikan klaim ini bukanlah pekerjaan yang mudah . Hal ini pertama kali diselesaikan oleh Alan Turing, salah satu computer scientisct pertama(beberapa orang akan beargumen bahwa ia adalah seorang matematikawan, tetapi banyak computer scientist yang memulai sebagai matematikawan). Hal ini disebur sebagai thesis Turing. 106
Jika anda belajar tentang teori komputasi anda akan memiliki kesempatan untuk melihat pembuktian ini.
Untuk memberikan anda ide apa yang bisa anda lakukan dengan alat yang telah kita pelajari sejauh ini, mari kita melihat pada beberapa metode untuk mengevaluasi definisi fungsi rekursif matematika. Definisi rekursif sama dengan definisi melingkar, yaitu definisi yang berisi apa yang didefinisikan.
Jika anda melihat pada definisi faktorial, anda mungkin mendapatkan seperti: 0! = 1 n! = n ∙ (n − 1)! (Faktorial biasanya disimbolkan dengan !, jangan bingung dengan operaror ! Pada Java yang berarti NOT.) definisi ini menyatakan bahwa faktorial 0 adalah 1, dan faktorial nilai yang lain, adalah n dikalikan dengan faktorial dari n1. Jadi 3! adalah 3 kali 2!, dimana 2! faktorial adalah 2 kali 1!, yang merupakan 1 kali 0!. Sehingga didapatkan 3! sama dengan 3 kali 2 kali 1 kali 1 yang menghasilkan 6.
Jika anda bisa menulis definisi rekursif dari sesuatu, anda biasanya dapat menulis program Java untuk menghitungnya. Langkah pertama adalah untuk membuktikan parameter apa yang akan dimiliki fungsi tersebut, dan apa nilai kembaliannya. Dengan sedikit pemikiran, anda bisa menyimpulkan bahwa faktorial menggunakan bilangan bulat sebagai parameter dan juga mengembalikan bilangan bulat: public static int factorial (int n) { } Jika argumen sama dengan 0, maka akan mengembalikan 1:
107
public static int factorial (int n) { if (n == 0) { return 1; } } Jika tidak, ini adalah bagian yang menarik, kita harus membuat pemanggilan rekursif untuk mencari nilai faktorial n1, dan kemudian mengalikannya dengan n. public static int factorial (int n) { if (n == 0) { return 1; } else { int recurse = factorial (n1); int result = n * recurse; return result; } } Jika kita melihat pada aliran eksekusi pada program, maka akan serupa dengan metode nLines pada bab sebelumnya, jika kita memanggil faktorial dengan nilai 3: Karena 3 tidak sama dengan 0, kita mengambil cabang kedua dan menghitung faktorial dari n1.... Karena 2 tidak sama dengan 0, kita mengambil cabang kedua dan menghitung faktorial dari n1.... Karena 1 tidak sama dengan 0, kita mengambil cabang kedua dan menghitung faktorial dari n1....
108
Karena 0 sama dengan 0, kita mengambil cabang pertama dan mengembalikan nilai 1 tanpa memanggil metode rekursif. Nilai kembalian 1 dikalikan dengan n, yaitu 1, dan hasilnya dikembalikan Nilai kembalian 1 dikalikan dengan n, yaitu 2, dan hasilnya dikembalikan. Nilai kembalian 2 dikalikan dengan n, yaitu 3, dan hasilnya adalah 6. dikembalikan pada main, yang memanggil faktorial(3).
Berikut ini diagram stack untuk urutan pemanggilan fungsi di atas: Nilai kembalina dipelihatkan dilewatkan kembali pada stack.
Perhatikan bahwa pada instance terakhir dari faktorial, variabel lokal recurse dan result tidak ada karena ketika n=0 cabang yang menciptakan mereka tidak dieksekusi.
5.9 Leap of faith Mengikuti aliran eksekusi adalah salah satu cara untuk membaca program, tetapi
109
sebagaimana telah anda lihat pada bagian sebelumnya, hal ini akan sulit untuk beberapa jenis program. Sebagai alternatif kita bisa menggunakan cara “leap of faith.” Ketika anda melihat pemanggilan metode, sebagai ganti mengikuti aliran eksekusi, anda mengasumsikan bahwa metode bekerja dengan benar dan mengembalikan nilai yang cocok.
Sebenarnya, anda telah mempraktekkan “leap of faith” ketika anda menggunakan metode bawaan. Ketika anda menggunakan metode Math.cos atau drawOval. Anda tidak perduli dengan implementasi metode ini. Anda hanya berasumsi bahwa metode ini bekerja, karena orang yang menulis metode ini adalah programmer yang baik. Ketika kita telah yakin bahwa metode ini benar dengan mencoba dan menguji kode—kita bisa menggunakan metode ini tanpa melihat kode itu kembali.
Hal yang sama berlaku ketika anda memanggil salah satu metode kepunyaan anda. Misalnya pada bagian 5.7 kita menulis metode isSingleDigit yang menghitung apakah sebuah bilangan ada diantara 0 dan 9.
Hal yang sam dengan program rekursif. Ketika anda melihat pada pemanggilan rekursif. Sebagai ganti melihat pada aliran program, anda harus berasumsi bahwa pemanggilan rekursif bekerja dengan baik (menghasilkan nilai yang benar), dan lalu tanyakan pada diri anda, “Asumsikan bahwa saya bisa mencari faktorial n1, dapatkah saya menghitung faktorial n?” Pada kasus ini, jelas bahwa anda bisa, dengan mengalikannya dengan n.
Tentu, merupakan hal yang aneh untuk mengasumsikan metode bekerja dengan benar ketika kita belum selesai menuliskannya, tetapi itulah mengapa kita menyebutnya “leap 110
of faith”
5.10 Satu Contoh Lagi Pada contoh sebelumnya saya menggunakan variabel sementara untuk mengetahui langkah, dan untuk membuat kode lebih mudah untuk didebug, tetapi saya bisa menghemat beberapa baris: public static int factorial (int n) { if (n == 0) { return 1; } else { return n * factorial (n1); } } Dari sekarang saya akan menggunakan versi yang lebih singkat, tetapi saya merekomendasikan anda untuk menggunakan versi yang lebih eksplisit ketika mengembangkan kode. Ketika anda telah membuat nya bekerja, anda bisa menciutkannya
Setelah faktorial, contoh klasik tentang definisi rekursif fungsi matematika adalah fibonacci. Yang memiliki definisi berikut: f ibonacci(0) = 1 f ibonacci(1) = 1 f ibonacci(n) = f ibonacci(n − 1) + f ibonacci(n − 2); Diterjemahkan ke Java sehingga menjadi public static int fibonacci (int n) { 111
if (n == 0 || n == 1) { return 1; } else { return fibonacci (n1) + fibonacci (n2); } } Jika anda mencoba untuk mengikuti aliran eksekusi, bahkan dengan nilai yang kecil, kepala anda akan meledak. Tetapi menurut prinsip “leap of faith”, jika kita berasumsi bahwa dua pemanggilan rekursif (ya, anda bisa melakukan dua pemanggilan rekursif) bekerja dengan benar, maka dengan jelas kita bisa mendapatakan hasil yang benar dengan menambahkan keduanya.
5.11 Daftar Kata return type : Bagian deklarasi metode yang menunjukkan nilai dengan tipe apa yang akan dikembalikan return value : Nilai yang disediakan sebagai hasil dari pemanggilan metode. dead code : Bagian program yang tidak akan pernah dieksekusi, sering terjadi arena diletakkan setelah return scaffolding : kode yang digunakan selama pengembangan program tetapi bukan merupakan bagian dari versi akhir void : tipe Kembalian khusu yang menunjukkan metode void; yaitu metode yang tidak mengembalikan nilai. overloading : Memiliki lebih dari satu metode dengan nama yang sama tetapi parameter yang berbeda. Ketika anda memanggil metode yang dioverloading. Java mengetahui versi mana yang digunakan dengan
melihat pada argumen yang 112
anda sediakan boolean : Tipe variabel yang hanya bisa berisi dua nilai true dan false operator kondisional : operator yang membandingkan dua nilai dan
menghasilkan
nilai boolean yang menunjukkan hubungan antara kedua operan. Operator logika : operator yang menggunakan nilai boolean dan menghasilkan nilai boolean. Inisialisasi : pernyataan yang mendeklarsikan variabel baru dan menugaskan nilai ke dalam nya pada waktu yang sama.
5.12 Latihan latihan 5.1 Tulis sebuah metode yang bernam isDivisible yang mengambil dua bilangan bulat n dan m mengembalikan nilai true jika n habis dibagi m dan false jika tidak. Latihan 5.2 apakah keluaran dari program berikut? Tujuan dari latihan ini adalah untuk melatih anda mengerti tentang operator logika dan aliran eksekusi pada metode. public static void main (String[] args) { boolean flag1 = isHoopy (202); boolean flag2 = isFrabjuous (202); System.out.println (flag1); System.out.println (flag2); if (flag1 && flag2) { System.out.println ("ping!"); } if (flag1 || flag2) { 113
System.out.println ("pong!"); } } public static boolean isHoopy (int x) { boolean hoopyFlag; if (x%2 == 0) { hoopyFlag = true; } else { hoopyFlag = false; } return hoopyFlag; } public static boolean isFrabjuous (int x) { boolean frabjuousFlag; if (x > 0) { frabjuousFlag = true; } else { frabjuousFlag = false; } return frabjuousFlag; } latihan 5.3 Jarak antara dua titik (x1,y1) dan (x2,y2) adalah
114
Tulislah metode bernama distance yang mengambil empat double sebagai parameter x1,y1, x2, y2 dan mencetak jarak antara titik. Anda harus berasumsi terdapat metode yang bernam sumSquares yang menghitung dan mengembalikan jumlah dari kuadrat argumen nya sebagai contoh: double x = sumSquares(3.0, 4.0); akan menugaskan 25 ke x. Tujuan dari latihan ini adalah menulis metode baru dengan menggunakan metode yang sudah ada. Anda harus hanya menulis satu metode : distance. Anda tidak harus menulis sumSquares atau main dan anda tidak harus memanggil distance
Latihan 5.4 Tujuan dari latihan ini adalah untuk mempraktekkan sintaks dari “fruitful methods” a) Cari solusi anda sendiri untuk latihan 3.5 dan pastikan anda masih bisa mengkompilasi dan menjalankannya. b) Ubahlah multadd menjadi metode yang mengembalikan nilai, jadi sebagai ganti mencetak hasilnya, ia akan mengembalikannya. c) Di mana saja di dalam program multadd dipanggil, ganti pemanggilannya jadi ia menyimpan hasilnya dalam variabel dan/atau mencetak hasil d) Ubah juga yikes dengan cara yang sama. Latihan 5.5 Tujuan dari latihan ini adalah untuk menggunakan diagram stack untuk mengerti tentang pengeksekusian dari program rekursif. public class Prod {
115
public static void main (String[] args) { System.out.println (prod (1, 4)); } public static int prod (int m, int n) { if (m == n) { return n; } else { int recurse = prod (m, n1); int result = n * recurse; return result; } } } a. gambar diagram stack yang menunjukkan keadaan program sesaat sebelum instance terakhir prod selesai. Apakah keluaran dari program? b. Jelaskan dengan beberapa kata apa yang sebenaranya dilakukan prod. c. tulis kembali prod tanpa menggunakan variabel sementara recurse dan result.
Latihan 5.6 Tujuan latihan ini adalah untuk menerjemahkan definisi rekursif ke dalam metode Java. Fungsi Ackerman didefinisikan untuk bilangan bulat non negatif sebagai berikut:
116
Tulis metode bernama ack yang mengambil dua int sebagai parameter, menghitung dan mengembalikan nilai dari fungsi Ackerman.
Uji implementasi anda dengan memanggilnya dari metode main dan mencetak nilai kembaliannya.
Peringatan: nilai keluaran akan bertambah besar dengan cepat. Anda harus mencobanya untuk nilai m dan n (tidak lebih dari 2). Latihan 5.7 a. Buat program bernama Recurse.java dan tulis metode metode berikut: // first: kembalikan karakter pertama dari String yang diberikan public static char first (String s) { return s.charAt (0); } // last: kembalikan String baru yang berisi semua kecuali huruf pertama dari // String yang diberikan public static String rest (String s) { return s.substring (1, s.length()); } // length: kembalikan panjang dari string yang diberikan public static int length (String s) { return s.length(); } b. Tulis beberap kode pada main yang mengetes setiap metode metode ini. Anda harus
117
yakin bahwa mereka bisa bekerja dengan baik dan anda bisa mengerti apa yang mereka kerjakan. c. Tulis metode yang bernama printString yang mengambil String sebagai parameter dan mencetak huruf huruf dari String tersebut satu setiap baris. Metode ini harus lah merupakan metode void. d. Tulis metode yang bernama reverseString yang mengambil sebuah String sebagai parameter dan mengembalikan String baru. String yang baru harus berisi huruf huruf yang sama dengan parameter nya tetapi dengan urutan yang terbalik. Misalnya keluaran kode di bawah String backwards = reverseString ("Allen Downey"); System.out.println (backwards); adalah yenwoD nellA Latihan 5.8 a. Buat program bernama Sum.java tulis dua metode di bawah public static int methOne (int m, int n) { if (m == n) { return n; } else { return m + methOne (m+1, n); } } public static int methTwo (int m, int n) { if (m == n) {
118
return n; } else { return n * methTwo (m, n1); } b. Tulis beberapa baris pada main untuj menguji metode metode ini. Panggil mereka beberapa kali dengan nilai yang berbedam dan lihat apa yang anda dapatkan. Dengan beberapa kombinasi dari pengetesan, cari tahu apa yang dilakukan metode ini dan beri mereka nama yang sesuai. Tambahkan komentar yang menjelaskan fungsi nya c. Tambahkan pernyataan println pada awal metode sehingga mereka mencetak argumen setiap kali dipanggil. Teknik ini berguna untuk mendebug program rekursif, karena ia mendemontrasikan aliran eksekusi.
Latihan 5.9 Tulis metode rekursif bernama power yang mengambil double x dan sebuah integer n dan mengembalikan xn.. Petunjuk: definisi rekursif dari power(x,n)=x*power(x,n10, juga ingat bahwa apapun yang dipengkatkan 0 adalah 1.
Latihan 5.10 (latihan ini berdasarkan pada halaman 44 buku Structure and Intepetation of Computer Programs karangan Abelson dan Sussman) Algoritma berikut dikenal sebagai algoritma Euclid. Algoirtma ini berdasarkan dari observasi bahwa, jika r adalah sisa ketika a dibagi dengan b maka faktor pembagi terbesar(fpb) dari a dan b sama dengan faktor pembesar dari b dan r. sehingga bisa dinyatakan dengan persamaan gcd(a,b)=gcd(b.r)
119
Yang sukses untuk mengurangi masalah penghitungan GCD ( Greatest Common Divisor) contoh penerapan algoritma, gcd(36, 20) = gcd(20, 16) = gcd(16, 4) = gcd(4, 0) = 4 Yang menghasilkan GCD dari 36 dan 20 adalah 4. Dapat diperlihatkan bahwa untuk berapapun angka awal, pengurangan berulang menghasilkan pasangan dimana bilangan yang kedua adalah 0. dan GCD adalah nomor yang lain pada pasangan itu. Tulis sebuah metode bernama gcd yang mengambil dua parameter bilangan bulat dan menggunakan algoritma Euclid untuk menghitung dan mengembalikan greatest common divisor (gcd)/Faktor Persekutuan Terbesar (FPB) dari kedua bilangan.
120
Bab 6 Iterasi 6.1
Multiple Assignment
Saya belum pernah mengatakan hal ini sebelumnya, tapi adalah sesuatu yang legal dalam Java untuk membuat lebih dari satu assignment terhadap satu variabel yang sama. Akibat yang akan timbul dengan adanya assignment berganda ini adalah terjadinya penggantian nilai lama dari suatu variabel dengan nilai yang baru. Perhatikan contoh berikut ini: int fred = 5; System.out.print(fred); fred = 7; System.out.println(fred); Output dari program ini adalah 57. Kok bisa? Ini terjadi karena pada kesempatan pertama kita mencetak nilai 5 menggunakan metode print sedangkan pada kesempatan berikutnya kita mencetak nilai 7 menggunakan metode println . Karena sifat multiple assigment inilah saya mendeksripsikan bahwa variabelvariabel dalam Java merupakan penampung (container) untuk nilainilai. Ketika Anda memberi (assign) sebuah nilai untuk suatu variabel, Anda berarti mengubah isi dari container tersebut, perhatikan gambar di bawah ini: int fred = 5;
fred
5
fred = 7;
fred
5 7
Ketika terjadi multiple assignment terhadap suatu variabel, adalah penting untuk 121
membedakan antara sebuah pernyataan assignment dengan sebuah pernyataan equality (kesamaan). Karena Java menggunakan simbol “ = ” sebagai simbol assignment, terkadang kita sering tergoda untuk menganggap pernyataan seperti a = b sebagai pernyataan equality. Padahal bukan! Untuk memperjelas masalah ini, simaklah beberapa sifat berikut: equality bersifat komutatif, sementara assignment tidak. Sebagai contoh, dalam lingkungan matematika; jika a = 7 maka 7 = a. Namun tidak dalam lingkungan Java! Jika a = 7; merupakan pernyataan yang legal maka pernyataan sebaliknya, yaitu 7 = a; tidaklah demikian. Lebih jauh lagi, dalam matematika, sebuah pernyataan yang bersifat equality akan selalu benar untuk selamanya. Sebagai contoh, jika a = b sekarang, maka a akan selalu sama dengan b. Dalam Java, sebuah pernyataan assignment memang dapat membuat dua variabel bernilai sama, tapi keadaannya tidak akan sama dengan apa yang telah terjadi dalam dunia matematika. int a = 5; int b = a; a = 3;
// a dan b sekarang sama
// a dan b sekarang tidak lagi sama
Baris terakhir kode di atas memang mengubah nilai variabel a, tapi tidak dengan variabel b. Nilai b masih tetap sama dengan nilai variabel a yang pertama, yaitu 5. Dalam lingkungan bahasa pemrograman lain, sebuah simbol alternatif lain sering digunakan sebagai simbol pernyataan assignment. Misalnya: “ < ” atau " := ". Hal ini dilakukan sebagai langkah antisipasi untuk mencegah kerancuan. Meskipun multiple assignment seringkali berguna, Anda harus tetap hatihati dalam menggunakannya. Jika nilai dari suatu variabel selalu berubahubah pada lokasi/bagian yang berbedabeda di dalam program, maka Anda akan menemui kesulitan ketika hendak mendebug dan membaca kodenya. 122
6.2
Iterasi
Salah satu fungsi komputer yang paling sering dimanfaatkan manusia adalah untuk melakukan sesuatu (baca: tugas) yang sifatnya berulangulang. Mengulangulang tugas yang sama atau identik tanpa melakukan satu kesalahan pun adalah sesuatu yang dapat dikerjakan dengan sangat baik oleh komputer, tapi tidak oleh manusia. Kita telah melihat programprogram yang menggunakan teknik rekursi untuk melakukan perulangan, seperti nLines dan countdown . Bentuk perulangan seperti inilah yang disebut dengan iterasi, dan Java menyediakan beberapa fitur bahasa yang dapat memudahkan kita untuk membuat programprogram yang iteratif. Dua fitur yang akan kita lihat pertama adalah pernyataan while dan pernyataan for .
6.3
Pernyataan While
Dengan pernyataan while , kita dapat menulis ulang countdown dengan cara sebagai berikut: public static void countdown (int n) { while (n > 0) { System.out.println (n); n = n 1; } System.out.println ("Blastoff!"); } Anda dapat menulis pernyataan while ini laiknya menulis kalimat dalam Bahasa Inggris. Maksudnya, kode di atas dapat dibaca dengan kalimat seperti ini, "While n is 123
greater than zero, continue printing the value of n and then reducing the value of n by 1. When you get to zero, print the word Blastoff!". Dalam Bahasa Indonesia berarti kalimatnya akan menjadi seperti ini, "Selama n masih lebih besar daripada 0, teruslah mencetak nilai n, kemudian kurangi nilai n dengan 1. Ketika Anda sudah sampai pada nilai 0, cetaklah kata Blastoff!". Untuk lebih jelasnya, berikut ini merupakan alur eksekusi yang terjadi dalam kode di atas: 1. Cek/Evaluasi syarat (condition) yang berada dalam tanda kurung, apakah bernilai true atau false . 2. Jika syarat itu bernilai false , Java akan keluar (exit) dari pernyataan while tersebut lalu melanjutkan eksekusi ke pernyataan selanjutnya. 3. Jika syarat tersebut bernilai true , Java akan mengeksekusi setiap pernyataan yang berada dalam kurung kurawal, lalu kembali ke langkah nomor 1. Bentuk aliran program seperti inilah yang disebut loop (baca: putaran atau siklus). Disebut demikian karena pernyataan pada langkah yang ketiga akan membuat program kembali lagi ke bagian atas dari program itu sendiri. Perhatikan bahwa jika syarat langsung bernilai false pada saat masuk ke dalam loop, pernyataan yang berada di dalam loop tersebut tidak akan dieksekusi. Pernyataan yang berada di dalam loop biasanya disebut body (badan) dari loop. Badan loop harus merubah nilai dari satu atau lebih variabel sehingga syarat yang dievaluasi akan sampai pada nilai false dan loop pun akan berakhir. Jika tidak, loop tersebut akan melakukan putaran eksekusi selamanya. Keadaan inilah yang disebut dengan infinite loop (baca: putaran tanpa batas). Contoh infinite loop dalam kehidupan seharihari yang mungkin sering kita ditemui adalah instruksi dalam kemasan shampoo, "Cuci rambut Anda, bilas dengan air, lalu ulangi."
124
Dalam kasus countdown , kita dapat membuktikan bahwa loop pasti akan dihentikan karena kita tahu bahwa nilai dari variabel n adalah nilai yang terbatas (finite). Di situ kita dapat melihat bahwa nilai n akan semakin mengecil setiap kali melewati loop (tiap iterasi). Keadaan ini tentu akan membawa kita kepada nilai n yang sama dengan 0. Namun dalam kasuskasus loop lainnya, terkadang tidaklah mudah untuk mengatakannya. Perhatikan contoh berikut ini: public static void sequence (int n) { while (n != 1) { System.out.println (n); if (n%2 == 0) {
// n is even
n = n / 2; } else {
// n is odd
n = n*3 + 1; } } } Syarat loop di atas adalah n != 1, jadi loop akan terus diproses sampai nilai variabel n = 1 . Kondisi ini akan menyebabkan syarat bernilai false . Dalam tiap iterasinya, program akan mencetak nilai dari n sekaligus memeriksa apakah nilai n tersebut termasuk genap atau ganjil. Jika n termasuk genap, maka nilai n akan dibagi dengan 2. Tapi jika termasuk ganjil, maka nilai n akan diganti dengan nilai dari n*3 + 1. Sebagai contoh, jika nilai awal (argumen yang dilewatkan dalam metode sequence ) sama dengan 3, maka hasil yang akan dicetak adalah 3, 10, 5, 16, 8, 4, 2. Karena nilai n kadang bertambah dan kadang menurun, tidak ada bukti yang kuat bahwa n akan sama dengan 1, atau dengan kata lain agar proses dalam program dapat 125
dihentikan. Untuk beberapa nilai n, kita dapat membuktikan bahwa terminasi (penghentian) terhadap program akan terjadi. Sebagai contoh, jika nilai awal dari metode tersebut adalah bilangan yang termasuk kelipatan 2, maka nilai n akan selalu genap setiap kali melintasi loop, sampai kita sampai ke nilai n = 1. Dalam contoh sebelumnya kita dapat melihat manakala nilai n telah sama dengan 16 maka nilai n berikutnya pasti akan membawa variabel n ke 1 (16, 8, 4, 2, 1). catatan: angka 1 tidak dicetak dalam output. Nilainilai lain (selain kelipatan 2) tentunya tidak dapat dimasukkan ke dalam bentuk umum seperti di atas. Pertanyaan menariknya adalah; dapatkah kita membuktikan bahwa program di atas pasti akan dapat dihentikan (terminate) untuk semua nilai n? Sampai sejauh ini, belum ada yang dapat membuktikan kebenaran pertanyaan di atas!
6.4
Tabel
Salah satu manfaat loop yang paling baik adalah untuk membuat (generate) dan mencetak tabel data. Sebagai contoh, sebelum komputer dapat digunakan sebaik seperti sekarang, orangorang harus menghitung logaritma, sinus, cosinus, serta fungsifungsi matematika lainnya menggunakan tangan (baca: manual). Untuk membuat pekerjaanpekerjaan di atas menjadi lebih mudah, dibuatlah beberapa buku yang berisi tabeltabel panjang yang dapat membantu kita untuk melihat nilainilai dari beberapa fungsi. Untuk membuat tabeltabel ini dibutuhkan waktu yang lama. Dan membosankan! Akibatnya, hasilnya pun terkadang akan dipenuhi banyak error (kesalahan). Ketika komputer telah muncul beserta monitornya, salah satu reaksi awal yang muncul adalah, "Hebat! Kita dapat menggunakan komputer untuk membuat tabel tabel, sehingga tidak akan ada kesalahan lagi". Euforia ini secara umum memang akhirnya terbukti, tapi ini hanyalah secuil kemampuan saja dari begitu banyak hal yang dapat 126
dilakukan dengan komputer. Setelah komputer (juga kalkulator digital) digunakan secara luas oleh banyak kalangan, tabeltabel tersebut menjadi barang kuno. Setidaknya bisa dikatakan hampir demikian. Karena untuk beberapa operasi, komputer juga masih menggunakan tabeltabel untuk melihat jawaban yang paling mendekati, untuk kemudian kembali lagi menggunakan komputer ketika hendak memperbaiki nilai pendekatan tersebut. Dalam beberapa kasus, pernah terjadi kesalahan fatal dalam tabel tabel yang dibuat menggunakan komputer. Kasus populer menyangkut ini adalah kesalahan prosessor Intel Pentium generasi awal dalam melakukan operasi pembagian (division) untuk nilai floatingpoint. Meskipun sebuah "tabel logaritma" tidaklah terlalu penting lagi seperti beberapa tahun yang lalu, tapi kita masih dapat menggunakannya sebagai contoh yang bagus untuk menunjukkan bagaimana cara iterasi bekerja. Program berikut ini akan mencetak urutan nilainilai di kolom bagian kiri dan logaritmanya di kolom bagian kanan: double x = 1.0; while (x < 10.0) { System.out.println (x + " " + Math.log(x)); x = x + 1.0; }
Output dari program ini tampak sebagai berikut: 1.0 0.0 2.0 0.6931471805599453 3.0 1.0986122886681098 4.0 1.3862943611198906 5.0 1.6094379124341003 6.0 1.791759469228055 7.0 1.9459101490553132 127
8.0 2.0794415416798357 9.0 2.1972245773362196 Dengan melihat nilainilai di atas, bisakah Anda memberi tahu default nilai basis dari fungsi log yang digunakan oleh Java? Karena nilai kelipatan 2 memiliki peran yang penting dalam ilmu komputer, terkadang kita ingin menemukan logaritma dengan nilai basis 2. Untuk menemukannya, kita bisa menggunakan formula berikut ini: log 2 x = log e x/log e 2 Ubahlah pernyataan print dalam kode di atas sehingga menjadi seperti berikut ini: System.out
.print ln(x + " " +
Math.log(x)/M
ath.lo g(2.0) );
pernyataan di atas akan menghasilkan: 1.0 0.0 2.0 1.0 3.0 1.5849625007211563 4.0 2.0 5.0 2.321928094887362 6.0 2.584962500721156 7.0 2.807354922057604 8.0 3.0 9.0 3.1699250014423126
Dari hasil di atas kita dapat melihat bahwa 1, 2, 4, dan 8 merupakan bilangan kelipatan 2. Ini karena logaritma mereka yang berbasis 2 menghasilkan bilangan dengan nilai pembulatan (rounded). Jika kita ingin menemukan logaritma dari bilanganbilangan lainnya selain 2, kita dapat memodifikasi program tersebut dengan cara seperti di bawah 128
ini; double x = 1.0; while (x < 100.0) { System.out.p
rintln (x + " " + Math.log(x)
Math.log(2.0)
);
/
x = x * 2.0; }
Ketimbang selalu menambahkan sesuatu ke variabel x setiap kali melewati loop yang akan menghasilkan deret aritmatika, sekarang kita akan mengalikan variabel x dengan sesuatu sehingga menghasilkan deret geometri. Berikut ini merupakan hasil dari kode di atas: 1.0 0.0 2.0 1.0 4.0 2.0 8.0 3.0 16.0 4.0 32.0 5.0 64.0 6.0
Tabel logaritma mungkin sudah tidak terlalu berguna untuk saat ini, tapi untuk seorang ilmuwan komputer (computer scientist), mengetahui bilangan kelipatan 2 atau pangkat 2 adalah wajib! Jika kamu sedang dalam keadaan idle (baca: malas atau bete), berusahalah utuk mengingat bahwa bilangan berpangkat atau kelipatan 2 dalam komputer hanya akan sampai pada angka 65536 (ini adalah hasil dari 216).
6.5
Tabel Dua Dimensi
Sebuah tabel berdimensi dua adalah sebuah tabel yang di dalamnya terdapat nilainilai 129
yang berada dalam perpotongan (interseksi) antara satu baris dan satu kolom. Contoh yang baik untuk memperlihatkan tabel ini adalah tabel perkalian (multiplication). Anggap saja Anda ingin mencetak tabel perkalian untuk nilai 1 sampai dengan 6. Cara yang bagus untuk memulaiya adalah dengan menulis sebuah loop sederhana yang mencetak kelipatan 2, semua dalam 1 baris. int i = 1; while (i <= 6) { System.out.print (2*i + " "); i = i + 1; } System.out.println (""); Baris pertama menginisialisasi sebuah variabel, yakni i. Variabel ini akan bertugas sebagai counter, atau variabel untuk melakukan loop. Ketika loop dieksekusi, nilai dari i akan terus naik mulai dari 1 hingga 6. Ketika i sama dengan 7, loop akan dihentikan. Setiap melewati loop, kita mencetak nilai dari 2 * i diikuti dengan tiga spasi. Karena kita menggunakan perintah print ketimbang println , semua output akan dicetak dalam satu baris yang sama. Seperti yang sudah saya singgung dalam bagian 2.4, dalam beberapa lingkungan pemrograman, output dari perintah print akan terus disimpan dalam memori sampai perintah println dipanggil. Jika program sudah berhenti, tapi Anda lupa memanggil perintah println , Anda mungkin tidak akan pernah melihat nilai yang disimpan tersebut. Output dari program di ini adalah: 2 4 6 8 10 12 So far, so good. Tahap berikutnya adalah membuat suatu bentuk enkapsulasi dan
130
generalisasi.
6.6
Enkapsulasi dan Generalisasi
Enkapsulasi (encapsulation) berarti mengambil beberapa bagian dari kode yang sudah ditulis untuk kemudian mengemasnya (wrapitup) dalam sebuah metode. Kita sebelumnya sudah melihat dua contoh enkapsulasi. Pertama, ketika kita menulis metode printParity
dalam bagian 4.3 dan isSingleDigit
dalam bagian 5.7.
Generalisasi berarti mengambil satu hal yang spesifik dari suatu bagian kode dalam metode, misalnya mencetak kelipatan 2, untuk kemudian membuatnya menjadi lebih umum. Dalam kasus ini berarti dengan cara membuat kode tersebut agar dapat mencetak nilai kelipatan dari n. public static void printMultiples (int n) { int i = 1; while (i <= 6) { System.out.print (n*i + " "); i = i + 1; } System.out.println (""); }
Untuk mengenkapsulasi, yang saya lakukan hanyalah menambah sesuatu dalam baris pertama. Dalam hal ini saya menambahkan nama metode, parameter, dan tipe pengembalian. Sedangkan untuk menggeneralisasi, yang saya lakukan adalah mengganti nilai 2 dengan parameter n. Jika saya memanggil metode ini dengan argumen 2 maka saya akan mendapatkan hasil yang sama dengan program sebelumnya. Dengan argumen 3, hasil yang akan kita lihat
131
adalah sebagai berikut: 3 6 9 12 15 18 dan dengan argumen 4, outputnya adalah sebagai berikut: 4 8 12 16 20 24 Mulai sekarang, Anda mungkin sudah dapat menerka bagaimana cara kita untuk membuat tabel perkalian: kita akan memanggil printMultiples
secara
berulangulang dengan argumen yang berbedabeda. Tidak hanya itu, di sini kita juga akan menggunakan loop yang berbeda untuk melakukan iterasi ke dalam barisbaris tersebut. int i = 1; while (i <= 6) { printMultiples (i); i = i + 1; } Pertamatama, perhatikan betapa miripnya loop ini dengan yang berada dalam metode printMultiples
. Saya hanya mengganti perintah print dengan sebuah
pemanggilan metode. Cool! Output dari program ini adalah sebagai berikut: 1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36
Bagaimana? Tampak seperti tabel perkalian bukan? Meskipun bentuknya juga kelihatan 132
agakagak miring sedikit. Jika bentukbentuk seperti ini mengganggu Anda, Java telah menyediakan metodemetode yang dapat memberi Anda kendali lebih baik atas masalahmasalah output, tapi saya belum mau bertamasya ke sana sekarang. Tunggu ya!
6.7
Metode
Dalam bagian sebelumnya saya menyebutkan, "semua kebaikan yang dapat diberikan oleh metode." Sebelumnya, mungkin Anda akan bertanyatanya, apa saja sih kebaikan kebaikan yang dimaksud? Berikut ini adalah beberapa alasan mengapa metodemetode tersebut mempunyai posisi yang sangat penting: •
Dengan memberi sebuah nama untuk satu set perintahperintah/pernyataan tertentu, Anda telah membuat program Anda semakin mudah untuk dibaca dan diperiksa (debug).
•
Memecah program Anda yang panjang ke dalam beberapa metode dapat memudahkan Anda untuk membaginya ke dalam beberapa bagian terpisah. Dengan keadaan seperti ini, Anda dapat memeriksa bagianbagian tersebut dalam lingkungan yang terisolir (bebas dari bagian lainnya). Untuk kemudian merangkainya kembali dalam program "besar" yang Anda inginkan.
•
Metode dapat memfasilitasi rekursi sekaligus iterasi.
•
Metode yang dirancang dengan sangat baik biasanya dapat digunakan oleh programprogram lainnya. Sekali saja Anda mampu menulis dan memeriksanya dengan baik, otomatis Anda akan dapat menggunakannya kembali.
6.8
Enkapsulasi .... sekali lagi!
Untuk mendemonstrasikan enkapsulasi lagi, Saya akan mengambil kode dari bagian sebelumnya lalu membungkusnya dalam sebuah metode: public static void printMultTable () { 133
int i = 1; while (i <= 6) { printMultiples (i); i = i + 1; } } Proses yang sedang saya demonstrasikan adalah sebuah strategi pengembangan (development plan) yang umum digunakan. Anda mengembangkan suatu kode secara bertahap dengan cara menambah beberapa baris dalam bagian main atau bagian lainnya, lalu ketika Anda tahu bahwa proses ini bekerja dengan baik, Anda lalu mengambil dan memasukkannya ke dalam sebuah metode. Alasan mengapa cara ini bermanfaat untuk Anda adalah karena terkadang Anda tidak mengetahui secara pasti kapan Anda akan membagi program Anda secara terpisah ke dalam beberapa metode sekaligus. Pendekatan dengan model seperti ini dapat memudahkan Anda untuk melakukan proses perancangan yang beriring /berbarengan dengan proses pembuatan program.
6.9
Variabel Lokal
Saat ini, mungkin Anda sedang kebingungan memikirkan tentang bagaimana sebuah variabel yang sama, yakni i, dapat digunakan oleh dua metode yang berhubungan sekaligus pada saat yang bersamaan (printMultiples printMultTable
dan
). Bukankah sebelumnya saya tidak pernah mengatakan kepada
Anda bahwa Anda hanya dapat mendeklarasikan sebuah variabel satu kali saja? Dan tidakkah hal yang demikian dapat menimbulkan masalah ketika salah satu dari metode tersebut mengganti nilai dari variabel miliknya yang kita permasalahkan tersebut?
134
Heeee ... bingung ya .... Jawaban untuk kedua pertanyaan tersebut adalah "tidak," karena variabel i di metode printMultiples
dan i dalam printMultTable
adalah dua variabel
yang tidak sama. Mereka memang mempunyai dua nama yang sama, tapi mereka tidak mengacu kepada satu lokasi penyimpanan yang sama. Oleh karena itulah, mengubah nilai salah satu dari mereka tidak akan memberi pengaruh apapun kepada nilai yang lainnya. Variabelvariabel yang dideklarasikan di dalam sebuah metode disebut variabel lokal karena mereka terlokalisasi dalam metodenya masingmasing. Anda tidak dapat mengakses sebuah variabel lokal dari luar metode yang menjadi "rumahnya". Namun Anda dapat memiliki lebih dari satu variabel dengan nama yang sama asalkan mereka tidak berada dalam satu metode yang sama. Adalah sebuah ide yang brillian untuk tetap menggunakan namanama variabel yang berbeda meskipun variabelvariabel itu tidak berada dalam satu metode yang sama. Ini sematamata dilakukan untuk menghindari kebingungan. Namun dalam beberapa kasus, terkadang akan lebih baik jika kita menggunakan nama variabel yang sama. Sebagai contoh, adalah sebuah tradisi atau keumuman dalam dunia pemrograman untuk menggunakan namanama variabel seperti i, j, dan k sebagai namanama variabel untuk loop. Jika Anda bersikeras untuk menghindari penggunaan ketiga variabel ini dalam sebuah metode hanya karena Anda telah menggunakannya di metode lainnya, mungkin Anda justru akan membuat program tersebut semakin sulit untuk dibaca.
6.10 Generalisasi. Sekali lagi dengannya .... Sebagai satu contoh generalisasi lagi, silahkan membayangkan sebuah program yang dapat mencetak tabel perkalian dengan ukuran berapapun, tidak hanya berupa tabel 6 x
135
6. Anda dapat menambahkan sebuah parameter ke dalam metode printMultTable
:
public static void printMultTable (int high) { int i = 1; while (i <= high) { printMultiples (i); i = i + 1; } } Saya mengganti nilai 6 dengan paramater yang saya namai; high . Jika saya memanggil printMultTable
dengan argumen 7, saya akan mendapatkan output
sebagai berikut: 1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36 7 14 21 28 35 42 Keren bukan? Kecuali bahwa mungkin saya menginginkan hasil di atas agar tampak lebih kotak (jumlah baris dan kolom yang sama; 7 x 7 bukan 7 x 6). Untuk mewujudkannya, saya harus menambahkan parameter lain dalam metode printMultiples
, untuk menspesifikasikan berapa jumlah kolom yang harus
dimiliki oleh tabel tersebut. Agar Anda lebih waspada, saya juga akan menamai parameter ini dengan nama high.
136
Hal ini akan menunjukkan kepada Anda semua bahwa dua atau lebih metode yang berbeda dapat memiliki parameterparameter dengan nama yang sama (persis seperti variabelvariabel lokal): public static void printMultiples
(int n, int high)
{ int i = 1; while (i <= high) { System.out.print (n*i + " "); i = i + 1; } newLine (); } public static void printMultTable (int high) { int i = 1; while (i <= high) { printMultiples (i, high); i = i + 1; } } Perhatikan bahwa ketika saya menambahkan sebuah parameter baru, saya harus mengubah baris pertama dari metode tersebut (antarmuka atau prototipenya). Selain itu, saya juga harus mengubah tempat di mana metode itu dipanggil dalam metode printMultTable
. Seperti yang diharapkan, program ini mampu membuat sebuah
tabel 7 x 7 yang tampak lebih "kotak": 1 2 3 4 5 6 7 2 4 6 8 10 12 14 137
3 6 9 12 15 18 21 4 8 12 16 20 24 28 5 10 15 20 25 30 35 6 12 18 24 30 36 42 7 14 21 28 35 42 49 Ketika Anda telah menggeneralisasi sebuah metode dengan tepat, terkadang Anda akan menjumpai program yang Anda hasilkan ternyata memiliki kapabilitas atau kemampuan yang tidak persis seperti Anda harapkan. Sebagai contoh, Anda mungkin memperhatikan bahwa tabel perkalian yang kita buat sebelumnya memiliki sifat simetris. Hal ini terjadi karena sifat komutatif dalam perkalian yakni ab = ba . Jadi, semua entri yang berada dalam tabel akan dicetak 2 kali. Anda dapat menghemat tinta dengan cara mencetak tabel tersebut setengah dari ukuran normalnya. Untuk melakukannya, Anda cukup mengganti sebuah baris dalam metode printMultTable
. Ubahlah perintah:
printMultiples(i, high); menjadi
printMultiples(i, i);
dengan pengubahan ini Anda akan mendapatkan hasil sebagai berikut: 1 2 4 3 6 9 4 8 12 16 5 10 15 20 25 6 12 18 24 30 36 7 14 21 28 35 42 49
138
Saya akan tinggalkan bagian ini khusus untuk Anda agar Anda dapat menemukan bagaimana program tersebut dapat menghasilkan output yang sedemikian rupa.
6.11 Daftar KataKata Istilah Loop
Arti Sebuah pernyataan yang dieksekusi berulangulang selama atau sampai beberapa syarat/keadaan (condition) dipenuhi.
Infinite loop
Sebuah loop yang syarat/kondisinya selalu bernilai benar.
Body
Pernyataan yang berada dalam loop.
Iterasi
Proses satu kali eksekusi yang terjadi dalam badan (body) loop, termasuk di dalamnya adalah pengecekan nilai syarat/kondisi.
Enkapsulasi
Membagi sebuah program yang besar dan kompleks ke dalam komponenkomponen kecil (seperti metode) untuk kemudian mengisolasinya dari komponenkomponen lainnya (contoh, dengan cara menggunakan variabel lokal).
Variabel lokal
variabel yang dideklarasikan di dalam sebuah metode dan eksis hanya di dalam metode tersebut. Variabel lokal tidak dapat diakses dari luar metode yang menjadi "rumah" mereka. Variabel ini juga tidak akan berinterferensi (bertabrakan) dengan metodemetode lainnya.
Generalisasi
Mengganti sesuatu yang spesifik tapi tidak penting (misalnya nilai konstan) dengan sesuatu yang lebih cocok/tepat secara umum (seperti variabel atau parameter). Generalisasi membuat kode lebih fleksibel, memungkinkan untuk digunakan kembali, dan terkadang lebih mudah dan nyaman untuk dibaca.
Development plan
Suatu proses untuk mengembangkan sebuah program. Dalam
139
Istilah
Arti bab ini, saya mendemonstrasikan sebuah teknik pengembangan yang berbasis pada pengembangan kode untuk melakukan sesuatu yang sederhana, khusus, lalu mengenkapsulasi dan menggeneralisasinya. Dalam bagian 5.2 saya mendemonstrasikan sebuah teknik yang saya sebut pengembangan bertingkat. Di babbab akhir saya akan menunjukkan teknik pengembangan yang lain.
6.12 Latihan Latihan 6.1 public static void main (String[] args) { loop (10); } public static void loop (int n) { int i = n; while (i > 0) { System.out.pr
intln (i);
if (i%2 == 0) { i = i/2; } else { i = i+1; } } }
a) Buatlah sebuah tabel yang dapat menunjukkan nilai dari variabel i dan n selama loop sedang dieksekusi. Tabel tersebut harus berisi satu kolom untuk setiap variabel dan 140
satu baris untuk setiap iterasi. b) Apa output dari program ini?
Latihan 6.2 a) Enkapsulasilah fragmen kode di bawah ini, kemudian ubahlah bentuknya ke dalam metode yang menggunakan sebuah variabel bertipe String sebagai argumennya dan mengembalikan (tapi bukan mencetak) nilai terakhir dari count. b) Dalam sebuah kalimat, deskripsikan secara abstrak apa yang telah dihasilkan oleh metode buatan Anda tersebut. c) Asumsikan bahwa Anda telah menggeneralisasi metode berikut ini sehingga ia dapat digunakan untuk semua String. Menurut Anda, kirakira apa lagi yang dapat Anda lakukan sehingga metode tersebut dapat lebih tergeneralisasi? String s = "((3 + 7) * 2)"; int len = s.length (); int i = 0; int count = 0; while (i < len) { char c = s.charAt(i); if (c == '(') { count = count + 1; } else if (c == ')') { count = count 1; } i = i + 1; } System.out.pr
intln (count);
141
Latihan 6.3 Misalkan Anda diberi sebuah bilangan, misalkan a, lalu Anda ingin menemukan nilai akarnya. Salah satu cara yang dapat digunakan adalah dengan menebak mentahmentah nilai tersebut dengan sebuah bilangan misalkan, xo. Lalu, menaikkan tingkat kebenaran bilangan tersebut dengan menggunakan rumus berikut ini: x1 = ( x 0 + a/x 0 ) /2
(6.2)
Sebagai contoh, jika kita hendak menemukan akar dari 9. Kita bisa memulai tebakan kita dengan xo = 6, kemudian x1 = (6 + 9/6) / 2 = 15 / 4 = 3.75. Lihat, mulai mendekati bukan? Kita dapat mengulangi prosedur ini, dengan menggunakan x1 untuk menghitung nilai x2, x2 untuk x3, lalu seterusnya. Dalam contoh yang kita gunakan, berarti x2 = 3.075 dan x3 = 3.00091. Begitu seterusnya, sampai mendekati nilai pendekatan yang kita tetapkan. Buatlah sebuah metode dengan nama squareRoot yang menggunakan sebuah variabel bertipe double sebagai parameter dan mengembalikan sebuah nilai pendekatan dari parameter tersebut, dengan menggunakan algoritma di atas tentunya. Anda dilarang menggunakan metode Math.sqrt yang merupakan metode bawaan (builtin) Java. Gunakan a/2 sebagai nilai awal tebakan Anda. Metode Anda harus melakukan perulangan (iterasi) sampai metode ini menghasilkan dua nilai pendekatan yang berurutan dan juga memiliki selisih kurang dari 0.0001. Anda dapat menggunakan metode Math.abs untuk menghitung nilai mutlak.
Latihan 6.4 Dalam latihan 5.9 kita telah menulis sebuah versi rekursif untuk mencari bilangan pangkat (power) yang menggunakan variabel x bertipe double dan n bertipe integer
142
serta mengembalikan nilai xn. Sekarang, tulislah sebuah metode iteratif untuk melakukan hal yang sama.
Latihan 6.5 Bagian 5.10 memperlihatkan sebuah metode rekursif yang menghitung nilai dari fungsi faktorial. Tulislah sebuah versi iteratif dari metode faktorial.
Latihan 6.6 Salah satu cara untuk menghitung ex adalah dengan menggunakan bentuk ekspansi tak hingga berikut ini: e x = 1 + x + x
2
/2! + x 3 /3! + x 4 /4! + .... (6.3)
Jika variabel loop kita namai dengan i, maka urutan term yang kei akan sama dengan xi/i!. a) Buatlah sebuah metode dengan nama myexp yang mampu menjumlahkan n term pertama dari bentuk di atas. Anda dapat menggunakan metode factorial dari bagian 5.10 atau versi iteratifnya. Terserah! b) Anda dapat membuat metode ini lebih efisien jika saja Anda sadar bahwa dalam setiap iterasi, nilai numerator dari term sama dengan nilai predecessornya (sebelumnya) dikali dengan x, sementara denominatornya adalah sama dengan nilai predecessornya dikali dengan i. Gunakan hasil analisa ini untuk meniadakan penggunaan Math.pow dan factorial, lalu periksalah kembali dengan cermat bahwa Anda masih tetap akan mendapatkan hasil yang sama. c) Buatlah sebuah metode dengan nama check, yang menggunakan sebuah parameter, yakni x. Metode Anda ini akan memiliki kemampuan untuk mencetak nilai dari x, Math.exp(x) dan myexp(x) untuk berbagai variasi nilai dari x. Hasil yang akan 143
ditampilkan harus tampak seperti berikut: 1.0 2.708333333333333 2.718281828459045 Petunjuk: Anda dapat menggunakan String "\t" untuk mencetak sebuah karakter tab diantara kolomkolom dalam tabel tersebut. d) Ubahubahlah (variasikan) nilainilai term (argumen kedua yang dikirimkan oleh metode check kepada myexp), lalu lihatlah pengaruhnya terhadap akurasi dari hasil tersebut. Cocokkan nilai ini hingga nilai yang diperkirakan (estimasi) sesuai dengan jawaban ketika x sama dengan 1. e) Tulislah sebuah loop dalam main yang memanggil metode check dengan nilai 0.1, 1.0, 10.0, dan 100.0. Lihat bagaimana akurasi (ketepatan) dari hasil yang ditampilkan ketika nilai x juga bervariasi. Bandingkan jumlah digit dari agreement ketimbang membandingkan perbedaan antara nilai aktual dan nilai perkiraan. f) Tambahkan sebuah loop dalam main yang berfungsi untuk memeriksa myexp dengan nilai 0.1, 1,0, 10.0, dan 100.0. Berilah tanggapan untuk ketepatan yang dihasilkan. Latihan 6.7 Salah satu cara yang digunakan untuk mengevaluasi bentuk e^(x^2) adalah dengan menggunakan bentuk ekspansi takhingga berikut: e^(x^2) = 12 x + 3x 2/ 2! 4x 3 /3! + 5x 4 /4! ...... (6.4) Dengan kata lain, kita perlu menambahkan beberapa term dimana term yang kei akan sama dengan (1)i (i+1) xi / i!. Buatlah sebuah metode dengan nama Gauss yang menggunakan x dan n sebagai argumen. Metode ini akan mengembalikan nilai penjumlahan dari nterm pertama. Anda tidak diperkenankan untuk menggunakan metode factorial atau pow. 144
Bab 7 String 7.1
Memanggil metode dalam objek
Dalam Java dan bahasa pemrograman berorientasi objek lainnya, objek merupakan koleksi dari data dan sekumpulan metode yang saling berkaitan. Metodemetode digunakan untuk melakukan operasioperasi tertentu dalam objek, mengerjakan perhitungan dan terkadang memodifikasi data milik objek itu sendiri. Diantara semua tipe data yang telah kita bahas sampai saat ini, hanya String yang dapat dikategorikan sebagai suatu objek. Berdasarkan pengertian objek yang telah kita ungkap di atas, mungkin di antara Anda akan ada yang bertanya, “data apa yang dimiliki oleh suatu String yang juga berstatus objek?” dan “metodemetode apa saja yang dapat kita panggil untuk memfungsikan objekobjek yag berupa String?” Data yang terkandung dalam objek String adalah hurufhuruf dari string itu sendiri. Sebenarnya ada cukup banyak metode yang dapat digunakan untuk mengoperasikan atau beroperasi dalam objekobjek String, tapi di buku ini Saya hanya akan menggunakan beberapa di antaranya saja. Sisanya dapat Anda simak dalam halaman berikut: http://java.sun.com/javase/6/ docs/api/java/lang/String.html Metode pertama yang akan kita bahas adalah charAt. Metode ini dapat Anda gunakan untuk mengambil/mengekstrak hurufhuruf yag berada dalam sebuah String. Untuk menyimpan hasil pengambilan tersebut, kita membutuhkan sebuah variabel yang dapat menyimpan hurufhuruf tersebut sebagai sebuah satuan tersendiri (kebalikan dari String itu sendiri yang merupakan sekumpulan itemitem yang berurutan). Satu satuan huruf
145
itu disebut sebagai karakter (character), dan tipe data yang digunakan untuk menyimpannya disebut dengan char. char bekerja dengan cara yang sama seperti tipe datatipe data yang sudah kita lihat sebelumnya: char fred = 'c'; if (fred == 'c') { System.out.println (fred); }
Nilai karakter tampil dalam kurungan tanda petik satu 'c'. Ini berbeda dengan nilai variabel bertipe data String yang diapit dengan tanda petik ganda ( “ ” ). Nilai variabel bertipe data char hanya boleh mengandung satu huruf atau satu simbol saja. Berikut ini adalah cara menggunakan metode charAt: String fruit = "banana"; char letter = fruit.charAt(1); System.out.println (letter); Sintaks fruit.charAt mengindikasikan bahwa Saya sedang memanggil metode charAt dalam sebuah objek yang bernama fruit. Argumen yang Saya lewatkan (pass) untuk metode ini adalah 1. Hal ini mengindikasikan kepada Anda bahwa saya tampaknya ingin mengetahui huruf pertama dari string tersebut. Hasilnya adalah sebuah karakter yang saya simpan dalam sebuah variabel bertipe data char, yakni letter. Ketika saya mencetak nilai variabel letter itu, saya pun terkejut: a Hei .... bukankah a itu huruf kedua? Anda juga mungkin akan terkejut, kecuali kalau Anda adalah seorang Computer Scientist. Untuk beberapa alasan tertentu, para ilmuwan 146
komputer cenderung untuk memulai perhitungan dari 0. huruf yang berada pada urutan ke0 dalam string “banana” adalah b. sedangkan urutan yang ke1 adalah a, urutan ke2 adalah n, dst. Jadi, jika Anda menginginkan huruf yang berada pada urutan ke0 dari sebuah string, Anda harus menggunakan 0 sebagai argumen yang Anda lewatkan. Simaklah kode berikut ini:
char letter = fruit.charAt(0);
7.2
Length
Metode kedua untuk String yang akan kita bahas adalah length. Metode ini berfungsi untuk mengembalikan jumlah karakter yang menyusun suatu string. sebagai contoh: int length = fruit.length(); length tidak membutuhkan argumen apapun. Hal ini dapat dilihat dari tanda “ ( ) ” yang memang tidak terisi dengan apapun. Metode ini akan mengembalikan nilai yang bertipe integer, dalam kasus di atas, nilai yang dikembalikan adalah 6. Perhatian! Adalah sesuatu yang legal dalam Java untuk menamai sebuah variabel dengan nama yang sama seperti nama metode. Namun Anda harus tetap hatihati karena hal ini dapat menimbulkan kebingungan untuk pengguna yang sedang membaca kode tersebut. Untuk mencari nilai akhir dari sebuah string, terkadang Anda mungkin akan tergoda untuk melakukan sesuatu seperti yang tampak dalam dua baris berikut: int length = fruit.length(); char last = fruit.charAt(length);
// wrong!!
Itu tidak akan berhasil. Mengapa? Karena memang tidak ada huruf ke6 dalam string “banana”. Ingat! Kita melakukan perhitungan dari angka 0. Jadi, string yang memiliki 6 huruf akan diurut menggunakan nomor urutan 0 hingga 5, bukan 1 hingga 6. Untuk mendapatkan karakter terakhir, Anda harus mengurangkan length dengan angka 1. Perhatikan kode berikut: 147
int length = fruit.length(); char last = fruit.charAt(length 1);
7.3
Traversal
Pola/hal umum yang sering dijumpai dalam bentukbentuk string adalah kegiatan berikut; mulailah dari awal, pilihlah setiap karakter secara bergantian, lakukan sesuatu terhadap karakter itu, lalu lanjutkan hingga karakter terakhir. Pola pemrosesan ini dikenal dengan sebutan traversal. Cara alami untuk mengenkode sebuah traversal adalah dengan menggunakan pernyataan while: int index = 0; while (index < fruit.length()) { char letter = fruit.charAt (index); System.out.println (letter); index = index + 1; } Loop di atas akan mentraverse string dan mencetak setiap huruf dalam satu baris. Perhatikan bahwa syarat yang digunakan dalam kode di atas adalah index < fruit.length(). Artinya, ketika index sama dengan panjang dari string, syarat tersebut akan bernilai false. Akibatnya, badan dari loop itu tidak akan dieksekusi. Karakter terakhir yang kita akses adalah karakter dengan urutan fruit.length() 1. Nama dari variabel loop adalah index. Satu index merupakan suatu variabel atau nilai yang digunakan untuk menentukan satu anggota dari sebuah himpunan berurutan (baca: himpunan karakter yang berada dalam string). Index ini akan menentukan karakter mana (baca: nama karakter tersebut) yang Anda inginkan. Himpunan ini haruslah merupakan suatu himpunan berurutan agar setiap huruf dapat memilki index dan setiap index dapat mengacu kepada satu karakter saja. 148
Sebagai latihan, tulislah sebuah metode yang menggunakan String sebagai sebuah argumen dan yang mampu mencetak semua huruf dalam posisi yang berkebalikan (backwards). Cetak semua huruf tersebut dalam satu baris tampilan saja.
7.4
RunTime errors
Kembali lagi ke bagian 1.3.2, di sana saya telah menyinggung sedikit mengenai run time errors. Istilah ini digunakan untuk menunjukkan suatu error dalam program yang tidak akan ditampilkan kecuali kalau program itu telah dijalankan (running). Dalam Java, runtime error disebut juga dengan exceptions. Sejauh ini, Anda mungkin belum terlalu sering melihat runtime errors. Maklum saja, ini karena kita memang belum banyak mengerjakan sesuatu yang dapat menyebabkan terjadinya runtime errors itu sendiri. Nah, sekaranglah saatnya. Jika Anda menggunakan perintah charAt lalu pada saat yang bersamaan Anda juga menggunakan sebuah index yang bernilai negatif atau lebih besar dari length1, maka Anda akan menjumpai sebuah exceptions. Dalam kasus ini; StringIndexOutOf BoundsException. Cobalah dan lihat bagaimana exceptions ini bekerja. Jika program Anda menyebabkan terjadinya suatu exceptions, maka program tersebut akan mencetak sebuah pesan error yang mengindikasikan tipe exceptions sekaligus lokasi terjadinya error tersebut dalam program Anda.
7.5
Membaca Dokumentasi
Jika
Anda
mengunjungi
situs
http://java.sun.com/javase/6/docs/api/java/lang/String.html, lalu Anda mengklik link charAt, Anda akan mendapatkan dokumentasi yang tampak (mungkin mirip) seperti di bawah ini: public char charAt(int index)
149
Returns the character at the specified index. An index ranges from 0 to length() 1. Parameters: index the index of the character. Returns: the character at the specified index of this string. The first character is at index 0. Throws: StringIndexOut
OfBoun dsExce ption if the index is
out of range.
Baris pertama adalah prototipe milik metode charAt yang berisi nama dari metode ini, tipe data dari parameter dan nilai pengembalian yang digunakan. Baris berikutnya menginformasikan manfaat/fungsi dari metode. Berikutnya, merupakan informasi mengenai parameterparameter dan nilai pengembalian. Dalam kasus ini, penjelasan yang diberikan mungkin terkesan berulangulang, tapi dokumentasi seperti inilah yang merupakan bentuk standar dokumentasi dalam Java. Baris terakhir akan menjelaskan kepada kita mengenai exceptions, kalau memang benarbenar ada, yang dapat disebabkan karena penggunaan metode ini.
7.6
Metode indexOf
Dalam beberapa hal, indexOf dapat disebut sebagai lawan (anti) dari metode charAt. Alasannya? Ini karena charAt bekerja dengan cara mengambil nilai sebuah index lalu mengembalikan karakter yang diacu (diindeks) pada indeks tersebut. Sementara indexOf bekerja dengan cara mengambil sebuah karakter lalu mengembalikan nilai indeks tempat dimana karakter tersebut berada. charAt akan gagal manakala indeks yang dimintanya berada di luar jangkauan. Hal ini akan menyebabkan terjadinya exceptions pada program. Lain dengan metode indexOf yang akan mengalami kegagalan jika karakter yang diminta tidak berada dalam string yang dimaksud. Pada saat kasus seperti ini terjadi, metode ini akan mengembalikan nilai 150
1. String fruit = "banana"; int index = fruit.indexOf('a'); Kode di atas digunakan untuk menemukan indeks (lokasi) huruf ' a ' dalam string “banana”. Menariknya, huruf yang kita inginkan ternyata muncul sebanyak 3 kali. Jadi, metode ini (seharusnya Anda juga) mungkin akan mengalami “kebingungan” karena tidak tahu harus mengembalikan nilai indeks yang mana untuk huruf ' a '. Dalam kasus kasus seperti inilah terkadang sebuah dokumentasi menjadi sangat bermanfaat. Bila kita mengacu pada informasi dalam dokumentasi, kita akan tahu bahwa metode indexOf akan mengembalikan nilai indeks dari kemunculan karakter ' a ' yang pertama kali (a diindeks pada urutan yang pertama. Ingat, Java memulai indeks dari urutan 0 bukan 1). Untuk menemukan indeks dari kemunculan berikutnya, ada versi alternatif dari metode indexOf (penjelasan lebih lanjut mengenai kasus overloading semacam ini, silahkan membuka kembali bagian 5.4). Kita membutuhkan argumen kedua yang akan berfungsi untuk menginformasikan lokasi pada string dimana kita akan memulai pencarian. Jika kita memanggil int index = fruit.indexOf('a', 2); kode ini akan mulai melakukan pencarian pada huruf yang berada di urutan kedua (sekali lagi ingat, dalam Java ini berarti huruf ' n ' bukan ' a '), dan akan langsung menemukan karakter a yang kedua pada indeks ketiga. Jika huruf yang dicari tepat berada pada indeks dimana kita memulai pencarian, maka indeks ini sendirilah yang menjadi jawaban (nilai yang dikembalikan oleh indexOf). Dus, int index = fruit.indexOf('a', 5); akan mengembalikan nilai 5. Kalau kita membaca kembali dokumentasi resmi mengenai metode ini, mungkin tidak ada salahnya jika kita sedikit bereksperimen dengan cara memberikan nilai indeksmulai pencarian yang lebih besar ketimbang 151
jangkauan string yang kita maksudkan: Metode indexOf mengembalikan nilai indeks dari karakter pertama yang berada dalam string yang direpresentasikan melalui objek yang lebih besar atau sama dengan indeksmulai, atau 1 jika karakter yang dimaksud tidak berada dalam string tersebut. indexOf returns the index of the rst occurrence of the character in the character sequence represented by this object that is greater than or equal to fromIndex, or 1 if the character does not occur.
Salah satu cara untuk mengetahui maksud 'aneh' dokumentasi di atas adalah dengan mengujicobanya. Berikut hasil uji coba saya: ●
Jika indeksmulai lebih besar atau sama dengan length(), hasil yang akan dikembalikan adalah 1. Hasil ini mengindikasikan bahwa huruf yang dicari/diinginkan tidak berada dalam indeks yang lebih besar daripada indeks mulai.
●
Jika indeksmulai bernilai negatif, hasilnya adalah 1. hasil ini mengindikasikan bahwa kemunculan pertama huruf yang dicari berada dalam indeks yang lebih besar daripada nilai indeksmulai.
Jika Anda membaca ulang dokumentasi tersebut, Anda akan memahami bahwa dua keadaan di atas memang sesuai dengan definisi yang ada dalam dokumentasi. Meskipun mungkin definisi tersebut awalnya tidaklah terlalu jelas. Nah, karena kita semua sudah mengetahui cara kerja indexOf dengan benar, sekarang barulah kita bisa 152
menggunakannya dalam programprogram yang akan kita buat.
7.7
Looping dan Counting
Program berikut ini akan menghitung frekwensi kemunculan huruf ' a ' dalam string:
String fruit = "banana"; int length = fruit.length(); int count = 0; int index = 0; while (index < length) { if (fruit.charAt(index) == 'a') { count = count + 1; } index = index + 1; } System.out.println (count); Program ini menunjukkan sebuah istilah populer dalam dunia pemrograman, yakni counter. Variabel count awalnya diinisialisasi dengan nilai 0 lalu kemudian secara bertahap akan dinaikkan nilainya tiap kali program menemukan ' a '. Ketika kita keluar dari loop, count akan berisi nilai yang berupa jumlah total ' a ' yang ditemukan dalam string. Sebagai latihan awal, enkapsulasilah kode ini ke dalam sebuah metode dengan nama countLetters. Lalu generalisasilah kode itu sehingga dapat menerima string dan huruf sebagai argumenargumen. Untuk latihan yang kedua, tulisulanglah metode itu dengan memasukkan indexOf
153
untuk mencari letak huruf yang diinginkan ketimbang melakukan pemeriksaaan karakterkarakter tersebut satupersatu.
7.8
Operator Increment dan Decrement
Sebelum kita pergi lebih jauh, ada baiknya jika kita pahami terlebih dahulu apa yang dimaksud dengan increment dan decrement. ●
Increment berarti menaikkan/menambahkan nilai suatu variabel dengan nilai satu (1) secara berulangulang sampai syarat yang ditetapkan bernilai false.
●
Decrement berarti menurunkan/mengurangi nilai suatu variabel dengan nilai satu (1) secara berulangulang sampai syarat yang ditetapkan bernilai false.
Baik increment maupun decrement sebenarnya hanya operator biasa. Namun, dalam Java, keduanya memang mendapatkan operator khusus, yakni ' ++ ' untuk increment dan ' ' untuk decrement. Operator ++ akan menambahkan satu (1) untuk nilai yang sedang disimpan oleh suatu variabel yang bertipe int atau char. Sementara operator ' ' akan mengurangi satu. Keduanya tidak dapat digunakan untuk variabel bertipe double, boolean, atau String. Secara teknis, adalah legal dalam Java untuk mengincrement suatu variabel, untuk kemudian langsung menggunakannya dalam sebuah ekspresi. Sebagai contoh, Anda boleh membuat ekspresi seperti berikut: System.out.println (i++); Melihat ekspresi seperti ini, hasil yang akan ditampilkan tentunya belum jelas, apakah increment yang kita lakukan akan bekerja sebelum nilai tersebut dicetak ataukah sesudahnya. Mengingat ekspresiekspresi seperti ini dapat menimbulkan kebingungan, saya tidak menganjurkan kepada Anda untuk memakainya. Dan untuk membuat Anda lebih tidak ingin memakainya, saya tidak akan memberi tahu Anda hasil ekspresi di atas. Namun jika Anda sungguhsungguh hendak mengetahuinya, coba saja sendiri! 154
Menggunakan operator increment, kita dapat menulis ulang lettercounter: int index = 0; while (index < length) { if (fruit.charAt(index) == 'a') { count++; } index++; } Anda akan menjumpai pesan error jika menulis ekspresi seperti di bawah ini: index = index++; // WRONG!! Sayangnya, ekspresi tersebut dianggap legal secara sintaks. Jadi, kompiler Java mungkin tidak akan memberikan peringatan untuk Anda sebelumnya. Dampak yang timbul dari pernyataan ini adalah tidak berubahnya nilai variabel index yang seharusnya hendak kita ubah. Kesalahan seperti ini terkadang menjadi bug yang sulit untuk dilacak keberadaannya. Ingatlah, Anda dapat menulis ekspresi index = index + 1; atau index++; tapi sebaiknya jangan sampai mencampur/menggabungnya.
7.9
Strings are Immutable
Ketika Anda melihatlihat kembali dokumentasi untuk metode String, Anda mungkin akan memperhatikan metode toUpperCase dan toLowerCase. Metode ini sering menjadi biang masalah. Kok bisa? Karena keduanya tampak seolaholah telah mengubah string yang sudah ada ke bentuk lainnya. Padahal, sebenarnya tidak ada satupun metode yang dapat mengubah suatu string, karena string memang bersifat immune alias tidak bisa 155
diubahubah. Simak yang berikut ini! Ketika Anda memanggil toUpperCase dalam sebuah String, Anda akan mendapatkan sebuah String baru (untuk sementara anggap saja demikian) sebagai hasil dari nilai yang dikembalikan oleh metode. Sebagai contoh: String name = "Alan Turing"; String upperName = name.toUpperCase (); Setelah baris kedua dieksekusi, upperName akan berisi nilai “ALAN TURING”, tapi tidak juga, karena sebenarnya variabel name masih berisi “Alan Turing”.
7.10 Strings are Incomparable Terkadang ada saatsaat dimana membandingkan dua string merupakan hal yang penting. Perbandingan ini digunakan misalnya; untuk melihat apakah dua string yang dibandingkan tersebut sama ataukah tidak, atau untuk melihat string manakah yang berada di depan string lainnya bila dilihat secara alfabetis (in alphabetical order). Senang rasanya jika kita dapat menggunakan operatoroperator perbandingan yang sudah ada, seperti == dan >, tapi sayang, Java tidak mengizinkannya. Untuk membandingkan String, kita harus menggunakan metode equals dan compareTo. Contoh: String name1 = "Alan Turing"; String name2 = "Ada Lovelace"; if (name1.equals (name2)) { System.out.println ("The names are the same."); } int flag = name1.compareTo (name2); if (flag == 0) { 156
System.out.println ("The names are the same."); } else if (flag < 0) { System.out.println ("name1 comes before
name2."); } else if (flag > 0) { System.out.println ("name2 comes before
name1."); } Sintaks yang Anda lihat di atas mungkin kelihatan aneh (yaa, gitu deh). Untuk membandingkan dua string, Anda harus memanggil sebuah metode melalui salah satu string itu lalu melewatkan yang lainnya sebagai argumennya. Nilai kembalian dari metode equals terkesan lugas dan jelas; true jika kedua string berisi karakterkarakter yang sama, dan false jika tidak. Namun nilai yang dikembalikan oleh compareTo terkesan agak ganjil alias aneh. Parameter yang dibandingkan adalah karakter pertama dalam string saja. Jika kedua string itu sama, maka nilai kembalian adalah 0. Jika string pertama (string tempat metode compareTo dipanggil) mendahului string kedua dalam urutan alfabetis, nilai kembalian akan negatif. Jika tidak, maka nilainya positif. Dalam contoh ini, nilai pengembaliannya adalah 8. Ini disebabkan karena huruf kedua dalam string “Ada Lovelace” mendahului huruf kedua string “Alan Turing” sebanyak 8 huruf (huruf D dan L terpisah sebanyak 8 huruf dalam alfabet; cara menghitung; E F G H I J K L). Menggunakan metode compareTo terkadang malah merepotkan. Meskipun begitu, sisi positif dari metode ini adalah bahwa interface yang digunakannya bisa dibilang cukup lumayan sebagai standar untuk membandingkan berbagai tipe objek. Jadi, ketika Anda sudah memahami cara menggunakannya, Anda akan lebih siap untuk yang lainnya.
157
Sebagai pelengkap, saya harus mengakui bahwa adalah legal dalam Java, meskipun jarang sekali benar, menggunakan operator ' = = ' untuk tipe data String. Anda mengerti maksud saya? Tidak! Itu wajar, karena pembahasannya akan berada di bagianbagian berikutnya. Sabar ya .....
7.11 Daftar KataKata Istilah Object
Arti Sebuah koleksi yang mengandung data yang saling berkaitan dan sekumpulan metode yang dapat beroperasi di dalamnya. Objek yang telah kita gunakan sejauh ini adalah objek Graphic yang disediakan oleh system dan String.
Index
Sebuah variabel atau nilai yang digunakan untuk memilih satu anggota dari suatu himpunan terurut. Misalnya, satu karakter dalam string.
Traverse
Melakukan suatu operasi yang sama secara berulangulang untuk seluruh elemen yang berada dalam sebuah himpunan.
Counter
Sebuah variabel yang digunakan untuk menghitung sesuatu. Variabel ini biasanya diinisialisasi dengan nilai 0 lalu dinaikkan satusatu secara bertahap (increment).
Increment
menaikkan/menambahkan nilai suatu variabel dengan nilai satu (1) secara berulangulang sampai syarat yang ditetapkan bernilai false. Operatornya adalah ++
Decrement
menurunkan/mengurangi nilai suatu variabel dengan nilai satu (1) secara berulangulang sampai syarat yang ditetapkan bernilai false. Operatornya adalah
Exceptions
Sebuah run time error. Exceptions menyebabkan terhentinya 158
proses eksekusi sebuah program.
7.12 Latihan Latihan 7.1 Inti dari latihan ini adalah untuk mencoba beberapa operasi yang berkaitan dengan String dan memasukkan beberapa bagian yang belum dibahas dalam bagian ini. a) Buatlah sebuah program dengan nama Test.java, lalu buatlah sebuah metode main yang berisi ekspresiekspresi yang mengkombinasikan beberapa tipe data menggunakan operator ' + '. Sebagai contoh, apa yang akan terjadi jika Anda “menambahkan” sebuah String dan sebuah char? Apakah ekspresi ini akan menghasilkan penambahan (addition) atau penggabungan (concatenation). b) Buatlah tabel yang lebih besar ketimbang tabel di bawah ini! Lalu isilah setiap irisan (interseksi) yang terjadi antara tipe datatipe data ini dengan informasi yang memberitahu legaltidaknya penggunaan operasi ' + ' . Jika legal, maka termasuk kategori yang manakah operasi tersebut. Apakah penambahan atau penggabungan. c) Bayangkan dan pikirkanlah beberapa pilihan yang telah dibuat oleh developer Java ketika mereka hendak mengisi tabel ini. Berapa entri di dalamnya yang tampak masuk akal, jika kemungkinan untuk masuk akal itu sendiri mungkin tidak ada? Berapa banyak diantaranya yang tampak seperti hasil dari pemilihan acak atau suka suka (arbitrary) dari beberapa kemungkinan yang masuk akal? Berapa banyak diantaranya yang kelihatan sebagai hasil pilihan bodoh? d) Waktunya bermain tekateki: normalnya, pernyataan x++ equivalen dengan x = x + 1. Kecuali jika x adalah char. Dalam kasus ini, x++ adalah sesuatu yang legal. Namun tidak dengan x = x + 1.
159
e) Ujilah! Lalu lihatlah pesan error yang keluar. Jangan lupa, berusahalah juga untuk memahami apa yang sebenarnya terjadi dengan error itu.
Latihan 7.2 Apakah output yang akan dihasilkan program ini? Ungkapkanlah dalam sebuah kalimat, secara abstrak, sebenarnya apa sih bing itu (tapi bukan bagaimana cara ia bekerja)? public class Mystery { public static String bing (String s) { int i = s.length() 1; String total = ""; while (i >= 0 ) { char ch = s.charAt (i); System.out.println (i + " " + ch); total = total + ch; i; } return total; } public static void main (String[] args) { System.out.println (bing ("Allen")); } }
Latihan 7.3 160
Salah seorang temanmu menunjukkan metode berikut ini kepadamu. Ia juga menjelaskan bahwa jika variabel number adalah semua bilangan yang terdiri dari dua digit, maka program akan mengeluarkan bilangan tersebut secara terbalik (backward). Dia mengklaim bahwa kalau number sama dengan 17, output akan sama dengan 71. Benarkah itu? Jika tidak, jelaskan apa yang sebenarnya dikerjakan oleh program. Lalu modifikasilah program ini sehingga bisa berfungsi dengan baik dan tepat. int number = 17; int lastDigit = number%10; int firstDigit = number/10; System.out.println (lastDigit + firstDigit);
Latihan 7.3 Apakah output dari program ini? public class Rarefy { public static void rarefy (int x) { if (x == 0) { return; } else { rarefy (x/2); } System.out.print (x%2); } public static void main (String[] args) { rarefy (5);
161
System.out.println (""); } } Jelaskan dalam 4 s.d. 5 kata tentang apa yang sebenarnya dikerjakan oleh metode ini.
Latihan 7.5 a) Buatlah sebuah program dengan nama Palindrome.java. b) Buatlah sebuah metode dengan nama first dan last yang menggunakan sebuah String sebagai parameter. Metode first hanya akan mengembalikan karakter awal dari string sementara last hanya akan mengembalikan karakter terakhir dari string itu. c) Buatlah sebuah metode dengan nama middle yang menggunakan String sebagai parameter. Metode ini akan mengembalikan sebuah substring yang mengandung semua anggota string tersebut kecuali karakter awal dan akhirnya. Petunjuk: bacalah dokumentasi mengenai metode substring dalam kelas String. Eksekusilah beberapa uji coba yang dapat membuat Anda mengerti bagaimana cara substring bekerja sebelum Anda menulis middle. Apa yang akan terjadi jika Anda memanggil middle dalam sebuah string yang hanya berisi dua huruf atau karakter? Satu karakter? Tanpa karakter? Hmmm ... d) Pengertian umum untuk palindrome adalah sebuah kata yang apabila dibaca baik dari depan maupun dari belakang akan menghasilkan ejaan baca yang sama. Contohnya; “otto”, “palindromeemordnilap”. Cara alternatif untuk mendefinisikan kasuskasus seperti ini adalah dengan menentukan sebuah metode khusus dan spesifik yang dapat digunakan untuk menguji kasus itu sendiri. Misalnya, mungkin kita semua sepakat berkata, “satu huruf yang tunggal termasuk palindrome, dan sebuah kata yang terdiri dari dua huruf termasuk palindrome jika kedua huruf adalah sama, dan semua kata yang lain adalah palindrome jika huruf pertama dan terakhir adalah sama sementara
162
kata yang berada diantaranya termasuk palindrome.” Buatlah sebuah metode dengan teknik rekursif, isPalindrome, yang menggunakan sebuah String sebagai parameter dan mampu mengembalikan sebuah nilai boolean yang dapat dijadikan penanda apakah kata yang Anda masukkan termasuk palindrome ataukah bukan. e) Jika Anda sudah mempunyai metode pemeriksa palindrome yang dapat berfungsi dengan baik, carilah cara untuk menyederhanakannya dengan cara mengurangi jumlah persyaratan (conditions) yang harus diperiksa. Petunjuk: mungkin akan berguna bagi Anda untuk menetapkan string kosong sebagai palindrome. f) Di atas selembar kertas, rancanglah sebuah strategi iteratif untuk memeriksa bentuk palindrome. Ada beberapa pendekatan yang mungkin bisa dipakai untuk menyelesaikan ini, jadi buatlah rencana yang matang terlebih dahulu sebelum menulis kodenya. g) Implementasikan strategi yang sudah Anda putuskan ke dalam sebuah metode dengan nama isPalindromeIter.
Latihan 7.6 Sebuah kata dapat dikategorikan sebagai “abecedarian” jika hurufhuruf dalam kata tersebut muncul dalam urutan yang alfabetis (dari A ke Z). Sebagai contoh, 19 kata dari bahasa inggris berikut ini merupakan “abecedarian” yang semuanya terdiri dari 6 huruf. abdest, acknow, acorsy, adempt, adipsy, agnosy, befist, behint, beknow, bijoux, biopsy, cestuy, chintz, deflux, dehors, dehort, deinos, diluvy, dimpsy a) Deskripsikan sebuah algoritma untuk memeriksa apakah kata (String) yang dijadikan argumen dapat digolongkan sebagai “abecedarian”. Asumsikan bahwa semua kata hanya boleh berisi huruf kecil (lowercase). Algoritma Anda bisa bersifat rekursif
163
ataupun iteratif. b) Implementasikan algoritma Anda dalam metode yang disebut dengan isAbecedarian.
Latihan 7.7 dupledrome adalah sebuah kata yang hanya boleh mengandung huruf ganda, seperti “llaammaa” atau “ssaabb”. Saya menduga bahwa tidak ada satu pun dupledrome dalam katakata bahasa inggris yang sering digunakan. Untuk menguji dugaan itu, saya menginginkan sebuah program yang dapat membaca setiap satu kata dalam kamus lalu memeriksanya, kemudian memberi tahu apakah kata itu termasuk dalam dupledromity juga. Tulislah sebuah metode isDupledrome yang mengambil sebuah String sebagai parameter. Metode ini akan mengembalikan sebuah nilai bertipe boolean yang mengindikasikan apakah suatu kata memiliki sifat dupledromitas.
Latihan 7.8 Ketika namanama telah disimpan dalam komputer, terkadang namanama itu ditulis secara langsung, yakni dengan menempatkan nama depan di depan nama belakang, seperti “Allen Downey”, atau sebaliknya, dengan cara menempatkan nama belakang di depan nama depan namun di antara keduanya diberi tanda koma, seperti “Downey, Allen”. Keadaan ini dapat menyulitkan kita untuk membandingkan namanama tersebut dan menempatkannya dalam urutan yang alfabetis. Masalah yang masih ada kaitannya dengan hal semacam ini adalah bahwa beberapa individu memiliki nama dengan penempatan huruf kapital yang lucu, seperti teman saya, Beth DeSombre, atau bahkan tanpa satu huruf kapital pun, seperti poet e.e. cummings. Ketika komputerkomputer digunakan untuk membandingkan karakter karakter, mereka cenderung menempatkan semua huruf kapital dalam susunan yang salah. 164
Jika Anda mau, lihatlah buku telepon umum, seperti buku direktori kampus, lalu cobalah pahami bagaimana teknik yang dipakai oleh buku itu untuk menuliskan nama nama yang ada di dalamnya. Perhatikan namanama yang mengandung beberapa kata sekaligus seperti Van Houten
juga namanama dengan huruf kapital yang non
standar, seperti desJardins. Coba lihat, apakah Anda sanggup untuk merancang aturan/ramburambu pengurutannya. Jika Anda memiliki akses ke buku telepon negara negara di eropa, coba juga itu, dan telitilah kalaukalau aturan yang digunakan juga berbeda dengan standar Amerika. Hasil dari semua bentuk nonstandar ini adalah bahwa terkadang tidaklah tepat untuk mengurutkan namanama tersebut menggunakan aturan String yang biasa. Satu solusi yang umum adalah dengan cara menyimpan dua versi sekaligus untuk tiaptiap nama: pertama, untuk dicetak; kedua, format internal yang digunakan untuk melakukan pengurutan. Dalam latihan kali ini, Anda diminta menulis sebuah metode yang akan membandingkan dua nama dengan cara mengkonversikan keduanya ke dalam format standar. Kita akan bekerja dari bawah ke atas (bottomup), menulis beberapa metode penolong untuk kemudian baru menulis metode compareName. a) Buatlah sebuah program dengan nama Name.java. Dalam dokumentasi untuk String, bacalah tentang metode find, toLower, dan compareTo. Buatlah beberapa kode sederhana untuk menguji masingmasing metode. Pastikan Anda dapat memahami bagaimana cara kerja ketiganya. b) Buatlah metode hasComa yang menggunakan sebuah nama sebagai argumen serta mampu mengembalikan nilai boolean yang mengindikasikan apakah nama itu mengandung koma ataukah tidak. Jika ya, maka Anda sudah dapat menganggapnya sebagai format nama dengan nama belakang mendahului nama depan. c) Buatlah metode convertName yang menggunakan sebuah nama sebagai argumen. 165
Metode ini harus memeriksa apakah nama itu mengandung koma. Jika ya, maka metode ini hanya perlu mengembalikan string nama itu saja. Jika tidak, maka anggaplah nama tersebut termasuk dalam format namadepan duluan. Selain itu, metode ini juga harus mengembalikan sebuah string baru yang berisi nama yang telah dikonversikan ke format namabelakang duluan. d) Tulislah sebuah metode dengan nama compareName yang menggunakan dua buah nama sebagai argumen dan yang mengembalikan nilai 1 jika nama pertama mendahului nama kedua, dalam urutan abjad alfabet, 0 jika kedua nama bernilai sama secara alfabetis, dan 1 untuk keadaan lainnya. Metode yang Anda buat haruslah bersifat caseinsensitive. Artinya, metode ini tidak akan memedulikan apakah huruf huruf penyusun namanama yang Anda berikan nantinya merupakan huruf kapital (upper case) atau huruf nonkapital (lower case).
Latihan 7.9 a) Cincin Decoder Kapten Crunch bekerja dengan cara mengambil setiap huruf dalam sebuah string, lalu menambahkan ke masingmasing huruf tersebut nilai 13. Sebagai contoh, ' a ' menjadi ' n ' dan ' b ' menjadi ' o '. Hurufhuruf tersebut kemudian akan “berputar” (wrap around) di bagian akhir, sehingga ' z ' akan menjadi ' m ' (z + 13 = m) . Buatlah sebuah metode yang mengambil sebuah String sebagai parameter dan yang mengembalikan sebuah String baru yang mengandung versi enkode dari String tersebut. Anda harus mengasumsikan bahwa String yang akan dilewatkan sebagai parameter dapat berisi huruf kecil, huruf kapital, dan spasi. Tetapi tidak untuk tanda baca yang lain. Huruf kecil harus diubah ke dalam huruf kecil lagi, begitu pula dengan huruf kapital. Bagaimana dengan spasi? Anda tidak perlu mengenkodenya. b) Generalisasilah metode ini sehingga dapat menambahkan nilai berapa pun yang Anda
166
kehendaki ketimbang hanya mampu menambahkan 13 ke dalam setiap huruf. Sekarang Anda pastinya bisa mengenkode sesuatu dengan menjumlahkan 13 lalu mendekodenya dengan menambahkan 13. Ayo coba!
167
Bab 8 Interesting Objects 8.1
Apanya yang menarik?
Meskipun String adalah objek, tapi mereka bukanlah objek yang benarbenar menarik. Maklum, String itu: ●
Tidak bisa “diubahubah” alias immutable.
●
Mereka tidak mempunyai variabel instan.
●
Tidak mengharuskan kita untuk menggunakan perintah new ketika membuatnya.
Dalam bagian ini, kita akan menggunakan dua tipe objek baru yang merupakan bagian dari Java, yakni Point dan Rectangle. Mulai saat ini juga, saya ingin membuat Anda tahu bahwa points dan rectangle bukanlah objekobjek gambar yang akan tampil di layar. Keduanya merupakan variabel yang mengandung data, sama halnya dengan int dan double. Layaknya variabelvariabel lain, keduanya digunakan secara internal untuk mengerjakan perhitunganperhitungan. Definisidefinisi mengenai kelas Point dan kelas Rectangle berada dalam paket java.awt, jadi kita harus mengimport keduanya.
8.2
Paketpaket
Kelaskelas bawaan Java dibagi ke dalam beberapa paket, termasuk paket java.lang yang mengandung hampir semua kelas yang telah kita gunakan sejauh ini. Begitu juga dengan javax.swing yang berisi kelaskelas untuk bekerja dengan jendela (window), tombol (button), grafis (graphic), dsb. Untuk menggunakan sebuah paket, Anda harus mengimportnya, inilah yang menjadi
168
alasan kenapa program yang ada di bagian D.1 dimulai dengan sintaks import javax.swing.*. Tanda ' * ' mengindikasikan bahwa kita ingin mengimport semua kelas yang berada dalam paket SWING. Jika Anda mau, Anda dapat menuliskan kelaskelas yang Anda inginkan secara eksplisit, tapi sayangnya tidak terlalu menguntungkan. Kelaskelas dalam java.lang sudah diimport secara otomatis. Hal inilah yang menyebabkan kenapa programprogram yang kita tulis sejauh ini belum memerlukan sebuah perintah import. Semua perintah import tampil di bagian awal dari sebuah program, di luar definisi milik kelas.
8.3
Objek Titik (Point)
Dalam pengertian yang paling sederhana, sebuah titik (point) adalah dua angka (koordinat) yang kita anggap/sepakati sebagai sebuah objek tunggal. Dalam notasi matematika, titiktitik ini (points) sering ditulis dalam tanda kurung (parentheses), dengan satu koma yang memisahkan kedua koordinat. Contohnya, (0, 0) yang mengindikasikan titik asal/awal, dan (x, y) yang mengindikasikan titik di lokasi x satuan ke kanan dan y satuan ke atas dengan acuan titik asal (0, 0). Dalam Java, sebuah titik direpresentasikan dengan sebuah objek yang disebut Point. Untuk membuat sebuah titik yang baru, Ada harus menggunakan perintah new: Point blank; blank = new Point (3, 4); Baris pertama adalah contoh deklarasi variabel yang sudah umum alias konvensional: blank memiliki tipe data Point. Sementara baris kedua adalah sesuatu yang terlihat lucu; baris ini memanggil (invoke) perintah new, lalu menentukan tipe untuk objek yang baru itu, kemudian memberinya argumenargumen. Mungkin ini bukanlah sesuatu yang mengejutkan Anda bahwa argumenargumen yang berada dalam tanda kurung, (3, 4), 169
adalah koordinat dari titik yang baru kita buat. Hasil dari perintah new yang baru kita buat adalah sebuah acuan (reference) bagi titik yang baru. Saya akan menjelaskan masalah acuan ini secara lebih mendalam dalam bagian yang lain. Untuk sekarang, yang lebih penting adalah bahwa variabel blank berisi acuan kepada objek yang baru saja dibuat. Ada cara standar untuk menggambar diagram assignment seperti ini. Lihat gambar berikut:
Seperti biasa, nama variabel blank muncul di luar kotak sementara nilainilainya berada di dalam. Dalam kasus ini, nilai adalah suatu acuan, seperti yang ditunjukkan oleh sebuah dot dan anak panah dalam gambar. Anak panah itu menunjuk ke objek yang sedang kita acu. Kotak besar itu memperlihatkan objek yang baru saja kita buat beserta 2 nilai yang berada di dalamnya. x dan y adalah nama dari variabelvariabel instan. Jika disatukan/digabungkan, maka semua komponen, variabel, nilai, dan objek dalam program tersebut akan disebut dengan state. Diagramdiagram seperti ini yang menunjukkan state dari suatu program disebut juga dengan diagramdiagram state (state diagram). Ketika program berjalan (running), statenya juga akan berubah. Jadi, Anda harus menganggap diagram state sebagai sebuah snapshot (tangkapan) dari suatu titik tertentu ketika eksekusi sedang terjadi.
8.4
Variabel Instan
Kumpulan data yang menyusun sebuah objek sering juga disebut sebagai komponen, rekaman (record), atau field. Dalam Java, mereka disebut sebagai variabel instan karena 170
setiap objek, yang merupakan sebuah instan dari tipenya sendiri, memiliki salinan instan variabelnya sendiri. Hal ini persis seperti laci kecil yang ada di dalam sebuah mobil. Setiap mobil adalah instan dari tipe “mobil”, dan setiap mobil memiliki laci kecilnya masingmasing. Jika Anda menyuruh saya untuk mengambil sesuatu dari laci kecil dalam mobil Anda, maka Anda harus memberi tahu saya dahulu mobil mana yang menjadi kepunyaan Anda. Mirip dengan analogi di atas, jika Anda ingin membaca sebuah nilai dari sebuah variabel instan, maka Anda harus menentukan terlebih dahulu objek mana yang akan mengambil nilai tersebut. Dalam Java, aksi ini akan dikerjakan dengan menggunakan perintah “notasi dot”. Int x = blank.x; Ekspresi blank.x mengandug arti “pergilah ke tempat yang diacu oleh objek blank, lalu ambillah nilai x”. Dalam kasus ini, kita mengassign nilai tersebut ke dalam sebuah variabel lokal yakni x. Perhatikan bahwa tidak ada konflik yang terjadi antara variabel lokal x dengan variabel instan x. Fungsi notasi dot dalam kasus ini adalah untuk mengidentifikasi dengan pasti variabel mana yang sedang atau akan Anda acu. Anda dapat menggunakan notasi dot untuk ekspresi bahasa Java apapun, jadi bentuk berikut ini dianggap legal. System.out.println (blank.x + ", " + blank.y); int distance = blank.x * blank.x + blank.y * blank.y; Baris pertama mencetak 3, 4; sementara baris kedua menghasilkan nilai 25.
8.5
Objek Sebagai Parameter
Anda dapat melewatkan (pass) suatu objek sebagai parameter dengan cara yang sudah lazim digunakan. Contoh, public static void printPoint (Point p) { 171
System.out.println ("(" + p.x + ", " + p.y + ")"); } adalah sebuah metode yang menggunakan sebuah point sebagai argumen lalu mencetaknya dalam format standar. Jika Anda memanggil metode printPoint (blank), Anda akan mencetak (3, 4). Sebenarnya, Java mempunyai metode bawaan untuk mencetak Points. Jika Anda memanggil System.out.println(blank) Anda akan mendapatkan java.awt.Point[x=3,y=4] Ini merupakan format standar Java untuk mencetak mencetak suatu objek. Metode ini akan mencetak nama tipe, kemudian diikuti dengan isi dari objek tersebut, termasuk namanama dan nilainilai dari variabel instan. Untuk contoh tambahan, kita dapat menulis ulang metode distance dari bagian 5.2 agar ia menggunakan dua Point sebagai parameter ketimbang empat double. public static double distance (Point p1, Point p2) { double dx = (double)(p2.x p1.x); double dy = (double)(p2.y p1.y); return Math.sqrt (dx*dx + dy*dy); } Typecast di atas sebenarnya tidaklah terlalu bermanfaat; saya hanya menambahkannya sebagai pengingat bahwa variabel instan dalam sebuah Point adalah integer.
8.6
Rectangle
Rectangle memiliki sifat yang hampir mirip dengan Point, kecuali variabel instannya yang berjumlah empat. Keempat variabel itu antara lain; x, y, width, dan height. Selain itu, semuanya sama saja. Rectangle box = new Rectangle (0, 0, 100, 200); menghasilkan sebuah objek Rectangle baru sekaligus membuat variabel box akan 172
mengacu kepadanya. Gambar berikut ini akan menunjukkan efek dari assignment ini. Jika Anda mencetak box, Anda akan mendapatkan java.awt.Rectangle[x=0,y=0,width=100,height=200] Lagilagi, ini merupakan hasil dari metode bawaan Java yang sudah tahu bagaimana caranya mencetak objek Rectangle.
8.7
Objek sebagai tipe kembalian (Return Type)
Anda dapat menulis metode yang menghasilkan nilai kembalian bertipe objek. Sebagai contoh, findCenter menggunakan Rectangle sebagai argumen lalu mengembalikan sebuah Point, yang merupakan objek, yang berisi koordinatkoordinat pusat dari Rectangle: public static Point findCenter (Rectangle box) { int x = box.x + box.width/2; int y = box.y + box.height/2; return new Point (x, y); } Perhatikan bahwa Anda dapat menggunakan new untuk membuat sebuah objek baru, untuk kemudian langsung menggunakan hasilnya sebagai nilai kembalian.
8.8
Objek dapat diubahubah (Mutable)
Anda dapat mengubahubah isi dari sebuah objek dengan cara membuat sebuah assignment untuk salah satu variabel instan yang dimilikinya. Sebagai contoh, untuk “memindahkan” sebuah lingkaran tanpa mengubah ukurannya, Anda dapat memodifikasi nilai x dan y: box.x = box.x + 50; box.y = box.y + 100; Hasilnya dapat Anda lihat dalam gambar di bawah ini: 173
Kita dapat mengambil kode ini lalu mengenkapsulasinya dalam sebuah metode, lalu menggeneralisasinya agar dapat digunakan untuk memindahkan sebuah lingkaran dengan nilai berapa pun yang kita mau. public static void moveRect (Rectangle box, int dx, int dy) { box.x = box.x + dx; box.y = box.y + dy; } Variabel dx dan dy akan memberi tahu sejauh mana perpindahan yang telah terjadi pada lingkaran dalam setiap arah. Jika Anda memanggil metode ini, berarti Anda juga telah mengubah objek Rectangle yang dilewatkan sebagai sebuah argumen. Rectangle box = new Rectangle (0, 0, 100, 200); moveRect (box, 50, 100); System.out.println (box); akan mencetak java.awt.Rectangle[x=50,y=100,width=100,height=200]. Memodifikasi objek dengan cara melewatkan mereka sebagai argumen untuk sebuah metode memang dapat memberikan manfaat. Namun hal ini juga dapat menimbulkan kesulitan ketika kita hendak melakukan debugging. Kok bisa? Karena terkadang tidaklah terlalu jelas bagi kita untuk menentukan dengan cepat pemanggilan metode mana yang turut mengubah atau tidak mengubah masingmasing argumen yang mereka bawa. Nanti, saya akan membahas beberapa pro dan kontra yang berkaitan dengan teknik pemrograman seperti ini. 174
Untuk saat ini, kita sudah dapat menikmati “kemewahan” yang tersedia dalam metode metode bawaan Java. Termasuk di dalamnya translate, yang kemampuannya sepadan dengan metode moveRect, meskipun sintaks yang digunakan untuk memanggilnya mungkin agak sedikit berbeda. Ketimbang melewatkan Rectangle sebagai sebuah argumen, kita memanggil translate dalam objek Rectangle kemudian melewatkan variabel dx dan dy saja sebagai argumen. box.translate (50, 100); Pengaruh yang dihasilkan akan sama dengan moveRect.
8.9
Aliasing
Penting untuk diingat, ketika Anda mengassign suatu variabel dengan objek, ini berarti Anda sedang mengassign sebuah acuan (reference) untuk sebuah objek. Java mengizinkan Anda untuk membuat lebih dari satu variabel yang mengacu kepada satu objek yang sama. Untuk lebih jelasnya, perhatikan kode berikut ini: Rectangle box1 = new Rectangle (0, 0, 100, 200); Rectangle box2 = box1; kode di atas akan menghasilkan diagram state seperti yang tampak di bawah ini:
Baik box1 maupun box2 mengacu kepada objek yang sama. Dengan kata lain, objek ini memiliki dua nama, yakni box1 dan box2. Ketika seorang individu memiliki dua buah nama, kita menyebutnya dengan istilah aliasing. Nah, begitu juga dengan objek. Ketika dua variabel berada dalam status aliasing, semua perubahan yang dapat memengaruhi satu variabel akan turut memengaruhi variabel lainnya. Contohnya:
175
Baris pertama akan mencetak 100, yang merupakan lebar dari Rectangle yang diacu oleh box2. Sementara baris kedua digunakan untuk memanggil metode grow melalui box1. Pernyataan ini akan melebarkan/memperluas Rectangle sebanyak 50 pixel ke segala arah (silahkan membaca dokumentasinya agar lebih jelas). Perubahan yang terjadi ditunjukkan oleh gambar di bawah ini:
Melalui gambar ini, seharusnya Anda sudah dapat memahami, bahwa perubahan apapun yang terjadi pada box1 juga turut memengaruhi box2. Oleh karena itulah, nilai yang dicetak oleh pernyataan pada baris ketiga adalah 200, sebagai lebar dari hasil perluasan lingkaran. (Sebagai pertimbangan, Java juga mengizinkan nilai negatif sebagai koordinat Rectangle) Dari kode sederhana di atas, Anda mungkin akan langsung menyimpulkan bahwa kode yang menerapkan prinsip aliasing justru dapat merepotkan kita sendiri. Apalagi kalau sudah masuk ke dalam urusan debugging. Untuk itulah, sebaiknya Anda menghindari penggunaan teknik ini, atau gunakan ia dengan sehatihati mungkin.
8.10 Null Ketika Anda membuat sebuah variabel objek, Anda harus ingat bahwa Anda sedang mengacu kepada suatu objek. Jika Anda belum membuat variabel tersebut mengacu kepada suatu objek, maka nilai dari variabel itu akan sama dengan null. null adalah nilai yang khusus (juga sebuah keyword) dalam Java yang berarti “tidak ada objek”. Pendeklarasian bentuk Point blank;
176
akan sama dengan inisialisasi beriktu ini: Point blank = null;
Diagram state dari deklarasi di atas dapat dilihat melalui gambar di bawah ini:Nilai null direpresentasikan dengan sebuah dot tanpa anak panah. Jika Anda mecoba untuk menggunakan objek yang berstatus null, entah dengan mengaksesnya melalui variabel instan atau memanggilnya melalui sebuah metode, Anda akan mendapatkan sebuah pesan berbunyi, “NullPointerException”. Sistem akan menampilkan sebuah pesan error lalu menghentikan program itu. Point blank = null; int x = blank.x; // NullPointerException blank.translate (50, 50); // NullPointerException Di sisi lain, adalah legal dalam Java untuk melewatkan sebuah objek berstatus null sebagai sebuah argumen atau menerima salah satunya sebagai sebuah nilai kembalian. Faktanya, memang demikianlah adanya. Para pengguna Java terbiasa mengaplikasikan konsep ini untuk merepresentasikan sebuah himpunan kosong atau untuk menginformasikan sebuah keadaan error.
8.11 Garbage Collection Dalam bagian 8.9 kita telah berbicara mengenai apa yang akan terjadi jika lebih dari satu variabel mengacu kepada satu objek yang sama. Lalu sekarang, apa yang akan terjadi jika tidak ada satu pun variabel yang mengacu kepada suatu objek? Sebagai contoh: Point blank = new Point (3, 4); blank = null;
177
Baris pertama menghasilkan sebuah objek Point lalu membuat variabel blank mengacu kepadanya. Baris kedua mengubah blank sehingga ketimbang mengacu kepada suatu objek, ia justru lebih memilih untuk tidak mengacu kepada objek mana pun (objek null).
Jika tidak ada variabel yang mengacu kepada sebuah objek, maka tidak akan ada yang dapat membaca (read) atau menulisi (write) objek ini dengan nilai apa pun, atau memanggil sebuah metode melalui objek ini. Walaupun begitu, objek seperti ini tetap eksis dalam memori. Sebenarnya, kita bisa saja menyimpan objek ini dalam memori, tapi hal ini hanya akan membuangbuang ruang (space). Oleh karena itulah, selagi program Anda berjalan, sistem Java akan mencari objekobjek yang tidak terpakai ini secara berkala (periodically), untuk kemudian mengaturnya. Proses inilah yang disebut dengan garbage collection. Walhasil, ruang memori yang tadinya dipakai oleh objek ini akan dapat digunakan kembali sebagai bagian dari objek yang baru. Anda tidak perlu melakukan apapun untuk membuat proses garbage collection bekerja. Lagipula, umumnya Anda memang tidak akan memerhatikannya.
8.12 Objek dan Primitif Ada dua jenis tipe data dalam Java, tipe data primitif dan tipe data objek. Tipe primitif, seperti int dan boolean diawali dengan huruf kecil; sementara tipe objek diawali dengan huruf kapital. Perbedaan ini sangat bermanfaat karena dapat mengingatkan kita kepada beberapa perbedaan antara keduanya. ●
Ketika Anda mendeklarasikan sebuah tipe data primitif, Anda akan
178
mendapatkan ruang penyimpanan untuk nilai primitif. Ketika Anda mendeklarasikan variabel bertipe data objek, Anda akan mendapatkan ruang memori sebagai acuan (reference) kepada sebuah objek. Agar objek yang Anda buat memiliki ruang, Anda harus menggunakan perintah new. ●
Jika Anda tidak menginisialisasi sebuah tipe primitif, Java telah memberi nilai bawaan (default) untuk setiap tipe data tersebut. Contohnya, 0 untuk tipe data int dan true untuk tipe boolean. Nilai bawaan untuk tipetipe objek adalah null yang berarti tidak ada objek.
●
Variabelvariabel primitif diisolasi dengan baik oleh Java. Maksud pernyataan ini adalah bahwa Anda tidak dapat melakukan apapun melalui sebuah metode untuk mengubah variabel lain yang berada dalam metode lain. Variabelvariabel objek menjadi agak sulit dan membingungkan untuk digunakan karena mereka tidak diisolasi sebaik variabel bertipe primitif. Jika Anda melewatkan suatu referensi kepada sebuah objek sebagai sebuah argumen, maka metode yang Anda panggil dapat mengubah objek tersebut. Keadaan yang sama juga berlaku ketika Anda memanggil sebuah metode melalui suatu objek. Tetapi tentu saja, hal ini sebenarnya juga dapat bermanfaat untuk Anda, asalkan Anda hatihati ketika menggunakannya.
Ada satu perbedaan lagi antara tipe data primitif dengan tipe data objek. Anda tidak dapat menambahkan tipe data primitif baru ke dalam bahasa Java (kecuali kalau Anda termasuk dalam anggota komite standardisasi Java milik Sun Microsystem), tapi Anda dapat membuat tipe objek yang baru! Caranya? Kita akan melihatnya di bagian berikutnya.
179
8.13 Daftar KataKata Istilah
Arti Sebuah kumpulan dari kelaskelas (classes). Kelaskelas
paket (package)
yang dibawa (builtin) oleh Java dikelola dalam bentuk paketpaket.
AWT
Abstract Window Toolkit, salah satu paket terbesar dalam Java. Sebuah contoh dari suatu kategori/golongan. Kucing
instan (instance)
saya adalah suatu instan dari (baca: yang termasuk ke dalam golongan) “keluarga kucing”. Semua objek adalah instan dari beberapa kelas. Salah satu itemitem data yang membentuk sebuah
variabel instan
objek. Setiap objek (instan) memiliki salinan variabel variabel instan untuk kelasnya masingmasing. Sebuah nilai yang mengindikasi sebuah objek. Dalam
acuan (reference)
sebuah diagram state, suatu acuan diwakili dengan gambar anak panah.
aliasing
Suatu kondisi dimana dua atau lebih variabel mengacu ke objek yang sama. Suatu proses untuk mencari objekobjek yang tidak
garbage collection
punya acuan untuk kemudian mengelola kembali ruang memori yang telah dipakainya. Sebuah deskripsi lengkap dari semua variabel dan objek
state
sekaligus nilainilai yang dikandungnya pada suatu waktu tertentu selama eksekusi program sedang berlangsung. 180
Sebuah pencitraan (snapshot) atas kondisi/keadaan diagram state
yang terjadi pada sebuah program, ditunjukkan dengan gambar.
8.14 Latihan Latihan 8.1 a) Untuk program di bawah ini, gambarlah sebuah diagram stack yang menunjukkan variabelvariabel lokal dan parameterparameter dari main dan fred, lalu tunjukkan suatu objek yang diacu oleh variabelvariabel tersebut. b) Apakah output yang dihasilkan oleh program berikut ini? public static void main (String[] args) { int x = 5; Point blank = new Point (1, 2); System.out.println (fred (x, blank)); System.out.println (x); System.out.println (blank.x); System.out.println (blank.y); } public static int fred (int x, Point p) { x = x + 7; return x + p.x + p.y; }
181
Tujuan utama dari latihan ini adalah untuk memastikan bahwa Anda benarbenar memahami mekanisme untuk melewatkan objek sebagai parameter. Latihan 8.2 a) Untuk program berikut ini, gambarlah sebuah diagram stack yang menunjukkan keadaan dari program beberapa saat sebelum metode distance dikembalikan. Masukkan ke dalam gambar Anda itu semua variabel, parameter, dan objek yang diacu oleh variabelvariabel tersebut. b) Output apakah yang dihasilkan oleh program berikut ini? public static double distance (Point p1, Point p2) { int dx = p1.x p2.x; int dy = p1.y p2.y; return Math.sqrt (dx*dx + dy*dy); } public static Point findCenter (Rectangle box) { int x = box.x + box.width/2; int y = box.y + box.height/2; return new Point (x, y); } public static void main (String[] args) { Point blank = new Point (5, 8); Rectangle rect = new Rectangle (0, 2, 4, 4); Point center = findCenter (rect); double dist = distance (center, blank); System.out.println (dist); } 182
Latihan 8.3 Metode grow adalah bagian dari kelas Rectangle. Berikut ini merupakan dikumentasi mengenai grow (diambil dari situs Sun): public void grow(int h, int v) Grows the rectangle both horizontally and
vertically. This method modifies the rectangle so that it is h units larger on both the left and right side, and v units larger at both the top and bottom. The new rectangle has (x h, y v) as its top left corner, a width of width + 2h, and a height of height + 2v. Jika nilai negatif a) Output apakah yang dihasilkan oleh program berikut ini? b) Gambarkan sebuah diagram state yang menunjukkan keadaan dari program sesaat sebelum main berakhir. Masukkan ke dalam gambar Anda tersebut semua variabel dan objekobjek yang diacunya. c) Pada bagian akhir dari main, apakah p1 dan p2 termasuk aliasing? Mengapa atau mengapa tidak? public static void printPoint (Point p) { System.out.println ("(" + p.x + ", " + p.y +
")");
183
} public static Point findCenter (Rectangle box) { int x = box.x + box.width/2; int y = box.y + box.height/2; return new Point (x, y); } public static void main (String[] args) { Rectangle box1 = new Rectangle (2, 4, 7, 9); Point p1 = findCenter (box1); printPoint (p1); box1.grow (1, 1); Point p2 = findCenter (box1); printPoint (p2); } Latihan 8.4 Mungkin sekarang Anda sudah benarbenar bosan dengan metode faktorial, tapi sayangnya kita akan membuat satu versi lagi dari metode ini. a) Buatlah sebuah program baru dengan nama Big.java lalu mulailah dengan menulis sebuah versi iteratif dari factorial. b) Cetaklah sebuah tabel integer dari 0 s.d. 30 bersama faktorial mereka. Ketika sampai ke nilai 15an, Anda mungkin akan mendapati bahwa jawaban Anda sepertinya sudah tidak tepat lagi. Coba periksa! Mengapa begitu? c) BigInteger adalah metode bawaan Java yang dapat merepresentasikan bilangan
184
bilangan integer. Tidak ada batasatas kecuali ukuran memori dan kecepatan pemrosesan itu sendiri. Cetaklah dokumentasi untuk kelas BigInteger yang terdapat dalam paket java.math, lalu bacalah! d) Ada beberapa cara untuk membuat metode BigInteger, tapi yang saya rekomendasikan adalah dengan menggunakan valueOf. Kode berikut ini akan mengkonversikan sebuah integer ke bentuk metode BigInteger: int x = 17; BigInteger big = BigInteger.valueOf (x); Tulislah kode ini! Lalu cobalah beberapa kasus sederhana, seperti membuat sebuah BigInteger kemudian mencetaknya. Perhatikan bahwa metode println sudah mengetahui cara untuk mencetak BigInteger! Jangan lupa untuk menambahkan import java.math.BigInteger di awal program Anda. e) Sayangnya, karena BigInteger bukanlah tipe primitif, kita jadi tidak dapat menggunakan operator matematika biasa untuknya. Sebaliknya, kita akan menggunakan metode objek seperti add. Untuk menambah dua BigInteger, Anda harus memanggil add melalui salah satu objek dan melewatkan objek lainnya sebagai argumen. Sebagai contoh: BigInteger small = BigInteger.valueOf (17); BigInteger big = BigInteger.valueOf (1700000000); BigInteger total = small.add (big); Cobalah beberapa metode lainnya, seperti multiply dan pow. f) Konversikanlah metode factorial sehingga ia dapat melakukan penghitungan menggunakan BigInteger, lalu mengembalikan BigInteger sebagai hasilnya. Anda tidak perlu menghiraukan parameternya – karena tipe data yang akan digunakan tetaplah berupa integer.
185
g) Cobalah mencetak tabel lagi dengan metode factorial yang telah anda modifikasi. Benarkah hasilnya untuk angka di atas 30? Sampai seberapa besar (baca: angkanya) Anda dapat mempertahankan ketepatan ini? Saya telah menghitung faktorial dari semua angka mulai dari 0 s.d. 999. Namun karena mesin saya terlalu lambat, saya jadi perlu waktu yang agak lama. Angka terakhir, 999!, mempunyai 2565 digit. Bagaimana dengan Anda? Latihan 8.5 Banyak algoritmaalgoritma enkripsi sangat bergantung kepada kemampuan untuk menaikkan nilai integer yang besar ke bentuk integer berpangkat. Berikut ini adalah sebuah metode yang mengimplementasikan sebuah algoritma cepat untuk pemangkatan (exponentiation) integer. public static int pow (int x, int n) { if (n==0) return 1; // find x to the n/2 recursively int t = pow (x, n/2); // if n is even, the result is t squared // if n is odd, the result is t squared times x if (n%2 == 0) { return t*t; } else { return t*t*x; } } Masalah yang muncul dalam metode ini adalah bahwa ia hanya bekerja dengan benar 186
jika hasil yang didapatkan kurang dari 2 miliar. Tulisulanglah metode ini sehingga hasilnya merupakan bentuk BigInteger. Meskipun begitu, parameterparamater yang digunakan haruslah tetap berupa integer. Anda diizinkan menggunakan metode add dan multiply milik BigInteger, tapi tidak dengan metode pow, karena metode ini bisa jadi justru akan merusak 'keceriaan' Anda.
187
Bab 9 Membuat Objek Anda Sendiri 9.1
Definisidefinisi kelas dan tipetipe objek
Setiap kali Anda menulis sebuah definisi untuk kelas, Anda sebenarnya telah membuat sebuah tipe objek baru dengan nama yang sama dengan nama kelas Anda. Kembali ke bagian 1.5, ketika kita mendefinisikan sebuah kelas bernama Hello, pada saat yang sama (tanpa perlu Anda sadari) kita juga membuat sebuah tipe objek dengan nama Hello. Kita tidak membuat variabel apapun untuk tipe Hello, tidak juga menggunakan perintah new untuk membuat objekobjek Hello, tapi kita sudah berhasil melakukannya! Contoh di atas agak tidak masuk akal, karena kita pada dasarnya memang tidak bermaksud untuk membuat objek Hello. Lagipula, masih tidak jelas apa manfaatnya untuk kita kalau memang kita benarbenar telah membuatnya. Dalam bab ini,
kita
akan melihat beberapa contoh definisidefinisi kelas yang mampu menunjukkan kepada kita tipe objek baru yang bermanfaat. Berikut ini adalah halhal penting yang harus diperhatikan dalam bab ini: ●
Mendefinisikan sebuah kelas baru akan menyebabkan 'lahirnya' sebuah tipe objek baru dengan nama yang sama.
●
Suatu definisi untuk kelas dapat diasumsikan sebagai sebuah cetakan (template) untuk objek: definisi ini akan menentukan variabel instan mana yang dimiliki objek sekaligus metodemetode yang bisa beroperasi di atasnya.
●
Setiap objek merupakan milik beberapa objek sekaligus; oleh karena itulah, setiap objek juga merupakan instan dari beberapa kelas.
●
Ketika Anda menggunakan perintah new untuk membuat sebuah objek, Java 188
memanggil sebuah metode khusus yang disebut constructor untuk menginisialisasi variabelvariabel instan. ●
Semua metode yang beroperasi dalam sebuah tipe akan masuk dalam definisi kelas untuk tipe tersebut.
Berikut ini merupakan isuisu sintaks mengenai definisidefinisi kelas: ●
Nama kelas (atau tipe objek) selalu dimulai dengan huruf kapital yang akan membedakannya dengan tipetipe primitif dan namanama variabel.
●
Anda biasanya meletakkan satu definisi kelas dalam setiap file, dan nama file tersebut haruslah sama dengan nama kelas yang Anda buat dengan ekstensi .java. Sebagai contoh, kelas Time didefinisikan dalam sebuah file bernama Timed.Java.
●
Dalam program manapun, sebuah kelas dirancang untuk menjadi kelas startup (startup class). Kelas ini harus mengandung sebuah metode dengan nama main, yang akan menjadi tempat dimana eksekusi program dimulai. Kelas lain diizinkan untuk memiliki metode main, tapi ia tidak akan dieksekusi.
Dengan topiktopik di atas, sekarang kita akan melihat sebuah contoh kelas yang merupakan hasil buatan sendiri (userdefined type), yakni Time.
9.2
Time
Tujuan utama yang mendasari pembuatan sebuah tipe objek baru adalah untuk mengambil beberapa data yang saling berhubungan lalu mengenkaspsulasi mereka ke dalam sebuah objek yang dapat dimanipulasi (dilewatkan sebagai sebuah argumen, beroperasi dengan suatu metode) sebagai sebuah unit tunggal. Sebelumnya kita semua sudah melihat dua tipe bawaan Java seperti ini, yaitu Point dan Rectangle. Contoh lainnya, yang akan kita impelementasikan sendiri adalah Time. Tipe objek ini akan kita gunakan untuk merekam waktu dalam satu hari. Beberapa informasi yang 189
menyusun 'sebuah' waktu antara lain; jam, menit dan detik. Karena setiap objek waktu akan berisi data ini, kita memerlukan variabel instan untuk menyimpan data tersebut. Langkah pertama adalah menentukan tipe data yang cocok untuk variabel yang akan kita gunakan. Untuk variabel hour dan minute, kita sebaiknya menggunakan integer. Untuk membuat proses ini lebih menyenangkan, mari kita simpan data second dalam variabel bertipe double agar nantinya dapat merekam detikdetik yang 'kecil'. Variabel instan akan dideklarasikan di awal definisi kelas, di luar semua definisi metode yang ada, seperti ini bentuknya: class Time { int hour, minute; double second; } Lampiran kode di atas termasuk definisi kelas yang legal dalam Java. Diagram state untuk objek Time akan tampak sebagai berikut:
Setalah mendeklarasikan variabel instan, langkah selanjutnya adalah mendefinisikan/menentukan sebuah konstruktor untuk kelas yang baru. Fungsi utama dari konstruktor adalah untuk menginisialisasi variabel instan. Sintaks untuk membuat konstruktor sama dengan yang digunakan untuk membuat metode metode biasa, tapi dengan tiga pengecualian: ●
Nama konstruktor harus sama dengan nama kelas.
●
Konstruktor tidak mempunyai tipe dan atau nilai pengembalian.
●
Keyword static tidak boleh digunakan.
Berikut ini adalah contoh kelas Time: public Time () {
190
this.hour = 0; this.minute = 0; this.second = 0.0; } Perhatikan bahwa tidak ada apaapa di antara keyword public dan Time (yang biasanya terdapat tipe data pengembalian diantaranya) . Inilah salah satu cara yang digunakan oleh kita (juga compiler) untuk menyatakan bahwa lampiran kode di atas merupakan sebuah konstruktor. Konstruktor di atas tidak menggunakan satu argumen pun, sebagaimana yang diindikasikan oleh tanda kurung yang kosong; ( ). Setiap baris yang terkandung dalam konstruktor akan menginisialisasi sebuah variabel instan dengan nilai default yang kita buat sendiri (dalam kasus ini, kita menginisialisasi Time dengan waktu tepat di saat tengah malam, yaitu 0, 0, dan 0.0). keyword this merupakan kata tercadang khusus yang merupakan nama dari objek yang sedang kita buat. Anda dapat menggunakan this seperti anda menggunakan namanama objek lainnya. Sebagai contoh, Anda dapat membaca dan menulis variabel instan dengan this, lalu Anda juga dapat melewatkan this sebagai argumen kepada metode lainnya. Tetapi Anda tidak perlu mendeklarasikan this atau menggunakan perintah new untuk membuatnya. Kenyataannya, Anda bahkan tidak diizinkan untuk mengassignnya dengan sesuatu apapun. this merupakan produk sistem Java secara otomatis; yang harus Anda lakukan hanyalah menyimpan nilainilai ke dalam variabel instan miliknya. Itu saja! Suatu bentuk error yang sering dilakukan oleh para pemrogram ketika menulis sebuah konstruktor yaitu ketika mereka meletakkan perintah return di akhir kode untuk konstruktor mereka. Hindari kesalahan seperti ini!
191
9.4
Konstruktor Lagi
Konstruktorkonstruktor dalam Java juga dapat berstatus overloading seperti metode pada lazimnya. Akibatnya, Anda dapat menyediakan beberapa konstruktor sekaligus dalam sebuah tipe objek dengan parameterparameter yang berbeda. Java mengetahui konstruktor mana yang dipanggil dengan cara mencocokkan argumen yang digunakan oleh perintah new dengan parameterparameter yang digunakan oleh konstruktor konstruktor. Adalah sesuatu yang biasa dalam Java jika terdapat sebuah definisi kelas yang selain mengandung konstruktor yang tidak mempunyai satu argumen pun (seperti ditunjukkan di atas), ia juga mengandung sebuah konstruktor yang menggunakan beberapa parameter sekaligus yang identik dengan variabelvariabel instan miliknya. Contohnya: public Time (int hour, int minute, double second) { this.hour = hour; this.minute = minute; this.second = second; } Nama dan tipe paramater yang digunakan akan sama persis dengan nama dan tipe dari variabel instan. Semua konstruktor bertugas untuk menyalin informasi dari parameter parameter itu ke dalam variabelvariabel instan. Jika Anda kembali dan melihat dokumentasi untuk Point dan Rectangle, Anda akan melihat bahwa kedua kelas tersebut menyediakan konstruktor seperti ini. Status overloading dalam konstruktor dapat memberikan fleksibilitas bagi Anda untuk membuat sebuah objek terlebih dahulu lalu mengisi kekurangankekurangannya, atau Anda dapat mengumpulkan semua informasi sebelum membuat suatu objek yang benar benar sesuai dengan keinginan Anda. 192
Sejauh ini, apa yang sedang kita lakukan mungkin tidaklah terlalu menarik, dan memang demikianlah adanya. Menulis konstruktor adalah pekerjaan yang membosankan, maklum saja, proses ini memang dapat dianalogikan seperti proses mekanis (mereparasi mesin). Sekali saja Anda telah mampu menulis dua konstruktor sekaligus, Anda akan sadar bahwa tanpa berkedip pun Anda sebenarnya tetap bisa membuatnya. Cukup dengan memperhatikan daftar variabelvariabel instannya saja.
9.5
Membuat Sebuah Objek yang Baru
Meskipun konstruktorkonstruktor ini tampak seperti sebuah metode, Anda tetap tidak akan pernah bisa memanggilnya secara langsung. Sebaliknya, ketika Anda menggunakan perintah new, sistem akan mengalokasikan ruang memori untuk objek yang baru lalu memanggil konstruktor Anda untuk menginisialisasi variabelvariabel instannya. Program berikut ini memperlihatkan kepada kita dua cara untuk membuat dan menginisialisasi objek Time: class Time { int hour, minute; double second; public Time () { this.hour = 0; this.minute = 0; this.second = 0.0; } public Time (int hour, int minute, double second)
{ 193
this.hour = hour; this.minute = minute; this.second = second; } public static void main (String[] args) { // one way to create and initialize a Time
object Time t1 = new Time (); t1.hour = 11; t1.minute = 8; t1.second = 3.14159; System.out.println (t1); // another way to do the same thing Time t2 = new Time (11, 8, 3.14159); System.out.println (t2); } }
Sebagai latihan untuk Anda, cobalah pahami lalu tentukan bagaimana aliran eksekusi program ini. Dalam metode main, ketika kali pertama kita memanggil perintah new, kita tidak memberikan satu argumen pun, akibatnya, Java akan memanggil konstruktor pertama dari kelas Time di atas. Beberapa baris berikutnya mengassign nilainilai ke setiap variabelvariabel instan. 194
Pada kesempatan kedua ketika kita memanggil perintah new, kita memberikan argumenargumen yang cocok dengan parameterparameter dari konstruktor kedua. Cara penginisialisasian variabel instan seperti ini mungkin lebih jelas (dan efisien), tapi juga dapat lebih menyulitkan untuk dibaca. Hal ini bisa jadi disebabkan karena tidak jelasnya nilainilai apa yang akan diassign ke variabel instan yang mana.
9.6
Mencetak Sebuah Objek
Output dari program di atas adalah: Time@80cc7c0 Time@80cc807 Ketika Java mencetak nilai dari sebuah tipe objek buatan pengguna (userdefined object type), yang akan dicetaknya adalah nama dari tipe objek tersebut beserta suatu kode heksadesimal (basis 16) yang unik untuk setiap objek. Kode ini memang tidak artinya; kenyataannya, hasil ini bisa bervariasi diantara satu mesin dengan mesin lainnya atau bahkan diantara waktu pemrosesan yang satu dengan waktu pemrosesan yang lainnya. Meskipun begitu, kita masih tetap dapat memanfaatkannya untuk debugging, misalnya untuk kasus dimana kita tetap ingin mengawasi keberadaan suatu objek secara individual. Untuk mencetak objek yang menghasilkan pesan yang lebih manusiawi (baca: dapat dimengerti) bagi pengguna, Anda biasanya ingin menulis sebuah metode seperti printTime: public static void printTime (Time t) { System.out.println (t.hour + ":" + t.minute + 1. ":" + t.second);
195
} Bandingkanlah metode ini dengan metode printTime yang ada di bagian 3.10. Output metode ini, jika kita melewatkan t1 atau t2 sebagai argumen adalah 11:8:3.14159. Meskipun bentuk seperti ini kita anggap sebagai sebuah penunjuk waktu, tapi ia belumlah dapat dikategorikan ke dalam bentuk standar yang sudah kita kenal. Sebagai contoh, jika angka menit atau detik kurang dari 10, maka kita mengharapkan adanya awalan berupa angka 0 yang berfungsi sebagai penjagatempat (placekeeper). Selain itu, kita mungkin ingin membuang angkaangka yang termasuk dalam angka desimal/pecahan dari detik. Dengan kata lain, kita ingin sesuatu yang tampak seperti 11:08:03. Dalam lingkungan bahasabahasa pemrograman lainnya, biasanya sudah terdapat cara sederhana untuk mengendalikan format output yang berupa angkaangka. Namun dalam Java, tidak ada cara seperti itu. Java menyediakan tools yang sangat bagus untuk mencetak sesuatu yang sudah memiliki bentuk standar (formatted things) seperti waktu atau tanggal, juga untuk menginterpretasikan inputinput terformat (formatted input). Sayangnya, tools ini tidaklah terlalu mudah untuk digunakan, jadi saya tidak akan membahasnya di dalam buku ini. Tetapi jika Anda benarbenar ingin menggunakannya, silahkan melihatlihat dokumentasi mengenai kelas Date dalam paket java.util.
9.7 Operasi Pada Objek Meskipun kita tidak dapat mencetak waktu dalam format yang optimal, kita masih dapat menulis sebuah metode yang bisa memanipulasi objek Time. Dalam bagianbagian berikutnya, saya akan menunjukkan beberapa antarmuka (interface) yang mungkin dapat digunakan untuk metodemetode yang beroperasi melalui objekobjek. Untuk 196
beberapa operasi, Anda mempunyai beberapa pilihan antarmuka yang bisa digunakan, jadi Anda harus mempertimbangkan sendiri pro dan kontra untuk setiap pilihan: ●
pure function: menggunakan objekobjek/primitif sebagai argumen tetapi tidak memodifikasi objek. Nilai kembalian berupa primitif atau sebuah objek baru yang dibuat di dalam metode tersebut.
●
Modifier: menggunakan objek sebagai argumen dan memodifikasi beberapa atau semua bagian di dalamnya. Seringkali mengembalikan void.
●
Fillinmethod: salah satu argumennya adalah sebuah objek 'kosong' yang diisi oleh metode. Secara teknis, sebenarnya ini merupakan salah satu bentuk dari modifier.
9.8
Fungsifungsi murni
Sebuah metode dianggap sebagai fungsi murni jika hasilnya hanya bergantung kepada argumenargumen, dan pada saat yang bersamaan dia tidak memberikan efek samping; seperti memodifikasi sebuah argumen atau mencetak sesuatu. Satusatunya hasil dari melakukan pemanggilan terhadap sebuah fungsi murni adalah nilai kembalian itu sendiri.
Salah satu contohnya adalah after, yang membandingkan dua buah waktu, lalu mengembalikan nilai bertipe boolean yang mengindikasikan apakah operan pertama berada di belakang operan kedua: public static boolean after (Time time1, Time time2) { if (time1.hour > time2.hour) return true; if (time1.hour < time2.hour) return false; if (time1.minute > time2.minute) return true; 197
if (time1.minute < time2.minute) return false; if (time1.second > time2.second) return true; return false; }
Apa yang akan dihasilkan oleh metode ini jika kedua waktu adalah sama? Apakah hasil tersebut merupakan hasil yang tepat dan sesuai dengan apa yang kita harapkan dari metode ini? Jika Anda menulis dokumentasi untuk metode ini, maukah Anda menyebutkan kasus tersebut secara spesifik? Contoh kedua adalah addTime, yang mengkalkulasi jumlah dari dua buah waktu. Sebagai contoh, jika sekarang waktu menunjukkan 9:14:30, dan pemanggang roti Anda membutuhkan 3 jam lebih 35 menit, Anda dapat menggunakan addTime untuk menentukan kapan roti Anda akan selesai dipanggang: public static Time addTime (Time t1, Time t2) { Time sum = new Time (); sum.hour = t1.hour + t2.hour; sum.minute = t1.minute + t2.minute; sum.second = t1.second + t2.second; return sum; } Meskipun metode ini mengembalikan nilai yang berupa objek waktu, ia bukanlah sebuah konstruktor. Anda harus kembali lagi untuk membandingkan sintaks dari metode seperti ini dengan sintaks sebuah konstruktor. Mengapa? Karena kita seringkali menjadi terlalu mudah untuk kebingungan. Berikut ini adalah sebuah contoh mengenai bagaimana cara menggunakan metode ini. 198
Jika currentTime berisikan waktu saat ini, sementara breadTime mengandung total waktu yang dibutuhkan oleh pembuatroti Anda untuk membuat roti yang Anda inginkan, maka Anda harus menggunakan addTime untuk menentukan kapan roti tersebut akan selesai dibuat. Time currentTime = new Time (9, 14, 30.0); Time breadTime = new Time (3, 35, 0.0); Time doneTime = addTime (currentTime, breadTime); printTime (doneTime); Output dari program ini adalah 12:49:30.0, yang berarti sesuai dengan apa yang kita inginkan. Namun di sisi lain, ada kasuskasus tertentu di mana hasil yang dikeluarkan ternyata tidak sesuai. Bisakah Anda menunjukkan salah satunya? Masalahnya adalah bahwa metode ini ternyata tetap menjumlahkan dan menampilkan nilai detik dan menit meskipun nilai keduanya telah melampaui angka 60. Untuk mengatasinya, kita harus berupaya agar jumlah detik setelah angka 60 bisa ditambahkan kepada nilai menit (+1 menit). Begitu juga dengan jumlah menit, setiap menembus angka 60, nilai menit tersebut akan ditambahkan ke dalam jam (+1 jam). Kode berikut ini merupakan perbaikan dari kode di atas: public static Time addTime (Time t1, Time t2) { Time sum = new Time (); sum.hour = t1.hour + t2.hour; sum.minute = t1.minute + t2.minute; sum.second = t1.second + t2.second; if (sum.second >= 60.0) { sum.second = 60.0; sum.minute += 1; 199
} if (sum.minute >= 60) { sum.minute = 60; sum.hour += 1; } return sum; } Meskipun kode di atas sudah benar, tapi ia akan menjadi semakin kompleks. Nanti, saya akan menunjukkan sebuah pendekatan alternatif yang lebih singkat untuk menyelesaikan masalahmasalah seperti ini. Kode di atas menampilkan dua operator yang belum kita lihat sebelumnya, yakni ' += ' dan ' = '. Kedua operator ini memberikan cara yang jelas untuk menaikkan (increment) atau menurunkan (decrement) nilai variabelvariabel. Keduanya hampir sama dengan ' ++ ' dan ' ' kecuali bahwa: 1. Keduanya dapat digunakan untuk operasi variabel bertipe double, selain untuk integer. 2. Derajat kenaikan atau penurunan tidak harus 1. Pernyataan sum.second = 60.0; sama dengan sum.second = sum.second – 60;
9.9
Modifier
Untuk mencontohkan suatu modifier, perhatikanlah metode increment berikut! Metode ini menambahkan nilai detik ke dalam objek Time. Lagilagi, bentuk mentah dari metode ini akan tampak sebagai berikut: public static void increment
(Time time, double
secs) { 200
time.second += secs; if (time.second >= 60.0) { time.second = 60.0; time.minute += 1; } if (time.minute >= 60) { time.minute = 60; time.hour += 1; } } Baris pertama mengerjakan operasi dasar; remainder (nilai pengingat) bekerja dengan cara yang sama seperti pada kasus yang sudah kita lihat sebelumnya. Sudah benarkah metode ini? Apa yang terjadi jika argumen secs lebih besar daripada 60? Dalam kasus ini, tidak cukup mengurangi nilai 60 sebanyak satu kali saja. Kita harus terus mengurangi 60 sampai second bernilai kurang dari 60. kita dapat melakukannya dengan hanya mengganti perintah if dengan while: public static void increment
(Time time, double
secs) { time.second += secs; while (time.second >= 60.0) { time.second = 60.0; time.minute += 1; }
201
while (time.minute >= 60) { time.minute = 60; time.hour += 1; } } Solusi ini sudah tepat, tapi tampaknya tidak terlalu efisien. Bisakah Anda memberikan sebuah solusi yang tidak membutuhkan bentukbentuk perulangan (iteration)?
9.10 Fillin Methods Umumnya Anda akan jarang sekali menemui metode seperti addTime ditulis dengan antar muka yang berbeda (dari segi argumen dan nilai kembaliannya). Ketimbang membuat sebuah objek baru setiap kali addTime dipanggil, kita bisa menyuruh si pemanggil (the caller) untuk menyediakan objek 'kosong' sebagai tempat bagi addTime untuk menyimpan hasilnya. Bandingkan kode berikut ini dengan kode sebelumnya: public static void addTimeFill
(Time t1, Time t2,
Time sum) { sum.hour = t1.hour + t2.hour; sum.minute = t1.minute + t2.minute; sum.second = t1.second + t2.second; if (sum.second >= 60.0) { sum.second = 60.0; sum.minute += 1; } if (sum.minute >= 60) { sum.minute = 60; 202
sum.hour += 1; } } Satu keuntungan dari pendekatan seperti ini adalah bahwa si pemanggil memiliki kesempatan/opsi untuk menggunakan objek yang sama secara berulangulang untuk melakukan operasi penjumlahan yang beruntun. Pendekatan ini mungkin sedikit lebih efisien, meskipun dapat membingungkan juga ketika malah menyebabkan error. Di dalam dunia pemrograman, banyak yang lebih memilih untuk menghabiskan waktunya demi sebuah program dengan run time yang pendek (baca: cepat) ketimbang menghabiskan waktunya untuk mendebug kode program yang panjang.
9.11 Mana yang terbaik? Semua yang dapat dilakukan dengan menggunakan modifier dan fillinmethod dapat juga dikerjakan menggunakan fungsi murni. Dalam kenyataannya, ada sebuah bahasa pemrograman, yang disebut sebagai bahasa pemrograman fungsional, yang hanya mengizinkan penggunaan fungsifungsi murni saja. Beberapa pemrogram percaya bahwa program yang dibangun dengan menggunakan fungsi murni akan lebih cepat untuk dikembangkan dan lebih kecil risiko errornya ketimbang program yang dibangun dengan menggunakan modifier. Meskipun begitu, tetap ada saatsaat dimana modifier lebih sesuai untuk diterapkan, dan saatsaat dimana program fungsional menjadi tidak efisien. Secara umum, saya merekomendasikan Anda untuk menulis fungsi murni saja di manapun Anda berkesempatan untuk menulisnya, lalu berganti ke modifier ketika Anda melihat keuntungan besar di dalamnya. Pendekatan seperti inilah yang mungkin disebut dengan teknik pemrograman fungsional. 203
9.12 Pengembangan secara bertahap vs perencanaan Dalam bab ini saya telah menunjukkan sebuah pendekatan untuk mengembangkan sebuah program yang saya sendiri menyebutnya sebagai rapid prototyping with iterative improvement. Dalam tiap kasusnya, saya selalu menuliskan satu bentuk mentah (atau prototipe) yang berfungsi untuk mengerjakan kalkulasi dasar, lalu mengujinya dalam beberapa kasus, untuk kemudian langsung memperbaiki kesalahan/kelemahan yang terdapat di dalamnya. Walaupun efektif, pendekatan ini juga dapat menyebabkan lahirnya kode yang tidak perlu sekaligus membingungkan – karena digunakan untuk menyelesaikan banyak kasus khusus juga tidak dapat dipercaya (unreliable) – karena Anda sendiri masih belum bisa yakin apakah Anda telah menemukan semua error yang ada. Alternatif yang ada diantaranya adalah highlevel programming (pemrograman tingkat tinggi). Dalam teknik ini, Anda harus benarbenar mengandalkan deepthinking Anda terhadap suatu kasus. Untuk kasus Time di atas, deepthinking ini berarti Anda menganggap bahwa Time merupakan angka tigadigit dalam basis 60! Artinya, second adalah “kolom satuan”, minute adalah “kolom 60an”, lalu hour adalah “kolom 3600 an”. Ketika kita menulis addTime dan increment, sebenarnya kita juga sedang mengerjakan penjumlahan dalam basis 60. Inilah yang menjadi penyebab sehingga kita memerlukan “carry” dari satu kolom ke kolom berikutnya. Dus, pendekatan alternatif yang akan kita gunakan untuk menyelesaikan semua masalah ini adalah dengan cara mengkonversikan Time ke bentuk double. Hal ini kita lakukan karena sebuah fakta bahwa komputer memang sudah “mengerti” cara mengoperasikan aritmetika untuk bentuk double. Berikut ini adalah sebuah metode yang mengkonversikan objek Time ke bentuk double: 204
public static double convertToSeconds (Time t) { int minutes = t.hour * 60 + t.minute; double seconds = minutes * 60 + t.second; return seconds; } Sekarang, apa yang perlu kita kerjakan adalah menemukan cara untuk mengubah bentuk double ke bentuk Time. Sebenarnya kita bisa saja menulis sebuah metode untuk melakukannya, tapi akan lebih masuk akal jika kita menuliskannya sebagai konstruktor ketiga: public Time (double secs) { this.hour = (int) (secs / 3600.0); secs = this.hour * 3600.0; this.minute = (int) (secs / 60.0); secs = this.minute * 60; this.second = secs; } Konstruktor ini memang tampak sedikit berbeda dengan yang lainnya, mungkin karena ia melibatkan sejumlah kalkulasi dan assignment terhadap variabelvariabel instan. Anda mungkin harus berpikir lebih dalam lagi untuk meyakinkan diri Anda sendiri bahwa teknik yang saya gunakan untuk mengubah satu basis ke dalam basis lainnya adalah benar. Jika Anda sudah yakin, maka kita dapat menggunakan metode berikut untuk menulis ulang metode addTime: public static Time addTime (Time t1, Time t2) { double seconds = convertToSeconds (t1) + convertToSeconds (t2); return new Time (seconds); 205
} Kode di atas lebih singkat daripada versi aslinya. Selain itu, saya juga lebih mudah untuk menunjukkan kepada Anda bahwa metode ini memang benar (anggaplah, seperti biasa, bahwa metodemetode yang dipanggilnya juga benar). Untuk latihan, cobalah menulis ulang increment dengan cara yang sama.
9.13 Generalisasi Dalam beberapa kasus, mengubah satu bentuk dari basis 60 ke basis 10 atau sebaliknya adalah lebih sulit daripada berpacu dengan waktu itu sendiri. Konversi suatu basis itu lebih abstrak; bahkan intuisi kita terhadap “berpacu dengan waktu” itu sendiri lebih baik. Namun jika kita mempunyai pemahaman mendalam untuk memperlakukan waktu sebagai angka berbasis 60, lalu membuat “investasi” dengan cara menulis metode pengubah/konverter (convertToSecond dan konstruktor ketiga), maka kita akan mendapatkan program yang lebih pendek, lebih enak untuk dibaca dan didebug, juga lebih andal. Kita juga akan lebih mudah untuk menambahkan beberapa fitur di kesempatan lainnya. Misalnya, Anda membayangkan untuk mengurangi dua Time lalu mencari durasi/tempo antara keduanya. Pendekatan yang naif adalah dengan mengimplementasikan suatu pengurangan yang lengkap dengan teknik “peminjaman”. Menggunakan metode pengubah akan lebih mudah. Ironisnya, terkadang tidak menganggap ringan suatu masalah justru akan membuat masalah itu menjadi 'ringan' dengan sendirinya. (semakin kecil kasuskasus khusus, maka akan semakin kecil pula kesempatan untuk membuat error)
206
9.14 Algoritma Ketika Anda menulis sebuah solusi umum untuk suatu masalah, sebagai lawan dari solusi spesifik untuk suatu masalah khusus, sebenarnya Anda sudah menulis apa yang disebut dengan algoritma. Saya menyebut kata ini dalam bab 1, tapi tidak mendefinisikannya secara mendalam. Tidaklah mudah untuk mendefinisikannya, jadi saya akan mencoba beberapa pendekatan. Pertama, pikirkanlah beberapa hal yang tidak termasuk algoritma. Sebagai contoh, ketika Anda telah belajar untuk mengalikan beberapa angka berdigit tunggal, Anda mungkin ingat akan tabel perkalian. Sebagai dampaknya, Anda mengingat 100 solusi khusus, jadi pengetahuan Anda tersebut belumlah termasuk ke dalam apa yang disebut dengan algoritma. Tapi jika Anda adalah seorang “pemalas”, Anda mungkin akan mencuranginya dengan mengambil beberapa trik di dalamnya. Sebagai contoh, untuk menemukan hasil perkalian n dengan 9, Anda dapat menulis n – 1 sebagai digit pertama dan 10 – n sebagai digit kedua. Trik ini merupakan solusi umum untuk mengalikan sebua angka berdigit tunggal dengan 9. Nah, ini baru algoritma! Dengan cara yang sama, teknikteknik yang sudah Anda pelajari ketika melakukan penjumlahan dengan “penyimpanan”, pengurangan dengan peminjaman, dan pembagian yang panjang juga termasuk algoritma. Salah satu sifat algoritma adalah bahwa ia sama sekali tidak membutuhkan kecerdasan di dalamnya. Ia merupakan proses mekanik dimana setiap proses, dari awal hingga akhir, dibuat berdasarkan sekumpulan aturan tertentu. Menurut pendapat saya, adalah memalukan bagi seorang manusia untuk menghabiskan begitu banyak waktu di sekolah hanya untuk mengeksekusi algoritma yang, secara harfiah, tidak melibatkan kecerdasan sama sekali. 207
Justru, proses ketika merancang algoritma itu sendirilah yang menarik, menantang intelektual, sekaligus sebagai pusat dari apa yang kita sebut dengan programming. Beberapa hal yang dilakukan oleh manusia secara alamiah, tanpa kesulitan atau pun kesadaran pikiran, adalah hal yang paling sulit untuk diterjemahkan dengan algoritma. Memahami bahasa natural adalah salah satu contoh yang bagus. Kita semua memang melakukannya, tapi sampai sejauh ini, belum ada satu orang pun yang mampu menjelaskan bagaimana cara kita melakukannya, setidaknya dalam bentuk algoritma. Nantinya, Anda akan berkesempatan untuk meracang suatu algoritma sederhana untuk beberapa masalah.
9.15 Daftar KataKata Istilah
Arti Sebelumya, saya telah mendefinisikan kelas sebagai sebuah koleksi/kumpulan dari metodemetode yang saling berkaitan.
Class (kelas)
Dalam bab ini kita mempelajari bahwa sebuah definisi untuk kelas juga merupakan sebuah template untuk tipe baru suatu objek.
Instance (instan)
Salah satu anggota kelas. Setiap objek merupakan instan dari suatu kelas.
Constructor
Metode khusus yang menginisialisasi variabelvariabel instan
(konstruktor)
dari objek yang baru akan dibuat.
Project (proyek) Startup class
Sebuah koleksi dari satu atau lebih definisi kelas (satu kelas per satu file) yang membentuk sebuah program. Kelas yang mengandung metode main, tempat di mana
208
Istilah
Arti eksekusi program dimulai. Sebuah metode yang hasilnya bergantung kepada paramater
Function (fungsi)
parameternya sendiri, dan tidak memberikan dampak apa pun selain nilai yang dikembalikannya.
modifier
Suatu metode yang mengubah satu atau lebih objek yang diterimanya sebagai parameter, biasanya mengembalikan void. Suatu jenis metode yang mengambil sebuah objek “kosong”
Fillinmethod
sebagai sebuah parameter lalu mengisi variabelvariabel instannya ketimbang membuat suatu nilai kembalian. Metode jenis ini biasanya bukanlah pilihan terbaik untuk digunakan.
algoritma
Suatu himpunan aturan yang digunakan untuk menyelesaikan suatu masalah dengan proses mekanis.
9.16 Latihan Latihan 9.1 Dalam permainan Scrabble1, setiap petak mengandung sebuah huruf, yang bisa digunakan untuk mengeja sebuah kata, lalu sebuah nilai (score), yang digunakan untuk menentukan nilai dari setiap kata. a) Buatlah sebuah definisi kelas dengan nama Tile yang merepresetasikan petakpetak Scrabble. Variabelvariabel insatannya haruslah sebuah karakter dengan nama letter dan sebuah integer dengan nama value. b) Tulislah sebuah konstruktor yang menggunakan parameterparameter dengan nama letter dan value lalu inisialisasilah variabelvariabel instannya. c) Tulislah sebuah metode dengan nama printTile yang menggunakan satu objek Tile 209
sebagai parameter lalu mencetak variabelvariabel instannya dalam format yang enak dan mudah untuk dibaca. d) Tulislah sebuah metode dengan nama testFile yang dapat menghasilkan satu objek Tile dengan huruf Z dan nilai 10, lalu gunakan printTile untuk mencetak keadaan (the state) objek tersebut. Inti dari latihan ini adalah untuk melatih Anda memahami sisisisi mekanis pembuatan definisi kelas yang baru sekaligus membuat kode yang digunakan untuk mengujinya. Latihan 9.2 Tulislah sebuah definisi kelas untuk Date, sebuah tipe objek yang berisi tiga integer, yakni year, month, dan day. Kelas ini harus menyediakan dua konstruktor. Konstruktor Pertama tidak boleh menggunakan parameter apapun. Sedangkan konstruktor kedua harus menggunakan year, month, dan day sebagai parameter, kemudian menggunakan ketiganya untuk menginisialisasi variabelvariabel instan. Tambahkan kode ke dalam main yang membuat sebuah objek Date baru dengan nama birthday. Objek yang baru tersebut harus berisi tanggal lahir Anda. Anda dapat menggunakan kedua konstruktor tersebut. Latihan 9.3 Sebuah bilangan rasional (rational number) adalah suatu bilangan yang dapat ditampilkan sebagai suatu rasio (perbadingan) dari dua integer. Contohnya; 2/3 adalah bilangan rasional, dan Anda pun dapat menganggap 7 sebagai bilangan rasional dengan implisit 1 dalam denominatornya. Untuk tugas kali ini, Ada akan menulis sebuah definisi kelas untuk bilangan rasional. a) Perhatikan kode berikut ini dan pastikan Anda dapat memahami apa yang dikerjakannya: public class Complex { 210
double real, imag; // simple constructor public Complex () { this.real = 0.0; this.imag = 0.0; } // constructor that takes arguments public Complex (double real, double imag) { this.real = real; this.imag = imag; } public static void printComplex (Complex c) { System.out.println (c.real + " + " + c.imag + "i"); } // conjugate is a modifier public static void conjugate (Complex c) { c.imag = c.imag; } // abs is a function that returns a primitive public static double abs (Complex c) { return Math.sqrt (c.real * c.real + c.imag * c.imag); } // add is a function that returns a new Complex object public static Complex add (Complex a, Complex b) { return new Complex (a.real + b.real, a.imag + b.imag); } public static void main(String args[]) { // use the first constructor Complex x = new Complex (); 211
x.real = 1.0; x.imag = 2.0; // use the second constructor Complex y = new Complex (3.0, 4.0); System.out.println (Complex.abs (y)); Complex.conjugate (x); Complex.printComplex (x); Complex.printComplex (y); Complex s = Complex.add (x, y); Complex.printComplex (s); } } b) Buatlah sebuah program dengan nama Rational.java yang mendefinisikan sebuah kelas dengan nama Rational. Suatu objek Rational haruslah memiliki 2 variabel instan berupa integer yang digunakan untuk menyimpan nilai numerator dan denumerator dari bilangan rasional. c) Tulislah sebuah konstruktor tanpa parameter yang menginisialisasi kedua variabel instan tersebut dengan nilai 0. d) Tulislah sebuah metode dengan nama printRational yang menggunakan objek Rational sebagai sebuah argumen lalu mencetaknya ke dalam format yang dapat diterima oleh Anda sendiri. e) Tulislah sebuah metode main yang mampu menghasilkan sebuah objek baru bertipe Rational, inisialisasilah variabelvariabel instannya dengan nilainilai tertentu, lalu cetaklah objek tersebut. f) Sampai sejauh ini, Anda telah membuat programprogram yang dapat diuji (debuggable), meski dalam jumlah yang tidak terlalu banyak. Sekarang ujilah semua 212
program itu, lalu debuglah jika memang perlu. g) Tulislah sebuah konstruktor kedua untuk kelas Anda. Kelas ini menggunakan dua argumen yang sekaligus berfungsi untuk menginisialisasi variabelvariabel instan. h) Tulislah sebuah metode dengan nama negate yang mengubah tanda dari suatu bilangan rasional. Metode ini haruslah termasuk ke dalam tipe modifier yang akan mengembalikan void. Tambahkan barisbaris ke dalam metode main untuk menguji metode baru itu. i) Tulislah sebuah metode dengan nama invert yang membalik angka dengan cara menukar (swapping) numerator dengan denumerator. Ingatingatlah pola swapping yang telah kita lihat sebelumnya. Ujilah metode ini dengan menambahkan beberapa baris kode dalam metode main. j) Tulislah sebuah metode dengan nama toDouble yang dapat mengubah bilangan rasional ke bentuk double (bilangan floatingpoint) sekaligus mengembalikan hasilnya. Metode ini termasuk ke dalam kelompok fungsi murni; jadi ia tidak akan mengubah atau memodifikasi objek yang dioperasikannya. Lalu seperti biasanya, ujilah metode yang baru Anda buat itu. k) Tulislah sebuah modifier dengan nama reduce yang dapat mengurangi sebuah bilangan rasional sampai ke bentuknya (term) yang terkecil dengan cara menemukan GCD dari numerator dan denumerator untuk kemudian membagi atas dan bawah dengan GCD. Metode ini seharusnya berupa fungsi murni yang tidak mengubah variabelvariabel instan milik objek tempat dimana dia dipanggil. l) Tulislah sebuah metode dengan nama add yang menggunakan dua bilangan rational sebagai argumen dan mengembalikan sebuah objek Rational yang baru. Objek yang dikembalikan, jelas, harus mengandung jumlah dari kedua argumen tersebut. Tujuan dari latihan ini adalah untuk menulis sebuah definisi kelas yang melibatkan berbagai jenis metode sekaligus yakni; konstruktor, modifier, dan fungsifungsi murni. 213
214
Bab 10 Array Array adalah suatu kumpulan nilai yang diidentifikasi dengan sebuah indeks. Anda dapat membuat sebuah array berisi nilainilai yang bertipe int, double, atau tipe lainnya. Tapi ingat, semua nilai tersebut harus sama tipenya. Dilihat dari sintaksnya, tipe array tampak seperti tipe Java lainnya kecuali bahwa tipe ini diikuti dengan simbol ' [ ] '. sebagai contoh, int [ ] adalah tipe array untuk integer dan double [ ] adalah tipe array untuk double. Anda dapat mendeklarasikan variabelvariabel bertipe ini dengan cara yang sudah lazim digunakan: int[] count; double[] values; Sampai Anda menginisialisasi variabelvariabel ini, semua akan diinisialisasi dengan null. Untuk membuat array, gunakan perintah new. count = new int[4]; values = new double[size]; Assignment pertama membuat variabel count akan mengacu pada sebuah array yang berisi empat nilai bertipe integer. Sementara Assignment kedua akan menyebabkan values mengacu pada suatu array bertipe double. Jumlah elemen dalam variabel values bergantung kepada size. Anda dapat menggunakan ekspresi integer apapun untuk membuat ukuran array. Gambar berikut ini menunjukkan representasi array dalam diagram keadaan.
215
Nomor yang dicetak tebal di dalam kotak merupakan elemen dari array. Sedangkan nomornomor yang berada di luar kotak merupakan indeks yang digunakan untuk mengidentifikasi setiap kotak. Ketika Anda mengalokasikan sebuah array baru, elemen elemennya akan diinisialisasi ke nol (0).
10.1 Mengakses Elemen Untuk menyimpan nilainilai di dalam array, gunakanlah operator []. Sebagai contoh, count [0] akan mengacu kepada elemen ke0 dari array tersebut. Sedangkan count [1] akan mengacu kepada elemen ke1 array tersebut. Anda dapat menggunakan operator [] dimanapun di dalam sebuah ekspresi: count[0] = 7; count[1] = count[0] * 2; count[2]++; count[3] = 60; Semua pernyataan di atas merupakan bentuk assignment yang legal dalam Java. Berikut ini adalah pengaruh yang ditimbulkan oleh lampiran kode di atas:
Mulai sekarang Anda harus memperhatikan bahwa keempat elemen dalam array ini diindeks mulai dari 0 sampai dengan 3. Ini berarti bahwa tidak ada elemen dengan nomor indeks yang sama dengan 4. Anda semestinya sudah terbiasa dengan bentuk bentuk seperti ini karena sebelumnya kita sudah melihat hal yang sama terjadi pada penomoran indeks untuk variabel bertipe data String. Meskipun begitu, terkadang kita seringkali melakukan kesalahan dengan menggunakan nomor indeks yang di luar batas array itu sendiri. Kesalahan semacam ini akan menghasilkan sebuah eksepsi, yakni ArrayOutOfBoundsExceptions. Laiknya yang terjadi pada kemuculan semua eksepsi, 216
Anda juga akan mendapatkan sebuah pesan error yang akan menyebabkan terhentinya eksekusi program Anda. Anda dapat menggunakan ekspresi apapun sebagai suatu indeks, selama ia bertipe data int. Salah satu cara yang paling umum untuk mengindeks sebuah array adalah dengan menggunakan variabel perulangan (loop variable). Simaklah contoh berikut ini: int i = 0; while (i < 4) { System.out.println (count[i]); i++; } Bentuk ini merupakan bentuk perulangan while yang lazim digunakan untuk menghitung indeks mulai dari indeks 0 hingga 4. ketika variabel perulangan, i, berisi nilai 4, syarat perulangan tersebut akan menghasilkan kesalahan. Kesalahan ini menyebabkan terhentinya eksekusi program. Oleh karena itu, badan dari perulangan tersebut hanya dieksekusi ketika i sama dengan 0, 1, 2 dan 3. Setiap kali kita melewati perulangan di atas, kita menggunakan variabel i sebagai indeks untuk array. Bentuk penelusuran array seperti ini merupakan bentuk yang paling sering digunakan. Array dan perulangan memadu bersama laksana memakan sate madura yang gurih dengan wedang jahe manis nan hangat.
10.2 Menyalin Array Ketika Anda menyalin sebuah variabel bertipe array, ingatlah bahwa Anda sebenarnya sedang menyalin sebuah acuan kepada array tersebut. Sebagai contoh: double[] a = new double [3]; double[] b = a;
217
Kode ini akan membuat sebuah array yang diisi dengan tiga elemen bertipe double, lalu membuat dua variabel yang berbeda mengacu kepadanya. Situasi ini merupakan salah satu bentuk aliasing.
Perubahan apapun yang Anda lakukan pada satu array akan memberikan pengaruh kepada array lainnya. Mungkin ini bukanlah perilaku yang Anda inginkan; sebaliknya, Anda harus membuat sebuah salinan dari array, dengan mengalokasikan sebuah array baru dan menyalin setiap elemen dari astu array ke array lainnya. double[] b = new double [3]; int i = 0; while (i < 4) { b[i] = a[i]; i++; }
10.3 Bentuk Perulangan for Perulanganperulangan yang sudah kita tulis sejauh ini merupakan perulangan yang sudah terlalu sering kita lihat. Semuanya dimulai dengan menginisialisasi sebuah variabel; menggunakan suatu test, atau sebuah persyaratan, yang bergantung kepada variabel itu sendiri; lalu di dalam perulangan itu mereka mengerjakan sesuatu untuk variabel tersebut, seperti misalnya melakukan increment untuknya. Ada sebuah bentuk perulangan alternatif bagi bentuk sangat umum seperti di atas, yakni perulangan for. Bentuk ini mampu mengekspresikan perulangan secara tepat dan lugas. Bentuk umum sintaksnya tampak sebagai berikut: for (INITIALIZER; CONDITION; INCREMENTOR) { BODY 218
} Pernyataan ini eqivalen dengan INITIALIZER; while (CONDITION) { BODY INCREMENTOR } Selain lebih lugas, bentuk ini juga lebih mudah untuk dibaca. Ini disebabkan karena semua pernyataan yag terkait dengan perulangan itu sendiri ditempatkan di dalam satu baris. Sebagai contoh: for (int i = 0; i < 4; i++) { System.out.println (count[i]); } Eqivalen dengan int i = 0; while (i < 4) { System.out.println (count[i]); i++; } Untuk latihan Anda, tulislah sebuah perulangan for yang mampu menyalin elemen elemen yang terdapat dalam sebuah array.
219
10.4 Array dan Objek Dalam beberapa kesempatan, array bertingkahlaku laiknya objek: ●
ketika Anda mendeklarasikan sebuah variabel array, Anda akan mendapatkan sebuah acuan/referensi ke sebuah array.
●
Anda harus menggunakan perintah new untuk membuat sebuah array.
●
Ketika Anda melewatkan sebuah array sebagai argumen, Anda berarti melewatkan sebuah acuan, yang berarti bahwa metode yang Anda panggil dapat mengubah elemen yang dikandung array.
Beberapa objek yang sudah kita lihat, seperti Rectangles, dapat disamakan dengan array, dalam artian bahwa keduanya merupakan kumpulan dari nilainilai yang diberi nama. Hal ini mengundang sebuah pertanyaan, “Apa beda array dengan 4 elemen bertipe integer dengan sebuah objek bertipe Rectangle?”; Jika Anda melihat kembali definisi “array” di awal bab, Anda akan menemukan sebuah perbedaan, yakni bahwa elemen dari sebuah array diidentifikasi menggunakan indeks, sementara elemenelemen (variabel instan) dari suatu objek menggunakan nama, seperti x, width, dst. Perbedaan lain antara array dan objek adalah bahwa semua elemen dari sebuah array haruslah bertipe sama. Meskipun hal yang sama juga terjadi pada objek Rectangle, namun pada kesempatan lain kita juga mengetahui bahwa ada objek lain yang mempunyai variabelvariabel instan dengan tipe data yang berbedabeda. Coba lihat kembali objek Time.
10.5 Panjang Array Sebenarnya, array sudah memiliki satu variabel instan, yakni length. Mudah diterka, variabel ini berisi informasi (nilai) mengenai panjang suatu array (jumlah elemen 220
elemen). Adalah ide brilian untuk menggunakan nilai variabel instan ini sebagai batas atas untuk sebuah perulangan ketimbang menggunakan nilai yang konstan. Dengan variabel ini, jika ukuran array suatu array mengalami perubahan, maka Anda tidak perlu lagi bersusah payah mengubah semua pernyataan yang terkait dengan perulangan; ini akan bekerja dan berfungsi untuk semu ukuran array. for (int i = 0; i < a.length; i++) { b[i] = a[i]; }
Eksekusi terakhir yang terjadi dalam badan perulangan di atas adalah ketika i sama dengan a.length – 1. ekspresi ini merupakan indeks untuk elemen terakhir array tersebut. Ketika i sama dengan a.length, syarat perulangan akan bernilai salah sehingga badan perulangan tidak akan dieksekusi lagi. Hal ini menguntungkan untuk kita karena jika ekspresi tersebut sampai dieksekusi maka eksepsi tidak akan dapat dihindari. Lampiran kode ini mengasumsikan bahwa array b mempunyai jumlah elemen yang sama dengan array a. Sebagai latihan, tulislah sebuah metode cloneArray yang menggunakan sebuah array bertipe integer sebagai parameter. Metode ini nantinya akan mampu menghasilkan sebuah array baru yang memiliki ukuran yang sama, menyalin elemenelemen dari array pertama ke array yang baru, lalu mengembalikan nilai kembalian yang berupa acuan ke array baru tersebut.
10.6 AngkaAngka Acak (Random) Programprogram komputer pada umumnya selalu mengerjakan hal yang sama setiap kali mereka dieksekusi. Sifat seperti ini membuat programprogram komputer pantas disebut sebagai sesuatu yang deterministik. Biasanya, sesuatu yang determinis adalah 221
hal yang baik karena kita cenderung untuk mengharapkan perhitungan yang sama untuk menghasilkan hasil yang sama pula. Namun untuk beberapa aplikasi, ternyata kita lebih menginginkan sebuah komputer yang memiliki sifat tidak bisa diterka (unpredictable). Permainanpermainan (games) yang dimainkan dengan sebuah komputer adalah contoh yang jelas, tapi masih banyak lagi contoh yang lain tentunya. Membuat sebuah program yang benarbenar bersifat nondeterministik adalah perkara yang sangat tidak mudah. Namun tetap ada cara untuk membuat sebuah program agar tampak bersifat nondeterministik. Salah satu cara adalah dengan menggunakan angka angka acak lalu memanfaatkannya untuk menentukan keluaran/hasil sebuah program. Java menyediakan metode bawaan yang dapat membuat angkaangka pseudorandom (imitasi terhadap definisi angka random sesungguhnya). Angkaangka ini sebenarnya tidaklah sungguhsungguh random secara matematis, tapi untuk tujuan yang hendak kita capai, mereka tampaknya bisa mengerjakan apa yang kita maksudkan. Periksalah dokumentasi untuk metode random dalam kelas Math. Nilai kembalian merupakan angka bertipe double antara 0.0 dan 1.0. untuk lebih jelasnya; angkaangka ini lebih besar atau sama dengan 0.0 dan kurang dari 1.0. Setiap kali Anda memanggil metode random, Anda akan medapatkan angka berikutnya dalam urutan yang bersifat pseudorandom (alias tidak terurut). Untuk melihat contohnya, eksekusilah perulangan ini: for (int i = 0; i < 10; i++) { double x = Math.random (); System.out.println (x); } Untuk menghasilkan bilangan acak bertipe double antara 0.0 dengan batas atas yang berupa variabel seperti high, Anda dapat mengalikan
x dengan high.
222
Bagaimana cara Anda membuat sebuah bilangan acak antara variabel low dengan high? Bagaimana cara Anda membuat bilangan acak bertipe integer? Latihan 10.1 Tulislah sebuah metode randomDouble yang menggunakan dua variabel bertipe double, yakni low dan high, yang mampu mengembalikan nilai acak bertipe double, yakni x dimana low ≤ x < high. Latihan 10.2 Tulislah sebuah metode randomInt yang menggunakan dua argumen, yakni low dan high, yang mampu mengembalikan nilai acak bertipe integer diantara low dan high dimana low ≤ x ≤ high.
10.7 Array dari Angkaangka Random (Acak) Jika implementasi metode randomInt buatan Anda berhasil, maka setiap nilai dalam interval (range) antara low dan high harus memiliki probabilitas kemunculan yang sama. Jika Anda membuat sebuah deretan angka yang panjang, maka setiap nilai harus muncul, setidaknya mendekati, angka yang sama setiap kali kemunculannya. Salah satu cara untuk menguji metode Anda adalah dengan membuat angkaangka acak dalam jumlah yang banyak, lalu menyimpannya ke dalam array, untuk kemudian menghitung jumlah kemunculan setiap nilai yang telah ditampilkan. Metode berikut ini menggunakan satu argumen, yakni ukuran array. Metode ini mengalokasikan sebuah array baru yang berisi elemenelemen bertipe integer. Kemudian mengisinya dengan bilanganbilangan acak, lalu mengembalikan sebuah acuan/referensi kepada array yang baru. public static int[] randomArray (int n) {
223
int[] a = new int[n]; for (int i = 0; i
for (int i = 0; i
2 44 81
Hasil ini kelihatan seperti sungguhsungguh acak. Hasil yang Anda peroleh mungkin akan berbeda. Jika ini merupakan hasil ujian, maka ini adalah hasil ujian yang buruk, sang guru mungkin akan menampilkan hasilnya kepada kelas dalam bentuk histogram. Histogram merupakan suatu kumpulan penghitungpenghitung (counter) yang menyimpan jejak mengenai jumlah kemunculan suatu nilai dalam setiap kesempatan. Untuk nilainilai ujian, kita mungkin menyediakan 10 penghitung untuk menyimpan jejak mengenai berapa banyak murid yang mendapatkan nilai dalam interval 90an, 80an, dst. Subbab berikutnya akan memperlihatkan Anda mengenai cara untuk membuat suatu histogram.
10.8 Pencacahan Sebuah pendekatan yang bagus untuk masalahmasalah seperti ini adalah dengan cara memikirkan sebuah metode sederhana yang mudah untuk ditulis, sekaligus memiliki kemungkinan untuk dimanfaatkan. Kemudian Anda dapat mengkombinasikannya untuk menghasilkan sebuah solusi. Tentu, adalah tidak mudah untuk mengetahui metode mana yang bisa dimanfaatkan dalam waktu yang singkat, tapi seiring meningkatnya pengalaman Anda, maka akan semakin banyak pula ideide Anda. Selain itu, terkadang kita juga tidak bisa selalu menyadari dengan jelas dan pasti suatu hal yang menurut kita mudah untuk ditulis. Namun pendekatan yang mungkin berhasil adalah dengan cara mencari penyelesaian atas submasalah yang sesuai dengan polapola masalah yang sebelumnya sudah pernah Anda lihat. 225
Kembali ke subbab 7.7, kita telah melihat sebuah perulangan yang melakukan penelusuran sebuah string lalu menghitung jumlah kemunculan suatu huruf. Anda dapat menganggap pola yang dianut oleh program ini sebagai sebuah pola dengan nama “penelusuran dan penghitungan”. Elemenelemen dalam pola ini antara lain: ●
Sebuah kumpulan kontainer yang dapat ditelusuri, seperti sebuah array atau string.
●
Sebuah pengujian yang dapat Anda lakukan kepada setiap elemen yang berada di dalam kontainer tersebut.
●
Sebuah pencacah (counter) yang menyimpan jejak tentang berapa banyak elemen yang telah melewati ujian.
Dalam kasus ini, kontainer yang dimaksudkan adalah suatu array yang bertipe integer. Pengujian yang dilakukan adalah dengan cara memasukkan nilainilai yang ada ke dalam intervalinterval nilai yang ada. Berikut ini adalah metode inRange yang menghitung jumlah suatu elemen dalam array yang masuk ke dalam interval nilai yang ada. Parameterparameter yang digunakan adalah array dan dua integer yang akan menentukan batas atas dan bawah suatu interval. public static int inRange (int[] a, int low, int high) { int count = 0; for (int i=0; i
= low && a[i] < high) count++; } return count; } Ketika saya mendeskripsikan metode ini, saya tidak terlalu peduli apakah suatu nilai yang sama dengan low atau high akan turut masuk ke dalam interval ataukah tidak, tapi 226
dari kode tersebut Anda dapat melihat bahwa low akan ikut masuk sementara high tidak. Dari sini kita langsung dapat mencegah terjadinya penghitungan ganda untuk satu elemen. Sekarang kita dapat menghitung banyak nilai (cacah bukan jumlah) yang berada dalam interval yang kita inginkan: int[] scores = randomArray (30); int a = inRange (scores, 90, 100); int b = inRange (scores, 80, 90); int c = inRange (scores, 70, 80); int d = inRange (scores, 60, 70); int f = inRange (scores, 0, 60);
10.9 Histogram Kode yang kita tulis dan lihat sejauh ini tampaknya seperti sesuatu yang selalu berulangulang. Tetapi selama interval nilai yang kita miliki tidaklah terlalu besar, hal ini bukanlah masalah serius. Tapi coba bayangkan jika Anda memiliki 100 interval. Apakah Anda mau menulis kode berikut ini? int count0 = inRange (scores, 0, 1); int count1 = inRange (scores, 1, 2); int count2 = inRange (scores, 2, 3); ... int count3 = inRange (scores, 99, 100); Saya pikir tidak! Apa yang kita inginkan sesungguhnya adalah suatu cara untuk menyimpan 100 integer, dan lebih bagus lagi kalau kita bisa memakai sebuah indeks untuk mengaksesnya. Biar saya tebak, Anda pasti langsung terpikir dengan “array!” 227
Pola penghitungan yang akan kita gunakan adalah sama, tak peduli apakah kita menggunakan satu pencacah (single counter) atau satu array berisi pencacah. Dalam kasus ini, kita menginisialisasi array di luar perulangan; lalu, di dalam perulangan itu, kita panggil metode inRange untuk menyimpan hasilnya: int[] counts = new int [100]; for (int i = 0; i<100; i++) { counts[i] = inRange (scores, i, i+1); } Satusatunya 'kecurangan' di sini adalah bahwa kita menggunakan variabel perulangan untuk dua kepentingan sekaligus: sebagai indeks untuk array juga sebagai parameter untuk inRange.
10.10 Solusi SinglePass Meskipun kode ini bekerja, tapi ia belum bisa dianggap efisien. Karena setiap kali memanggil inRange, kode ini akan menelusuri semua array. Ketika jumlah interval bertambah, maka bertambah pula waktu untuk penelusuran. Akan lebih baik bagi kita untuk membuat satu singlepass yang dapat menelusuri seluruh array, dan untuk setiap nilai yang ditelusuri, cukup dengan menghitung interval tempat nilai tersebut berada. Kemudian barulah kita dapat menaikkan nilai pencacah yang kita gunakan. Dalam contoh ini, aspek komputasi menjadi tidak begitu penting. Hal ini terjadi karena kita dapat menggunakan nilai itu sendiri sebagai indeks untuk array pencacah (array of counters). Berikut ini adalah kode yang mampu menelusuri sebuah array yang berisi nilai nilai (skor) dengan hanya satu kali penelusuran. Hasilnya adalah sebuah histogram.
228
int[] counts = new int [100]; for (int i = 0; i < scores.length; i++) { int index = scores[i]; counts[index]++; }
Latihan 10.3 Enkapsulasilah kode ini dalam satu metode dengan nama scoreHist yang menggunakan array berisi nilainilai (skor) lalu mengembalikan sebuah histogram yang berisi nilai nilai dalam array tersebut. Modifikasilah metode tersebut sehingga histogram hanya memiliki 10 pencacah, lalu menghitung banyak nilai yang muncul dalam setiap intervalnya; maksudnya banyak nilai dalam interval 90an, 80an, dst.
10.11 Daftar KataKata Istilah
Arti Kumpulan nilainilai yang diberi nama, dimana semua nilai
Array
tersebut memiliki tipe yang sama dan setiap nilai diidentifikasi dengan indeks.
koleksi elemen index
Suatu bentuk struktur data yang berisi kumpulan item atau elemen Salah satu nilai di dalam array. Operator [] digunakan untuk memilih elemen dalam suatu array. Variabel integer atau nilai yang digunakan untuk mengindikasi (menunjukkan) satu elemen dalam array.
229
deterministik
Sebuah program yang mengerjakan halhal yang sama setiap kali digunakan/dipanggil. Suatu deretan angkaangka yang tampak seperti angkaangka
pseudorandom
yang diacak, tetapi sebenarnya tidak. Angkaangka tersebut hanyalah hasil dari komputasi yang determinis. Array bertipe integer dimana setiap nilai integer akan
histogram
mencacah banyak nilai yang masuk ke dalam interval interval yang sudah ditentukan.
10.12 Latihan Latihan 10.4 Tulislah metode areFactors yang menggunakan sebuah integer, n, dan sebuah array bertipe integer, lalu mengembalikan true jika angkaangka dalam array merupakan faktor dari n (dengan kata lain; n bisa dibagi dengan semua angka tersebut). Petunjuk : Lihat Latihan 5.1 Latihan 10.5 Tulislah metode yang menggunakan sebuah array integer dan sebuah integer dengan nama target sebagai argumen. Metode ini akan mengembalikan nilai yang berupa indeks tempat dimana target pertama kali muncul dalam array tersebut. Jika nilai yang dicari tidak ditemukan maka metode akan mengembalikan nilai 1. Latihan 10.6 Tulislah metode arrayHist dengan sebuah array integer sebagai argumen, yang dapat menghasilkan sebuah array histogram yang baru. Histogram ini harus berisi 11 elemen dengan tampilan sebagai berikut: elemen t 0 numbe r of elem ent s in the array that are <= 0
230
1 numbe r of elem ent s in the array that are == 1 2 numbe r of elem ent s in the array that are == 2
... 9 numbe r of elem ent s in the array that are == 9 10 numb er of elemen ts in the array that are >= 10
Latihan 10.7 Beberapa programmer tidak setuju dengan aturan umum yang menyebutkan bahwa variabel dan metode haruslah diberi nama yang mengandung makna. Sebaliknya, mereka justru beranggapan bahwa variabel atau metode seharusnya dinamai setelah nama buahbuahan. Untuk setiap metode di bawah ini, tulislah satu kalimat yang mendeskripsikan secara abstrak tugas/fungsi yang dikerjakannya. Lalu untuk setiap variabel, identifikasilah peran yang diembannya. public static int banana (int[] a) { int grape = 0; int i = 0; while (i < a.length) { grape = grape + a[i]; i++; } return grape; } public static int apple (int[] a, int p) { int i = 0; int pear = 0; while (i < a.length) { 231
if (a[i] == p) pear++; i++; } return pear; } public static int grapefruit (int[] a, int p) { for (int i = 0; i
public static void dub (int[] jub) { for (int i=0; i<jub.length; i++) { jub[i] *= 2; } } public static int mus (int[] zoo) { int fus = 0; for (int i=0; i
dalam array, elemenelemen yang dicari berada di antara lowIndex dan highIndex, termasuk di kedua ujungnya. Metode ini haruslah bersifat rekursif. Jika panjang interval adalah 1, yaitu ketika lowIndex == highIndex, maka kita akan segera mengetahui bahwa satusatunya elemen yang berada dalam interval tersebut adalah nilai maksimumnya. Dengan demikian, inilah base case yang akan kita gunakan. Jika terdapat lebih dari satu elemen dalam suatu interval, kita dapat memecah arraynya menjadi dua bagian; tentukan nilai maksimum dalam setiap bagian arraynya, lalu temukan nilai maksimum diantara nilai maksimum yang sudah ditentukan sebelumya. b) Metodemetode seperti maxInRange seringkali mengundang 'kejanggalan' ketika digunakan. Untuk menemukan elemen terbesar dalam sebuah array, kita harus menyediakan sebuah interval yang menyertakan semua elemen dalam array. double max = maxInRange (array, 0, a.length1); Tulislah metode max yang menggunakan sebuah array sebagai parameter dan maxInRange untuk menemukan dan mengembalikan nilai terbesar. Metodemetode seperti max terkadang disebut juga metode pembungkus (wrapper methods). Disebut demikian karena metodemetode tersebut meyediakan sebuah lapisan abstraksi (layer of abstraction) di sekeliling suatu metode yang janggal. Selain itu, metode ini juga menyediakan sebuah antarmuka untuk lingkungan di luarnya sehingga bisa lebih mudah untuk digunakan. Metode yang mengerjakan komputasinya sendiri dikeal dengan istilah helper method. Kita akan melihat pola ini lagi di subbab 14.9. c) Tulislah sebuah versi rekursif dari metode find menggunakan pola wrapperhelper. Metode find harus menggunakan sebuah array integer sekaligus sebuah integer tujuan/target. Metode ini harus mengembalikan indeks dari lokasi awal dimana 234
integer target muncul di dalam array. Jika tidak, metode akan menghasilkan 1. Latihan 10.10 Salah satu cara yang tidak efisien untuk mengurutkan elemenelemen dalam suau array adalah dengan cara menemukan elemen terbesar lalu menukarnya dengan elemen pertama, kemudian menemukan elemen terbesar kedua lalau menukarnya dengan elemen kedua, dst. a) Tulislah metode indexOfMaxInRange yang menggunakan sebuah array integer sebagai argumen, yang mampu menemukan elemen terbesar dalam interval yang telah ditentukan, lalu mengembalikan hasil yang berupa nilai indeksnya. Anda dapat memodifikasi versi rekursif dari maxInRange atau Anda dapat menulis versi perulangannnya dari awal. b) Tulislah metode swapElemet yang menggunakan sebuah array integer dan dua indeks, yang mampu menukar elemenelemen yang berada pada indeks yang telah ditentukan. c) Tulislah metode sortArray yang menggunakan sebuah array integer beserta metode indexOfMaxInRange dan swapElement untuk mengurutkan array dari yang terbesar hingga yang terkecil. Latihan 10.11 Tulislah metode letterHist yang menggunakan sebuah String sebagai parameter yang mampu menghasilkan histogram dari hurufhuruf yang berada di dalam String. Elemen ke0 dari histogram harus berisi jumlah huruf a yang berada dalam string, baik huruf kecil maupun kapital, sementara elemen ke26 harus berisi jumlah huruf z yang berada dalam string. Solusi Anda ini harus menerapkan pola satu kali penelusuran. Jangan lebih! Latihan 10.12
235
Sebuah kata disebut “doubloon” jika setiap huruf yang muncul dalam kata tersebut muncul tepat dua kali. Sebagai contoh, katakata berikut ini merupakan doubloon yang saya temukan dalam kamus milikku. Abba, Anna, appall, appearer, arraigning,
beriberi,
appeases,
bilabial,
boob,
Caucasus, coco, Dada, deed, Emmett, Hannah, horseshoer,
Otto, intestines,
Isis, mama,
Mimi, murmur, noon, papa, peep, reappear, redder, sees,Toto, Shanghaiings Tulislah metode isDoubloon yang mengembalikan nilai true jika kata yang dimasukkan merupakan doubloon dan false jika sebaliknya. Latihan 10.13 Dalam permainan scrabble, setiap pemain memiliki kotakkotak yang berisi huruf di dalamnya. Tujuan permainan ini adalah untuk membuat hurufhuruf yang pada awalnya tidak beraturan menjadi teratur sehingga dapat digunakan untuk mengeja katakata. Sistem penilaian yang digunakan sangat membingungkan, tetapi sebagai panduan dasar, kata yang lebih panjang lebih banyak nilainya ketimbang kata yang lebih pendek. Bayangkan saat ini ada seseorang yang memberi Anda sebuah kumpulan kotak yang berupa String, seperti “qijibo” lalu Anda juga diberi String lain untuk menguji coba, seperti “jib”. Tulislah metode testWord yang menggunakan dua String ini. Metode ini mengembalikan nilai true jika kumpulan kotak yang diberikan dapat digunakan untuk mengeja kata. Anda diperkenankan untuk memiliki lebih dari satu kotak dengan huruf yang sama, tapi Anda hanya boleh menggunakan masingmasing kotak sebanyak satu kali saja. Jangan lebih! Latihan 10.14
236
Dalam permainan Scrabble yang sebenarnya, ada beberapa kotak kosong yang bisa digunakan sebagai wild cards; yakni sebuah kotak kosong yang dapat digunakan untuk merepresentasikan huruf apa pun. Pikirkanlah sebuah algoritma untuk testWord yang berkenaan dengan konsep wild cards. Tapi ingat! Jangan sampai terlalu jauh berurusan dengan detil implementasi, misalnya memikirkan cara untuk merepresentasikan wild cards. Bukan itu! Cobalah untuk mendeskripsikan algoritmanya saja, menggunakan bahasa yang Anda kuasai, dengan pseudocode, atau Java.
237
Bab 11 Array berisi Objek 11.1 Komposisi Sejauh ini kita telah melihat beberapa contoh komposisi (kemampuan untuk mengombinasikan fiturfitur bahasa dalam beberapa variasi susunan). Salah satunya adalah dengan menggunakan pemanggilan metode sebagai bagian dari ekspresi. Contoh lainnya adalah struktur pernyataan tersarang; Anda dapat meletakkan pernyataan if di dalam perulangan while, atau di dalam pernyataan if lainnya, dst. Setelah melihat pola ini, dan setelah belajar mengenai array dan objek, Anda semestinya tidak perlu terkejut ketika mengetahui bahwa ternyata Anda boleh memiliki array berisi objek. Kenyataannya, Anda juga dapat memiliki sebuah objek yang berisi arrayarray (sebagai variabel instan); Anda boleh memiliki array yang mengandung array; Anda juga boleh memiliki objek yang mengandung objek, dsb. Di dua bab berikutnya, kita akan melihat beberapa contoh komposisi. Diantaranya adalah objek Card.
11.2 Objek Card Jika Anda tidak akrab dengan permainan kartu yang biasa dimainkan oleh banyak orang di seluruh dunia, maka sekarang adalah waktu yang tepat untuk memulainya. Karena kalau tidak, bab ini akan menjadi siasia. Ada 52 kartu dalam sebuah tumpukan kartu (deck). Masingmasing kartu tersebut akan masuk ke dalam satu di antara empat kelompok (suit) yang ada. Empat kelompok tersebut antara lain; Sekop (Spade), Hati (Heart), Permata (Diamond), dan Wajik (Club). Selain masuk ke dalam empat
238
kelompok, setiap kartu juga akan memiliki nomor urutan/peringkat (rank) yang membedakannya dengan kartukartu lain yang masih ada dalam kelompok yang sama. Nomor urutan/peringkat ini antara lain; As (ace), 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King. Dalam permainan kartu, kartu As bisa bernilai lebih tinggi ketimbang kartu King atau malah lebih rendah ketimbang kartu 2. Hal ini tergantung dengan jenis permainan kartu yang dimainkan. Jika kita hendak mendefinisikan sebuah objek untuk merepresentasikan permainan kartu, maka kita bisa dengan mudah menyebutkan variabel instan yang diperlukan: rank dan suit. Tepat sekali! Tapi sayangnya, menentukan tipe data apa yang akan digunakan untuk menyimpan kedua variabel ini justru yang agak sulit. Kemungkinan pertama adalah String yang bisa menyimpan sesuatu seperti “Spade” untuk suit dan “Queen” untuk rank. Satu masalah yang muncul dengan implementasi seperti ini adalah kita akan menemukan kesulitan ketika harus membandingkan kartu untuk memeriksa kartu mana yang memiliki suit atau rank yang lebih tinggi. Alternatif penyelesaian masalah adalah dengan menggunakan tipe integer untuk menyandi (encode) rank dan suit. Dengan melakukan “penyandian”, bukan berarti saya ingin mengerjakan apa yang mungkin langsung terpikir di kepala banyak orang, yakni enkripsi, atau menyandi kedua variabel ini dengan kode rahasia. Apa yang dimaksud dengan penyandian oleh seorang ilmuwan komputer adalah sesuatu yang seperti “buatlah sebuah bentuk pemetaan antara deretan angkaangka dengan sesuatu yang ingin saya tampilkan”. Sebagai contoh, Spades → 3 Hearts → 2 Diamonds → 1 Clubs → 0
239
Manfaat yang jelas bisa kita lihat dari pemetaan ini adalah bahwa variabel suit dipetakan ke tipe integer dalam keadaan terurut. Hal ini akan memberikan kemudahan untuk kita ketika hendak membandingkan suit dengan cara membandingkan integer. Pemetaan untuk variabel rank tampak sangat jelas; setiap nomor urut kartu akan dipetakan sesuai dengan integer yang sama, begitu juga dengan kartukartu yang menggunakan simbolsimbol: Jack → 11 Queen → 12 King → 13 Alasan saya menggunakan notasi matematika untuk pemetaan ini adalah karena mereka semua bukanlah bagian dari program Java yang akan kita buat. Mereka hanyalah merupakan bagian dari rancangan program (program design) yang tidak akan kita ikut seratakan secara eksplisit di dalam kode. Definisi kelas untuk tipe Card akan tampak sebagai berikut: class Card { int suit, rank; public Card () { this.suit = 0; this.rank = 0; } public Card (int suit, int rank) { this.suit = suit; this.rank = rank; } } Seperti biasa, saya menyediakan dua konstruktor di sini, satu yang menggunakan
240
sebuah parameter untuk setiap variabel instan, dan satu lagi yang tidak menggunakan satu parameter pun. Untuk membuat sebuah objek yang dapat merepresentasikan rank (urutan) 3 milik wajik, kita bisa menggunakan perintah new sebagai berikut: Card threeOfClubs = new Card (0, 3); Argumen pertama, 0, merepresentasikan kelompok wajik.
11.3 Metode printCard Ketika Anda membuat sebuah kelas baru, langkah pertama yang biasanya Anda kerjakan adalah mendeklarasikan variabel instan dan menulis konstruktor konstruktornya. Kemudian diikuti dengan menulis metode standar yang harus dimiliki oleh semua objek, termasuk satu metode yang akan mencetak objek tersebut, lalu satu atau dua metode untuk membandingkan objekobjek. Saya akan memulainya dengan metode printCard. Untuk mencetak objek Card yang dapat dibaca dengan mudah oleh manusia, kita akan memetakan kodekode integer ke bentuk yang berupa katakata. Cara alami untuk melakukannya adalah dengan menggunakan sebuah array berisi String. Anda bisa membuat sebuah array berisi String dengan cara yang sama seperti ketika Anda membuat sebuah array yang berisi tipe data primitif: String[] suits = new String [4]; Kemudian kita isi elemenelemen array ini dengan nilainilai sebagai berikut: suits[0] = "Clubs"; suits[1] = "Diamonds"; suits[2] = "Hearts"; suits[3] = "Spades"; Membuat sebuah array dan langsung menginisialisasinya merupakan operasi yang 241
sangat sering dilakukan. Saking seringnya, sampaisampai Java malah sudah menyediakan sintaks khusus untuk melakukannya: String[]
suits
= {"Clubs",
"Diamonds" , "Hearts",
"Spades"};
Pengaruh yang dihasilkan oleh pernyataan di atas akan sama dengan yang ditimbulkan oleh deklarasi, alokasi dan assignment secara terpisah. Diagram keadaan dari array ini akan tampak seperti gambar berikut:
Elemenelemen array merupakan sesuatu yang mengacu (references) ke String, ketimbang menjadi String itu sendiri. Hal ini adalah fakta umum yang berlaku untuk semua array yang berisi objekobjek, sebagaimana yang akan saya bahas lebih detail di bagian berikutnya. Sekarang, yang kita butuhkan adalah array berisi String lain yang digunakan untuk mengkode balik (decode) peringkatperingkat dalam kartu: String[] ranks = { "narf", "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King" };
Elemen “narf” digunakan sebagai penjagatempat (placekeeper) untuk elemen ke0 dari array yang tidak akan pernah dipakai dalam permainan kartu. Indeks array yang bisa dipakai hanyalah 1 s.d. 13. Elemen yang tidak digunakan ini memang tidak penting tentunya. Sebenarnya kita bisa saja memulainya dari urutan ke0, seperti biasa, tapi akan lebih baik bagi kita untuk mengkode 2 dengan 2, 3 dengan 3, 4 dengan 4, dst. Dengan arrayarray ini, kita bisa memilih String yang tepat dengan menggunakan variabel suit dan rank sebagai indeks. Dalam metode printCard, publi c st atic voi d pr intC ard ( Car d c) {
242
Strin g[] su its = { "C lubs ", "Di amon ds", "Hear ts", "Spad es" } ; Strin g[] rank s = { "n arf" , "A ce", "2" , "3 ", "4", "5", "6", " 7", "8" , "9 ", " 10", "Ja ck", "Qu een" , "K ing" }; Syste m.ou t.pr intl n (ran ks[c .ran k] + " of " + suits [c.s uit] ); }
Ekspresi suits[c.suit] artinya adalah “gunakan variabel instan suit dari objek c sebagai sebuah indeks ke dalam array dengan nama suits, lalu pilih string yang ada di situ.” hasil dari kode berikut Card card = new Card (1, 11); printCard (card); adalah Jack of Diamonds.
11.4 Metode sameCard Kata “sama” termasuk salah satu kata dari beberapa kata dalam bahasa alami (natural language) yang tampaknya baru akan jelas maksudnya ketika Anda memberinya dengan maksudmaksud tertentu. Sebagai contoh, jika saya berkata “Chris dan saya mempunyai mobil yang sama,” maka di sini saya bermaksud menginformasikan bahwa mobil miliknya dan milik saya mempunyai model dan tahun pembuatan yang sama. Jika saya berkata bahwa bahwa “Chris dan saya mempunyai ibu yang sama,” di sini saya bermaksud mengatakan bahwa ibunya dan ibu saya adalah satu ibu yang sama. Jadi, ide mengenai “kesamaan” bisa dimaknai berbedabeda tergantung dengan konteks kalimatnya. Ketika Anda berbicara mengenai objek, Anda juga akan menemui ambiguitas yang sama. Sebagai contoh, jika dua kartu adalah kartu yang sama, apakah itu berarti 243
keduanya memiliki data yang sama (rank dan suit), ataukah keduanya hanya merupakan dua objek Card yang sama? Untuk melihat apakah dua objek berbeda mengacu ke objek yang sama, kita bisa menggunakan operator “ == ”. Sebagai contoh: Card card1 = new Card (1, 11); Card card2 = card1; if (card1 == card2) { System.ou t.prin tln ("card1 and card2 are the same object."); }
Bentuk kesamaan seperti ini disebut shallow equality (tidak dalam) karena hanya membandingkan objek yang di acu, bukan isi dari objek tersebut. Untuk membandingkan isi objek tersebut – deep quality – kita bisa menulis sebuah metode dengan nama sameCard. public static boolean sameCard (Card c1, Card c2) { return (c1.suit == c2.suit && c1.rank == c2.rank); }
Sekarang jika kita membuat dua objek yang berbeda yang berisi data yang sama, kita bisa menggunakan metode
sameCard
untuk melihat apakah keduanya
merepresentasikan kartu yang sama: Card card1 = new Card (1, 11);
244
Card card2 = new Card (1, 11); if (sameCard (card1, card2)) { System.out.println same
("card1 and card2 are the
card.");
} Dalam kasus ini, card1 dan card2 adalah dua objek berbeda yang berisi data yang sama.
Jadi, persyaratan (condition) di dalam kode di atas bernilai True. Bagaimana bentuk diagram keadaan ketika card1 == card2 bernilai True? Di bagian 7.10 saya mengatakan bahwa Anda sebaiknya jangan pernah menggunakan operator == dalam String karena operator ini sebenarnya tidak mengerjakan apa yang Anda harapkan. Ketimbang membandingkan isi String (deep equality), kita lebih baik memeriksa apakah kedua String merupakan objek yang sama (shallow equality) ataukah tidak.
11.5 Metode compareCard Untuk tipe datatipe data primitif, terdapat operatoroperator kondisional yang dapat membandingkan nilainilai sekaligus menentukan nilai mana yang lebih besar daripada nilai lainnya. Operatoroperator ini (<, rel="nofollow">, dll) tidak bisa digunakan untuk tipetipe
245
objek. Khusus untuk String, ada sebuah metode builtin yang dinamai compareTo. Untuk Card, kita harus menulisnya sendiri. Metode ini akan kita sebut sebagai metode compareCard. Berikutnya, kita akan menggunakan metode ini untuk mengurutkan tumpukan kartu. Beberapa himpunan/kelompok (set) data berada dalam keadaan yang memang sudah terurut. Ini artinya, Anda bisa membandingkan dua elemen apapun dari kelompok data tersebut untuk kemudian langsung mengatakan elemen mana yang lebih besar atau sebaliknya. Angkaangka yang termasuk ke dalam kelompok tipe data integer dan floatingpoint bisa Anda jadikan contoh dalam hal ini. Namun ada juga kelompok data yang tidak terurut secara alami (baca: tidak untuk diurutkan). Maksudnya, tidak ada cara untuk menyebutkan bahwa satu elemen lebih besar daripada elemen lainnya. Sebagai contoh, buahbuahan merupakan kelompok data yang tidak bisa atau memang tidak untuk diurutkan. Hal inilah yang menyebabkan kita tidak akan pernah bisa membandingkan buah jeruk dengan buah apel menggunakan sudut pandang perbandingan antara dua angka seperti pada contoh sebelumnya. Dalam Java, tipe boolean juga tidak termasuk ke dalam kelompok tipe data terurut. Oleh karena itulah, kita tidak bisa menyebut True lebih besar daripada False. Kelompok kartu yang biasa dimainkan bisa dianggap sebagai kelompok data yang terurut secara parsial. Maksudnya, terkadang kita memang bisa membandingkan dua kartu tetapi terkadang malah sebaliknya. Contohnya, saya mengetahui bahwa kartu wajik dengan nomor 3 lebih tinggi daripada kartu wajik dengan nomor 2, dan kartu permata bernomor 3 lebih tinggi daripada kartu wajik bernomor sama. Tapi kartu mana yang lebih baik, kartu wajik dengan nomor 3 ataukah kartu permata yang hanya bernomor 2? Kartu pertama memiliki peringkat yang lebih tinggi, tapi kartu lainnya memiliki kelompok yang lebih tinggi.
246
Agar kartukartu ini bisa dibandingkan, kita harus memutuskan variabel mana yang lebih penting, variabel rank (peringkat) ataukah suit (kelompok). Jujur saja, dua pilihan di atas akan sangat tergantung dengan selera masingmasing individu. Namun untuk kasus ini, saya berpendapat bahwa variabel suit lebih penting, karena ketika Anda membeli satu set kartu, kartukartu itu tersusun dalam susunan di mana kartukartu wajik ditempatkan di atas permata dan kelompok lainnya. Bukan disusun berdasarkan nomor kartu. Dengan keputusan di atas, kita bisa menulis metode compareCard. Di sini kita membutuhkan dua buah Card sebagai parameter, angka 1 sebagai nilai kembalian jika kartu yang ditulis pertama menang dan 1 jika sebaliknya, serta 0 jika keduanya sama (dari segi isi/kandungan objek Card tersebut). Terkadang akan membingungkan juga jika kita selalu menggunakan ketiga nilai di atas sebagai nilai kembalian, tetapi fakta berkata lain, ketiga nilai tersebut sebenarnya telah menjadi standar untuk metode metode yang berhubungan dengan perbandingan. Awalnya, kita akan membandingkan kartukartu tersebut berdasarkan kelompok: if (c1.suit > c2.suit) return 1; if (c1.suit < c2.suit) return 1;
Jika kedua pernyataan di atas tidak ada yang bernilai True, maka kelompokkelompok kartu tersebut tentunya bernilai sama. Jika ini yang terjadi, kita harus membandingkan peringkatnya: if (c1.rank > c2.rank) return 1; if (c1.rank < c2.rank) return 1;
Jika kedua pernyataan di atas juga tidak bernilai True, maka peringkatnya tentu sama, jadi kita akan mengembalikan nilai 0. Dalam model pemeringkatan seperti ini, kartu As 247
akan dianggap lebih rendah daripada kartu dengan nomor 2. Untuk latihan, perbaikilah metode di atas agar kartu As bisa bernilai lebih tinggi ketimbang kartu King, lalu enkapsulasilah kode Anda ini ke dalam sebuah metode.
11.6 Array berisi Card Alasan saya memilih Card sebagai objek untuk bab ini adalah karena terdapat sebuah manfaat jelas yang bisa didapat dari array berisi kartu, yakni sebuah tumpukan. Berikut ini adalah lampiran kode untuk membuat sebuah tumpukan yang berisi 52 kartu. Card[] deck = new Card [52]; Berikut ini adalah diagram keadaan untuk objek ini:
Hal penting yang harus dilihat di sini adalah bahwa array yang kita buat hanya berisi referensi ke objek; tapi tidak menampung objekobjek Card itu sendiri. Nilainilai dari elemen array diinisialisasi dengan null. Anda bisa mengakses elemenelemen array dengan cara yang sudah lazim dipakai: if (deck[3] == null) { System.out.println ("No cards yet!"); } Tapi jika Anda mencoba mengakses variabelvariabel instan dari Card yang memang tidak ada atau belum dideklarasikan, maka Anda akan mendapatkan NullPointerException. deck[2].rank; // NullPointerException Tidak perlu diperdebatkan lagi, sintaks di atas merupakan sintaks yang tepat untuk mengakses kartu dengan nomor 2 yang berada dalam tumpukan (sebenarnya yang
248
ketiga – kita memulainya dari urutan ke0 bukan?). Ini adalah contoh lain dari bentuk composition, kombinasi antara sintaks untuk mengakses satu elemen dalam sebuah array dan sebuah variabel instan dari suatu objek. Cara termudah untuk mengisi suatu tumpukan dengan objekobjek Card adalah dengan menggunakan perulangan tersarang (nested loop): int index = 0; for (int suit = 0; suit <= 3; suit++) { for (int rank = 1; rank <= 13; rank++) { deck[index] = new Card (suit, rank); index++; } } Perulangan bagian luar akan menghasilkan kelompokkelompok kartu, mulai dari nol sampai dengan tiga (0 s.d. 3). Untuk setiap kelompok kartu, perulangan bagian dalam akan menghasilkan peringkatnya, yakni mulai dari 1 s.d. 13. Karena perulangan (loop) bagian luar dieksekusi 4 kali dan perulangan bagian dalam 13 kali, total eksekusi yang terjadi di dalam badan perulangan adalah 52 kali (sama dengan 13 x 4). Saya menggunakan variabel index untuk melacak keberadaan kartu berikutnya di dalam tumpukan. Diagram keadaan berikut ini menunjukkan keadaan tumpukan setelah dua kartu dimasukkan:
249
Latihan 11.1 Enkapsulasilah kode pembuat tumpukan ini ke dalam sebuah metode dengan nama buildDeck yang tidak menggunakan parameter dan menghasilkan sebuah array berisi kumpulan Card.
11.7 Metode printDeck Ketika Anda bekerja dengan array, adalah sesuatu yang wajar untuk menulis sebuah metode yang akan mencetak isi array tersebut. Sebelumnya kita telah melihat pola penelusuran array beberapa kali. Jadi, Anda seharusnya sudah cukup akrab dengan metode berikut ini: public static void printDeck (Card[] deck) { for (int i=0; i<deck.length; i++) { printCard (deck[i]); } } Karena parameter deck bertipe Card[], elemen dalam deck akan bertipe Card. Oleh karena itu, deck[i] merupakan argumen legal untuk metode printCard. 250
11.8 Searching Metode berikutnya yang ingin saya tulis adalah findCard. Metode ini akan melakukan pencarian di dalam array berisi Card untuk melihat apakah array tersebut memiliki kartu yang sedang dicari. Mungkin metode ini tidak jelas akan dimanfaatkan untuk apa, tapi hal ini justru memberikan kesempatan bagi saya untuk mendemonstrasikan dua cara untuk melakukan pencarian, yaitu linear search dan bisection search. Teknik linear search lebih sering digunakan; teknik ini menggabungkan penelusuran tumpukan dan pembandingan setiap kartu dengan kartu yang kita cari. Ketika kita menemukannya, kita akan mengembalikan indeks lokasi kartu tersebut. Tapi jika kita tidak menemukannya di dalam tumpukan, kita mengembalikan nilai 1. public static int findCard (Card[] deck, Card card) { for (int i = 0; i< deck.length; i++) { if (sameCard (deck[i], card)) return i; } return 1; } Argumenargumen findCard kita namai dengan nama card dan deck. Mungkin akan terlihat ganjil jika kita menggunakan sebuah variabel dengan nama yang sama dengan tipenya (variabel card bertipe Card). Hal seperti ini adalah sesuatu yang legal dan lazim dipakai, meskipun terkadang hal ini justru akan membuat kode yang kita tulis menjadi lebih sulit untuk dibaca. Namun dalam kasus ini, saya kira hal ini bisa berfungsi dengan baik. Metode ini akan langsung mengembalikan nilai kembalian ketika kartu yang dicari sudah ditemukan. Ini berarti, kita tidak perlu menelusuri semua indeks dalam tumpukan jika kartu yang dicari sudah ditemukan. Jika perulangan yang dilakukan sudah 251
dihentikan tanpa menemukan kartu yang dicari, kita akan segera tahu bahwa kartu tersebut tidak berada di dalam tumpukan. Oleh karena itu, metode ini akan mengembalikan nilai 1. Jika kartukartu yang berada dalam tumpukan tidak berada dalam posisi yang terurut, maka tidak ada cara yang lebih cepat untuk melakukan pencarian ketimbang cara ini. Kita harus memeriksa setiap kartu karena tanpa ini, kita tidak akan bisa memastikan bahwa kartu yang kita cari memang benarbenar tidak ada di dalam tumpukan itu. Namun ketika Anda mencari sebuah kata di dalam kamus, Anda tidak mencari kata tersebut secara linear atau kata demi kata. Hal ini terjadi karena katakata tersebut berada dalam posisi yang sudah terurut secara alfabetis. Akibatnya, Anda mungkin menggunakan sebuah algoritma yang sama dengan bisection search: 1. Mulailah mencari dari bagian tengah. 2. Pilihlah sebuah kata dalam halaman lalu bandingkanlah setiap kata tersebut dengan kata yang Anda cari. 3. Jika Anda menemukan kata yang Anda cari, berhenti. 4. Jika kata yang sedang Anda cari, secara alfabetis lebih besar daripada kata yang berada dalam halaman, pindahlah ke halaman berikutnya lalu kerjakan kembali langkah 2. 5. Jika kata yang sedang Anda cari, secara alfabetis lebih kecil daripada kata yang berada dalam halaman, pindahlah ke halaman sebelumnya lalu kerjakan kembali langkah 2. Jika Anda sudah menemukan dua kata yang adjacent (saling mengiringi, contohnya; kata fakta dengan faktor) di suatu halaman dalam kamus tetapi Anda tidak mendapati kata yang Anda cari (misalnya; fakto) yang seharusnya terletak diantara dua kata itu, Anda bisa menyimpulkan bahwa kata yang Anda cari tersebut tidak tercantum dalam kamus itu. Satusatunya alternatif yang mungkin terjadi adalah kata yang sedang Anda 252
cari tidak berada dalam berkas kamus tersebut, tapi ini justru kontradiktif dengan asumsi yang ada; bahwa katakata sudah tersusun dalam keadaan terurut alfabetis. Dalam kasus tumpukan kartu ini, jika kita tahu bahwa kartukartu tersebut berada dalam posisi yang terurut, kita bisa menulis metode findCard yang lebih cepat. Cara terbaik untuk menulis bisection search adalah dengan menggunakan teknik rekursif. Hal ini terjadi karena bisection sebenarnya merupakan sesuatu yang secara alamiah bersifat rekursif. Trik yang digunakan adalah dengan cara menulis sebuah metode findBisect yang menggunakan dua indeks sebagai parameter, yakni low dan high, yang akan mengindikasikan segmen/zona dari array yang akan dikenai pencarian (termasuk dua parameter itu sendiri, low dan high). 1. Untuk mencari di dalam array, pilihlah sebuah indeks di antara low dan high (berilah nama mid) lalu bandingkan dengan kartu yang sedang Anda cari. 2. Jika Anda telah menemukannya, hentikan pencarian. 3. Jika kartu yang berada dalam mid lebih tinggi daripada kartu yang Anda cari, carilah kartu tersebut di daerah dalam interval antara low hingga mid1. 4. Jika kartu yang berada dalam mid lebih rendah daripada kartu yang Anda cari, carilah kartu tersebut di daerah dalam interval antara mid1 hingga high. Langkah 3 dan 4 kelihatan seperti sesuatu yang bersifat rekursif. Berikut ini adalah implementasinya dalam kode Java: public static int findBisect
(Card[] deck, Card
card, int low, int high) { int mid = (high + low) / 2; int comp = compareCard (deck[mid], card);
253
if (comp == 0) { return mid; } else if (comp > 0) { return findBisect (deck, card, low, mid1); } else { return findBisect (deck, card, mid+1, high); } } Ketimbang memanggil metode compareCard tiga kali, saya lebih memilih untuk memanggilnya sekali lalu menyimpan hasilnya. Meskipun kode ini merupakan inti (kernel) dari bisection search, tetapi kode ini masih menyisakan sesuatu yang janggal. Dalam kode di atas, terlihat bahwa jika kartu yang kita cari tidak berada di dalam tumpukan, metode ini akan berekursi selamanya. Kita membutuhkan sebuah cara untuk mendeteksi kondisi ini dan mengatasinya dengan cepat (misalnya dengan cara mengembalikan nilai 1). Cara termudah untuk menyatakan bahwa kartu Anda tidak berada dalam tumpukan adalah jika memang benarbenar tidak ada kartu lagi dalam tumpukan. Kondisi ini terjadi ketika high lebih rendah daripada low. Sebenarnya, masih ada kartu di dalam tumpukan, itu pasti, namun yang saya maksudkan di sini adalah bahwa tidak ada kartu di dalam segmen tumpukan yang telah diindikasikan dengan low dan high. Dengan tambahan baris seperti di bawah ini, metode ini akan bekerja dengan baik: public static int findBisect
(Card[] deck, Card
card, int low, int high) { System.out.println (low + ", " + high); if (high < low) return 1; 254
int mid = (high + low) / 2; int comp = deck[mid].compareCard (card); if (comp == 0) { return mid; } else if (comp > 0) { return findBisect (deck, card, low, mid1); } else {
return findBisect
(deck, card, mid+1,
high); } }
Saya menambahkan sebuah pernyataan print di awal metode agar saya bisa memantau urutan pemanggilan rekursif yang terjadi sekaligus meyakinkan diri saya sendiri bahwa metode ini akan sampai ke base case. Saya mencoba kode berikut ini: Card card1 = new Card (1, 11); System.out.println
(findBisect
(deck, card1, 0,
51)); Lalu saya mendapatkan hasil sebagai berikut: 0, 51 0, 24 13, 24 19, 24 22, 24 23 255
Kemudian saya buat sebuah kartu yang tidak berada di dalam tumpukan, yakni kartu permata bernomor 15. Saya mencoba untuk mencarinya, lalu saya mendapatkan hasil seperti ini: 0, 51 0, 24 13, 24 13, 17 13, 14 13, 12 1 Uji coba ini belum membuktikan benartidaknya program ini. Bahkan tidak diketahui berapa jumlah uji coba yang harus dilakukan untuk memastikan kebenaran program ini. Di lain pihak, dengan melihat beberapa contoh kasus sekaligus mempelajari kode tersebut, mungkin Anda bisa meyakinkan diri Anda sendiri. Jumlah pemangilan rekursif yang terjadi bisa dibilang relatif kecil, antara 6 atau 7. Ini berarti bahwa kita hanya perlu memanggil compareCard sebanyak 6 atau 7 kali. Bandingkan dengan 52 kali kemungkinan pemanggilan jika kita menggunakan pencarian linear. Secara umum, bisection search lebih cepat daripada linear search. Bahkan untuk array dengan ukuran yang besar. Dua kesalahan yang sering terjadi dalam programprogram rekursif adalah kealpaan dalam memasukkan base case sehingga program tidak akan pernah menemui pemicu untuk berhenti melakukan pemanggilan. Kesalahan seperti ini akan menyebabkan terjadinya pemanggilan rekursi yang tidak berhingga (infinite recursion). Dalam keadaan seperti ini, Java (secara otomatis) akan mengeluarkan StackOverflowException.
256
11.9 Tumpukan dan subtumpukan Melihat kembali interface yang digunakan oleh metode findBisect public static int findBisect (Card[] deck, Card card, int low, int high)
mungkin masih masuk akal untuk menganggap tiga parameter di atas, yakni; deck, low, dan high, sebagai parameter tunggal yang menentukan sebuah subdeck. Cara berpikir seperti ini sebenarnya sudah lazim digunakan, dan terkadang saya menyebutnya sebagai parameter abstrak (abstract parameter). Maksud saya dengan kata 'abstrak' adalah sesuatu yang secara literal tidak termasuk ke dalam kode program, tetapi digunakan untuk mendeskripsikan fungsi dari program dalam level yang lebih tinggi. Sebagai contoh, ketika Anda memanggil sebuah metode, lalu melewatkan sebuah array dengan batasan berupa variabel low dan high ke dalamnya, maka tidak akan ada yang dapat mencegah metode terpanggil untuk mengakses bagianbagian dari array yang berada di luar batas. Jadi, Anda sebenarnya tidak mengirim sebuah subbagian dari tumpukan; sebaliknya, Anda benarbenar mengirimkan semua tumpukan. Namun selama si penerima bermain sesuai dengan aturan, saya kira masih masuk akal untuk menganggap tumpukan di atas sebagai sebuah subtumpukan, tentunya secara abstrak. Masih ada satu contoh lagi tentang abstraksi seperti ini yang mungkin bisa Anda temui dalam subbab 9.7, yakni ketika saya menyebutnyebut mengenai struktur data yang “kosong”. Alasan saya menggunakan tanda petik untuk kata kosong adalah untuk menyiratkan kepada Anda bahwa kata kosong di situ sebenarnya tidaklah terlalu tepat. Semua variabel sebenarnya memiliki nilai sepanjang program tersebut berfungsi. Ketika Anda menciptakan variabelvariabel tersebut, semua sudah diberi dengan nilainilai bawaan (default). Jadi, sejatinya tidak ada satu objek yang layak disebut sebagai objek yang kosong. 257
Tapi jika sebuah program bisa menjamin bahwa nilai terbaru dari sebuah variabel tidak pernah dibaca sebelum ditulis, maka nilai terbaru tersebut termasuk irrelevan. Oleh karena itu, secara abstrak, dalam kondisi seperti ini, kita bisa saja menganggap variabel tersebut sebagai variabel yang “kosong”. Cara berpikir seperti ini, di mana sebuah program dipersepsikan melebihi (beyond) apa yang bisa ditulis merupakan salah satu bagian penting dalam kerangka berpikir para ilmuwan komputer (computer scientist). Terkadang, kata “abstrak” terlalu sering digunakan. Saking seringnya, sampaisampai ia sering kehilangan maknanya dalam berbagai konteks kalimat. Walaupun begitu, abstraksi merupakan ide utama dalam dunia ilmu komputer (sebagaimana juga terjadi di bidangbidang lainnya). Definisi abstraksi yang lebih umum adalah sebagai berikut; “suatu proses yang bertujuan untuk memodelkan sebuah sistem kompleks dengan menggunakan deskripsi sederhana agar dapat menekan detaildetail yang tidak penting pada saat menangkap perilakuperilaku yang relevan.”
11.10 Daftar KataKata Istilah encode
Arti Merepresentasikan satu himpunan/set nilai dengan satu set nilai lainnya menggunakan pemetaan antara keduanya.
Shallow equality
Kesamaan acuan. Dua pengacu (references) menunjuk ke objek yang sama.
Deep quality
Kesamaan nilai. Dua pengacu (references) yang menunjuk ke objek yang memiliki nilai yang sama.
Abstract
Satu set parameter yang beraksi bersamasama sebagai sebuah
parameter
parameter.
abstraction
Proses menginterpretasi/memaknai sebuah program (atau sesuatu 258
yang lain) dengan pemaknaan yang lebih tinggi daripada apa yang direpresentasikan secara literal di dalam kode.
11.11 Latihan Latihan 11.2 Bayangkanlah sebuah permainan kartu yang tujuannya adalah untuk mengumpulkan kartukartu dengan jumlah total sama dengan 21. Skor total untuk setiap pemain adalah skor total dari semua kartu yang ada dalam genggaman tangan pemain. Skor untuk masingmasing kartu adalah sebagai berikut: kartu As bernilai 1, semua kartu yang bergambar (Jack, Queen, King) bernilai 10, sementara kartukartu yang lain diberi nilai sesuai dengan peringkatnya. Contoh: pemain yang memegang kartu As, 10, Jack, dan 3 memiliki skor total sebesar 1 + 10 + 10 + 3 = 24. Tulislah metode handScore yang menggunakan sebuah array berisi kartukartu sebagai argumen. Nilai yang akan dikembalikan adalah skor total dari kartukartu tersebut. Peringkatperingkat kartu yang digunakan harus berdasarkan pada pemetaan yang ada dalam subbab 11.2, dengan kartu As sebagai kartu yang bernilai 1. Latihan 11.3 Metode printCard yang ada dalam subbab 11.3 menggunakan sebuah objek Card dan mengembalikan sebuah string yang merepresentasikan kartu yang diminta. Buatlah sebuah metode kelas untuk kelas Card dengan nama parseCard. Metode ini menggunakan sebuah String sebagai argumen dan mampu mengembalikan kartu yang terkait dengan String tersebut. Anda bisa mengasumsikan bahwa String tersebut berisi nama dari sebuah kartu dalam format yang valid seperti yang sudah dihasilkan oleh metode printCard.
259
Dengan kata lain, string itu akan berisi satu spasi di antara variabel rank dan kata “of”, dan di antara kata “of” dengan variabel suit. Jika string itu tidak berisi nama kartu yang legal, maka metode akan mengembalikan objek berisi null. Tujuan dari latihan ini adalah untuk mengulang konsep tentang parsing dan mengimplementasikan sebuah metode yang dapat memparse suatu string yang ditentukan. Latihan 11.4 Tulislah sebuah metode, suitHist, yang menggunakan sebuah array berisi kartukartu sebagai parameter dan mengembalikan sebuah histogram dari kelompokkelompok kartu yang berada di genggaman tangan seorang pemain. Solusi yang Anda gunakan hanya boleh menelusuri array sebanyak satu kali saja, tidak lebih! Latihan 11.5 Tulislah sebuah metode isFlush yang menggunakan sebuah array berisi kartukartu sebagai sebuah parameter dan mampu mengembalikan nilai True jika kartukartu yang sedang dipegang berstatus flush, dan False jika sebaliknya. Flush adalah sebuah keadaan ketika seorang pemain mempunyai lima atau lebih kartu yang berasal dari kelompok yang sama.
260
Bab 12 Objek berisi Array 12.1 Kelas Deck Dalam bab sebelumnya, kita sudah bekerja dengan sebuah array yang berisi objek, tetapi saya juga menyebutkan bahwa kita juga berkesempatan memiliki objek yang berisi sebuah array sebagai sebuah variabel instan. Dalam bab ini kita akan membuat sebuah objek baru, yakni Deck, yang berisi sebuah array yang berisi Card sebagai variabel instannya. Definisi kelasnya tampak sebagai berikut: class Deck { Card[] cards; public Deck (int n) { cards = new Card[n]; } }
Nama variabel instannya adalah cards. Nama ini digunakan agar kita bisa membedakan antara objek Deck dengan array berisi Card yang dikandungnya. Berikut ini adalah diagram keadaan yang menunjukkan keadaan objek Deck ketika tidak ada satu pun kartu dimasukkan ke dalamnya.
261
Seperti biasa, konstruktor akan menginisialisasi variabel instan, tapi dalam kasus ini, konstruktor menggunakan perintah new untuk membuat sebuah array yang berisi kartu kartu. Meskipun begitu, konstruktor ini tidak membuat kartu yang bisa masuk ke dalamnya. Untuk melakukannya, kita akan menulis konstruktor lain yang akan membuat sebuah tumpukan berisi 52 kartu lalu mengisinya dengan objekobjek Card: public Deck () { cards = new Card[52]; int index = 0; for (int suit = 0; suit <= 3; suit++) { for (int rank = 1; rank <= 13; rank++) { cards[index] = new Card (suit, rank); index++; } } }
Perhatikan kemiripan metode di atas dengan metode buildDeck. Bedanya, di sini kita harus sedikit mengubah sintaksnya agar bisa digunakan sebagai konstruktor. Untuk memanggilnya, kita akan menggunakan perintah new: Deck deck = new Deck (); Sekarang, karena kita sudah mempunyai kelas Deck, kita bisa meletakkan semua metode yang terkait dengan Deck di dalam definisi kelas Deck. Bila kita melihat kembali semua metode yang sudah kita tulis sejauh ini, salah satu calon metode 262
(kandidat) yang bisa dimasukkan ke dalam definisi kelas adalah metode printDeck (subbab 11.7). Inilah metode tersebut setelah ditulis ulang. Hal ini dilakukan agar metode ini bisa berfungsi ketika dipakai oleh objek Deck: public static void printDeck (Deck deck) { for (int i=0; i<deck.cards.length; i++) { Card.printCard (deck.cards[i]); } }
Sesuatu yang paling jelas harus diganti adalah tipe parameternya, dari Card[] menjadi Deck. Perubahan kedua adalah bahwa sekarang kita tidak bisa lagi menggunakan perintah deck.length untuk mendapatkan panjang array. Hal ini terjadi karena deck sekarang merupakan objek dari Deck, bukan sebuah array lagi. deck memang berisi array, tapi dia sendiri bukanlah sebuah array. oleh karena itu, kita harus menulis perintah deck.cards.length untuk mengekstrak array dari objek Deck untuk kemudian mengambil ukuran array tersebut. Untuk alasan yang sama, kita harus menggunakan perintah deck.cards[i] untuk mengakses sebuah elemen milik array, bukan dengan perintah deck[i]. Perubahan terakhir adalah bahwa pemanggilan printCard harus dilakukan secara eksplisit. Hal ini sebagai bukti bahwa metode ini memang benarbenar didefinisikan di dalam kelas Card. Untuk metodemetode yang lainnya, masih tidak jelas apakah mereka harus dimasukkan ke dalam kelas Card ataukah Deck. Sebagai contoh, findCard menggunakan argumen yang terdiri dari sebuah Card dan Deck. Oleh karena itu, Anda sebenarnya bisa
263
meletakkan metode ini ke dalam salah satu kelas yang disebutkan.
12.2 Shuffling Untuk permainanpermainan kartu yang sering dimainkan, Anda harus mampu melakukan pengacakan (kocok) satu tumpukan kartu. Kegiatan ini disebut dengan shuffling. Dalam subbab 10.6 kita telah melihat cara membuat bilangan acak, tapi Anda mungkin masih bingung untuk mengimplementasikannya ke dalam permainan kartu. Satu kemungkinan yang bisa diambil adalah dengan menggunakan cara kita (baca: manusia) ketika mengacak kartu. Umumnya, pengacakan dilakukan dengan cara membagi tumpukan kartu ke dalam dua bagian. Setelah itu, kita akan menyusun ulang tumpukan itu dengan mengambil satu kartu secara acak dari dua tumpukan tersebut. Karena manusia tidak bisa mengacak dengan sempurna, setelah 7 kali pengulangan, biasanya urutan kartu dalam tumpukan menjadi tampak seperti benarbenar tidak beraturan. Pun begitu dengan sebuah program komputer, setelah 8 kali melakukan pengacakan, urutan kartu dalam tumpukan justru kembali ke dalam posisi semula. Untuk mendiskusikan hal seperti ini, silahkan mengunjungi http://www.wiskit.com/marilyn/craig.html atau Anda bisa mencarinya di mesin pencari menggunakan kata kunci “perfect shuffle”. Algoritma pengacakan yang lebih baik adalah dengan cara menelusuri tumpukan kartu satu kali untuk kemudian memilih dua kartu lalu menukarnya dalam tiap perulangan. Berikut ini adalah ringkasan cara kerja algoritma ini. Untuk mendeskripsikan program ini, saya akan menggunakan kombinasi antara pernyataanpernyataan dalam Java dengan katakata dalam bahasa alamiah (baca: bahasa yang kita gunakan seharihari). Inilah yang disebut dengan pseudocode: for (int i=0; i<deck.length; i++) { // pilihlah sebuah bilangan acak di antara i dan 264
// deck.cards.len
gth
// tukarkan kartu urutan ke i dengan kartu yang sudah // terpilih secara acak.
} Hal positif ketika menggunakan pseudocode seperti di atas adalah bahwa terkadang kita dapat melihat metode yang akan kita butuhkan dengan lebih jelas. Dalam kasus ini, kita membutuhkan metode seperti randomInt, yang dapat memilih bilangan bertipe integer secara acak di dalam interval antara variabel low dan high. Selain itu, metode ini juga mampu mengambil dua indeks lalu menukar kartu yang berada pada posisi indeks yang terpilih. Anda mungkin dapat menulis metode randomInt dengan melihat subbab 10.6, tapi Anda juga harus hatihati terhadap indeks yang dihasilkan, karena mungkin saja indeks tersebut keluar dari batasan interval. Anda juga bisa membuat metode swapCard sendiri tentunya. Satusatunya trik yang akan Anda gunakan adalah apakah Anda hanya akan menukar referensi yang menunjuk ke posisi kartu atau Anda justru akan menukar juga isi dari kartukartu tersebut. Pilihan Anda akan sangat menentukan kecepatan program yang Anda buat secara keseluruhan. Saya akan meninggalkan implementasi masalah ini sebagai latihan.
12.3 Pengurutan (Sorting) Karena sebelumnya kita telah mengacakacak tumpukan kartu, sekarang kita membutuhkan suatu cara untuk menempatkan semuanya kembali ke posisi terurut. Ironisnya, ada sebuah algoritma untuk melakukan pengurutan yang sangat mirip dengan pengacakan. Algoritma ini sering disebut selection sort. Disebut demikian karena ia 265
bekerja dengan cara menelusuri array secara berulangulang lalu memilih kartu terendah dalam setiap perulangan. Pada perulangan pertama, kita menemukan kartu terendah (the lowest) lalu menukarnya dengan kartu yang berada di posisi ke0. Pada perulangan berikutnya, yaitu yang kei, kita akan mendapatkan kartu terendah di sebelah kanan i lalu menukarnya dengan kartu di indeks yang kei. Berikut ini merupakan pseudocode dari selection sort: for (int i=0; i<deck.length; i++) { // temukan kartu terendah yag berada dalam i atau // yang berada di daerah sebelah kanannya // tukarlah kartu urutan kei dengan kartu terendah } Lagilagi, pseudocode akan menolong kita dengan memakai rancangan helper methods. Dalam kasus ini, kita bisa menggunakan swapCards kembali, jadi kita hanya membutuhkan satu buah metode lagi, yakni findLowestCard. Metode ini membutuhkan sebuah array berisi kartukartu dan sebuah indeks tempat di mana ia pertama kali melakukan pencarian. Sekali lagi, saya akan meninggalkan implementasinya khusus untuk pembaca.
12.4 Subtumpukan (Subdecks) Apa yang harus kita lakukan untuk merepresentasikan kartukartu yang berada dalam
266
genggaman tangan seorang pemain (Hand) atau beberapa bentuk subset lain dari satu tumpukan kartu yang penuh? Kemungkinan pertama adalah dengan cara menulis sebuah kelas baru dengan nama Hand, yang mungkin akan mewarisi sifatsifat kelas Deck. Kemungkinan lainnya, yang akan saya tunjukkan di sini, adalah merepresentasikan Hand menggunakan sebuah objek Deck yang jumlahnya kurang dari 52 kartu. Kita mungkin membutuhkan sebuah metode, yaitu subdeck, yang menggunakan sebuah Deck dan sekumpulan indeks. Metode ini akan mengembalikan sebuah Deck baru yang berisi subset dari kartukartu yang telah ditentukan: public static Deck subdeck (Deck deck, int low, int high) { Deck sub = new Deck (highlow+1); for (int i = 0; i<sub.cards.length; i++) { sub.cards[i] = deck.cards[low+i]; } return sub; }
Ukuran subdeck adalah highlow+1. Hal ini terjadi karena kita juga akan mengikutsertakan kartu terendah dan kartu tertinggi. Proses komputasi semacam ini bisa menimbulkan kebingungan, dan juga bisa membawa kita ke dalam error yang termasuk offbyone error. Membuat gambar atau visualisasi merupakan cara yang ampuh untuk menghindarinya. Karena kita menyediakan sebuah argumen dengan peringkat new, maka konstruktor yang dipanggil akan menjadi yang pertama, yang hanya mengalokasikan array tapi tidak mengalokasikan kartu apa pun. Di dalam perulangan for, subtumpukansubtumpukan akan dibuat beserta salinan referensi yang ada dalam tumpukannya. 267
Berikut ini merupakan diagram keadaan sebuah subtumpukan yang dibuat menggunakan parameter low = 3 dan high = 7. Hasilnya adalah sebuah hand berisi 5 kartu yang saling berbagi referensi dengan tumpukan utamanya; Anda dapat menganggap hand tersebut sebagai nama alias dari tumpukan.
Saya tidak menyarankan Anda untuk menggunakan konsep aliasing karena perubahan yang terjadi pada satu subtumpukan akan langsung memengaruhi subtumpukan lain. Kecuali kalau objekobjek yang akan dikenai aliasing tersebut merupakan objek yang tidak bisa diubah (immutable). Dalam kasus ini, kita tidak bisa mengubah peringkat atau kelompok sebuah kartu. Sebaliknya, kita justru akan membuat setiap kartu satu kali saja lalu memperlakukannya sebagai objek yang immutable. Dengan alasan ini, penggunaan aliasing untuk Card merupakan keputusan yang masih relevan.
12.5 Pengacakan dan Pembagian Kartu (Shuffling and Dealing) Dalam subbab 12.2 saya menulis pseudocode untuk algoritma pengacakan. Anggaplah saat ini kita mempunyai sebuah metode dengan nama shuffleDeck. Metode ini menggunakan sebuah tumpukan sebagai argumen lalu mengacaknya. Kita akan mengacak tumpukan dengan cara seperti berikut: Deck deck = new Deck (); 268
shuffleDeck (deck); Kemudian, untuk membagi/mendistribusikan kartu ke beberapa hand lain, kita bisa menggunakan subdeck: Deck hand1 = subdeck (deck, 0, 4); Deck hand2 = subdeck (deck, 5, 9); Deck pack = subdeck (deck, 10, 51); Kode ini akan menempatkan 5 kartu di hand pertama, 5 kartu berikutnya di hand yang lain, dst. Pada saat Anda berpikir mengenai proses bagibagi kartu (dealing), apakah Anda juga berpikir mengenai pembagian kartu yang harus dilakukan secara satu per satu untuk setiap pemain, dengan teknik roundrobin, laiknya permainan kartu dalam dunia nyata? Saya sempat kepikiran juga tentang ini, tapi kemudian saya menganggapnya sebagai sesuatu yang tidak terlalu penting ketika harus diimplementasikan ke dalam program komputer. Teknik roundrobin digunakan untuk menghindari pengacakan yang tidak sempurna sekaligus kecurangan yang mungkin dilakukan oleh sang pembagi kartu (dealer). Tetapi Anda tidak usah khawatir, isu di atas bukanlah masalah penting dalam dunia komputer. Contoh ini dapat menjadi sebuah peringatan yang berguna terhadap salah satu metafora berbahaya dalam dunia rekayasa (engineering): terkadang kita memaksa komputer untuk mengerjakan halhal tidak perlu yang memang berada di luar kemampuannya, atau mengharapkan kemampuankemampuan dari komputer yang sebenarnya memang tidak dimilikinya. Ini semua terjadi karena kita telah melebihlebihkan sebuah metafora jauh di atas ambang batasnya sendiri. Berhatihatilah terhadap penggunaan analogi yang salah.
269
12.6 Mergesort Dalam subbab 12.3, kita telah melihat sebuah algoritma pengurutan sederhana yang akhirnya malah menimbulkan ketidakefisienan bagi program itu sendiri. Untuk mengurutkan data sebanyak n item, algoritma ini harus menelusuri array sebanyak n kali. Dan untuk setiap penelusuran, dibutuhkan waktu sejumlah n. Oleh karena itu, total waktu yang dibutuhkan dalam satu pemrosesan lengkap adalah n 2. Dalam bab ini saya akan memberikan sebuah algoritma yang lebih efisien, yakni mergesort. Untuk mengurutkan n item, mergesort membutuhkan waktu sebanyak n log n. Mungkin tidak terlalu bagus, tapi jika nilai n bertambah semakin besar, maka perbedaan antara n2 dan n log n menjadi sangat signifikan. Cobalah dengan beberapa nilai n lalu lihat hasilnya. Ide dasar dari mergesort adalah sebagai berikut: jika Anda mempunyai dua subtumpukan yang keduanya sudah dalam posisi terurut, maka Anda akan lebih mudah (juga cepat) untuk menggabungkannya ke dalam satu tumpukan tunggal yang terurut. Cobalah beberapa hal di bawah ini: 1. Buatlah dua subtumpukan yang masingmasing terdiri dari 10 kartu kemudian urutkanlah keduanya sehingga ketika keduanya saling berhadaphadapan, kartu terendah akan berada di posisi teratas. Letakkan kedua subtumpukan tersebut tepat di depan muka Anda. 2. Bandingkan kartu teratas dari masingmasing subtumpukan lalu pilihlah yang terendah di antara keduanya. Tukarkan posisi keduanya lalu tempatkan keduanya ke dalam tumpukan baru yang akan menjadi tumpukan tunggal. 3. Ulangi langkah kedua sampai kartukartu di kedua subtumpukan itu habis. Kartukartu yang sudah dikenai proses pada langkah kedua adalah kartukartu yang telah digabung ke dalam satu tumpukan tunggal. 270
Hasilnya adalah sebuah tumpukan tunggal yang terurut tentunya. Berikut ini merupakan pseudocode algoritma di atas: public static Deck merge (Deck d1, Deck d2) { /* buat sebuah tumpukan
yang cukup besar untuk
menampung semua kartu dari dua subtumpuk
Deck
result
d2.cards.leng
an */
= new Deck
(d1.cards.len
gth +
th);
/* gunakan indeks i untuk mengetahui posisi kita dalam tumpukan yang pertama, dan indeks j untuk tumpukan kedua */ int i = 0; int j = 0; // indeks k digunakan untuk menelusuri tumpukan result for (int k = 0; k < result.car
ds.len gth; k++) {
// jika d1 kosong, d2 menang; jika d2 kosong, d1 menang; // jika dua kondisi ini tidak terpenuhi, bandingka n kartu kartu yang berada dalam dua subtumpuka // masukkan pemenangnya
n
ke dalam tumpukan yang baru
} return result;
271
}
Cara terbaik untuk menguji metode merge adalah dengan membuat sebuah tumpukan lalu mengacaknya, gunakan subtumpukan untuk membuat dua hand dengan jumlah kartu yang sedikit, lalu gunakan algoritma yang ada di bab sebelumnya untuk mengurutkan setiap subtumpukan milik hand. Setelah selesai, Anda bisa melewatkan kedua subtumpukan milik hand ke metode merge untuk melihat apakah metode ini berhasil atau justru sebaliknya. Jika Anda melihat metode merge ini berhasil dengan baik, cobalah implementasi sederhana dari metode mergeSort berikut ini: public static Deck mergeSort (Deck deck) { // temukan titik tengah tumpukan tersebut // bagilah tumpukan itu menjadi dua subtumpukan // urutkan kedua subtumpukan menggunakan
sortDeck
// gabungkan dua subtumpukan itu lalu kembalikan // hasilnya } Nah, kalau Anda juga berhasil mengimplementasikan kode di atas dengan benar, halhal yang menarik akan segera terjadi! Sesuatu yang ajaib dalam mergesort adalah fakta bahwa teknik ini melakukan proses yang bersifat rekursif. Di saat Anda mengurutkan subtumpukansubtumpukan itu, Anda tidak perlu lagi repotrepot memanggil metode lama yang lebih lambat. Anda cukup menggunakan metode mergeSort milik Anda sendiri yang lebih keren di dalamnya. Bukan hanya soal penggunaan pemikiran/ide yang bagus, langkah di atas sebenarnya kita lakukan agar kita dapat mencapai kinerja yang sebelumnya telah saya janjikan.
272
Agar dapat berfungsi dengan baik, Anda harus menambahkan base case ke dalam program Anda agar program itu tidak merekursif selamanya. Base case yang sederhana adalah subtumpukan yang berisi 0 atau 1 kartu. Jika mergeSort menerima subtumpukan seperti ini, ia bisa mengembalikan subtumpukan itu tanpa mengubahnya sama sekali karena subtumpukan seperti itu tentu berada dalam posisi yang sudah terurut. Berikut ini adalah versi rekursif dari mergesort: public static Deck mergeSort (Deck deck) { // if the deck is 0 or 1 cards, return it // find the midpoint of the deck // divide the deck into two subdecks // sort the subdecks using mergesort // merge the two halves and return the result } Seperti biasa, ada dua paradigma pengembangan untuk program yang bersifat rekursif; Anda bisa melihatnya dari sisi keseluruhan proses yang akan dieksekusi atau Anda juga bisa melihatnya sebagai sesuatu yang bisa dipilahpilah menjadi bagianbagian kecil. Saya telah mencontohkan contohcontoh di atas agar Anda bisa mempraktikkan paradigma kedua. Ketika Anda menggunakan sortDeck untuk mengurutkan subtumpukansubtumpukan, Anda tentu tidak merasa harus mengikuti dan memperhatikan keseluruhan proses yang dieksekusi bukan? Anda hanya perlu menganggap bahwa metode sortDeck akan bekerja dengan baik karena Anda telah memeriksanya (debug) sebelum digunakan. Well, sesuatu yang harus Anda lakukan jika ingin melihat mergeSort bersifat rekursif adalah dengan mengganti satu algoritma pengurutan dengan algoritma lainnya. Saya kira Anda tidak mempunyai alasan yang cukup bagus untuk melihat program ini dengan 273
cara yang lain. Sebenarnya, ketika Anda menulis programprogram bersifat rekursif, Anda harus memberikan perhatian lebih pada saat membuat base case. Base case tersebut haruslah benar dan bisa berfungsi dengan tepat. Selain faktor ini, membuat versi rekursif dari suatu program tentunya tidak akan menjadi masalah lagi untuk Anda. Semoga berhasil!
12.7 Daftar KataKata Istilah
Arti
pseudocode
Suatu teknik untuk merancang program komputer dengan cara membuat deskripsi singkat (rough drafts) program tersebut menggunakan kombinasi antara bahasa natural (bahasa yang kita gunakan seharihari) dengan bahasa Java
Helper method
Metode kecil yang umumnya tidak melakukan sesuatu secara signifikan untuk dirinya sendiri tetapi justru malah menolong metode lain agar metode tersebut dapat berfungsi lebih baik
12.8 Latihan Latihan 12.1 Tulislah sebuah versi metode findBisect yang menggunakan sebuah subtumpukan sebagai argumennya ketimbang menggunakan sebuah tumpukan dan interval indeks (lihat subbab 11.8). Versi mana yang kirakira lebih rawan dengan error? Versi mana yang menurut Anda lebih efisien? Latihan 12.2 Dalam versi kelas Card sebelumnya, sebuah tumpukan diimplementasikan sebagai 274
sebuah array yang berisi Card. Contoh; ketika kita melewatkan sebuah “deck” sebagai parameter, tipe parameter yang sebenarnya adalah Card[]. Dalam bab ini, kita telah mengembangkan sebuah representasi alternatif untuk sebuah tumpukan, yaitu sebuah objek dengan nama Deck yang mengandung sebuah array berisi kartukartu sebagai variabel instannya. Dalam latihan ini, Anda akan mengimplementasikan sebuah representasi baru untuk sebuah tumpukan. a) Buatlah file tambahan dengan nama Deck.java ke dalam program Anda. File ini akan berisi semua definisi mengenai kelas Deck. b) Buatlah sebuah konstruktor untuk kelas ini seperti yang telah ditunjukkan dalam subbab 12.1. c) Di antara metodemetode yang ada di dalam kelas Card, pilihlah metode yang sesuai dan dapat dijadikan sebagai anggota kelas Deck yang baru. Pindahkan metode metode yang Anda pilih tersebut ke dalam kelas baru itu lalu buatlah beberapa perubahan signifikan agar program Anda bisa dicompile dan dieksekusi kembali dengan benar. d) Lihat kembali program tersebut kemudian identifikasilah setiap tempat ketika sebuah array yang berisi Card digunakan untuk merepresentasikan sebuah tumpukan. Modifikasilah keseluruhan program sehingga program akan menggunakan objek Deck daripada yang lainnya. Anda bisa melihat metode printDeck dalam subbab 12.1 sebagai contoh. Perubahan ini sebaiknya dilakukan dengan sistem per metode. Jadi, di sini Anda diminta untuk langsung memeriksa perubahan yang terjadi setiap kali Anda telah selesai memodifikasi sebuah metode. Di sisi lain, jika Anda memang benarbenar yakin dengan apa yang Anda lakukan, Anda juga bisa menggunakan perintah searchandreplace. 275
Latihan 12.3 Tujuan latihan ini adalah untuk mengimplementasikan algoritma pengacakan dan pengurutan yang telah dipelajari dalam bab ini. a) Tulislah metode swapCards yang menggunakan sebuah tumpukan (array yang berisi kartukartu) dan dua indeks. Metode ini berfungsi untuk menukar kartukartu yang berada dalam dua lokasi tersebut. PETUNJUK: Metode ini harus menukar referensi yang mengacu ke dua kartu itu, bukan isinya. Cara ini tidak hanya memiliki kinerja lebih cepat, tapi juga dapat memudahkan kita ketika harus berhadapan dengan kasus di mana kartukartu tersebut sedang disamarkan (aliasing). b) Tulislah metode shuffleDeck yang menggunakan algoritma di subbab 12.2. Anda mungkin ingin memanfaatkan randomInt yang ada di latihan 10.2. c) Tulislah metode findLowestCard yang menggunakan compareCard untuk menemukan kartu terendah di dalam interval kartukartu yang diberikan (antara lowIndex hingga highIndex). d) Tulislah metode sortDeck yang menyusun satu tumpukan kartu dari kartu terendah hingga yang tertinggi. Latihan 12.4 Untuk membuat hidup para pengacak dan pembagi kartu (cardcounter) semakin sulit, sekarang banyak kasino yang menggunakan mesin pengacak yang mengacak kartu secara secara naiksatu per satu (incremental shuffling). Ini berarti, setiap selesai membagi kartu untuk satu pemain, kartu yang telah dibagi tersebut akan dikembalikan lagi ke dalam tumpukan. Kartu ini dikembalikan lagi ke dalam tumpukan tanpa melakukan pengacakan ulang. Jadi, mesin hanya memasukkan kartu itu ke posisi lain secara acak. 276
Tulislah metode incrementalShuffle yang menggunakan sebuah Deck dan sebuah Card sebagai argumen. Metode ini berfungsi untuk memasukkan atau menyisipkan kartu ke dalam tumpukan di posisi yang acak. Berikut ini merupakan contoh sebuah incremental algorithm. Latihan 12.5 Tujuan latihan ini adalah untuk menulis sebuah program yang dapat membuat permainan poker untuk beberapa hand secara acak lalu mengelompokkannya. Dengan cara ini, kita dapat memperkirakan probabilitas dari masingmasing hand. Jangan khawatir jika Anda belum pernah memainkannya; saya akan memberi tahu semua yang perlu Anda ketahui. a) Untuk pemanasan, tulislah sebuah program yang menggunakan shuffleDeck dan subdeck untuk membuat dan mencetak masingmasing lima kartu yang berada di empat hand pemain poker yang sudah diacak. Bagaimana, sudah mengerti? Berikut ini adalah beberapa status hand dalam permainan poker, dalam posisi yang terurut naik: pair: dua kartu dengan peringkat (rank) yang sama two pair: dua pasang kartu dengan peringkat yang sama three of a kind: tiga kartu dengan peringkat yang sama straight: lima kartu dengan peringkat yang berurutan flush: lima kartu dengan kelompok (suit) yang sama full house: tiga kartu dengan satu peringkat yang sama, dua kartu lainnya tidak sama four of a kind: empat kartu dengan peringkat yang sama straight flush: lima kartu yang peringkatnya berurutan dan tergabung dalam satu kelompok yang sama
277
b) Tulislah metode isFlush yang menggunakan sebuah Deck sebagai parameter dan mengembalikan nilai bertipe boolean sebagai indikator apakah hand yang diperiksa berstatus flush ataukah bukan. c) Tulislah sebuah metode isThreeKind yang menggunakan sebuah hand dan mengembalikan sebuah nilai bertipe boolean yang mengindikasikan apakah hand tersebut termasuk Three Of A Kind ataukah bukan. d) Tulislah sebuah perulangan yang menghasilkan beberapa ribu hand dan memeriksa apakah handhand tersebut termasuk Flush ataukah Three Of A Kind. e) Tulislah beberapa metode yang bisa menguji statusstatus hand poker lainnya. Beberapa diantaranya mungkin lebih mudah ketimbang lainnya. Mungkin akan lebih baik jika Anda menulis beberapa helpermethod yang bisa digunakan secara umum untuk beberapa metode penguji sekaligus. f) Dalam beberapa permainan poker, masingmasing pemain akan mendapatkan tujuh kartu. Dari ketujuh kartu tersebut, mereka diizinkan untuk membuat kombinasi dari lima kartu saja. Modifikasilah program Anda agar bisa menghasilkan hand yang terdiri dari tujuh kartu sekaligus menghitung ulang probabilitas yang mungkin terjadi. Latihan 12.6 Sebagai sebuah tantangan khusus untuk Anda, pikirkanlah sebuah algoritma yang mampu memeriksa wild card yang terdapat dalam handhand yang sedang bermain. Sebagai contoh, jika “deuces are wild”, ini berarti bahwa jika Anda mempunyai sebuah kartu dengan peringkat 2, Anda bisa menggunakan kartu ini untuk merepresentasikan kartu apa pun yang berada dalam tumpukan. Latihan 12.7 Tujuan latihan ini adalah agar Anda bisa mengimplementasikan mergesort. 278
a) Dengan pseudocode yang ada di subbab 12.6, tulislah metode dengan nama merge. Ujilah metode ini sebelum Anda memasukkannya ke dalam metode mergeSort. b) Tulislah versi sederhana dari mergeSort, yakni yang mampu membagi tumpukan menjadi dua. Gunakan sortDeck untuk mengurutkan kedua bagian tersebut, lalu gunakan metode merge untuk membuat sebuah tumpukan baru yang sudah terurut. c) Tulislah versi mergeSort yang benarbenar bersifat rekursif. Ingatlah bahwa sortDeck merupakan sebuah modifier sementara mergeSort adalah sebuah fungsi. Ini berarti bahwa keduanya dipanggil dengan cara yang berbeda: sortDeck (deck); // modifies existing deck deck = mergeSort (deck); // replaces old deck with new
279
Bab 13 Pemrograman Berorientasi Objek 13.1 Bahasa Pemrograman dan Tekniktekniknya Ada begitu banyak bahasa pemrograman di dunia ini, begitu juga dengan teknikteknik pemrograman (sering disebut sebagai paradigma). Tiga teknik yang sudah muncul dalam buku ini antara lain; prosedural, fungsional, dan objectoriented. Meskipun Java sering disebutsebut sebagai bahasa pemrograman berorientasi objek, tapi kita masih diperkenankan untuk menulis program dengan teknik mana pun. Teknik yang saya peragakan di sini termasuk teknik prosedural. Programprogram Java yang sudah ada beserta paketpaket bawaan Java ditulis menggunakan gabungan dari ketiga teknik tersebut. Bedanya, programprogram dan paketpaket tersebut lebih cenderung ditulis menggunakan paradigma pemrograman berorientasi objek. Bukan perkara mudah untuk mendefinisikan arti dari pemrograman berorientasi objek, namun beberapa poin berikut ini bisa Anda jadikan pegangan untuk melihat karakteristiknya: ●
Definisi objek (yang berupa kelaskelas) biasanya berkaitan dengan objekobjek yang berada di dunia nyata. Contoh; di subbab 12.1, pembuatan kelas Deck merupakan langkah menuju penggunaan konsep pemrograman berorientasi objek.
●
Metodemetode yang dibuat umumnya berupa metode objek (yang Anda panggil melalui objek) ketimbang metode kelas (yang bisa langsung Anda panggil). Sejauh ini, semua metode yang kita tulis merupakan metodemetode kelas. Dalam bab ini, kita akan mencoba menulis beberapa metode objek.
280
●
Fitur bahasa yang sangat terkait dengan sifat pemrograman berorientasi objek adalah inheritance. Saya akan membahasnya dalam bab ini.
Akhirakhir ini, pemrograman berorientasi objek menjadi sangat populer, dan bahkan ada orang yang menganggapnya sebagai teknik pemrograman yang lebih hebat (superior) ketimbang teknikteknik lainnya. Saya berharap bahwa dengan menyuguhkan kepada Anda cara menggunakan beberapa teknik yang lain berarti saya telah memberi Anda alat untuk memahami sekaligus mengevaluasi keabsahan klaim di atas.
12.3 Metode Objek dan Metode Kelas Ada dua tipe metode dalam Java; metode kelas (class method) dan metode objek (object method). Sejauh ini, semua metode yang kita tulis termasuk ke dalam tipe metode kelas. Metode ini bisa dikenali dari keberadaan kata kunci static yang berada di baris pertama pada tiap metode. Sementara itu, semua metode yang tidak mempunyai static bisa dianggap sebagai metode objek. Meskipun kita belum menulis satu pun metode objek, sebenarnya kita telah memanggil beberapa diantaranya. Kapan pun Anda memanggil sebuah metode melalui sebuah objek, maka itulah yang disebut dengan metode objek. Misalnya metode charAt dan semua metode yang kita panggil melalui objek String. Semua yang bisa ditulis sebagai metode kelas juga bisa ditulis sebagai metode objek. Begitu pula sebaliknya. Terkadang, akan tampak lebih alamiah untuk menggunakan salah satunya saja. Sebentar lagi kita akan melihat bahwa metodemetode objek seringkali terlihat lebih singkat dan padat ketimbang metodemetode kelas.
13.3 Objek Terkini Ketika Anda memanggil sebuah metode melalui sebuah objek, objek tersebut akan
281
menjadi objek terkini (the current object). Di dalam metode ini, Anda bisa mengacu ke variabel instan milik objek terkini menggunakan namanya, tanpa harus menulis nama objek tersebut secara spesifik. Selain itu, Anda juga bisa mengacu ke objek terkini menggunakan kata kunci this. Kita sebelumnya telah melihat this digunakan di dalam konstruktor. Faktanya, Anda bisa saja menganggap konstruktorkonstruktor sebagai metode objek yang bersifat khusus.
13.4 Bilangan Kompleks Untuk contoh kasus, sepanjang bab ini kita akan mencoba membuat definisi kelas untuk bilangan kompleks. Bilangan kompleks banyak digunakan di lingkungan matematika dan dunia perekayasaan (engineering). Di samping itu, banyak juga perhitungan perhitungan yang menggunakan aritmetika kompleks. Sebuah bilangan kompleks merupakan hasil penjumlahan antara bilangan real dan bilangan imajiner. Bilangan ini biasanya ditulis dalam bentuk x + yi, di mana x adalah bilangan realnya, sementara y merupakan bilangan imajinernya. Notasi i sendiri adalah representasi dari akar 1. Oleh karena itu, i * i = 1. Kode berikut ini merupakan definisi kelas untuk tipe objek baru, Complex: class Complex { // instance variables double real, imag; // constructor public Complex () { this.real = 0.0; this.imag = 0.0; }
282
// constructor public Complex (double real, double imag) { this.real = real; this.imag = imag; } } Harusnya tidak ada yang mengejutkan Anda dari lampiran kode ini. Variabelvariabel instannya bertipe double yang mewakili bilangan real dan imajiner. Kedua konstruktor di atas termasuk jenis konstruktor yang sudah lazim dipakai: satu konstruktor tidak membutuhkan satu parameter pun dan mengassign nilainilai default untuk variabel variabel instan, sementara konstruktor lainnya menggunakan dua parameter yang identik dengan variabel instannya. Seperti yang sudah kita lihat sebelumnya, kata kunci this digunakan untuk mengacu ke objek yang sedang diinisialisasi. Di dalam main, atau di mana pun kita ingin membuat objek Complex, kita mempunyai pilihan; untuk membuat objeknya dahulu lalu mengatur variabelvariabel instannya atau melakukan keduanya sekaligus pada saat yang bersamaan: Complex x = new Complex (); x.real = 1.0; x.imag = 2.0; Complex y = new Complex (3.0, 4.0);
13.5 Fungsifungsi dalam Bilangan Kompleks Mari kita lihat beberapa operasi yang mungkin akan kita kerjakan untuk mengolah bilanganbilangan kompleks. Nilai absolut dari sebuah bilangan kompleks didapat dari 283
hasil
x 2 y 2
. Metode abs adalah fungsi murni yang menghitung nilai mutlak. Bila
ditulis dalam bentuk metode kelas, seperti inilah kodenya: // class method public static double abs (Complex c) { return Math.sqrt (c.real * c.real + c.imag * c.imag); } Versi abs ini menghitung nilai absolut milik c, objek Complex yang diterimanya sebagai parameter. Versi lain dari abs adalah sebuah metode objek; metode ini menghitung nilai absolut milik objek terkini (objek yang memanggil metode). Oleh karena itu, metode ini tidak akan membutuhkan parameter apa pun. // object method public double abs () { return Math.sqrt (real*real + imag*imag); } Saya menghilangkan kata kunci static untuk mengindikasikan bahwa metode ini merupakan metode objek. Selain itu, saya akan menghilangkan parameter yang tidak diperlukan. Di dalam metode, saya bisa mengacu ke variabel instan real dan imag dengan namanya tanpa harus menyertakan suatu objek secara spesifik. Java mengetahui secara implisit bahwa saya sedang mengacu ke variabelvariabel instan milik objek terkini. Jika saya ingin membuatnya bersifat eksplisit, saya akan menambahkan kata kunci this: // ob ject met hod publi c do uble abs () { ret urn Math .sqr t (t his. real * this. real + this. imag *
284
this. imag ); }
Tapi itu hanya akan menambah panjang kode ini, ketimbang membuatnya tampak lebih ringkas. Untuk memanggil metode ini, kita bisa memanggilnya melalui sebuah objek, sebagai contoh: Compl ex y = n ew C ompl ex ( 3.0, 4.0 ); doubl e re sult = y .abs ();
13.6 Fungsifungsi lainnya dalam Bilangan Kompleks Operasi lain yang mungkin akan kita perlukan adalah fungsi penjumlahan. Anda bisa menjumlahkan bilanganbilangan kompleks dengan menjumlahkan masingmasing bilangan dengan pasangannya masingmasing; real dengan real dan imajiner dengan imajiner. Dalam bentuk metode kelas, bentuknya akan seperti berikut: publi c st atic Com plex add (Co mple x a, Com plex b) { r etu rn n ew C ompl ex ( a.re al + b.r eal, a.i mag + b. imag ); }
Untuk memanggil metode ini, kita akan melewatkan kedua operan sebagai argumen: Complex sum = add (x, y); Dalam bentuk metode objek, metode ini hanya perlu satu argumen yang akan ditambahkan ke objek yang sedang digunakan (current object). publi c Co mple x ad d (C ompl ex b ) { r etur n ne w Co mple x (r eal + b. real , im ag + b.i mag) ; }
Lagilagi, di sini kita bisa mengacu ke variabel instan milik objek terkini secara implisit, tapi untuk mengacu ke variabel instan milik objek b kita harus menamai b 285
secara eksplisit menggunakan notasi titik (dot). Untuk memanggil metode ini, Anda bisa memanggilnya melalui salah satu operan lalu melewatkan operan lainnya sebagai argumen. Complex sum = x.add (y); Dari contohcontoh ini, Anda bisa melihat bahwa objek terkini (this) bisa mengambil alih dari salah satu parameter. Karena alasan inilah objek terkini sering juga disebut sebagai parameter implicit.
13.7 Modifier Seperti contohcontoh sebelumnya, kita akan melihat metode conjugate. Metode ini merupakan metode pengubah yang mentransformasikan sebuah bilangan Complex ke bentuk konjugasi kompleksnya. Konjugasi kompleks dari x + yi adalah x yi. Sebagai metode kelas, bentuknya akan menjadi seperti ini: public static void conjugate (Complex c) { c.imag = c.imag; } Dalam bentuk metode objek, kodenya akan menjadi seperti ini: public void conjugate () { imag = imag; } Mulai sekarang, Anda harusnya sudah paham bahwa mengubah suatu metode dari satu bentuk ke bentuk lainnya merupakan proses mekanis. Dengan sedikit latihan, Anda akan mampu melakukannya tanpa harus berpikir panjang terlebih dahulu. Hal ini akan bagus sekali karena Anda memang sudah seharusnya tidak perlu terhambat untuk
286
menulis salah satu diantaranya. Anda harus akrab dengan keduanya agar Anda bisa langsung memilih mana yang lebih cocok dengan operasi yang sedang Anda tulis. Sebagai contoh, saya berpikir bahwa add harusnya ditulis sebagai metode kelas karena metode ini merupakan operasi simetris dari dua operan. Anda juga bisa menampilkan kedua operan sebagai parameter. Bagi saya, agak ganjil rasanya jika kita memanggil satu metode melalui sebuah operan lalu melewatkan operan lainnya sebagai argumennya. Di sisi lain, beberapa operasi sederhana yang digunakan untuk satu objek bisa ditulis dengan baik menggunakan metodemetode objek (pun jika metodemetode itu membutuhkan beberapa argumen tambahan).
13.8 Metode toString Setiap tipe objek mempunyai sebuah metode, yakni toString yang mampu menghasilkan representasi string dari objek tersebut. Ketika Anda mencetak sebuah objek menggunakan print atau println, Java akan memanggil metode toString milik objek tersebut. Versi default metode ini akan mengembalikan sebuah string yang berisi tipe objek beserta sebuah identifier unik (lihat subbab 9.6). Ketika Anda mendefinisikan sebuah tipe objek yang baru, Anda bisa mengoverride perilaku (behaviour) default dari suatu metode dengan membuat sebuah metode baru yang perilakunya sesuai dengan keinginan Anda. Berikut ini tampilan toString di dalam kelas Complex: public String toString () { return real + " + " + imag + "i"; }
287
Tentu saja, tipe kembalian milik toString adalah String. Metode ini tidak membutuhkan parameter apa pun. Anda bisa memanggil toString dengan cara yang sudah biasa kita pakai: Complex x = new Complex (1.0, 2.0); String s = x.toString ();
atau Anda juga bisa memanggilnya langsung melalui println: System.out.println (x); Dalam kasus ini, outputnya adalah 1.0 + 2.0i. Versi toString ini masih belum cukup baik jika bilangan imajinernya bernilai negatif. Sebagai latihan, buatlah sebuah versi yang lebih baik:
13.9 Metode equals Ketika Anda menggunakan operator == untuk membandingkan dua objek, yang sebenarnya Anda maksudkan adalah, “apakah dua objek ini merupakan objek yag sama?” Itu dia, apakah kedua objek mengacu ke lokasi yang sama dalam memori. Untuk kebanyakan tipe, definisi di atas bukanlah definisi yang tepat untuk menjelaskan prinsip kesamaan (equality). Sebagai contoh, dua bilangan kompleks dianggap sama jika bilangan real keduanya sama dan begitu pula dengan bilangan imajiner mereka. Mereka tidak harus berupa objek yang sama. Ketika Anda mendefinisikan sebuah tipe objek yang baru, Anda bisa membuat definisi persamaan sendiri dengan menulis sebuah metode objek, yaitu equals. Untuk kelas Complex, bentuknya akan tampak sebagai berikut: public boolean equals (Complex b) { return (real == b.real && imag == b.imag); } 288
Dari hasil konvensi, metode ini akan selalu menjadi metode objek yang mengembalikan nilai bertipe boolean. Dokumentasi untuk equals di dalam kelas Object memberikan beberapa panduan yang harus Anda terapkan ketika Anda ingin membuat definisi persamaan Anda sendiri: Metode equals mengimplementasikan sebuah relasi ekuivalensi: ●
Refleksif: untuk setiap nilai yang diacu x, x.equals(x) harus bernilai true.
●
Simetris: untuk setiap nilai yang diacu x dan y, x.equals(y) harus bernilai true jika dan hanya jika y.equals(x) juga bernilai true.
●
Transitif: untuk setiap nilai yang diacu x, y dan y, jika x.equals(y) bernilai true dan y.equals(z) juga bernilai true maka x.equals(z) juga harus bernilai true.
●
Konsisten: untuk setiap nilai yang diacu x dan y, pemanggilan berulangulang x.equals(y) harus bernilai true secara konsisten, atau bernilai false secara konsisten.
●
Untuk setiap nilai yang diacu x, x.equals(null) harus bernilai false.
Definisi equals yang saya berikan sudah memenuhi semua persyaratan di atas kecuali satu. Yang mana? Sebagai latihan, coba perbaiki!
13.10 Memanggil Satu Metode Objek dari Metode Objek Lain Seperti yang Anda harapkan, Java mengizinkan Anda untuk memanggil satu metode objek dari metode objek lainnya. Sebagai contoh, untuk menormalisasi bilangan kompleks, Anda akan membagi kedua bagian berdasarkan nilai mutlaknya. Sebenarnya masih tidak cukup jelas kenapa ini bisa bermanfaat, inilah kenyataannya. Mari kita tulis metode normalize sebagai sebuah metode objek, lalu membuatnya sebagai modifier. 289
public void normalize () { double d = this.abs(); real = real/d; imag = imag/d; } Baris pertama akan menemukan nilai mutlak milik objek terkini dengan cara memanggil abs melalui objek terkini. Dalam kasus ini, saya menyebut objek terkini secara eksplisit, meski pun sebenarnya saya juga bisa melewatkannya. Jika Anda memanggil satu metode objek melalui metode objek lainnya, Java akan mengasumsikan bahwa Anda sedang memanggil metode tersebut melalui objek terkini. Latihan 13.1 Tulisulang lah normalize agar menjadi fungsi murni. Lalu tulisulang lah metode ini agar bisa menjadi metode kelas.
13.11 Keganjilan dan Error Jika Anda mempunyai metode objek dan metode kelas dalam satu definisi kelas yang sama, Anda justru akan lebih sering menjadi bingung. Cara yang lazim dipakai untuk mengelola suatu definisi kelas adalah dengan meletakkan semua konstruktor di awal awal kode, diikuti dengan semua metode objek lalu baru menuliskan semua metode kelasnya. Anda dapat memiliki sebuah metode objek dan metode kelas dengan nama yang sama selama keduanya tidak sama dalam jumlah dan tipe parameter yang digunakan. Seperti lazimnya sebuah overloading, Java akan menentukan versi mana yang dipanggil oleh pengguna dengan melihat argumen yang akan dilewatkan. Sekarang, karena kita sudah tahu apa maksud dan manfaat dari kata kunci static, Anda 290
mungkin berpendapat bahwa main termasuk metode kelas., yang juga berarti bahwa tidak ada “objek terkini” ketika metode ini sedang dipanggil. Karena objek terkini tidak boleh ada dalam sebuah metode kelas, Anda akan menghasilkan error jika bersikeras menggunakan kata kunci this. Jika Anda mencobanya, Anda akan mendapatkan pesan error yang berbunyi: “Undefine variable: this”. Selain itu, Anda juga tidak akan bisa mengacu ke variabel instan tanpa notasi dot dan nama objeknya. Jika masih berani mencobanya, Anda akan mendapatkan pesan: “Can't make a static reference to non static variable ....”. Pesan ini sebenarnya bukanlah pesan error yang baik karena masih menggunakan bahasa yang di luar standar. Tapi jika Anda sudah paham apa maksudnya, otomatis Anda juga akan mengerti apa maksudnya.
13.12 Inheritance Kemampuan suatu bahasa yang paling sering dikaitkan dengan pemrograman berorientasi objek adalah inheritance. Inheritance adalah suatu kemampuan untuk mendefinisikan sebuah kelas baru yang merupakan hasil modifikasi dari kelas yang sebelumnya telah didefinisikan (termasuk kelaskelas builtin). Manfaat utama dari fitur ini adalah agar Anda dapat menambahkan metodemetode atau variabelvariabel instan baru ke dalam kelas yang sudah ada tanpa harus memodifikasinya. Hal ini sangat berguna jika Anda hendak melakukan sesuatu terhadap kelaskelas builtin yang memang tidak bisa diubahubah meski pun Anda sangat ingin melakukannya. Disebut inheritance karena kelas yang baru akan mewarisi (inherit) semua variabel instan dan metode yang ada dalam kelas yang sudah ada terlebih dahulu itu. Dengan menggunakan analogi di atas, kelaskelas yang sudah ada/eksis terlebih dahulu itu
291
sering disebut dengan kelaskelas induk (parent class).
13.13 Lingkaran yang bisa digambar Berikut ini merupakan satu contoh inheritance, di sini kita akan mengambil kelas Rectangle yang sudah kita buat sebelumnya lalu kita beri tambahan agar “bisa digambar”. Kita akan membuat sebuah kelas baru yang bernama DrawableRectangle yang akan mewarisi semua variabel instan dan metode milik kelas Rectangle. Ditambah sebuah metode draw yang akan mengambil sebuah objek Graphics sebagai parameter lalu menggambar lingkaran yang kita inginkan. import java.awt.*; class DrawableRectangle extends Rectangle { public void draw (Graphics g) { g.drawRect (x, y, width, height); } } Ya, itulah keseluruhan definisi kelas yang kita maksud. Baris pertama mengimpor paket java.awt yang menjadi tempat di mana Rectangle dan Graphics didefinisikan. Baris berikutnya mengindikasikan bahwa DrawableRectangle diwariskan dari Rectangle. Kata kunci extends digunakan untuk mengidentifikasi kelas yang kita warisi, yang dalam hal ini kita sebut sebagai kelas induk (parent class). Sisanya adalah definisi dari metode draw, yang mengacu ke variabel instan x, y, width dan height. Mungkin akan terkesan ganjil jika kita mengacu ke variabel instan yang tidak tampak dalam definisi kelas, tapi ingatlah bahwa mereka sebenarnya sudah diwariskan oleh kelas induk. 292
Untuk membuat dan menggambar sebuah DrawableRectangle, Anda bisa menggunakan kode berikut ini: public static void draw (Graphics g, int x, int y, int width, int height) { DrawableRectangle
dr = new DrawableRectangle
(); dr.x = 10; dr.y = 10; dr.width = 200; dr.height = 200; dr.draw (g); } Parameterparameter dalam draw adalah sebuah objek Graphics dan kotak untuk menampilkan gambar lingkaran (bukan koordinatkoordinat lingkaran tersebut). Lagilagi, mungkin akan terkesan ganjil bila menggunakan perintah new untuk sebuah kelas yang tidak mempunyai konstruktor. DrawableRectangle mewarisi konstruktor bawaan (default) milik kelas induknya, jadi tidak ada masalah di sana. Kita bisa menset variabel instan milik dr lalu memanggil metodemetode melalui dirinya sendiri dengan cara yang sudah biasa kita lakukan sebelumnya. Ketika kita memanggil grow atau metodemetode lain milik Rectangle melalui dr, Java akan menggunakan metode yang sudah didefinisikan dalam kelas induk.
13.14 Hierarki Kelas Dalam Java, semua kelas cenderung memperluas beberapa kelas yang sudah ada. Kelas yang paling dasar adalah Object. Kelas ini tidak mengandung variabel instan tapi menyediakan metode equals dan toString untuk kelas lainnya. Banyak sekali kelas yang memperluas Object, termasuk hampir semua kelas yang 293
sudah kita tulis dan kelaskelas bawaan seperti Rectangle. Semua kelas yang tidak menuliskan nama kelas induknya secara eksplisit akan mewarisi kelas Object secara default. Bagaimana pun juga, beberapa rantai pewarisan ada yang lebih panjang dari apa yang kita perkirakan. Sebagai contoh, di Appendix D.6, di situ kita melihat bahwa kelas Slate merupakan perluasan dari kelas Frame, yang merupakan perluasan dari kelas Window, lalu berturutturut Container, Component, sampai ke kelas Object. Tidak peduli seberapa panjang rantai pewarisan ini, Object merupakan induk dari semua induk kelas yang ada. Semua kelas dalam Java bisa dikelola ke dalam sebuah “silsilah keluarga” (family tree) yang disebut hierarki kelas (class hierarchy). Object biasanya muncul di bagian atas, dengan semua “anak” yang berada di bawahnya. Jika Anda melihat dokumentasi mengenai Frame, sebagai contoh, Anda akan melihat hierarki yang menyusun silsilah kelas Frame.
13.15 Rancangan Berorientasi Objek Inheritance merupakan fitur yang sangat bagus (powerfull). Beberapa program yang seringkali kompleks tanpanya, bisa ditulis secara lugas dan sederhana dengannya. Selain itu, inheritance bisa memfasilitasi pemakaian ulang suatu kode (code reuse). Ini karena Anda memiliki kesempatan untuk mengkustomisasi perilaku kelaskelas builtin tanpa harus mengubahnya. Di sisi lain, inheritance juga dapat menyebabkan suatu program lebih sulit dibaca. Hal ini terjadi jika kodenya ditulis tanpa adanya kejelasan; kapan sebuah metode bisa dipanggil, di mana mencari definisinya. Sebagai contoh, salah satu metode yang bisa Anda panggil melalui Slate adalah getBounds. Bisakah Anda menemukan dokumentasi 294
mengenai metode ini? Saking sulitnya mencari dokumentasi untuk metode ini, terkadang kita malah beranggapan bahwa janganjangan dokumentasinya terdapat di induk dari induk dari induk dari induk Slate. Dan jangan lupa, banyak halhal yang bisa dikerjakan dengan konsep inheritance juga bisa dikerjakan sama baiknya (bahkan lebih) jika dikerjakan tanpanya.
13.16 Daftar KataKata Istilah
Arti Metode yang dipanggil melalui sebuah objek, yang
object method
beroperasi dengan objek itu, yang diacu menggunakan kata kunci this dalam Java atau “objek terkini” dalam bahasa Indonesia. Metode dengan kata kunci static. Metode kelas tidak
class method
dipanggil melalui objekobjek dan mereka juga tidak memiliki kaitan dengan objek terkini.
current object this
Objek tempat di mana sebuah metode objek dipanggil. Di dalam metode, objek ini diacu dengan kata kunci this. Kata kunci yang mengacu ke objek terkini Semua yang tidak dituliskan atau diungkapkan secara
implicit
langsung. Di dalam sebuah metode objek, Anda bisa mengacu ke variabel instan secara implisit (tanpa menyebutkan nama objek yang Anda maksud). Semua yang dituliskan atau dinyatakan secara lengkap. Di
explicit
dalam metode kelas, semua referensi yang mengacu ke variabel instan harus dinyatakan secara eksplisit.
295
13.17 Latihan Latihan 13.2 Ubahlah metode kelas berikut ini ke dalam bentuk metode objek. public static double abs (Complex c) { return Math.sqrt (c.real * c.real + c.imag * c.imag); } Latihan 13.3 Ubahlah metode objek berikut ini ke dalam bentuk metode kelas. public boolean equals (Complex b) { return (real == b.real && imag == b.imag); } Latihan 13.4 Latihan ini merupakan lanjutan dari latihan 9.3. Tujuannya adalah untuk melatih Anda menulis sintaks metode objek dan membuat Anda akrab dengan beberapa pesan error yang relevan dengan masalah ini. a) Ubahlah metodemetode kelas yang ada dalam kelas Rational ke bentuk metode objek. Lalu buatlah perubahan seperlunya dalam metode main. b) Buatlah beberapa kesalahan! Cobalah memanggil metode kelas dengan cara yang sama seperti ketika Anda memanggil metode objek. Lalu lakukan hal yang sebaliknya. Cobalah mencari tahu halhal mana yang legal dan mana yang tidak. Selain itu, pelajari juga pesanpesan error yang Anda dapat ketika Anda sengaja mengacaukan keadaan. c) Pikirkan mengenai pro dan kontra yang menyelimuti penggunaan dua tipe metode 296
ini; kelas dan objek. Tipe mana yang lebih jelas (secara umum)? Cara mana yang lebih alami untuk mengekspresikan komputasi (atau, agar lebih adil dan jelas, tipe komputasi seperti apa yang bisa diekspresikan paling baik menggunakan masing masing bentuk)?
297
Bab 14 Linked List 14.1 Acuanacuan dalam Objek Di dalam bab sebelumnya kita sudah melihat bahwa variabelvariabel instan sebuah objek dapat berupa array dan atau juga objek. Satu kemungkinan yang paling menarik adalah bahwa sebuah objek bisa berisi sebuah acuan yang mengacu ke objek lain yang sama tipenya. Ada sebuah struktur data yang akan mengambil manfaat besar dari fitur ini; list. List terbuat dari dari kumpulan node yang dalam setiap nodenya berisi suatu acuan ke node berikutnya dalam list. Selain itu, setiap node biasanya berisi satu unit data yang disebut cargo. Dalam contoh pertama kita, kargo ini berupa integer tunggal. Namun di kesempatan berikutnya kita akan menulis sebuah list generic yang dapat menyimpan objek dari tipe apapun.
14.2 Kelas Node Seperti biasa, ketika kita menulis sebuah kelas baru, kita akan memulainya dengan variabel instan. Lalu dengan satu atau dua konstruktor baru kemudian diteruskan dengan pembuatan metode toString agar kita bisa menguji mekanisme dasar dalam hal pembuatan dan penyajian tipe baru tersebut. public class Node { int cargo; Node next;
298
public Node () { cargo = 0; next = null; } public Node (int cargo, Node next) { this.cargo = cargo; this.next = next;
} public String toString () { return cargo + ""; }
} Deklarasi untuk variabelvariabel instan sudah mengikuti spesifikasi yang ada secara alami, sementara sisanya tinggal menyesuaikan dengan variabel instan. Ekspresi cargo + "" sebenarnya agakagak aneh, tapi justru inilah cara terbaik untuk mengkonversi sebuah integer menjadi String. Untuk menguji implementasi ini, kita akan meletakkan kode seperti ini dalam main: Node node = new Node (1, null); System.out.println (node); Hasilnya adalah 1 agar tambah menarik, kita membutuhkan sebuah list yang terdiri dari dua atau lebih node! Node node1 = new Node (1, null); 299
Node node2 = new Node (2, null); Node node3 = new Node (3, null); Kode ini menghasilkan tiga node tapi kita masih belum memiliki list karena nodenode tersebut belum kita hubungkan (linked). Diagram keadaannya akan tampak sebagai berikut:
Untuk menghubungkan nodenode itu, kita harus merancang kode sedemikian rupa sehingga node pertama akan mengacu ke node kedua, sementara node kedua mengacu ke node ketiga. node1.next = node2; node2.next = node3; node3.next = null; Acuan milik node ketiga adalah null. hal ini mengindikasikan bahwa node ini merupakan akhir dari list. Sekarang diagram keadaannya akan tampak sebagai berikut:
Sekarang kita semua sudah tahu cara membuat nodenode sekaligus bagaimana cara menghubungkannya agar menjadi list. Apa yang mungkin kurang jelas sampai saat ini 300
adalah kenapa.
14.3 List sebagai Collection Sesuatu yang menyebabkan list bermanfaat adalah karena list merupakan suatu teknik untuk merangkai objekobjek yang berbeda menjadi satu entitas tunggal, biasanya diistilahkan dengan collection. Dalam contoh ini, node pertama dari list akan bertindak sebagai acuan untuk keseluruhan list. Jika kita ingin melewatkan list sebagai parameter maka yang harus kita lewatkan hanyalah acuan yang mengacu ke node pertama. Sebagai contoh, metode printList menggunakan satu node sebagai argumennya. Dimulai dari bagian kepala (head) list, metode ini mencetak setiap node sampai selesai (ditandai dengan acuan yang berupa null). public static void printList (Node list) { Node node = list; while (node != null) { System.out.print (node); node = node.next; } System.out.println (); } Untuk memanggil metode ini, kita hanya perlu melewatkan sebuah acuan ke node pertama: printList (node1); Di dalam printList, kita mempunyai sebuah acuan yang mengarah ke node pertama 301
dalam list, tapi tidak ada variabel yang mengacu ke nodenode lainnya. Kita harus menggunakan nilai next dari setiap node untuk mendapatkan node berikutnya. Diagram berikut ini menunjukkan nilai list dan nilai yang diambil oleh node:
Pergerakan untuk menelusuri sebuah list seperti ini sering disebut dengan istilah traversal. Sama seperti pola penelusuran yang telah kita lakukan terhadap elemen elemen suatu array. Adalah hal yang biasa/lazim untuk menggunakan variabel loop seperti node untuk mengacu ke setiap node dalam list secara beruntun. Hasil metode ini adalah 123 Jika menggunakan konvensi yang sudah ada, list sebaiknnya dicetak dalam tanda kurung (parentheses) dengan koma di antara masingmasing elemen, misalnya (1, 2, 3). Untuk latihan Anda, ubahlah printList sedemikian sehingga ia bisa menghasilkan output seperti format di atas. Untuk tambahan, tulisulanglah printList menggunakan perulangan for ketimbang memakai while.
14.4 List dan Rekursi Rekursi dan list berjalan seiringseirama laksana fava beans dan chianti yang nikmat. Sebagai contoh, berikut ini merupakan algoritma untuk mencetak sebuah list dari bagian 302
belakang: 1. Pisahlah list ke dalam dua bagian: node pertama (sebagai kepala (head)) dan sisanya (sebagai ekor (tail)). 2. Cetak ekornya dari belakang. 3. Cetak kepalanya. Tentu saja, langkah kedua, pemanggilan rekursinya, mengasumsikan bahwa kita sudah mempunyai suatu cara untuk mencetak list dari belakang. Tapi jika kita menganggap bahwa pemanggilan rekursi ini bekerja dengan baik – leap of faith – barulah kita boleh yakin kepada diri kita sendiri kalau algoritma ini telah bekerja dengan baik dan benar. Yang kita butuhkan adalah base case, lalu sebuah cara untuk membuktikan bahwa untuk setiap list yang kita gunakan, kita akan bisa kembali lagi ke base case. Pilihan paling wajar untuk base case adalah sebuah list dengan satu elemen tunggal, tapi akan lebih baik kalau kita mau menggunakan list kosong, yang direpresentasikan dengan null. public static void printBackward (Node list) { if (list == null) return; Node head = list; Node tail = list.next; printBackward (tail); System.out.print (head); }
Baris pertama merupakan base case yang tidak mengembalikan nilai apapun. Dua baris berikutnya memecah list menjadi dua; head dan tail. Sementara dua baris terakhir digunakan untuk mencetak list itu sendiri. 303
Kita memanggil metode ini dengan cara yang sama seperti ketika kita memanggil printList: printBackward (node1); Hasilnya adalah sebuah backward list. Bisakah kita membuktikan bahwa metode ini pasti akan selalu bisa dihentikan? Dengan kata lain, apakah ia akan selalu sampai menuju base case? Sayangnya, jawabannya adalah tidak. Ada beberapa list yang justru akan membuat lumpuh metode ini.
14.5 Infinite List Tidak akan ada yang dapat mencegah sebuah node untuk mengacu kembali ke node sebelumnya dalam list, tidak terkecuali node itu sendiri. Sebagai contoh, gambar berikut ini menunjukkan sebuah list dengan dua node, satu di antaranya mengacu ke dirinya sendiri.
Jika kita memanggil printBackward, metode ini akan terus melakukan rekursi tanpa henti. Perilaku semacam ini akan membuat kita sulit bekerja dengannya. Tapi meskipun begitu, mereka sebenarnya seringkali berguna. Contohnya, kita bisa merepresentasikan suatu bilangan dalam bentuk list yang berisi digitdigit, lalu menggunakan infinite list untuk merepresentasikan bagian atau angkaangka (fraction) yang berulang (muncul lebih dari 1 kali). Terlepas dari semua itu, sangat disayangkan karena kita tidak dapat membuktikan 304
bahwa printList dan printBackward bisa dihentikan. Hal terbaik yag bisa kita lakukan adalah mengeluarkan pernyataan bersifat hipotesis (hypotetical statement), “jika suatu list tidak berisi perulangan, maka metode ini akan berhenti”. Klaim seperti ini disebut precondition. Klaim ini menekankan pada satu hambatan yang terdapat dalam salah satu parameter lalu mendeskripsikan perilaku metode tersebut jika hambatan yang dimaksud muncul. Kita akan melihat contoh lain sesaat lagi.
14.6 Teorema Ambiguitas Fundamental Ada bagian dalam printBackward yang mungkin akan membuat Anda mengangkat kedua alis: Node head = list; Node tail = list.next; Setelah argumen pertama, head dan list mempunyai tipe dan nilai yang sama. Lalu kenapa saya membuat satu variabel baru? Alasannya adalah karena kedua variabel tersebut memiliki fungsi yang berbeda. Kita menganggap head sebagai sebuah acuan yang mengacu ke node tunggal, sementara tail, kita menganggapnya sebagai acuan yang mengacu ke node pertama dalam list. “Aturan aturan” ini memang bukan bagian dalam program; tapi keduanya ada dalam benak sang programmer. Assignment kedua membuat sebuah acuan baru ke node kedua dalam list, tapi dalam kasus ini kita menganggapnya sebagai sebuah list. Jadi, meskipun head dan tail memiliki tipe yang sama, keduanya tetap memainkan peran yang berbeda. Ambiguitas seperti ini sangat bermanfaat, tapi dapat menyebabkan programprogram dengan list justru sulit dibaca. Saya sering menggunakan namanama variabel seperti
305
node dan list untuk mendokumentasikan bagaimana saya menggunakan sebuah variabel, dan terkadang saya juga membuat variabel tambahan agar tidak terjadi ambiguitas lagi. Saya bisa menulis printBackward tanpa head dan tail, tapi saya pikir hal ini justru akan membuatnya semakin sulit untuk dibaca: public static void printBackward (Node list) { if (list == null) return; printBackward (list.next); System.out.print (list); }
Ketika kita melihat pemanggilan dua fungsi, kita harus ingat bahwa printBackward memperlakukan argumennya sebagai sebuah list sementara print memperlakukan argumennya sebagai sebuah objek tunggal. Ingatlah mengenai teorema ambiguitas fundamental: Sebuah variabel yang mengacu ke sebuah node mungkin akan memperlakukan node tersebut sebagai sebuah objek tunggal atau sebagai node pertama dalam list.
14.7 Metode Objek Untuk NodeNode Anda mungkin akan heran kenapa printList dan printBackward masuk ke dalam golongan metode kelas. Saya sudah membuat klaim sebelumnya bahwa semua yang bisa ditulis sebagai metode kelas juga bisa ditulis sebagai metode objek; masalahnya adalah format mana yang Anda anggap lebih jelas. Dalam kasus ini terdapat sebuah alasan bagus kenapa kita sampai menggunakan metode objek. Adalah suatu yang legal mengirimkan null sebagai argumen ke metode kelas, 306
tapi adalah hal sebaliknya untuk memanggil sebuah metode objek melalui objek null. Node node = null; printList (node); // legal node.printList (); // NullPointerException Pembatasan ini justru membuat kita akan tampak aneh ketika hendak menulis kode pemanipulasi list yang bersih dalam paradigma berorientasi objek. Tapi nanti, kita akan melihat cara untuk menyelesaikan masalah ini.
14.8 Memodifikasi List Satu cara yang jelasjelas bisa dipakai untuk memodifikasi sebuah list adalah dengan mengubah kargo salah satu node yang ada. Namun, operasi yang lebih menarik adalah dengan cara menambahkan, menghilangkan, atau mengubah urutan nodenode tersebut. Sebagai contoh, kita akan menulis sebuah metode yang dapat menghilangkan node kedua dalam list lalu memberikan hasil yang berupa acuan ke node yang dihilangkan. public static Node removeSecond (Node list) { Node first = list; Node second = list.next; // make the first node refer to the third first.next = second.next; // separate the second node from the rest of the // list second.next = null; return second; }
307
Lagilagi, di sini saya menggunakan variabel temporer agar kode di atas lebih mudah dibaca. Berikut ini adalah cara menggunakan metode ini. printList (node1); Node removed = removeSecond (node1); printList (removed); printList (node1); Hasilnya adalah (1, 2, 3) the original list (2) the removed node (1, 3) the modified list Berikut ini adalah diagram keadaan yang menunjukkan efek yang ditimbulkan oleh operasi ini.
Apa yang akan terjadi jika memanggil metode ini lalu melewatkan sebuah list yang hanya berisi satu elemen (satu singleton)? Apa yang terjadi jika melewatkan list kosong sebagai sebuah argumen? Adakah precondition untuk metode ini?
14.9 Wrappers dan Helpers Untuk beberapa operasi list, akan sangat berguna jika kita pisahkan metode ini menjadi 308
dua. Sebagai contoh, untuk mencetak satu list dari belakang dalam bentuk yang konvensional, yakni (3, 2, 1), kita bisa menggunakan printBackward untuk mencetak 3, 2, tapi kita membutuhkan satu metode terpisah untuk mencetak tanda kurung dan node pertamanya. Kita akan menyebutnya printBackwardNicely. public static void printBackwardNicely
(Node list)
{ System.out.print ("("); if (list != null) { Node head = list; Node tail = list.next; printBackward (tail); System.out.print (head); } System.out.println (")"); } Kembali, adalah ide yang bagus untuk memeriksa metodemetode seperti ini untuk melihat apakah mereka benarbenar berfungsi dengan kondisikondisi khusus seperti list kosong atau sebuah singleton. Di bagian manapun dalam program, ketika kita menggunakan metode ini, kita akan memanggil printBackwardNicely lalu ia akan memanggil printBackward untuk kita. Dalam kondisi seperti ini, printBackwardNicely bertindak sebagai wrapper. Sementara printBackward, yang berada dalam printBackwardNicely, disebut sebagai helper.
309
14.10 Kelas intList Ada beberapa masalah serius dan penting terkait dengan cara kita dalam mengimplementasikan list. Dalam hukum sebab akibat yang selalu berubahubah, saya akan memperlihatkan sebuah cara alternatif untuk mengimplementasikan listnya dahulu baru kemudian menjelaskan kepada Anda semua masalah apa yang dapat diselesaikannya. Pertamatama, kita akan membuat sebuah kelas baru, IntList. Variabel instan kelas ini adalah sebuah integer yang berisi panjang list dan sebuah acuan ke node pertama dalam list. Objekobjek IntList bertindak sebagai manipulator list milik objekobjek Node. public class IntList { int length; Node head; public IntList () { length = 0; head = null; } } Satu hal bagus mengenai kelas IntList adalah karena ia dapat memberi kita tempat, secara alami, untuk meletakkan fungsi pembungkus (wrapper) seperti printBackwardNicely di mana bisa membuat sebuah metode objek dalam kelas IntList. public void printBackward () { System.out.print ("("); if (head != null) {
310
Node tail = head.next; Node.printBackward (tail); System.out.print (head); } System.out.println (")"); } Untuk membuatnya menjadi semakin membingungkan, saya menamai ulang printBackwardNicely. Nah, karena sekarang ada dua metode dengan nama printBackward: satu ada dalam kelas Node (helper) sementara satunya lagi berada di dalam kelas IntList (wrapper). Agar wrapper bisa memanggil helper, ia harus mengidentifikasi kelasnya secara eksplisit (Node.printBackward). Jadi, satu keuntungan dari kelas IntList adalah karena ia dapat menyediakan tempat yang bagus untuk meletakkan fungsifungsi pembungkus (wrapper). Keuntungan lainnya adalah karena ia dapat memudahkan kita untuk menambah atau menghilangkan elemen pertama dalam list. Sebagai contoh, addFirst merupakan metode objek milik IntList; metode ini menggunakan sebuah int sebagai argumennya lalu meletakkannya ke posisi pertama dalam list. public void addFirst (int i) { Node node = new Node (i, head); head = node; length++; } Seperti biasa, untuk memeriksa kode seperti ini, adalah hal yang bagus untuk memikirkan beberapa kasus khusus. Seperti, apa yang terjadi jika list pada awalnya
311
sudah diinisialisasi dalam keadaan kosong?
14.11 Invariants Beberapa list memang sudah berada dalam keadaan yang “wellformed”, sementara yang lain tidak. Sebagai contoh, jika sebuah list berisi sebuah perulangan, ini akan menyebabkan rusaknya beberapa metode yang sudah kita buat. Jadi, mungkin kita menginginkan agar list kita tidak berisi perulangan sama sekali. Syarat lainnya adalah bahwa nilai length dalam objek IntList harus sama dengan jumlah node yang berada dalam list. Persyaratan seperti ini disebut invariants karena, idealnya, mereka harusnya selalu bernilai true untuk semua objek sepanjang waktu. Menentukan invarian untuk objek objek merupakan praktik pemrograman yang bermanfaat karena dapat memudahkan kita ketika hendak meningkatkan tingkat kebenaran suatu kode, memeriksa integritas struktur data yang digunakan, dan mendeteksi error. Satu hal yang sering membingungkan mengenai invarian adalah bahwa mereka terkadang justru dilanggar dalam kode program. Contohnya, di tengah addFirst, setelah kita menambahkan nodenya, tapi sebelum kita menaikkan nilai length, invariannya justru dilanggar. Pelanggaran seperti ini masih bisa diterima; kenyataannya, terkadang mustahil bagi kita untuk memodifikasi sebuah objek tapa melanggar sebuah invarian meskipun itu untuk sesaat saja. Normalnya, persyaratan yang harus disertakan adalah bahwa semua metode yang melanggar suatu invarian harus bisa mengembalikan/memperbaikinya (restore) lagi. Jika ada sesuatu yang signifikan dalam kode sehingga suatu invarian ternyata dilanggar, adalah penting bagi komentar untuk menjelaskannya, sehingga tidak akan ada lagi operasi yang bergantung kepada invarian tersebut.
312
14.12 Daftar KataKata Istilah
Arti Struktur data yang mengimplementasikan sebuah koleksi
list
menggunakan sekumpulan nodenode yang saling terhubung. Suatu elemen dalam list, biasanya diimplementasikan
node
sebagai sebuah objek yang berisi acuan ke objek lain yang sama tipenya.
cargo link
Item data yang dikandung sebuah node. Sebuah acuan objek yang dimasukkan ke dalam objek.
generic data structure Sejenis struktur data yang berisi data dari tipe apapun. precondition
Sebuah fakta yang harus bernilai true agar sebuah metode bisa bekerja dengan baik. Sebuah fakta yang seharusnya bernilai true untuk sebuah
invariant
objek sepanjang waktu (kecuali mungkin ketika objek tersebut sedang dimodifikasi). Sebuah metode yang bertindak sebagai penengah antara
wrapper method
sebuah metode pemanggil (caller) dan metode penolong (helper method), seringkali berupa interface yang lebih bersih ketimbang metode penolongnya.
14.13 Latihan Latihan 14.2 Mulailah dengan mengunduh file IntList.java dari http://thinkapjava/code/IntList. Link ini berisi definisidefinisi mengenai IntList dan Node dari bab ini, bersama kode yang 313
mendemonstrasikan dan menguji beberapa metode. Compile lalu eksekusi program itu. Hasilnya akan tampak sebagai berikut: (1, 2, 3) (3, 2, 1) Latihan berikut ini meminta Anda untuk menulis metode objek tambahan bagi kelas IntList. Tetapi mungkin Anda ingin menulis beberapa metode penolong dalam kelas Node seperti biasa. Setelah Anda menulis masingmasing metode, tambahkan kode itu ke dalam main lalu ujilah. Yakinlah untuk menguji beberapa kasus khusus seperti listlist kosong atau singleton. Untuk setiap metode, identifikasilah semua precondition yang diperlukan agar semua metode bisa berfungsi dengan benar lalu tambahkan komentar yang akan bisa menjadi dokumentasi untuknya. Komentar Anda juga harus mengindikasikan apakah masing masing metode merupakan konstruktor, fungsi, ataukah modifier. a) Tulislah metode removeFirst yang mampu menghapus node pertama dari sebuah list. Metode ini mampu mengembalikan kargo yang dimiliki oleh list tersebut. b) Tulislah metode set yang menggunakan sebuah index, i, dan sebuah item dari kargo. Metode ini mampu menukar kargo node urutan kei dengan kargo yang diberikan. c) Tulislah metode add yang menggunakan sebuah index, i, dan sebuah item dari kargo. Metode ini akan menambahkan sebuah node baru yang berisi kargo yang diberikan ke posisi i. d) Tulislah metode addLast yang menggunakan sebuah item dari kargo lalu menambahkannya ke posisi buncit dalam list. e) Tulislah metode reverse yang bisa memodifikasi IntList, yang dapat memutarbalik urutan nodenode yang ada di dalamnya. 314
f) Tulislah metode append yang menggunakan sebuah IntList sebagai parameter. Metode ini bisa menambahkan salinan dari nodenode yang ada dalam parameter list (list yang menjadi parameter) ke posisi buncit dari list yang sedang aktif dipakai saat itu (current list). g) Tulislah metode checkLength yang mengembalikan nilai true jika panjang field yang ada sama dengan jumlah node yang ada dalam list, dan false jika sebaliknya. Metode ini tidak boleh menyebabkan terjadinya exception di bawah kondisi apapun, dan ia harus bisa berhenti bahkan jika list tersebut berisi perulangan. Latihan 14.2 Satu cara untuk merepresentasikan angka yang sangat besar adalah dengan menggunakan sebuah daftar (list) berisi digitdigit, yang biasanya disimpan dalam posisi yang sudah terurut. Sebagai contoh, angka 123 akan direpresentasikan dengan list (3, 2, 1). Tulislah sebuah metode yang membandingkan dua bilangan yang direpresentasikan sebagai IntList dan yang bisa mengembalikan 1 jika bilangan pertama yang lebih besar, 1 jika yang kedua lah yang lebih besar, dan 0 jika keduanya sama.
315
Bab 15 Stacks 15.1 Tipe Data Abstrak Semua tipe data yang telah kita lihat sejauh ini merupakan tipe data konkret. Artinya, kita harus menentukan, dengan lengkap, bagaimana tipe datatipe data tersebut diimplementasikan. Sebagai contoh, kelas Card merepresentasikan sebuah kartu menggunakan dua integer. Sebagaimana yang telah saya kemukakan sebelumnya, teknik di atas bukanlah satusatunya cara untuk merepresentasikan sebuah kartu; sebenarnya masih banyak cara lain untuk mengimplementasikannya. Abstract Data Type (Tipe Data Abstrak), atau ADT, merupakan suatu kumpulan operasi (atau metode) dan bentuk semantik dari operasioperasi tersebut (apa yang dilakukan oleh operasi) tapi dia tidak menentukan implementasi operasioperasi itu sendiri. Hal inilah yang membuat mereka abstrak. Kenapa ADT berguna? ●
ADT menyederhanakan tugas Anda ketika hendak menentukan sebuah algoritma jika Anda bisa mengidentifikasi operasioperasi yang Anda butuhkan tanpa harus berpikir pada saat yang bersamaan mengenai bagaimana operasi operasi tersebut akan dikerjakan.
●
Karena ada banyak alternatif cara untuk mengimplementasikan ADT, mungkin akan berguna bagi kita untuk menulis sebuah algoritma yang bisa digunakan untuk beberapa implementasi sekaligus.
●
Varianvarian ADT yang sudah populer, seperti Stack ADT dalam bab ini, sering diimplementasikan di dalam pustaka standar (standard library) sehingga
316
mereka bisa ditulis sekali dan langsung digunakan oleh banyak programmer. ●
Operasioperasi ADT menyediakan sebuah level bahasa tingkattinggi yang umum digunakan untuk memilih, menentukan, dan membahas suatu algoritma.
Ketika kita berbicara mengenai ADT, kita sering membedakan antara kode yang menggunakan ADT, disebut client code, dari kode yang mengimplementasikan ADT, disebut provider code karena kode ini menyediakan sebuah kumpulan standar yang terdiri dari layananlayanan.
15.2 The Stack ADT Dalam bab ini, kita akan melihat salah satu ADT yang sudah banyak dikenal, yakni stack. Stack merupakan suatu koleksi (collection). Ini artinya, stack adalah struktur data yang berisi lebih dari satu elemen. Koleksi lain yang sudah kita lihat sebelumnya antara lain; array dan list. Seperti yang sudah saya sebutkan, sebuah ADT didefinisikan dengan suatu kumpulan yang terdiri dari operasioperasi. Stack bisa melakukan operasioperasi berikut ini: Constructor : membuat sebuah stack yang baru sekaligus kosong. Push: menambahkan item baru ke dalam stack. Pop: menghapus dan mengembalikan sebuah item dari stack. Item yang dikembalikan harus berupa item terakhir yang ditambahkan. IsEmpty: memeriksa apakah stack dalam keadaan kosong. Sebuah stack sering juga disebut dengan “last in, first out”, atau struktur data LIFO, karena item terakhir yang ditambahkan akan menjadi item pertama yang akan dihilangkan.
317
15.3 Objek Stack dalam Java Java menyediakan sebuah tipe objek bawaan yang disebut Stack yang mengimplementasikan tipe data abstrak stack. Anda harus berusaha untuk membedakan kedua hal ini – versi ADT dan versi Java – dengan jelas. Sebelum menggunakan kelas Stack, kita harus mengimpornya terlebih dahulu dari java.util. Berikut ini merupakan sintaks untuk membuat sebuah Stack yang baru: Stack stack = new Stack (); pada mulanya, stack yang kita buat tidak berisi satu elemen pun alias kosong, kita bisa mengkonfirmasi keadaan ini dengan metode isEmpty yang akan mengembalikan hasil berupa nilai boolean: System.out.println (stack.isEmpty ()); Stack merupakan struktur data yang bersifat generik. Ini berarti bahwa kita bisa menambahkan item bertipe apapun ke dalamnya. Meskipun begitu, dalam implementasi Java, kita hanya bisa menambahkan tipetipe objek. Untuk contoh pertama, kita akan menggunakan objek Node yang digunakan dalam bab sebelumnya. Mari kita mulai dengan membuat lalu mencetak sebuah daftar yang pendek (short list): IntList list = new IntList (); list.addFirst (3); list.addFirst (2); list.addFirst (1); list.print (); hasilnya adalah (1, 2, 3). Untuk meletakkan sebuah objek Node ke dalam stack, gunakan metode push: stack.push (list.head); Perulangan berikut ini akan menelusuri daftar tersebut dan memasukkan (push) semua 318
node ke dalam stack. for (No de nod e = list .hea d; nod e != nul l; nod e = node .nex t) { stack .pus h (n ode) ; }
Kita bisa mengambil/menghapus sebuah elemen dari stack menggunakan metode pop. Object obj = stack.pop (); Tipe kembalian dari pop berupa Object! Ini terjadi karena implementasi stack tidak benarbenar mengetahui tipetipe objek yang dikandungnya. Ketika kita memasukkan objekobjek Node, mereka dikonversikan secara otomatis menjadi Object. Ketika kita mengambil objekobjek tersebut dari stack, kita harus mengcast mereka menjadi Node kembali. Node node = (Node) obj; System.out.println (node); Sayangnya, programmer justru akan mengalami kesulitan besar untuk melacak jejak objekobjek dalam stack dan juga ketika hendak mengcast mereka kembali ke tipe yang benar sebelum mereka diambil. Jika Anda mencoba untuk mengcast sebuah objek ke dalam tipe yang salah, Anda akan mendapatkan sebuah pesan error; ClassCastException. Perulangan berikut ini merupakan idiom yang sering dipakai untuk mengambil semua elemen dari dalam stack, lalu berhenti ketika stack tersebut sudah kosong: while (!stack.isEmpty ()) { Node node = (Node) stack.pop (); System.out.print (node + " "); }
319
Hasilnya adalah 3 2 1. Dengan kata lain, kita baru saja menggunakan sebuah stack untuk mencetak elemenelemen yang ada dalam daftar secara terbalik! Perhatikan, cara ini bukanlah format standar yang biasa digunakan untuk mencetak sebuah daftar, tapi dengan stack, hal ini justru sangat mudah dilakukan. Anda harus membandingkan kode ini dengan implementasi yang sudah kita gunakan untuk metode printBackward yang berada dalam bab sebelumnya. Terdapat semacam kaitan yang alamiah sekaligus parallel antara versi rekursif dari printBackward dengan algoritma stack di sini. Perbedaannya adalah karena printBackward menggunakan runtime stack untuk melacak keberadaan nodenode ketika ia menelusuri suatu daftar, lalu mencetak hasilnya secara terbalik dengan rekursi. Algoritma stack sebenarnya melakukan hal yang sama, hanya saja algoritma ini menggunakan objek Stack ketimbang menggunakan runtime stack.
15.4 Kelas Pembungkus Untuk setiap primitif dalam Java, terdapat sebuah objek bawaan yang disebut dengan kelas pembungkus (wrapper classes). Sebagai contoh, kelas pembungkus untuk int adalah Integer; untuk double disebut dengan Double. Kelas pembungkus menjadi bermanfaat karena beberapa faktor: ●
Anda bisa menginstansiasi kelas pembungkus lalu membuat objekobjek yang berisi nilainilai primitif. Dengan kata lain, Anda bisa membungkus sebuah nilai primitif dengan sebuah objek. Hal ini akan bermanfaat jika Anda ingin memanggil sebuah metode yang membutuhkan sebuah tipe objek.
●
Setiap kelas pembungkus berisi nilainilai khusus (seperti nilai minimum da maximum untuk sebuah tipe), dan metodemetode yang berfungsi untuk mengkonversi dari satu tipe ke tipe lainnya. 320
15.5 Membuat Kelas Pembungkus Cara yang paling cepat untuk membuat sebuah objek pembungkus adalah dengan menggunakan konstruktornya sendiri: Integer i = new Integer (17); Double d = new Double (3.14159); Character c = new Character (’b’); Secara teknis, sebenarnya String bukanlah sebuah kelas pembungkus karena tipe ini tidak berhubungan dengan tipe primitif manapun, tapi sintaks untuk membuat objek String adalah sama seperti cara berikut: String s = new String ("fred"); Di sisi lain, belum pernah ada seorang pun yang menggunakan konstruktor untuk objek objek String karena Anda bisa mendapatkan hasil yang sama dengan nilai String yang lebih sederhana: String s = "fred";
15.6 Membuat Kelas Pembungkus Lagi Beberapa kelas pembungkus memiliki konstruktor kedua yang menggunakan sebuah String sebagai argumennya lalu untuk mengubahnya ke bentuk yang diinginkan. Sebagai contoh: Integer i = new Integer ("17"); Double d = new Double ("3.14159"); Proses pengubahan/konversi tipe ini sayangnya tidak kokoh (robust). Sebagai contoh, jika String yang digunakan tidak berada dalam format yang benar maka String tersebut 321
akan menyebabkan munculnya NumberFormatException. Semua karakter non numerik dalam String, termasuk spasi, akan mengakibatkan gagalnya proses konversi. Integer i = new Integer ("17.1"); // WRONG!! Double d = new Double ("3.1459 "); // WRONG!!
Adalah ide yang bagus untuk memeriksa format String yang digunakan sebelum Anda mencoba untuk mengubahnya.
15.7 Mengeluarkan NilaiNilai Java mengetahui cara mencetak objekobjek pembungkus, cara termudah adalah dengan mengambil suatu nilai adalah cukup dengan mencetak objek tersebut: Integer i = new Integer (17); Double d = new Double (3.14159); System.out.println (i); System.out.println (d);
Alternatif lain yang bisa Anda gunakan adalah dengan memanfaatkan metode toString. Metode ini bisa Anda gunakan untuk mengubah isi objek pembungkus menjadi String; String istring = i.toString(); String dstring = d.toString(); Akhirnya, jika Anda hanya ingin mengambil nilai primitif dari suatu objek, ada sebuah metode objek dalam tiap kelas pembungkus yang mengerjakan tugas ini: int iprim = i.intValue (); double dprim = d.doubleValue (); Ada juga metodemetode yang bisa digunakan untuk mengubah objekobjek 322
pembungkus ke tipe primitif yang berbedabeda. Anda harus memeriksa dokumentasi untuk masingmasing kelas pembungkus untuk melihat apa saja yang bisa diubah dan apa yang tidak.
15.8 MetodeMetode Bermanfaat dalam Kelas Pembungkus Seperti yang telah saya sebutkan, kelaskelas pembungkus berisi metodemetode bermanfaat yang berkaitan dengan masingmasing tipe. Sebagai contoh, kelas Character berisi metodemetode, dalam jumlah yang melimpah, yang bisa digunakan untuk mengubah karakter ke bentuk upper case dan lower case. Juga untuk memeriksa apakah sebuah karakter merupakan angka, huruf, ataukah simbol. Kelas String juga berisi metodemetode untuk melakukan pengubahan ke bentuk upper dan lower case. Meskipun demikian, Anda harus tetap ingat bahwa mereka adalah fungsi, bukan modifier (lihat subbab 7.9). Untuk contoh tambahan, kelas Integer juga berisi metodemetode untuk menginterpretasikan dan mencetak integerinteger ke bentuk yang berbedabeda. Jika Anda mempunyai sebuah String yang berisi angka dalam basis 6, Anda bisa mengubahnya ke basis 10 menggunakan parseInt. String base6 = "12345"; int base10 = Integer.parseInt (base6, 6); System.out.println (base10);
Karena parseInt merupakan sebuah metode kelas, Anda memanggilnya dengan cara menyebutkan nama kelas dan metodenya memakai notasi dot. Basis 6 mungkin tidak terlalu berguna, tetapi heksadesimal (basis 16) dan oktaf (basis 8) merupakan sesuatu yang sangat sering dipakai dalam dunia ilmu komputer.
323
15.9 Ekspresi Postfix Dalam banyak bahasa pemrograman, ekspresiekspresi matematika ditulis dengan bantuan operator yang diletakkan di antara dua operan, seperti dalam 1 + 2. format seperti ini disebut infix. Format alternatif yang digunakan oleh beberapa kalkulator adalah postfix. Dalam postfix, operatornya mengikuti operanoperannya, misalnya 1 2 +. Alasan yang membuat postfix terkadang berguna adalah bahwa terdapat semacam kecenderungan alamiah untuk mengevaluasi suatu postfix menggunakan sebuah stack. ●
Dimulai dari depan/awal sebuah ekspresi, ambil satu term (operator atau operan) dalam satu waktu. ●
Jika termnya berupa operan, masukkan (push) dia ke dalam stack.
●
Jika termnya berupa operator, ambil (pop) operanoperan tersebut dari dalam stack, kerjakan operasi untuk mereka, lalu masukkan hasilnya ke dalam stack lagi.
●
Ketika kita sudah sampai ke ujung/akhir sebuah ekspresi, harusnya sudah terdapat sebuah operan yang tertinggal dalam stack. Operan itulah yang menjadi hasilnya.
Sebagai latihan, gunakan algoritma ini untuk mengerjakan ekspresi 1 2 + 3 *. Latihan ini menunjukkan salah satu kelebihan postfix: kita tidak perlu lagi menggunakan tanda kurung untuk mengatur urutan suatu operasi. Untuk mendapatkan hasil yang sama dalam infix. Kita mungkin harus menulis (1 + 2) * 3. Sebagai latihan, tulislah ekspresi postfix yang ekuivalen dengan 1 + 2 * 3?
324
15.10 Parsing Untuk mengimplementasikan algoritma yang ada di bab sebelumnya, kita harus bisa menelusuri suatu string lalu membaginya ke dalam dua kelompok yaitu; operan dan operator. Proses ini merupakan sebuah contoh parsing (penguraian), lalu hasilnya – yang berupa string individual – disebut tokens. Java menyediakan sebuah kelas bawaan yang disebut StringTokenizer yang bisa menguraikan string lalu memecahnya menjadi tokentoken. Untuk menggunakan kelas ini, Anda harus mengimpornya dari java.util. Dalam bentuknya yang paling sederhana, StringTokenizer menggunakan spasispasi untuk menandai batas antara tiap token. Sebuah karakter yang menandai suatu pembatas disebut delimiter. Kita bisa membuat StringTokenizer dengan cara yang sudah sering kita gunakan, dengan melewatkan sebuah argumen berupa string yang akan kita uraikan. StringTokenizer
st = new StringTokenizer
("Here are
four tokens."); Perulangan berikut ini merupakan idiom standar untuk mengambil tokentoken dari StringTokenizer. while (st.hasMoreTokens ()) { System.out.println (st.nextToken()); } Hasilnya adalah: Here are
325
four tokens. Untuk menguraikan ekspresiekspresi, kita memiliki opsi untuk menentukan karakter tambahan yang akan kita gunakan sebagai delimiter. StringTokenizer
st = new StringTokenizer
("11
22+33*", " +*/");
Argumen kedua adalah sebuah String yang berisi semua karakter yang akan digunakan sebagai delimiter. Hasilnya adalah: 11 22 33 Cara ini memang bekerja dengan baik untuk mengambil semua operannya tapi sayangnya kita juga akan kehilangan operatoroperatornya. Untungnya, masih ada satu opsi lagi dari StringTokenizer. StringTokenizer
st = new StringTokenizer
("11
22+33*", " +*/", true); Argumen ketiga berkata, “Ya, kami ingin memperlakukan delimiterdelimiter sebagai tokentoken.” sekarang hasilnya adalah 11 22 + 33 326
* Ini hanyalah tokentoken yang akan kita gunakan untuk mengevaluasi ekspresiekspresi ini.
15.11 Mengimplementasikan ADT Salah satu tujuan dasar dari ADT adalah untuk memisahkan kebutuhan sang penyedia (provider), si penulis kode yang mengimplementasikan ADT, dan sang klien (client), orang yang menggunakan ADT. Sang penyedia hanya perlu mengkhawatirkan masalah benartidaknya pengimplementasian – apakah sudah sesuai dengan ketentuan dari ADT ataukah belum – bukan pada bagaimana kodenya akan digunakan. Sebaliknya, sang klien mengasumsikan bahwa implementasi yang diterapkan untuk suatu ADT adalah sudah benar sehingga tidak perlu lagi mengkhawatirkan detailnya. Ketika Anda sedang menggunakan salah satu kelas bawaan Java, Anda memiliki kemewahan untuk berpikir secara eksklusif sebagai seorang klien. Ketika Anda mengimplentasikan sebuah ADT, di sisi yang lain, Anda juga harus menulis kode dari klien untuk mengujinya. Untuk kasus ini, Anda sebaiknya berhati hati terhadap aturan yang akan Anda terapkan untuk instan yang akan Anda gunakan. Di bagian selanjutnya, kita akan menukar gears dan melihat salah satu cara untuk mengimplementasikan stack ADT yakni dengan bantuan array. Sekarang mulailah berpikir laksana seorang provider.
15.12 Implementasi Array untuk Stack ADT Variabelvariabel instan untuk mengimplementasikan kasus ini adalah sebuah array yang berisi sekumpulan Object. Array ini sendiri nantinya akan berisi itemitem yang berada dalam stack dan sebuah indeks, berupa integer, yang akan memberi tahu kita 327
tentang sisa spasi yang ada dalam array. Pada mulanya, array ini kosong. Sementara indeksnya diset dengan nilai 0. Untuk menambahkan sebuah elemen ke dalam stack (push), kita akan menyalin acuan yang mengacu kepadanya ke dalam stack lalu menaikkan indeksnya satu per satu (increment). Untuk mengambil sebuah elemen (pop), kita harus mendecrement indeks terlebih dahulu baru mengeluarkannya. Berikut ini definisi kelasnya: public class Stack { Object[] array; int index; public Stack () { this.array = new Object[128]; this.index = 0; } } Seperti biasa, jika kita sudah menuliskan variabelvariabel instannya, maka otomatis kita juga akan menuliskan konstruktor di dalamnya. Untuk sekarang, jumlah elemen yang akan kita gunakan adalah 128 item. Nanti, kita akan mempelajari alternatif lain yang lebih baik untuk menangani kasus semacam ini. Memeriksa kosongtidaknya stack bisa dilakukan dengan cara yang sederhana. public boolean isEmpty () { return index == 0; } Meskipun sederhana, penting untuk diingat bahwa jumlah elemen di dalam stack tidak sama dengan ukuran array. Pada awalnya, ukuran array adalah 128 tetapi jumlah 328
elemennya sama dengan 0. Implementasi untuk push dan pop bisa dilakukan dengan cara seperti berikut. public void push (Object item) { array[index] = item; index++; } public Object pop () { index; return array[index]; } Untuk menguji kedua metode ini, kita bisa memanfaatkan kode klien yang sebelumnya kita gunakan untuk menguji Stack bawaan. Kemudian, ketimbang menggunakan impelementasi stack milik java.util, program kita justru akan memakai implementasi yang telah kita tulis sebelumnya. Jika segala sesuatunya berjalan sesuai dengan rencana, program harusnya langsung bisa berfungsi tanpa perlu penambahan di sanasini. Dan ingat, salah satu nilai lebih ketika menggunakan ADT adalah karena Anda bisa mengubah suatu implementasi tanpa harus mengubah kode kliennya.
15.13 Mengubah Ukuran Array Sebuah kelemahan dari penggunaan implementasi seperti ini adalah karena ia telah membuat alokasi array secara kaku untuk setiap Stack yang akan dibuat. Jika pengguna memasukkan lebih dari 128 item ke dalam stack, maka program akan mengeluarkan eksepsi yang berbunyi ArrayIndexOutOfBounds.
329
Alternatif untuk kasus ini adalah dengan mengizinkan kode klien untuk menentukan ukuran array tersebut. Cara ini memang menyelesaikan masalahnya, tapi hal ini hanya bisa dilakukan dengan syarat bahwa sang klie harus tahu terlebih dahulu jumlah item yang dibutuhkan. Dan sayangnya, hal ini sepertinya tidak bisa terjadi setiap saat. Berikut ini versi push yang telah diperbaiki: public void push (Object item) { if (full ()) resize (); // di sini kita bisa membuktikan bahwa // indeks < array.length array[index] = item; index++; } Sebelum meletakkan item baru ke dalam array, kita akan memeriksa apakah array sudah penuh? Jika ya, kita panggil metode resize. Setelah pernyataan if, kita akan tahu apakah; 1. memang sudah terdapat ruang yang tersisa dalam array, atau 2. array sudah diubah ukurannya sehingga terdapat ruang yang tersisa di dalamnya. Jika full dan resize sudah benar, maka selanjutnya kita bisa membuktikan bahwa index < array.length, dan oleh karena itu pernyataan berikutnya tidak akan menyebabkan eksepsi lagi. Sekarang, yang harus kita lakuka adalah mengimplementasikan full dan resize. private boolean full () { return index == array.length; } 330
private void resize () { Object[] newArray = new Object[array.length
*
2]; // kita asumsikan bahwa array yang lama sudah penuh for (int i=0; i<array.length; i++) { newArray[i] = array[i]; } array = newArray; }
Kedua metode dideklarasikan secara private, ini berarti keduanya tidak bisa dipanggil dari kelas yang lain. Bisanya hanya dari kelas ini. Hal semacam ini bisa diterima dengan alasan karena memang tidak alasan bagi kode klien untuk menggunakan fungsi ini, atau bahkan hanya sekedar ingin menggunakannya, karena metode ini memang sengaja dibuat untuk menjadi pembatas antara kode provider dengan kode klien. Implementasi untuk metode full sangat sederhana; metode ini hanya memeriksa apakah indeks telah melampaui ukuran indeks yang telah ditetapkan sebelumnya. Pun begitu dengan implementasi untuk metode resize, hanya dengan asumsi bahwa array yang lama sudah penuh. Dengan kata lain, asumsi tersebut merupakan prasayarat untuk metode ini. Sangat mudah bagi kita untuk melihat bahwa prasyarat ini sudah memenuhi syarat karena satusatunya cara untuk memanggil resize adalah jika full bernilai true. Nilai ini hanya bisa terjadi jika idex == array.length. Di akhir metode resize, kita mengganti array yang lama dengan array yang baru
331
(menyebabkan array lama berstatus “garbage collected”). array.length yang baru berukuran dua kali lebih besar ketimbang array yang lama, sementara index belum berubah sehingga pernyataan index < array.length pastilah bernilai benar. Faktafakta di atas merupakan postcondition untuk resize; sesuatu yang harus bernilai true ketika metode telah selesai dieksekusi (selama prasyaratnya juga sudah dipenuhi). Prasyarat, postcondition, dan invariant merupakan tools yang sangat berguna untuk menganalisa program sekaligus menunjukkan tingkat ketepatan (correctness) yang terkandung di dalamnya. Dalam contoh ini saya telah menunjukkan sebuah teknik pemrograman yang memfasilitasi analisis program sekaligus sebuah teknik pendokumentasian yang bisa membantu peningkatan mutu keakuratannya.
15.14 Daftar KataKata Istilah
Arti Sebuah tipe data (umumnya merupakan kumpulan dari
ADT
objek) yang didefinisikan dengan sekumpulan operasi, tapi yang bisa diimplementasikan dengan berbagai cara.
client provider
Sebuah program yang menggunakan ADT (atau sebutan untuk orang yang menulis programnya). Kode yang mengimplmentasikan suatu ADT (atau sebutan untuk orang yang menulisnya). Salah satu kelas dalam Java, seperti Double dan Integer
wrapper classes
yang memungkinkan objekobjek mengandung tipetipe primitif, dan metodemetode yang beroperasi di atas primitif.
private
Kata kunci dalam Java yang mengindikasikan bahwa sebuah metode atau variabel instan tidak bisa diakses dari luar 332
definisi kelasnya sendiri. Suatu cara dalam menulis ekspresiekspresi matematika infix
dengan posisi operator yang berada di antara operan operannya. Suatu cara dalam menulis ekspresiekspresi matematika
postfix
dengan posisi operator yang berada di belakang operan operannya. Membaca sebuah string yang terdiri dari karakterkarakter
parse
atau tokentoken untuk menganalisa struktur tata bahasanya (grammatical structure). Sekumpulan karakterkarakter yang diperlakukan sebagai
token
sebuah unit untuk tujuan parsing, seperti kata laiknya dalam bahasa alamiah.
delimiter predicate
Sebuah karakter yang digunakan untuk memisahkan token token, seperti tanda baca laiknya dalam bahasa alamiah. Sebuah pernyataan matematis yang bisa bernilai benar atau salah. Sebuah predikat yang harus bernilai benar di akhir sebuah
postcondition
metode (dengan catatan bahwa prasyaratnya juga bernilai benar di awal metode).
15.15 Latihan Latihan 15.1 Tulislah metode reverse yang mengambil sebuah array berisi integer, menelusuri array itu, memasukkan itemitem ke dalam stack, lalu mengambil itemitem itu dari stack,
333
meletakkan kembali ke dalam array dalam posisi yang terbalik dari keadaan sebelumnya. Tujuan latihan ini adalah untuk mempraktikkan mekanisme pembuatan objekobjek pembungkus, memasukkan dan mengeluarkan objekobjek, dan mengtypecast objek objek generik ke bentuk yang lebih spesifik. Latihan 15.2 Latihan ini berdasarkan solusi dari latihan 14.1. mulailah dengan membuat salinan impelemntasi Anda dari metode IntList yang disebut LinkedList. a) Ubahlah implementasi linked list ke dalam sebuah list yang generik dengan membuat kargo untuk sebuah Object ketimbang untuk sebuah integer. Modifikasilah kode pengujinya sesuai dengan kondisi sekarang lalu jalankan programnya. b) Tulislah sebuah metode LinkedList dengan nama split yang menggunakan sebuah String, memecahnya menjadi katakata (menggunakan spasi debagai delimiter), lalu mengembalikan sebuah list yang berisi String, dengan satu kata untuk setiap node dalam list tersebut. Implementasi Anda haruslah efisien, ini berarti bahwa waktu yang dihabiskan harus sesuai dengan jumlah kata yang ada dalam string. c) Tulislah sebuah metode LinkedList dengan nama join yang mengembalikan sebuah String yang berisi representasi String dari setiap objek yang berada dalam list, terurut sesuai dengan urutan kemunculannya, dengan sebuah spasi yang menjadi pemisah. d) Tulislah metode toString untuk LinkedList. Latihan 15.3 Tulislah sebuah implementasi dari Stack ADT menggunakan implementasi LinkedList milik Anda sendiri sebagai struktur data utamanya. Ada dua pendekatan umum untuk kasus ini: stack itu kemungkinan akan berisi sebuah LinkedList sebagai sebuah variabel instan, atau kelas Stack mungkin akan memperluas kelas LinkedList itu 334
sendiri. Pilihlah yang kedengarannya lebih baik untuk Anda, atau, jika Anda memang benarbenar seorang yang berambisi, impelementasikan saja keduanya lalu buatlah perbandingan antara keduanya. Latihan 15.4 Tulislah program Balance.java yang bisa membaca sebuah file dan memeriksa bahwa tanda kurung, (), dan kurung kotak, [], dan kurung kurawal, {}, telah ditulis dengan seimbang dan diletakkan di tempat yang benar. Petunjuk: Lihat bagian C untuk kode yang mampu membaca barisbaris dalam suatu file. Latihan 15.4 Tulislah metode evalPostfix yang menggunakan sebuah String berisi ekspresi postfix dan mampu mengembalikan nilai bertipe double yang mengandung hasil perhitungannya. Anda bisa menggunakan StringTokenizer untuk menguraikan String dan sebuah Stack berisi Double untuk mengevaluasi ekspresi tersebut. Langkahlangkah berikut ini adalah saran saya untuk pengembangan program milik Anda. a) Tulislah sebuah program yang bisa meminta user untuk memasukkan string sekaligus mencetaknya, berulangulang, sampai si pengguna mengetikkan kata “quit”. Lihat bagian C untuk informasi mengenai cara memasukkan data melalui keyboard. Anda bisa melihat kode berikut ini sebagai permulaan: public static void inputLoop () throws IOException { BufferedReader stdin = new BufferedReader (new
InputStreamReader (System.in));
while (true) { System.out.print ("=>"); // Cetak hasil masukan 335
String s = stdin.readLine(); // ambil masukan if (s == null) break; // periksa apakah s sama dengan "quit" // cetak s } } b) Identifikasikan helper methods yang menurut Anda bisa dimanfaatkan, lalu tulis dan debug mereka dalam lingkungan yang terisolir. Saran: isOperator, isOperand, parseExpression, performOperation. c) Kita tahu bahwa kita ingin menambahkan nilai int ke dalam stack lalu mengambilnya kembali, dengan kata lain, kita akan membutuhkan sebuah kelas pembungkus (wrapper classes). Pastikan Anda tahu bagaimana cara melakukannya lalu ujilah operasioperasi tersebut dalam lingkungan yang terisolir. Mungkin membuat mereka menjadi helper methods. d) Tulislah versi evaluate yang hanya menangani satu jenis operator saja (seperti penjumlahan). Ujilah dalam lingkungan yang terisolir. e) Hubungkan evaluator Anda dengan perulangan input/output Anda sendiri. f) Tambahkan operasioperasi lainnya. g) Ketika Anda tahu bahwa kode ini bekerja, Anda mungkin ingin mengevaluasi struktur rancangannya. Bagaimana cara Anda membagi kode ini ke dalam kelas kelas? Variabel instan apa yang harus dimiliki oleh kelaskelas tersebut? Parameter parameter apa yang harus dilewatkan? h) Agar rancangan Anda tambah elegan, Anda juga harus bisa membuat kode Anda bersifat bulletproof, maksudnya adalah agar metode ini tidak menyebabkan terjadinya eksepsi di bawah kondisi apapun, bahkan ketika si pengguna mengetikkan sesuatu yang aneh misalnya. 336
Bab 16
Antrian dan Antrian Prioritas
Bab ini akan menyajikan dua ADT sekaligus: antrian dan antrian prioritas. Dalam kehidupan seharihari, sesuatu yang disebut dengan antrian adalah sederetan pelanggan yang menunggu untuk suatu pelayanan tertentu. Dalam banyak kasus, pelanggan pertama yang berada dalam barisan biasanya akan dilayani terlebih dahulu. Tetapi tunggu dulu, karena ternyata tidak semua kasus serta merta seperti itu. Sebagai contoh, di bandara, calon penumpang yang pesawatnya akan terbang sesaat lagi akan langsung ditaruh di pertengahan antrian. Begitu juga di supermarket, seorang pelanggan yang baik hati biasanya mendahulukan pelanggan lain yang belanjaannya lebih sedikit. Aturan untuk menentukan siapa yang harus dilayani berikutnya disebut queueing discipline (disiplin antrian). Aturan yang paling sederhana adalah FIFO, atau “First In First Out”. Aturan yang paling umum digunakan disebut priority queueing, di sini masingmasing pelanggan akan diberi tingkat prioritas, pelanggan yang memiliki prioritas paling tinggi akan didahulukan, tanpa melihat kapan si pelanggan itu datang. Alasan yang membuat saya menyebutnya sebagai aturan yang paling umum adalah karena prioritas itu sendiri bisa berdasarkan pada apa pun yang memungkinkan: kapan sebuah penerbangan akan dilaksanakan, berapa banyak barang yang dimiliki oleh pelanggan, atau seberapa penting si pelanggan tersebut jika dibandingkan pelanggan lainnya. Tapi tentu, tidak semua aturan antrian bersifat “adil”, toh bukankah keadilan itu sendiri sejatinya berada dalam persepsi masingmasing individu. ADT Antrian (The Queue ADT) dan ADT Antrian Prioritas (The Priority Queue ADT)
337
mempunyai sekumpulan operasi dan antarmuka yang sama. Perbedaannya terletak di dalam semantik operasioperasinya: satu Antrian menggunakan kebijakan FIFO, sementara Antrian Prioritas (seperti namanya) menggunakan kebijakan pemrioritasan dalam antrian. Laiknya sebagian besar ADT, ada beberapa cara untuk mengimplementasikan antrian. Karena antrian sendiri merupakan kumpulan item, kita bisa menggunakan mekanisme dasar manapun untuk menyimpan koleksikoleksi tersebut, termasuk array dan list. Di sini kita akan memilih teknik yang unjuk kerjanya paling bagus – berapa lama waktu yang dibutuhkannya untuk melaksanakan operasioperasi yang kita inginkan – dan tentu saja dalam hal kemudahan penggunaannya.
16.1 ADT Antrian ADT antrian akan didefinisikan oleh operasioperasi berikut ini: constructor: membuat satu antrian baru yang kosong. add: menambahkan satu item baru ke dalam antrian. remove: menghapus dan mengembalikan satu item dari antrian. Item yang dikem balikan pertama kali adalah item yang pertama kali ditambahkan. isEmpty: memeriksa apakah suatu antrian dalam keadaan kosong ataukah tidak. Berikut ini adalah implementasi dari antrian secara umum, berdasarkan kelas bawaan java.util.LinkedList: public class Queue { private LinkedList list; public Queue () { list = new LinkedList (); } 338
public boolean isEmpty () { return list.isEmpty (); } public void add (Object obj) { list.addLast (obj); } public Object remove () { return list.removeFirst (); } } Satu objek berisi satu variabel instan, yakni list yang mengimplementasikannya. Untuk metodemetode lainnya, yang harus kita lakukan hanyalah memanggil salah satu metode dari kelas LinkedList. Kita bisa menulis implementasi yang sama sedikit lebih jelas dengan memanfaatkan fitur inheritance: public class Queue extends LinkedList { public Object remove () { return removeFirst (); } }
Oke, kelihatannya sudah lebih jelas keltimbang sebelumnya! Karena Queue merupakan perluasan (anak) dari LinkedList, kita akan mewarisi konstruktornya, yakni isEmpty 339
dan add. Kita juga mewarisi remove, tetapi sayang versi remove yang kita dapat dari kelas LinkedList tidak melakukan apa yang kita maksudkan; metode ini justru menghapus elemen terakhir dalam list (daftar), bukan yang pertama. Kita akan mengatasinya dengan membuat remove versi baru yang akan mengoverride versi yang kita warisi. Pilihan yang akan kita ambil bergantung pada beberapa faktor. Pewarisan memang dapat menyebabkan suatu implementasi menjadi lebih jelas, selama masih ada metode metode dalam kelas induk yang bisa dimanfaatkan. Tetapi, fitur ini juga bisa membuat suatu implementasi justru lebih sulit untuk dibaca dan didebug. Hal ini terjadi karena metodemetode untuk kelas yang baru tersebut tidak berada di tempat yang sama. Selain itu, fitur ini juga bisa menyebabkan munculnya perilaku yang mungkin tidak Anda harapkan. Ini terjadi karena kelas yang baru akan mewarisi semua metode dari kelas induk, tidak hanya yang Anda butuhkan. Keadaan seperti ini akan mengakibatkan kelas Queue versi kedua juga memiliki metodemetode seperti removeLast dan clear. Meskipun keduanya bukan merupakan bagian dari ADT Queue. Implementasi pertama lebih aman; dengan mendeklarasikkan satu variabel instan yang bersifat private, kita bisa mencegah klien yang ingin memanggil metodemetode yang berada dalam LinkedList.
16.2 Veneer Dengan menggunakan sebuah LinkedList untuk mengimplementasikan satu Antrian, kita bisa mengambil manfaat dari kode yang sudah ada; kode yang baru saja kita tulis hanya menerjemahkan metodemetode LinkedList ke dalam metodemetode Queue. Implementasi seperti ini disebut veneer (kayu pelapis nan tipis). Dalam kehidupan di dunia nyata, veneer adalah sebuah lapisan tipis yang terbuat dari kayu berkualitas baik 340
yang digunakan dalam pembuatan mebel untuk melapis kayu berkualitas kurang baik di bawahnya. Ilmuwan komputer menggunakan metafora ini untuk mendeskripsikan sebuah kode kecil yang menyembunyikan detail suatu implementasi sekaligus menyediakan antarmuka yang lebih sederhana dan terstandardisasi. Contoh kasus tentang Antrian akan mendemonstrasikan salah satu hal menarik mengenai veneer, yakni kemudahan dalam implementasi, dan salah satu bahaya ketika menggunakan veneer adalah karena performace hazard! Normalnya, ketika kita memanggil sebuah metode, sebenarnya pada saat itu kita tidak terlalu peduli mengenai detail implementasinya. Tapi ada satu “detail” yang mungkin ingin kita ketahui – karakter unjuk kerja dari suatu metode. Berapa lama waktu yang diperlukan oleh metode, sebagai sebuah fungsi dari angka itemitem yang berada dalam list (daftar)? Untuk menjawab pertanyaan di atas, kita harus mengetahui lebih banyak hal mengenai implementasi. Jika kita mengasumsikan bahwa LinkedList benarbenar di implementasikan sebagai sebuah linked list (daftar yang saling terkait), maka im plementasi dari metode removeFirst mungkin akan kelihatan seperti ini: public Object removeFirst () { Object result = head; if (head != null) { head = head.next; } return result.cargo; } Kita mengasumsikan bahwa head mengacu ke node pertama dalam list, dan bahwa setiap nodenya berisi kargo serta sebuah acuan yang mengarah ke node berikutnya 341
dalam list. Tidak ada pemanggilan perulangan atau fungsi di sini. Akibatnya, runtime metode ini hampir selalu sama setiap waktu. Metode seperti ini disebut dengan constant time operation. Dalam realita, metode ini mungkin akan terlihat lebih cepat ketika list sedang kosong. Hal ini terjadi karena metode akan melompati bagian badan (body) dari bagian persyaratan (conditional), tapi perbedaannya tidak terlalu signifikan. Unjuk kerja addLast sangat berbeda. Berikut ini adalah sebuah implementasi yang bersifat hipotesis: public void addLast (Object obj) { // special case: list kosong if (head == null) { head = new Node (obj, null); return; } Node last; for (last = head; last.next != null; last = las t.next) {
// telusuri
list untuK menemukan
node
terakhir } last.next = new Node (obj, null); } Syarat pertama menangani kasus khusus, yakni penambahan sebuah node baru ke dalam list kosong. Dalam kasus ini, lagilagi, run time tidak bergantung kepada panjang list. Meskipun begitu, dalam kasus yang lebih umum, kita harus menelusuri list untuk 342
menemukan elemen terakhir agar kita bisa membuatnya mengacu ke node yang baru. Penelusuran ini menghabiskan waktu yang sesuai dengan panjang list. Karena run time merupakan fungsi linear dari panjang, kita bisa mengatakan bahwa metode ini termasuk linear time. Dibandingkan dengan constant time, linear time bisa dikategorikan sebagai yang buruk sekali.
16.3 Linked Queue Kita menginginkan sebuah implementasi dari ADT Antrian yang bisa melakukan semua operasi dalam waktu yang konstan. Salah satu cara untuk menyelesaikan masalah ini adalah dengan mengimplementasikan linked queue. Cara ini bisa dibilang sama dengan linked list karena ia terdiri dari objekobjek Node yang saling terhubung, mulai dari yang berjumlah 0 (tanpa Node) atau lebih dari itu. Perbedaannya adalah bahwa suatu antrian memelihara acuan yang berasal dari node pertama dan terakhir, seperti tampak dalam gambar.
Inilah tampilan implementasi linked Queue: public class Queue { public Node first, last; public Queue () { first = null; last = null;
343
} public boolean isEmpty () { return first == null; } }
Sampai sejauh ini, tampaknya implementasi ini masih biasabiasa saja. Di dalam antrian yang kosong, baik first maupun last diisi dengan null. Untuk memeriksa apakah suatu list sedang kosong ataukah tidak, kita hanya perlu memeriksa salah satu diantaranya. Implementasi metode add tampaknya lebih rumit karena kita harus berurusan dengan beberapa kasus khusus. public void add (Object obj) { Node node = new Node (obj, null); if (last != null) { last.next = node; } last = node; if (first == null) { first = last; } }
Syarat pertama memeriksa dan memastikan bahwa last sudah mengacu ke sebuah node; jika ya, maka kita harus membuatnya mengacu ke node yang baru. Syarat yang kedua berurusan dengan kasus khusus di mana list pada awalnya memang berstatus kosong. Dalam kasus ini, baik first maupun last mengacu ke node yang baru. Metode remove juga berurusan dengan beberapa kasus khusus. 344
public Object remove () { Node result = first; if (first != null) { first = first.next; } if (first == null) { last = null; } return result; } Syarat pertama memeriksa apakah ada node di dalam antrian. Jika ya, kita harus menyalin node next ke first. Syarat kedua terkait dengan kasus khusus bahwa list pada saat yang bersamaan sedang kosong, dalam hal ini kita harus membuat last menjadi null. Untuk latihan, buatlah diagramdiagram yang menunjukkan operasioperasi ini, baik dalam kasus normal maupun kasus khusus, dan yakinkan diri Anda sendiri bahwa diagramdiagram tersebut memang benar adanya. Jelas sekali, implementasi ini tentu lebih rumit ketimbang implementasi veneer, dan lebih sulit lagi untuk membuktikan bahwa implementasi ini memang benar. Keuntungannya adalah karena kita telah berhasil mencapai tujuan yang kita inginkan: add dan remove adalah metode yang bersifat constant time operation.
16.4 Circular Buffer Implementasi umum lainnya dari antrian adalah circular buffer. “Buffer” merupakan nama umum untuk menyebutkan sebuah lokasi penyimpanan yang sifatnya
345
sementara/temporer, meskipun ia juga sering digunakan untuk menyebutkan suatu array, seperti yang terjadi dalam kasus ini. Sekarang Anda tentu akan lebih paham dengan apa yang dimaksud dengan kata “circular” di sini. Implementasi dari circular buffer sama dengan implementasi array untuk stack di subbab 15.12. Itemitem antrian disimpan di dalam sebuah array, dan kita menggunakan indeks untuk mengetahui posisi kita di dalam array. Dalam implementasi stack, ada sebuah indeks tunggal yang menunjuk ke ruang (space) berikutnya yang masih kosong. Dalam implementasi antrian, terdapat dua indeks: first menunjuk ke ruang dalam array yang berisi pelanggan pertama dalam barisan sementara next menunjuk ke ruang berikutnya yang masih kosong. Gambar berikut ini menunjukkan sebuah queue dengan dua item (direpresentasikan dengan titiktitik).
Ada dua cara untuk memikirkan variabelvariabel untuk first dan last. Secara literal, keduanya bertipe integer, dan nilai mereka ditunjukkan dalam kotak di sebelah kanan. Walaupun begitu, secara abstrak, mereka merupakan indeks dari sebuah array. Oleh karena itulah, mereka sering digambarkan sebagai sebuah anak panah yang menunjuk ke lokasi dalam array. Representasi anak panah sebenarnya sangat bagus, tapi Anda harus ingat bahwa indeksindeks tersebut bukanlah acuan; mereka hanyalah integer. Berikut ini merupakan implementasi array dari sebuah antrian yang belum lengkap: public class Queue { public Object[] array; public int first, next; public Queue () { array = new Object[128]; first = 0; 346
next = 0; } public boolean isEmpty () { return first == next; } Variabelvariabel instan dan konstruktornya tampak normal dan biasabiasa saja, tapi kita lagilagi menjumpai masalah ketika dihadapkan dengan ukuran yang harus digunakan untuk array. Nantinya kita akan menyelesaikan masalah ini, sebagaimana yang telah kita lakkan dengan stack, dengan cara mengubah ukuran arraynya ketika array tersebut sudah terisi penuh. Implementasi isEmpty terkesan agak mengejutkan. Anda mungkin harus berpikir bahwa first == 0 akan mengindikasikan sebuah antrian kosong, tapi itu menge yampingkan fakta bahwa kepala antrian tidaklah harus berada di depan array. Se baliknya, kita tahu bahwa antrian dikatakan kosong jika head sama dengan next, di mana dalam kasus ini berarti sudah tidak ada lagi itemitem yang tersisa. Sekali saja kita melihat implementasi add dan remove, kondisi tersebut akan lebih masuk akal. public void add (Object item) { array[next] = item; next++; } public Object remove () { Object result = array[first]; first++; return result; } 347
Metode add terlihat seperti push di subbab 15.12; ia meletakkan item baru ke dalam ruang kosong di bagian berikutnya lalu menaikkan nilai indeksnya. Implementasi remove sama saja. Metode ini mengambil item pertama dari antrian lalu menaikkan first sehingga ia mengacu ke kepala baru milik antrian. Gambar berikut ini menunjukkan seperti apa tampilan antrian ketika kedua item sudah dihapus.
Adalah selalu benar bahwa next menunjuk ke ruang yang masih kosong. Jika first telah melebihi next dan menunjuk ke ruang yang sama, maka first sedang mengacu ke lokasi yang “kosong”. Saya memberi tanda kutip pada kata kosong karena ada kemungkinan bahwa lokasi yang ditunjuk oleh first sebenarnya memiliki suatu nilai (kita tidak melakukan apapun untuk memastikan apakah lokasi yang kosong berisi null); di sisi lain, karena kita sudah mengetahui kalau antrian itu memang kosong; kita tidak akan pernah membaca lokasi ini, jadi kita bisa menganggapnya, secara abstrak, sebagai sesuatu yang kosong. Latihan 16.1 Modifikasilah remove sehingga ia bisa mengembalikan null jika suatu antrian sedang kosong. Masalah berikutnya dari implementasi ini adalah karena ia akan menghabiskan banyak ruang. Ketika kita menambahkan sebuah item, kita juga menaikkan nilai next dan ketika 348
kita kita menghapus sebuah item kita menaikkan first, tapi sayangnya kita tidak menurunkan nilai keduanya. Apa yang akan terjadi jika kita sudah sampai di ujung array? Gambar berikut ini menunjukkan keadaan antrian setelah kita menambahkan empat item lagi:
arraynya sekarang telah penuh. Tidak ada lagi “ruang kosong berikutnya”. Jadi sekarang next tidak bisa menunjuk ke manamana lagi. Satu kemungkinan adalah kita bisa mengubah ukuran array, seperti yang telah kita lakukan dengan impelementasi untuk stack. Tapi di kasus itu, arraynya akan terus membesar tanpa peduli dengan berapa banyak jumlah item yang sedang berada di dalam antrian. Solusi yang lebih baik adalah dengan melakukan “wrap around” (baca: putaran) ke awal array lalu menggunakan kembali ruangruang yang masih ada di sana. “wrap around” seperti ini adalah alasan kenapa implementasi ini disebut dengan “circular buffer”. Salah satu cara untuk memutar indeks adalah dengan menambahkan sebuah kasus khusus dimanapun kita menaikkan nilai sebuah indeks: next++; if (next == array.length) next = 0; Alternatif yang lebih cantik adalah dengan menggunakan operator modulus: next = (next + 1) % array.length; 349
Sampai di sini kita masih memiliki satu masalah terakhir yang harus diselesaikan. Bagaimana kita bisa tahu kalau antrian benarbenar sudah penuh, maksudnya; kita sudah tidak bisa lagi menambahkan item lainnya? Gambar berikut ini menunjukkan tampilan antrian ketika sedang berstatus “penuh”.
Masih tersisa satu ruang kosong di dalam array, tapi antrian itu sudah penuh karena jika kita menambahkan item lainnya, maka kita harus menaikkan next sedemikian rupa sehingga next == first, dan dalam kasus ini, antrian akan kelihatan sedang berada dalam status kosong! Untuk menghindari hal kondisi seperti di atas, kita akan mengorbankan satu ruag dalam array. Jadi bagaimana kita bisa bilang kalau antrian itu sudah penuh? if ((next + 1) % array.length == first) Dan apa yang harus kita lakukan jika arraynya penuh? Dalam kasus ini, mengubah ukuran array mungkin adalah satusatunya cara. Latihan 16.2 Tulislah sebuah impelementasi antrian menggunakan circular buffer yang bisa mengubah ukurannya sendiri ketika sedang perlu.
350
16.5 Priority Queue ADT Antrian Prioritas mempunyai antarmuka yang sama seperti ADT Antrian, tapi semantiknya berbeda. Antarmukanya adalah sebagai berikut: constructor: membuat sebuah antrian yang baru sekaligus kosong. Add: menambahkan sebuah item baru ke dalam antrian. remove: menghapus dan mengembalikan sebuah item dari antrian. Item yang dikembalikan adalah item yang mempunyai prioritas tertinggi. isEmpty: memeriksa apakah antriannya kosong ataukah tidak. Yang dimaksud dengan perbedaan semantik adalah bahwa item yang telah dihapus dari antrian tidaklah harus berupa item yang pertama kali dimasukkan/ ditambahkan. Sebaliknya, item tersebut harus berstatus sebagai item dengan prioritas tertinggi. Apa yang dimaksud dengan prioritas, dan bagaimana mereka dibandingkan dengan satu sama lainnya tidak ditentukan oleh implementasi Antrian Prioritas. Semuanya tergantung dari itemitem apa yang berada di dalam antrian tersebut. Sebagai contoh, jika itemitem di dalam antrean mempunyai nama, kita mungkin akan memilih mereka secara sesuai urutan alfabet. Jika mereka merupakan skor permainan bowling, kita mungkin memilih dari urutan tertinggi ke urutan terendah, tapi jika mereka itu skor permainan golf, mungkin kita akan memprioritaskan angka/item terendah ke yang tertinggi. Jadi sekarang kita mempunyai masalah baru. Kita menginginkan sebuah implementasi Antrean Prioritas yang sifatnya generik – harus bisa bekerja dengan objek apapun – tapi pada saat yang bersamaan, kode yang mengimplementasikan Antrian Prioritas harus memiliki kemampuan untuk membandingkan objekobjek yang dikandungnya.
351
Kita sudah melihat satu cara untuk mengimplementasikan struktur data generik menggunakan Object, tapi itu tidak menyelesaikan masalah ini, karena tidak ada cara untuk membandingkan Object kecuali kita sudah tahu objek itu bertipe apa. Jawabannya terdapat dalam sebuah fitur Java yang disebut metaclass.
16.6 Metaclass Metaclass adalah suatu kumpulan kelas yang menyediakan satu kumpulan metode. Definisi metaclass mengharuskan sebuah kelas untuk memenuhi persyaratan yang sudah ditentukan agar bisa menjadi anggota dari suatu kumpulan. Terkadang metaclassmetaclass memiliki nama yang diakhiri dengan kata “able” untuk mengindikasikan kemampuan dasar (fundamental) yang dibutuhkan oleh metaclass. Sebagai contoh, semua kelas yang menyediakan metode dengan nama draw bisa menjadi anggota dari metaclass Drawable. Semua kelas yang mengandung metode start bisa menjadi anggota dari metaclass Runnable. Java menyediakan sebuah metaclass bawaan yang bisa kita gunakan dalam sebuah implementasi untuk Antrian Prioritas. Metaclass ini disebut Comparable, dan artinya sesuai dengan namanya. Semua kelas yang menjadi anggota metaclass Comparable harus menyediakan sebuah metode dengan nama compareTo yang mampu membandingkan dua objek lalu mengembalikan sebuah nilai yang mengindikasikan apakah satu argumen lebih besar atau lebih kecil ketimbang argumen lainnya, atau apakah keduanya merupakan dua argumen yang sama. Umumnya kelaskelas bawaan Java merupakan anggota dari metaclass Comparable, termasuk kelaskelas pembungkus numerik seperti Integer dan Double. Di subbab berikutnya saya akan menunjukkan bagaimana caranya menulis sebuah ADT
352
yang bisa memanipulasi sebuah metaclass. Lalu kita akan melihat bagaimana caranya menulis sebuah kelas baru yang akan menjadi anggota dari metaclass yang sudah ada. Di bab berikutnya kita akan melihat bagaimana caranya mendefinisikan sebuah metaclass baru.
16.7 Implementasi Array untuk Antrian Prioritas Dalam implementasi Antrian Prioritas, setiap kali kita menentukan tipe itemitem yang berada dalam antrian, kita menentukan metaclass Comparable. Sebagai contoh, variabelvariabel instan berikut merupakan sebuah array yang berisi Comparable dan sebuah integer: public class PriorityQueue { private Comparable[] array; private int index; }
Seperti biasa, index merupakan indeks dari ruang berikutnya yang masih tersisa/kosong dalam array. Variabelvariabel instan dideklarasikan dengan private agar kelaskelas lainnya tidak bisa mengakses mereka secara langsung. Konstruktor dan isEmpty masih sama dengan yang pernah kita lihat sebelumnya. Ukuran awal dari arraynya silahkan Anda tentukan sendiri. public PriorityQueue () { array = new Comparable [16]; index = 0; } public boolean isEmpty () { return index == 0; 353
} add sama dengan push: public void add (Comparable item) { if (index == array.length) { resize (); } array[index] = item; index++; }
Satusatunya metode penting dalam kelas adalah remove, yang harus menelusuri array untuk menemukan lalu menghapus item terbesar: public Comparable remove () { if (index == 0) return null; int maxIndex
= 0;
// find the index of the item with the highest priority for (int i=1; i
areTo (array[max Index]) > 0) {
maxIndex = i;
} } Comparable result = array[maxInd
ex];
// move the last item into the empty slot index ;
354
array[maxIndex
] = array[index];
return result; }
Ketika kita menelusuri array, maxIndex menjaga jejak elemen terbesar yang sudah kita lihat sejauh ini. Yang dimaksud dengan “terbesar” di sini akan ditentukan oleh metode compareTo. Dalam kasus ini, metode compareTo disediakan oleh kelas Integer, dan ia melakukan apa yang memang kita inginkan – angka terbesar (lebih positif) adalah pemenangnya.
16.8 Klien Antrian Prioritas Implementasi Antrian Prioritas seluruhnya ditulis menggunakan susut pandang objek objek Comparable, tapi tidak ada objek yang bisa disebut sebagai sebuah objek dari metaclass Comparable! Kalau tidak percaya, cobalah untuk membuat satu objek saja: Comparable comp = new Comparable (); // ERROR Anda akan melihat pesan compiletime yang berbunyi “java.lang.Comparable is an interface. It can't be instantiated”. Di Java, metaclassmetaclass juga disebut dengan interface (antarmuka). Sampai sejauh ini saya telah menghindari kata ini karena ia juga memiliki beberapa arti sekaligus, tapi sekarang Anda semua harus tahu. Kenapa metaclassmetaclass itu tidak bisa diinstansiasi? Karena sebuah metaclass hanya menentukan apaapa yang sudah disyaratkan (Anda harus mempunyai metode compareTo); ia sendiri tidak menyediakan suatu implementasi. Untuk membuat sebuah objek Comparable, Anda harus membuat satu objek yang dimiliki oleh satu paket Comparable, seperti Integer. Kemudian barulah Anda bisa menggunakan objek tersebut dimanapun metaclass Comparable dipanggil. PriorityQueue pq = new PriorityQueue (); 355
Integer item = new Integer (17); pq.add (item); Kode ini akan membuat satu Antrian Prioritas yang baru sekaligus kosong, juga sebuah objek Integer. Kemudian kode ini akan menambahkan Integer ke dalam antrian. Metode add mengharapkan sebuah Comparable sebagai sebuah parameter, jadi dia dengan senang hati menerima sebuah Integer. Jika kita mencoba untuk melewatkan sebuah Rectangle yang bukan merupakan anggota Comparable, kita akan mendapatkan sebuah pesan compiletime yang berbunyi, “Incompatible type for method. Explicit cast needed to convert java.awt.Rectangle to java.lang.Comparable”. Itulah yang dikatakan oleh compiler kepada kita. Jadi, jika kita ingin membuat konversi itu terjadi, maka kita harus melakukannya secara eksplisit. Kita mungkin akan melakukan apa yang dikatakan oleh compiler tersebut: Rectangle rect = new Rectangle (); pq.add ((Comparable) rect);
Tetapi di dalam kasus seperti ini kita justru menerima pesan error yang terkait dengan ClassCastException. Ketika Rectangle mencoba untuk melewatkan sebagai sebuah Comparable, sistem runtime akan memeriksa apakah cara ini memenuhi semua persyaratan, lalu menolaknya. Jadi, itulah yang akan kita dapatkan jika kita mengikuti saran dari compiler. Untuk mengeluarkan itemitem dari antrian, kita harus membalik prosesnya: while (!pq.isEmpty ()) { item = (Integer) pq.remove (); System.out.println (item); } 356
Perulangan ini menghapus semua item dari antrian lalu mencetaknya. Proses ini mengasumsikan bahwa itemitem yang berada di dalam antrian merupakan Integer. Karena jika mereka bukan
Integer, kita akan mendapatkan sebuah
ClassCastException.
16.9 Kelas Golfer Akhirnya, mari kita lihat bagaimana caranya membuat sebuah kelas baru yang akan menjadi anggota Comparable. Sebagai sebuah contoh atas sesuatu dengan definisi yang tidak biasa tentang prioritas “tertinggi” kita akan menggunakan para pemain golf (golfers): public class Golfer implements Comparable { String name; int score; public Golfer (String name, int score) { this.name = name; this.score = score; } } Definisi kelas dan konstruktor selalu hampir sama dengan yang sudah kita buat sebelumnya; bedanya adalah karena untuk saat ini kita harus bisa membuat deklarasi agar kelas Golfer bisa mengimplementasikan metaclass Comparable (Golfer implement Comparable). Dalam kasus ini kata kunci implement berarti bahwa Golfer mengimplementasikan antarmuka yang ditentukan oleh Comparable.
357
Jika sekarang kita mencoba untuk mengcompile Golfer.java, kita akan mendapatkan pesan yang berbunyi “class Golfer must be declared abstract. It doesn't define int compareTo (java.lang.Object) from interface java.lang. Comparable”. Dengan kata lain, agar bisa menjadi Comparable, Golfer harus menyediakan sebuah metode dengan nama compareTo. Oke, kalau begitu, mari kita tulis sekarang juga: public int compareTo (Object obj) { Golfer that = (Golfer) obj; int a = this.score; int b = that.score; // dalam golf, yang kecil itu lebih baik if (ab) return 1; return 0; } Dua hal yang ada di sini membuat kita sedikit terkejut. Satu, parameternya merupakan sebuah Object. Ini karena secara umum si pemanggil tidak mengetahui objek bertipe apa yang sedang dibandingkan. Sebagai contoh, dalam PriorityQueue.java, ketika kita memanggil compareTo, kita juga melewatkan sebuah Comparable sebagai parameter. Kita tidak perlu tahu apakah ia termasuk Integer, Golfer, atau apapun itu. Di dalam compareTo, kita harus mengubah parameter dari sebuah Object menjadi Golfer. Seperti biasa, akan ada risiko ketika kita menggunakan tipe cast seperti ini: jika kita mengcast ke tipe yang salah maka kita akan mendapatkan pesan eksepsi. Akhirnya, kita bisa membuat beberapa pemain golf:
358
Golfer tiger = new Golfer ("Tiger Woods", 61); Golfer phil = new Golfer ("Phil Mickelson", 72); Golfer hal = new Golfer ("Hal Sutton", 69); Lalu memasukkan mereka ke dalam antrian: pq.add (tiger); pq.add (phil); pq.add (hal); Ketika kita mengeluarkan mereka: while (!pq.isEmpty ()) { golfer = (Golfer) pq.remove (); System.out.println (golfer); } Mereka akan ditampilkan dalam urutan terbalik (khusus untuk pemain golf): Tiger Woods
61
Hal Sutton
69
Phil Mickelson
72
Ketika kita beralih dari Integer ke Golfer, kita tidak perlu membuat perubahan apapun dalam PriorityQueue.java. Sama sekali tidak! Jadi sekarang kita telah berhasil mengatasi jurang antara PriorityQueue dan kelaskelas yang menggunakannya. Dengan kondisi ini, kita bisa menggunakan ulang kode tersebut tanpa harus mengubahnya. Lebih jauh lagi, kita bisa memberikan kendali kepada kode klien melalui definisi yang sudah kita buat untuk compareTo. Dengan cara seperti ini, implementasi PriorityQueue akan menjadi lebih adaptif. 359
16.10 Daftar KataKata Istilah queue queueing discipline
Arti Sekumpulan objek yang sedang menantikan suatu layanan atau sejenisnya. Aturan yang menentukan anggota antrian mana yang akan dihapus lebih dulu. “first in first out”, sebuah aturan antrian di mana anggota
FIFO
yang pertama kali datang akan menjadi yang pertama kali dihapus. Suatu aturan antrian di mana setiap anggotanya mempunyai
priority queue
prioritas yang ditentukan oleh faktorfaktor eksternal. Anggota yang mempunyai prioritas tertinggi adalah anggota yang pertama kali kita hapus. Sebuah ADT yang mendefinisikan operasioperasi yang
Priority Queue
mungkin akan dilakukan oleh seseorang terkait dengan antrian berprioritas. Suatu definisi kelas yang mengimplementasikan sebuah ADT dengan definisidefinisi metode yang merupakan hasil
veneer
pemanggilan dari metodemetode lainnya, terkadang dengan beberapa perubahan kecil. Veneer tidak mengerjakan fungsi yang signifikan, tapi dia bisa meningkatkan atau menstandardisasi antarmuka yang dilihat oleh klien. Bahaya yang terkait dengan penggunaan veneer ketika
performance hazard
beberapa metode mungkin diimplementasikan secara tidak efisien sehingga metode tersebut justru malah tidak tampak oleh klien. 360
constant time linear time linked queue
Operasi yang memiliki runtime yang tidak bergantung kepada ukuran struktur datanya. Operasi yang runtimenya berupa fungsi linear dari ukuran struktur datanya. Implementasi antrian menggunakan linked list dan acuan ke node pertama dan terakhir. Implementasi antrian menggunakan array dan indeks dari
circular buffer
elemen pertama dan ruang kosong yang ada di sebelahnya (next available space). Kumpulan kelaskelas. Spesifikasi metaclass berisi
metaclass
persyaratanpersyaratan yang harus dipenuhi oleh suatu kelas agar bisa dimasukkan ke dalam kumpulan.
interface
Istilah Java untuk menyebut metaclass. Jangan sampai dibingungkan dengan arti kata interface yang lebih luas.
16.11 Latihan Latihan 16.3 Pertanyaan ini berdasarkan Latihan 9.3. Tulislah sebuah compareTo untuk kelas Rational yang memungkinkan Rational untuk mengimplementasikan Comparable. Petunjuk: jangan lupa bahwa parameter adalah sebuah Object.
Latihan 16.4 Tulislah definisi kelas untuk SortedList yang merupakan perluasan dari kelas LinkedList. SortedList sama dengan LinkedList; bedanya, elemenelemen SortedList 361
harus menjadi anggota Comparable, dan listnya tersusun dalam urutan terbalik. Tulislah sebuah metode objek add untuk SortedList yang menggunakan Comparable sebagai parameter dan mampu menambahkan objek baru ke dalam list, pada lokasi yang tepat sehingga listnya tetap berada dalam posisi terurut. Jika Anda mau, Anda bisa menulis satu metode helper dalam kelas Node.
Latihan 16.5 Tulislah satu metode objek, maximum, untuk kelas LinkedList yang bisa dipanggil melalui objek LinkedList. Metode ini bisa mengembalikan objek kargo terbesar dalam list, atau null jika listnya kosong. Anda bisa mengasumsikan bahwa semua elemen kargo menjadi milik kelas yang menjadi anggota metaclass Comparable, dan setiap dua elemen bisa dibandingkan satu sama lainnya.
Latihan 16.6 Tulislah implementasi untuk Antrian Prioritas menggunakan linked list. Ada tiga tahapan yang harus Anda proses: ●
Antrian Prioritas mungkin berisi sebuah objek LinkedList sebagai sebuah variabel instan.
●
Antrian Prioritas mungkin berisi sebuah acuan ke objek Node pertama dalam linked list.
●
Antrian Prioritas diwarisi dari kelas LinkedList yang sudah ada.
Pikirkan mengenai pro dan kontra dari ketiga tahapan di atas kemudian pilihlah salah satu diantaranya. Selain itu, Anda juga bisa memilih; untuk menjaga list agar tetap terurut (slow add, fast remove), atau tidak terurut (slow remove, fast add). 362
Latihan 16.7 Antrian Kegiatan adalah sebuah struktur data yang menjaga urutan kegiatan, di mana setiap kegiatan mempunyai waktu pelaksanaan yang terkait dengannya. Berikut ini adalah ADTnya: constructor: membuat antrian kegiatan yang baru dan kosong add: menambahkan sebuah kegiatan baru ke dalam antrian. Parameternya adalah kegiatan yang merupakan sebuah Object, dan waktu pelaksanaan kegiatan tersebut yang berupa objek Date. Objek kegiatannya tidak boleh bernilai null. nextTime: mengembalikan Date untuk pelaksanaan kegiatan berikutnya, kegiatan “berikut” di sini adalah kegiatan di dalam antrian yang dimulai lebih dulu. Jangan menghapus kegiatan ini dari antrian. Kembalikan nilai null jika antriannya kosong. nextEvent: mengembalikan kegiatan berikutnya (sebuah Object) dari antrian lalu menghapusnya dari antrian. Kembalikan nilai null jika antriannya kosong. Kelas Date didefinisikan dalam java.util. Kelas ini mengimplementasikan metaclass Comparable. Menurut dokumentasinya, metode compareTonya mengembalikan “the value 0 if the argument Date is equal to this Date; a value less than 0 if this Date is before the Date argument; and a value greater than 0 if this Date is after the Date argument”. Tulislah sebuah implementasi untuk antrian kegiatan menggunakan ADT PriorityQueue. Anda tidak boleh membuat asumsi mengenai bagaimana PriorityQueue diimplementasikan. Hint: buatlah sebuah kelas, Event, yang berisi Date dan sebuah Object kegiatan. Kelas ini harus mengimplementasikan Comparable dengan tepat.
363
Bab 17 Pohon (Trees) 17.1 Simpul Pohon Laiknya list, pohon terdiri dari simpulsimpul. Salah satu pohon yang paling banyak dikenal adalah pohon biner (binary tree). Setiap simpul pohon ini berisi sebuah acuan yang mengacu ke dua simpul lainnya (kemungkinan null). Definisi kelasnya akan tampak sebagai berikut: public class Tree { Object cargo; Tree left, right; } Seperti simpulsimpul dalam list, simpulsimpul pohon juga berisi kargo: dalam hal ini berupa Object yang generik. Variabelvariabel instan lainnya dinamai left dan right, yang sesuai dengan standar mata kita ketika direpresentasikan menggunakan gambar:
Puncak pohon (simpul yang diacu dengan nama tree) disebut akar (root). Agar sesuai 364
dengan istilahistilah dalam dunia perpohonan (baca: metafora pohon), simpulsimpul lain akan disebut cabangcabang (branches). Sementara simpul yang berada di posisi bagian bawah yang mengacu ke null disebut daundaun (leaves). Mungkin di sini kita telah melakukan sesuatu yang ganjil dengan menggambar akar di atas sementara daun daun malah di bawah. Tapi tunggu dulu, itu bukanlah hal teraneh. Untuk membuatnya lebih aneh lagi, ilmuwan komputer mengombinasikannya dengan metafora lain: pohon keluarga (the family tree). Simpul yang berada di atas biasanya disebut parent (induk) sementara simpulsimpul yang diacunya disebut children (anak). Simpulsimpul dengan induk yang sama disebut siblings, begitu juga sebaliknya. Akhirnya, kita juga akan menjumpai kosakata bidang geometri ketika berbicara mengenai pohon. Saya sudah menyebutkan kanan dan kiri, dan sekarang ada lagi “up” (menuju ke induk/akar) dan “down” (menuju ke anak/daun). Selain itu, semua simpul yang mempunyai jarak yang sama dari akar merupakan level dari sebuah pohon. Saya sendiri tidak tahu kenapa kita harus menggunakan tiga metafora sekaligus ketika berbicara tentang pohon, tapi itulah fakta yang ada.
17.2 Membuat Pohon Proses penyusunan simpulsimpul pohon mirip dengan proses ketika kita merangkai list. Kita mempunyai sebuah konstruktor untuk simpulsimpul pohon yang menginisialisasi variabelvariabel instan. public Tree (Object cargo, Tree left, Tree right) { this.cargo = cargo; this.left = left; this.right = right; } 365
Awalnya kita akan mengalokasikan tempat untuk simpulsimpul anak terlebih dahulu: Tree left = new Tree (new Integer(2), null, null); Tree right = new Tree (new Integer(3), null, null); Kita bisa membuat simpul induk lalu mengaitkannya ke simpul anak pada saat yang bersamaan: Tree tree = new Tree (new Integer(1), left, right); Kode ini menghasilkan keadaan seperti yang ditunjukkan pada gambar sebelumnya.
17.3 Menelusuri Pohon Cara paling alami untuk menelusuri sebuah pohon adalah dengan memakai metode yang rekursif. Sebagai contoh, untuk menambahkan semua integer ke dalam satu pohon, kita bisa menulis metode kelas sebagai berikut: public static int total (Tree tree) { if (tree == null) return 0; Integer cargo = (Integer) tree.cargo; return cargo.intValue()
+ total (tree.left)
+
total (tree.right); } Metode di atas merupakan metode kelas karena kita akan menggunakan null untuk merepresentasikan pohon yang kosong. Nantinya, pohon kosong inilah yang akan kita jadikan base case metode rekursif kita. Jika suatu pohon diketahui kosong, metode kita akan mengembalikan nilai 0. Kalau tidak, metode ini akan membuat dua panggilan bersifat rekursif untuk menemukan nilai total dari dua anaknya. Akhirnya, metode ini akan menambahkan nilai total ini ke dalam kargonya lalu mengembalikan nilai total. 366
Meskipun metode ini bekerja dengan baik, ada beberapa kesulitan yang akan menghadang kita ketika kita hendak mengimplementasikannya dengan konsep pemrograman berorientasi objek. Metode ini harusnya tidak boleh muncul dalam kelas Tree karena ia membutuhkan kargo untuk menjadi objekobjek Integer. Jika kita membuat asumsi ini dalam Tree.java maka kita akan kehilangan manfaatmanfaat yang bisa kita ambil dari penggunaan struktur data generik. Di sisi lain, kode ini mengakses variabelvariabel instan simpulsimpul pohon. Jadi dia “mengetahui” lebih banyak ketimbang yang seharusnya mengenai implementasi suatu pohon. Jika kita mengubah implementasi ini, kodenya nanti rusak (baca: tidak bisa dipakai) . Nanti di bagian lain dalam bab ini, kita akan mengembangkan sebuah cara untuk menyelesaikan masalah ini, membolehkan kode klien untuk menelusuri pohon yang berisi objek apapun tanpa merusak perbedaan abstraksi antara kode klien dan implementasinya. Sebelum kita sampai ke sana, mari kita melihat sebuah aplikasi yang memanfaatkan konsep pohon ini.
17.4 Pohon Ekspresi Pohon merupakan cara alami untuk merepresentasikan struktur/susunan suatu ekspresi matematika. Tidak seperti notasi dalam bidang lain, pohon mampu merepresentasikan proses komputasi tanpa harus mendatangkan ambiguitas. Sebagai contoh, ekspresi infix untuk 1 + 2 * 3 termasuk yang mendatangkan ambiguitas, kecuali kalau kita mengetahui bahwa unsur perkalian diproses lebih dahulu ketimbang unsur penjumlahan. Gambar berikut ini menunjukkan komputasi yang sama:
367
Simpulsimpul di atas bisa saja merupakan sebuah operan seperti 1 dan 2 atau berupa operatoroperator seperti + dan *. Operanoperan merupakan simpulsimpul daun; sementara simpulsimpul operator berisi acuan yang mengacu ke operanoperan mereka (semua operator di sini disebut operator biner (binary), karena mereka mempunyai tepat dua operan). Berdasarkan gambar di atas, tidak usah ditanya lagi bagaimana urutan operasi ini terjadi: unsur perkalian dikerjakan terlebih dahulu sebelum proses penjumlahan terjadi. Pohon ekspresi seperti ini mempunyai banyak manfaat. Contoh yang akan kita lihat adalah dalam proses pengubahan (baca: penerjemahan) dari satu format (postfix) ke format lainnya (infix). Pohonpohon yang sama digunakan di dalam compiler untuk melakukan proses penguraian (parse), optimalisasi dan menerjemahkan program.
17.5 Penelusuran Saya sebelumnya sudah menunjukkan bahwa rekursi sebenarnya merupakan cara yang alami untuk menelusuri satu pohon. Kita bisa mencetak isi suatu pohon ekspresi dengan cara seperti ini: 368
public static void print (Tree tree) { if (tree == null) return; System.out.print (tree + " "); print (tree.left); print (tree.right); } Dengan kata lain, untuk mencetak satu pohon, langkah pertama adalah dengan mencetak isi akarnya, lalu mencetak semua subpohon sebelah kiri, baru kemudian mencetak seluruh subpohon yang ada di bagian kanan. Cara penelusuran seperti ini disebut preorder, karena isi dari akar muncul sebelum isi anakanaknya muncul. Untuk ekspresi contoh tadi, outputnya adalah + 1 * 2 3. Hasil ini berbeda dari hasil yang didapat ketika menggunakan postfix atau infix; output ini adalah notasi baru yang dikenal dengan istilah prefix. Dalam notasi ini, operatoroperator muncul sebelum operanoperan mereka. Anda mungkin sudah menduga bahwa jika kita menelusuri suatu pohon dengan urutan yang berbeda maka kita akan mendapatkan ekspresi dalam notasi yang berbeda pula. Sebagai contoh, jika kita mencetak subpohon dulu, lalu dilanjutkan dengan simpul akar: public static void printPostorder (Tree tree) { if (tree == null) return; printPostorder (tree.left); printPostorder (tree.right); System.out.print (tree + " "); } Kita akan mendapatkan ekspresi postfix (1 2 3 * +)! Seperti namanya, urutan 369
penelusuran seperti ini disebut postorder. Akhirnya, untuk menelusuri sebuah pohon secara inorder, kita cetak dulu bagian kiri pohon, kemudian akar, diakhiri dengan bagian kanannya: public static void printInorder (Tree tree) { if (tree == null) return; printInorder (tree.left); System.out.print (tree + " "); printInorder (tree.right); }
Hasilnya adalah 1 + 2 * 3, yang merupakan ekspresi infix. Jujur saja, saya harus mengatakan bahwa sebenarnya saya telah mengacuhkan komplikasi penting. Terkadang, ketika kita menulis sebuah ekspresi infix, kita harus menggunakan tanda kurung (parentheses) untuk menjaga urutan operasioperasinya. Jadi, penelusuran secara inorder belumlah cukup untuk menghasilkan satu ekspresi infix. Meskipun begitu, dengan beberapa perbaikan, pohon ekspresi dan ketiga penelusuran rekursif tersebut mampu memberikan teknik/cara yang umum untuk menerjemahkan ekspresi dari satu format ke format lainnya.
17.6 Enkapsulasi Seperti yang sudah saya katakan sebelumnya, masih ada masalah dengan teknik/cara yang kita ambil dalam menelusuri pohonpohon: cara ini membuat kita harus memisahkan kode klien (aplikasi yang menggunakan pohon) dengan kode provider (implementasi Pohon). Idealnya, suatu pohon harusnya bersifat umum; ia tidak harus
370
tahu segala sesuatu tentang pohonpohon ekspresi. Sementara kode yang menghasilkan sekaligus menelusuri pohonpohon ekspresi tidak perlu tahu segala sesuatu tentang implementasi pohonpohon. Prinsip/standar perancangan seperti ini disebut object encapsulation (enkapsulasi objek). Istilah ini digunakan agar Anda tidak kebingungan dengan istilah yang sudah ada dalam subbab 6.6, yakni method encapsulation. Untuk versi saat ini, kode Tree tahu terlalu banyak tentang kliennya. Sebaliknya, kelas Tree harus menyediakan kemampuan/kapabilitas umum untuk melakukan penelusuran satu pohon dengan beragam cara. Ketika penelusuran sedang dilakukan, ia harus bisa memproses operasi di setiap simpul yang ditentukan oleh klien. Untuk memfasilitasi perbedaan kepentingan ini, kita akan membuat sebuah metaclass baru yang disebut Visitable. Itemitem yang disimpan dalam sebuah pohon disyaratkan agar bisa dikunjungi (visitable). Ini berarti bahwa itemitem tersebut akan mendefinisikan sebuah metode – visit – yang akan melakukan apapun yang diinginkan oleh klien terhadap masingmasing simpul. Dengan cara ini, Pohon kita akan mampu melakukan penelusuran sementara klien mampu mengerjakan operasioperasi simpul. Berikut ini adalah langkahlangkah yang harus kita kerjakan untuk membuat metaclass yang bisa digunakan oleh kedua kubu sekaligus, klien dan provider: 1. Definisikanlah sebuah metaclass yang mampu mengidentifikasi metodemetode yang akan dibutuhkan oleh kode provider untuk melakukan pemanggilan melalui komponenkomponennya. 2. Tulislah kode provider sebagai satu metaclass yang baru, sebagai lawan dari Object yang bersifat generik. 3. Definisikanlah sebuah kelas yang menjadi bagian dari metaclass dan mampu mengimplementasikan metodemetode yang memang dibutuhkan oleh klien. 4. Tulislah kode klien untuk menggunakan kelas yang baru tersebut. 371
Subbab berikutnya akan mendemonstrasikan langkahlangkah ini.
17.7 Mendefinisikan metaclass Sebenarnya terdapat dua cara untuk mengimplementasikan sebuah metaclass dalam Java, sebagai interface atau sebagai abstract class. Perbedaan antara keduanya tidaklah terlalu penting untuk saat ini, jadi kita akan mulai dengan mendefinisikan sebuah antarmuka (interface). Definisi antarmuka tampak seperti definisi kelas, dengan dua perbedaan: ●
kata kunci class diganti dengan interface, dan
●
definisi metode tidak mempunyai badan (bodies).
Definisi antarmuka menentukan metodemetode yang harus diimplementasikan oleh sebuah kelas agar bisa menjadi anggota metaclass. Syaratsyarat yang harus dipenuhi antara lain; nama, tipetipe parameter, dan tipe pengembalian dari setiap metode. Definisi Visitable adalah sebagai berikut public interface Visitable { public void visit (); } Itu saja, cukup! Definisi visit tampak seperti definisi metode pada umumnya, kecuali bahwa sekarang kita tidak memerlukan badan. Definisi ini mengindikasikan bahwa semua kelas yang mengimplementasikan Visitable harus mempunyai metode dengan nama visit yang tidak membutuhkan parameter apapun dan mengembalikan void. Seperti definisi kelas lainnya, definisi antarmuka memiliki nama file yang sama dengan nama kelasnya (dalam hal ini Visitable.java).
372
17.8 Mengimplementasikan Metaclass Jika kita menggunakan sebuah pohon ekspresi untuk menghasilkan infix, maka “mengunjungi” satu simpul berarti mencetak isiisinya. Karena isi dari sebuah pohon ekspresi berupa token, kita akan membuat kelas baru yakni Token yang mengimplementasikan Visitable. public class Token implements Visitable { String str; public Token (String str) { this.str = str; } public void visit () { System.out.print (str + " "); } } Ketika kita mengcompile definisi kelas ini (yang berada dalam file Token.java), compiler akan memeriksa apakah metodemetode itu memenuhi persyaratan yang ditentukan oleh metaclass. Jika tidak, ia akan menyebabkan munculnya pesan error. Sebagai contoh, jika kita salah menyebutkan nama metode yang dikira visit, kita mungkin akan menerima sesuatu seperti, “class Token must be declared abstract. It does not define void visit() from interface Visitable.” Ini adalah satu dari sekian banyak pesan error yang solusinya tidak tepat. Ketika compiler tersebut mengatakan bahwa suatu kelas “must be declared abstract,” apa yang dimaksud dengan pernyataan tersebut adalah bahwa Anda harus memperbaiki kelas itu sehingga ia mengimplementasikan antarmuka dengan tepat. Terkadang saya sampai berpikir untuk melawan orang yang 373
menulis pesan ini. Langkah berikutnya adalah memodifikasi parser untuk meletakkan objekobjek Token ke dalam pohon ketimbang String. Berikut ini contoh kecilnya: String expr = "1 2 3 * +"; StringTokenizer
st = new StringTokenizer
(expr, "
+*/", true); String token = st.nextToken(); Tree tree = new Tree (new Token (token), null, null)); Kode ini menggunakan token pertama dalam string lalu membungkusnya ke dalam objek Token, kemudian meletakkan Token tersebut ke dalam satu simpul pohon. Jika Tree di sini membutuhkan kargo agar bisa masuk ke dalam metaclass Visitable, ia akan mengubah Token sehingga bisa menjadi bagian dari objek Visitable. Ketika kita menghapus Visitable dari pohon, kita harus mengcastnya kembali ke bentuk Token. Latihan 17.1 Tulislah versi printPreorder dengan nama visitPreorder yang menelusuri pohon dan memanggil visit di setiap simpul dalam preorder. Alur eksekusi untuk metodemetode seperti visitPreorder tampak tidak biasa. Klien memanggil sebuah metode yang disediakan oleh implementasi Tree, lalu implementasi Tree ini akan memanggil satu metode yang disediakan oleh klien. Pola seperti ini disebut callback; adalah langkah yang baik untuk membuat kode provider yang lebih umum tanpa harus memecah abstraksi yang sudah dibuat.
17.9 Kelas Vector Vector merupakan kelas bawaan Java yang berada dalam paket java.util. Kelas ini 374
merupakan implementasi dari sebuah array yang berisi Object dengan fitur tambahan, yakni kemampuan untuk mengubah ukurannya secara otomatis, sehingga kita tidak perlu capekcapek melakukannya. Sebelum menggunakan kelas Vector ini, Anda harus memahami beberapa konsep terlebih dahulu. Setiap Vector mempunyai kapasitas masingmasing, yakni ruang yang sudah dialokasikan untuk menyimpan nilainilai dan ukuran yang merupakan banyak nilai yang berada dalam vektor. Gambar berikut ini merupakan diagram sederhana dari sebuah vektor yang berisi tiga elemen, tapi kapasitasnya tujuh.
Ada dua kelompok metode untuk mengakses elemenelemen yang berada dalam vektor. Metodemetode tersebut menyediakan semantik dan kemampuan errorchecking yang berbedabeda. Hatihati, mungkin mereka akan sering membuat Anda kebingungan. Metode paling sederhana adalah get dan set. Dua metode ini menyediakan semantik yang mirip dengan apa yang dimiliki oleh operator indeks array, []. Metode get menggunakan indeks integer dan mengembalikan elemen yang berada pada posisi yang diinginkan. Sementara set menggunakan sebuah indeks dan satu elemen, lalu menyimpan elemen yang baru itu pada posisi yang diinginkan, mengganti elemen sebelumnya yang sudah berada di dalam posisi itu. Metode get dan set tidak digunakan untuk mengubah ukuran vektor (jumlah elemen). Hal ini merupakan tanggung jawab kode klien untuk memastikan agar suatu vektor mempunyai ukuran yang cukup sebelum get dan set dipanggil. Metode size mengembalikan jumlah elemen yang ada dalam vektor. Jika Anda mencoba mengakses satu elemen yang tidak eksis/ada (yaitu elemen dengan indeks 3 sampai 6), Anda akan 375
menerima eksepsi ArrayIndexOutOfBounds. Kumpulan metode lain mengikutsertakan beberapa versi dari add dan remove. Metode metode ini mampu mengubah ukuran vektor dan kapasitasnya, jika diperlukan. Salah satu versi dari add menggunakan satu elemen sebagai parameter lalu menambahkannya ke ujung vektor (baca: bagian belakang). Metode ini lebih aman karena tidak menyebabkan eksepsi. Versi lain dari add menggunakan satu indeks dan satu elemen. Seperti set, ia meletakkan elemen baru di posisi yang diinginkan. Bedanya, add tidak mengganti elemen yang sudah ada sebelumnya. Metode ini akan menaikkan ukuran vektor, lalu menggeser elemenelemen yang ada ke sebelah kanan untuk memberi ruang bagi elemen yang baru. Oleh karena itu, pemanggilan metode seperti ini; v.add (0, elt) berarti menambahkan elemen baru di awal vektor. Sayangnya, metode ini selain tidak aman juga kurang efisien; ia bisa menyebabkan eksepsi berupa ArrayIndexOutOfBounds dan, dalam banyak implementasi, metode ini termasuk ke dalam linear time (sesuai dengan ukuran vektornya). Secara umum, para klien tidak perlu khawatir mengenai kapasitas. Kapanpun ukuran vektor berubah, kapasitas akan diperbaharui secara otomatis. Karena alasan unjuk kerja, beberapa aplikasi memegang kendali atas fungsi ini, yang sekaligus menjadi penyebab kenapa ada metodemetode tambahan untuk menaikkan dan menurunkan kapasitas. Karena kode klien tidak mempunyai akses untuk menggunakan implementasi vektor, adalah tidak jelas bagaimana cara kita menelusurinya. Tentu saja, satu kemungkinan adalah dengan memakai variabel perulangan sebagai indeks untuk vektor: for (int i=0; i
376
Tidak ada yang salah dengan kode di atas, tapi ada cara lain yang juga bisa digunakan untuk mendemonstrasikan kelas Iterator. Vektorvektor menyediakan satu metode, yakni iterator, yang bisa mengembalikan objekobjek Iterator sehingga memungkinkan metode ini untuk melakukan penelusuran vektor.
17.10 Kelas Iterator Iterator adalah antarmuka yang berada dalam java.util.package. Antarmuka ini terdiri dari tiga metode: hasNext: apakah iterasi ini mempunyai elemen yang lebih? next: mengembalikan elemen berikutnya, atau mengembalikan sebuah eksepsi jika kosong remove: menghapus elemen yang paling baru dari struktur data yang kita telusuri Contoh berikut ini menggunakan sebuah iterator untuk menelusuri dan mencetak elemenelemen yang terdapat dalam satu vektor. Iterator it = vector.it (); while (it.hasNext ()) { System.out.println (it.next ()); } Setelah Iterator dibuat, ia akan menjadi objek yang terpisah dari Vector aslinya. Perubahan yang terjadi berikutnya tidak akan memengaruhi Iterator itu sendiri. Pada kenyataannya, jika Anda memodifikasi Vector setelah membuat Iterator, Iterator itu akan menjadi tidak valid. Jika Anda mengakses Iterator itu lagi, hal ini akan menyebabkan eksepsi ConcurrentModification.
377
Dalam subbab sebelumnya kita menggunakan metaclass Visitable untuk mengizinkan satu klien menelusuri struktur data tanpa harus mengetahui detail implementasinya. Iteratoriterator menyediakan cara lain bagi kita untuk melakukan hal yang sama. Dalam kasus yang pertama, provider melakukan perulangannya lalu memanggil kode klien untuk “mengunjungi” masingmasing elemen. Dalam kasus berikutnya, provider memberikan kliennya satu objek yang bisa digunakannya untuk memilih satu elemen pada suatu waktu. Latihan 17.2 Tulislah sebuah kelas, PreIterator, yang mengimplementasikan antarmuka Iterator lalu buatlah sebuah metode, preorderIterator untuk kelas Tree yang mengembalikan satu PreIterator yang mampu memilih elemenelemen Tree secara preorder. PETUNJUK: Cara termudah untuk membuat Iterator adalah dengan meletakkan elemenelemen ke dalam sebuah vektor yang urutannya sesuai dengan apa yang Anda inginkan. Kemudian Anda bisa memanggil metode iterator melalui vektor tersebut.
17.11 Daftar KataKata Istilah binary tree root leaf parent
Arti Pohon yang tiap simpulnya mengacu ke 0, 1, atau 2 simpul yang saling terkait satu sama lainnya. Simpul tertinggi yang ada pada sebuah pohon, yang tidak diacu oleh simpul manapun. Simpul terbawah pada sebuah pohon yang tidak mengacu ke simpul manapun.. Simpul yang mengacu ke salah satu atau banyak simpul
378
dalam pohon. child level preorder postorder
Salah satu simpul yang diacu oleh simpul lainnya. Kumpulan simpul yang masingmasing memiliki jarak yang sama ke root. Sebuah teknik penelusuran pohon di mana setiap simpul dikunjungi lebih dahulu sebelum simpul anakanaknya. Sebuah teknik penelusuran pohon; kunjungi simpul anak dari semua simpul sebelum simpul itu sendiri. Sebuah teknik penelusuran pohon; kunjungi subpohon
inorder
bagian kiri, kemudian akarnya, lalu baru kunjungi bagian kanannya.
linked queue class variable binary operator
Implementasi antrian menggunakan linked list dan acuan ke node pertama dan terakhir. Variabel static yang dideklarasikan di luar semua metode. Variabel ini bisa diakses dari metode manapun. Operator yang menggunakan dua operan. Suatu rancangan yang tujuannya adalah untuk menjaga implementasi dari satu objek agar selalu terpisah dari
object encapsulation
implementasi objek lainnya. Begitu juga dengan kelas. Satu kelas tidak harus mengetahui detail implementasi kelas lainnya. Suatu rancangan yang tujuannya adalah untuk menjaga
method encapsulation antarmuka dari suatu metode terpisah dari detail implementasinya. callback
Alur eksekusi di mana kode provider memanggil sebuah metode yang disediakan oleh klien.
379
17.12 Latihan Latihan 17.3 a) Apa hasil ekspresi postfix 1 2 + 3 * ? b) Ekspresi postfix apa yang ekuivalen dengan ekspresi infix 1 + 2 * 3? c) Apa hasil ekspresi postfix 17 1 – 5 /, asumsikan bahwa simbol / melakukan pembagian integer?
Latihan 17.4 Tinggi sebuah pohon adalah jalur terpanjang dari akar ke daun manapun. Tinggi bisa didefinisikan secara rekursif se[erti berikut: ●
Tinggi dari pohon yang null adalah 0.
●
Tinggi dari pohon yang tidak null adalah 1 + max (leftHeight, rightHeight), di mana leftHeight adalah tinggi dari simpul anak sebelah kiri dan rightHeight adalah tinggi dari simpul anak sebelah kanan.
Tulislah metode height yang bisa menghitung tinggi dari parameter yang berupa Tree.
Latihan 17.5 Bayangkan kita sedang mendefinisikan sebuah Tree yang berisi objekobjek Comparable sebagai kargo: public class ComparableTree { Comparable cargo; Tree left, right; }
Tulislah metode kelas Tree, findMax, yang mengembalikan kargo terbesar dalam 380
pohon. Kata “terbesar” di sini ditentukan oleh metode compareTo. Latihan 17.6 Pencarian biner pohon merupakan pohon khusus di mana setiap simpul pohon N: semua kargo di sebelah kiri subpohon N < kargo dalam simpul N dan kargo yang ada di simpul N < semua kargo di sebelah kanan subpohon N Dengan menggunakan definisi kelas seperti di atas, buatlah sebuah metode objek contains yang menggunakan satu Object sebagai argumen lalu mengembalikan nilai True jika objeknya muncul dalam pohon, atau False jika tidak. Anda bisa mengasumsikan bahwa objek yang menjadi target dan semua objek yang berada dalam pohon merupakan anggota dari metaclass Comparable. public class SearchTree { Comparable cargo; SearchTree left, right; }
Latihan 16.7 Dalam matematika, satu himpunan (set) merupakan suatu kumpulan elemen yang tidak ada duplikasi di dalamnya. Antarmuka java.util.Set digunakan untuk memodelkan himpunan matematika. Metode yang dibutuhkannya adalah add, contains, containsAll, remove, size, dan iterator. Tulislah kelas TreeSet yang mewarisi kelas SearchTree sekaligus mampu mengimplementasikan Set. Agar semuanya bisa tetap berjalan dengan sederhana, Anda bisa mengasumsikan bahwa null tidak akan muncul dalam pohon atau sebagai argumen 381
dari metode manapun. Latihan 17.8 Tulislah metode union yang menggunakan dua Set sebagai argumen lalu mengembalikan sebuah TreeSet baru yang berisi semua elemen yang muncul di kedua Set itu. Anda bisa memasukkan metode ini ke dalam implementasi TreeSet, atau membuat kelas baru yang mewarisi java.util.TreeSet yang menyediakan union di dalamnya. Latihan 17.9 Tulislah metode intersection yang menggunakan dua Set sebagai parameter lalu mengembalikan sebuah TreeSet baru yang berisi semua elemen yang muncul dalam kedua Set tersebut. Metode union dan intersection bersifat generik karena parameterparameternya bisa bertipe apa saja dalam metaclass Set. Bahkan kedua parameter itu tidak harus memiliki tipe yang sama. Latihan 17.10 Salah satu alasan kenapa antarmuka Comparable bermanfaat adalah karena ia mengizinkan satu tipe objek untuk memutuskan bahwa semua teknik pengurutan sebenarnya tepat. Untuk tipetipe seperti Integer dan Double, teknik pengurutan yang tepat sudah jelas, tapi ada banyak contoh di mana suatu pengurutan bergantung pada apa yang ingin direpresentasikan oleh si objek. Dalam golf, sebagai contoh saja, skor yang lebih rendah lebih baik ketimbang yang lebih tinggi; jika kita membandingkan dua objek Golfer, objek yang mempunyai skor lebih rendah akan menjadi pemenangnya. a) Tulislah definisi kelas Golfer yang berisi nama dan skor bertipe integer sebagai variabel instan. Kelas ini harus mengimplementasikan Comparable dan
382
menyediakan metode compareTo yang bisa memberikan prioritas lebih tinggi untuk skor yang lebih rendah. b) Tulislah sebuah program yang bisa membaca file yang berisi namanama dan skor skor dari kumpulan (Set) pegolf. Program ini harus bisa membuat objek Golfer, meletakkan objekobjek tersebut ke dalam Antrian Prioritas lalu mengeluarkan dan mencetak mereka. Mereka harus ditampilkan dalam urutan terbalik (descending) berdasarkan prioritas. Hal ini sama saja dengan kenaikan urutan berdasarkan skor. Tiger Woods
61
Hal Sutton
69
Phil Mickelson
72
Allen Downey
158
PETUNJUK: Lihat bagian C untuk kode yang bisa membaca barisbaris dalam suatu file. Latihan 17.11 Tulislah implementasi Stack menggunakan Vector. Coba pikirkan, apakah memasukkan (push) elemen baru ke posisi awal stack akan lebih baik ketimbang memasukkannya ke posisi terakhir?
383
Bab 18 Heap 18.1 Implementasi Pohon menggunakan Array Apa yang dimaksud dengan mengimplementasikan pohon? Sejauh ini kita hanya melihat satu implementasi pohon yang menggunakan linked list. Tetapi sebenarnya ada struktur lain yang bisa kita identifikasi sebagai pohon. Dan segala hal yang bisa melakukan operasi pohon “dasar” bisa disebut sebagai pohon.
Jadi apa saja operasi operasi pohon itu? Dengan kata lain bagaimana mendefinisikan pohon ADT? constructor: Membangun sebuah pohon kosong. getLeft: Mengembalikan anak kiri dari simpul. getRight: Mengembalikan anak kanan dari simpul. getParent: Mengembalikan orang tua dari simpul. getCargo: Mengembalikan muatan dari simpul. setCargo: Memasukkan sebuah objek muatan pada simpul ini (dan membuat simpul jika dibutuhkan). Pada implementasi dengan linked list. Pohon kosong diwakili dengan nilai spesial null. getLeft dan getRight dilakukan dengan mengakses variabel instans dari simpul, demikian hal nya dengan getCargo dan setCargo. Dalam hal ini kita belum mengimplementasikan getParent( anda bisa berpikir bagaimana untuk melakukan hal ini)
384
Ada implementasi pohon lain yang menggunakan array dan indeks sebagai ganti objek dan referensi. Untuk melihat bagaimana ia bekerja, kita akan memulai dengan melihat implementasi campuran yang menggunakan array dan objek.
Gambar di bawah ini memperlihatkan sebuah pohon seperti sebelumnya yang pernah kita lihat. Pada sisi kanan ada sebuah array dari referensi yang mengacu pada muatan dari sebuah simpul.
Setiap simpul pada pohon memiliki indeks yang unik. Indeks ditempatkan pada simpul menurut pola yang ditentukan, dengan aturan seperti di bawah: 1. Anak kiri dari simpul dengan indeks i memiliki indeks 2i. 2. Anak kanan dari simpul dengan indeks i memiliki indeks 2i+1. 3. Orang tua dari simpul dengan indeks i memiliki indeks i/2 (pembulatan ke bawah). Menggunakan rumus ini, kita bisa mengimplementasikan getLeft, getRight, dan getParent dengan hanya menggunakan aritmatika; kita tidak harus menggunakan referensi untuk semua hal!
Karena kita tidak menggunakan referensi, berarti apa yang digunakan sekarang hanya objek muatan dan tidak ada yang lain. Sehingga kita bisa mengimplementasikan pohon sebagai sebuah array dari objek muatan. 385
Di bawah ini contoh implementasi pohon menggunakan array: public class ArrayTree { Object[] array; int size; public ArrayTree () { array = new Object [128]; } Sejauh ini tidak ada yang baru. Satu satunya variabel instans adalah array dari objek yang berisi muatan pohon. Konstruktor menginisialisasi array dengan kapasitas tertentu; Hasilnya adalah sebuah pohon kosong.
Berikut ini implementasi sederhana dari getCargo dan setCargo. public Object getCargo (int i) { return array[i]; } public void setCargo (int i, Object obj) { array[i] = obj; } Metode ini tidak melakukan pengecekan error apapun, jadi jika parameter salah, metode dapat menghasilkan eksepsi ArrayIndexOutOfBounds. Implementasi dari getLeft, getRight, dan getParent hanya menggunakan operasi aritmatika: public int getLeft (int i) { return 2*i; } public int getRight (int i) { return 2*i + 1; } public int parent (int i) { return i/2; } 386
Akhirnya kelas pohon menggunakan array telah siap digunakan. Pada kelas lain (klien), kita bisa menulis: ArrayTree tree = new ArrayTree (); tree.setCargo (1, "cargo for root"); Konstruktor membangun sebuah pohon kosong. Memanggil setCargo meletakkan kalimat “cargo for root” kedalam simpul akar. Untuk menambah anak pada simpul akar: tree.setCargo (tree.getLeft(1), "cargo for left"); tree.setCargo (tree.getRight(1), "cargo for right"); Pada kelas pohon kita dapat menyediakan sebuah metode yang mencetak isi dari pohon dalam bentuk preorder. public void print (int i) { Object cargo = tree.getCargo (i); if (cargo == null) return; System.out.println (cargo); print (getRight (i)); print (getLeft (i)); } Untuk memanggil metode ini, kita harus melewatkan indeks akar sebagai parameter. tree.print (1); Keluarannya adalah: cargo for root cargo for left cargo for right
387
Implementasi ini menyediakan operasi dasar yang mendefinisikan sebuah pohon. Seperti yang telah saya tunjukkan sebelumnya, implementasi pohon menggunakan linked list menyediakan operasi yang sama tetapi dengan sintaks yang berbeda.
Dalam beberapa hal, implementasi menggunakan array sedikit aneh. Pada suatu saat kita mengasumsikan bahwa muatan null menunjukkan simpul yang tidak ada, tetapi itu berati kita tidak dapat meletakkan objek null pada pohon sebagai muatan.
Masalah lain adalah subpohon tidak direpresentasikan sebagai objek; Mereka direpresentasikan dengan indeks pada array. Untuk melewatkan simpul pohon sebagai parameter, kita harus melewatkan sebuah referensi ke objek pohon dan indeks ke array. Akibatnya beberapa operasi yang mudah dilakukan pada implementasi dengan linked list, seperti mengganti keseluruhan subtree, lebih sulit dilakukan pada implementasi dengan array.
Pada sisi lain, implementasi dengan array hemat dalam hal ruang penyimpanan, karena tidak ada nya hubungan antara simpul, dan ada beberapa operasi yang lebih mudah dan cepat jika pohon diimplementasikan dengan array.
Heap adalah implementasi dari PriorityQueue ADT yang berdasarkan atas implementasi pohon menggunakan array. Yang akan lebih efisien jika dibandingkan dengan implementasi lain.
Untuk membuktikan klaim ini, akan kita lakukan dalam beberapa langkah. Pertama, kita akan membangun cara untuk membandingkan kinerja dari beberapa implementasi. Berikutnya, kita akan melihat implementasi yang dilakukan heap. Dan terakhir kita akan 388
membandingkan implementasi PriorityQueue menggunakan heap dengan yang lainnya (array dan list) dan melihat mengapa implementasi heap dianggap lebih efisien.
8.2 Analisis Kinerja Ketika kita membandingkan algoritma, kita harus memiliki cara untuk mengetahui kapan suatu algoritma lebih cepat dibandingkan dengan yang lain, atau menggunakan ruang yang lebih sedikit, atau menggunakan sumber daya lain yang lebih sedikit. Pertanyaan ini berat untuk dijawab secara detil, karena waktu dan ruang penyimpanan yang digunakan suatu algoritma bergantung pada implementasi dari algoritma tersebut, masalah tertentu yang akan diselesaikan, dan perangkat keras dimana program berjalan.
Tujuan dari bab ini adalah untuk mencari cara untuk mengetahui kinerja yang tidak tergantung pada semua hal itu, dan hanya bergantung pada algoritm itu sendiri. Untuk memulai, kita akan fokus pada waktu eksekusi; selanjutnya kita akan berbicara tentang sumber daya yang lain. Adapun masalahmasalah yang kita hadapi adalah: 1. Pertama, kinerja algoritma bergantung pada perangkat keras dimana ia berjalan, jadi kita tidak akan berbicara tentang waktu eksekusi dengan satuan mutlak seperti detik. Sebagai gantinya, kita menghitung operasi abstrak yang dilakukan suatu algoritma. 2. Kedua, kinerja sering bergantung pada masalah tertentu yang sedang kita coba untuk menyelesaikannya – beberapa masalah lebih mudah dibandingkan dengan yang lain. Untuk membandingkan algoritma, kita akan fokus pada skenario terburuk atau skenario rata rata (umum). 3. Ketiga, Kinerja bergantung pada ukuran masalah (biasanya, tetapi tidak selalu, banyak elemen dalam sebuah collection). Kita menyelesaikan masalah ini 389
dengan menganggap waktu eksekusi sebagai fungsi dari besar ukuran masalah. 4. Kinerja bergantung pada implementasi detail sebuah algoritma seperti pengalokasian objek dan pengeksekusian metode. Kita tidak akan mengurusi hal ini karena tidak terlau berpengaruh pada tingkat kompleksitas seperti banyak operasi abstrak menambah ukuran masalah. Untuk membuat masalah proses lebih nyata, perhatikan dua algoritma pengurutan bilangan bulat yang telah kita lihat. Yang pertama adalah selection sort, yang telah kita lihat pada bagian 12.3. Berikut ini pseudocode dari algoritma tersebut. selectionsort (array) { for (int i=0; i<array.length; i++) { // find the lowest item at or to the right of i // swap the ith item and the lowest item } } Untuk melakukan operasi yang dispesifikasikan pada pseudocode, kita menuliskan metode yang bernama findLowest dan swap. Berikut ini pseudocode metode findLowest dan swap:
// find the index of the lowest item between // i and the end of the array findLowest (array, i) { // lowest contains the index of the lowest item so far lowest = i; for (int j=i+1; j<array.length; j++) { // compare the jth item to the lowest item so far
390
// if the jth item is lower, replace lowest with j } return lowest; }
swap (i, j) { // store a reference to the ith card in temp // make the ith element of the array refer to the jth card // make the jth element of the array refer to temp }
Untuk menganalisa kinerja algoritma ini, langkah pertama adalah menentukan operasi apa yang akan dihitung. Seyogyanya program melakukan banyak hal seperti: menambah i, membandingkannya dengan panjang deck, mencari elemen terbesar pada array, dan lain lain. Sehingga tidaklah terlalu jelas hal mana yang akan dihitung.
Pilihan yang baik adalah berapa kali kita membandingkan dua item. Banyak pilihan lain yang akan menghasilkan hasil yang sama pada akhirnya, tetapi menggunakan pembandingan item akan mudah kita lakukan dan kita akan bisa membandingkan algoritma pengurutan apapun dengan mudah.
Langkah berikutnya adalah mendefinisikan “ukuran masalah”. Pada kasus ini kita memilih ukuran array, yang akan kita sebut n. Langkah terakhir, kita akan menurunkan sebuah fungsi yang memberitahu berapa banyak operasi abstrak (dalam kasus ini, perbandingan) yang harus kita lakukan, sebagai fungsi dari n.
391
Swap mengkopi beberapa referensi, tetapi tidak melakukan pembandingan apapun, jadi kita mengabaikan waktu untuk mengeksekusi swap. findLowest dimulai pada i dan menjelajahi array, membandingkan setiap item dengan lowest. Banyak item yang akan dibandingkan adalah sebesar ni, sehingga jumlah total perbandingan adalah ni1.
Selanjutnya kita akan melilhat berapa kali findLowest dipanggil serta nilai i nya. Terakhir kali dipanggil, i bernilai n2 sehingga jumlah perbandingan adalah 1. Iterasi sebelumnya melakukan dua perbandingan dan demikian seterusnya. Selama iterasi pertama, i bernilai 0 dan jumlah perbandingan adalah n1.
Jadi jumlah perbandingan total adalah 1+2+...+n1. Jumlahan ini sama dengan n 2 /2 − n/2. Untuk menjelaskan algoritma ini, kita akan mengabaikan n/2 dan menganggap total perbandingan adalah n2.
18.3 Analisis Merge Sort
Pada bagian 12.6 saya mengklaim bahwa mergesort membutuhkan waktu nlogn, tetapi saya tidak menjelaskan bagaimana atau mengapa. Sekarang saya akan menjelaskannya. Kembali, kita mulai dengan melihat pseudocode algoritma. mergeSort (array) { // find the midpoint of the array // divide the array into two halves // sort the halves recursively // merge the two halves and return the result }
392
Pada setiap level rekursi. Kita membagi array dalam dua bagian, memanggil dua metode rekursif, dan kemudian menggabungkannya. Secara grafis, proses nya adalah sebagai berikut:
Setiap baris pada diagram adalah tingkat rekursi. Pada baris teratas, array tunggal dibagi menjadi dua bagian. Pada baris paling bawah, n array dengan satu elemen tiap bagian digabung menjadi n/2 array dengan dua elemen setiap bagian.
Dua kolom pertama pada tabel memperlihatkan banyak array pada setiap tingkatan dan banyak elemen dalam setiap array. Kolom ketiga memperlihatkan berapa kali penggabungan dilakukan setiap tingkatan rekursi. Kolom berikutnya memperlihatkan banyak perbandingan setiap penggabungan dilakukan.
Jika and melihat pada pseudocode (atau pada implementasi anda) tentang penggabungan, anda pasti menyadari kasus terburuk nya adalah sebanyak m1. Dimana m adalah banyak elemen yang akan digabungkan.
Langkah berikutnya adalah mengalikan banyak penggabungan tiap tingkatan dengan banyak perbandingan setiap penggabungan. Hasilnya adalah kerja total tiap tingkatan. Pada titik ini kita mengambil keuntungan dari trik kecil. Kita tahu pada akhirnya kita hanya tertarik pada pangkat depan dari hasil yang didapatkan, sehingga kita bisa
393
mengabaikan 1 pada bagian perbandingan tiap penggabungan. Jika kita melakukannya, total kerja tiap tingkatan hanya n.
Berikutnya kita perlu tahu jumah tingkatan sebagai fungsi dari n. Kita akan mulai dengan array dengan n elemen dan membaginya dalam dua bagian sampai mendapatkan 1 elemen tiap bagian. Hal ini sama dengan mulai dari 1 dan mengalikan dengan 2 sampai mendapatkan n. Dengan kata lain, kita ingin mengetahui berapa kali kita harus mengalikan 2 sebelum kita mendapatkan n. Jawabannya adalah banyak tingkatan, l adalah logaritma basis dua dari n.
Terakhir, kita mengalikan total kerja tiap tingkatan, n, dengan banyak tingkatan, log 2 n untuk mendapatkan n log2 n.
18.4 Overhead Pertama kita mengabaikan sebagian besar operasi pada program dan menghitung hanya pembandingan. Kemudian kita hanya akan melihat pada kinerja pada kasus terburuk. Ketika kita menerjemahkan hasil dari analisis ini, kita harus menyimpannya pada ingatan. Karena mergesort n log2 n, kita bisa menganggapnya lebih baik dari selection sort, tetapi ini tidak berati bahwa mergesort selalu lebih cepat. Ini hanya berarti jika kita mengurutkan array yang lebih dan lebih besar, mergesort akan menang. Lama pengeksekusian sepenuhnya tergantung pada implementasi detail algoritma, termasuk pekerjaan tambahan, disamping pembandingan yang telah kita hitung. Pekerjaan tambahan ini kadang kadang disebut overhead. Ia tidak mempengaruhi analisis kinerja, tetapi mempengaruhi waktu eksekusi algoritma tersebut.
394
Sebagai contoh, implementasi mergesort kita sebenarnya mengalokasikan subarray sebelum membuat pemanggilan secara rekursif. Dan kemudian membiarkan mereka ditangani oleh garbage collector setelah digabungkan. Lihat kembali pada diagram mergesort, kita bisa melihat jumlah total ruang yang dialokasikan sama dengan n log 2 n, dan jumlah objek total yang dialokasikan adalah 2n. Selalu benar bahwa implementasi buruk dari algoritma yang baik adalah lebih baik dibandingkan implementasi baik dari algoritma yang buruk.
18.5 Implementasi PriorityQueue Pada bab 16 kita telah melihat sebuah implementasi dari PriorityQueue yang dibuat dengan array. Elemen pada array tidak terurut, sehingga mudah untuk menambahkan elemen baru, tetapi akan lebih susah untuk menghapus elemen, karena kita harus mencari item dengan prioritas tertinggi.
Sebagai alternatif implementasi dibuat berdasarkam sorted list. Pada kasus ini ketika kita menambah item baru kita menjelajahi list dan meletakkan item baru pada tempat yang benar. Implementasi ini menggunakan kemampuan dari list, dimana akan mudah menambah simpul baru pada pertengahan list. Begitu juga menghapus item dengan prioritas tertinggi, dimana ia akan selalu terdapat pada awal list.
Analisis kinerja dari operasi ini cukup mudah. Menambahkan item pada akhir array atau menghapus simpul dari awal list menggunakan waktu yang sama tanpa memperhatikan jumlah item yang ada pada list. Jadi kedua operasi ini menggunakan waktu yang konstan.
Setiap kali kita menjelajahi array atau list, menggunakan operasi dengan waktu yang 395
konstan tiap elemen, waktu sebanding dengan jumlah item. Itulah mengapa, menghapus sesuatu dari array dan menambahkan sesuatu pada list keduanya menggunakan waktu yang konstan.
Jadi berapa lama waktu yang digunakan untuk menambah dan kemudian menghapus n item dari PriorityQueue? Untuk implementasi array, n menggunakan waktu sebanding dengan n tetapi penghapusan menggunakan waktu lebih lama. Penghapusan yang pertama harus menjelajahi semua n elemen; kedua harus menjelajahi n1 dan demikian seterusnya, sampai penghapusan terakhir, yang hanya harus mencari pada 1 elemen. Sehingga total waktu nya adalah 1+2+...+n, yang menghasilkan n2/2+n/2. Jadi total untuk penambahan dan penghapusan adalah jumlah fungsi linear dan fungsi kuadrat, yang kita bisa tahu akan menghasilkan fungsi kuadrat.
Analisis pada implementasi list sama. Penambahan pertama tidak membutuhkan penjelajahan, tetapi setelah itu kita harus menjelajahi list setiap kali kita menambah sebuah item baru. Pada umumnya kita tidak mengetahui berapa banyak elemen pada list yang harus kita jelajahi, karena ia bergantung pada data dan dengan cara bagaiman mereka ditambahkan, tetapi kita bisa mengasumsikan rata rata kita harus menjelajahi setengah dari list. Sayangnya bahkan menjelajahi setengah dari list adalah operasi yang linear.
Jadi, sekali lagi, untuk menambah dan menghapus n item membutuhkan waktu n 2. Sehingga berdasarkan analisis kita tidak bisa mengatakan implementasi mana yang lebih baik; implementasi array dan list keduanya merupakan operasi kuadratik.
Jika kita mengimplementasikan priority queue menggunakan heap, kita bisa melakukan 396
penambahan dan penghapusan dengan waktu logn. Sehingga total waktu untuk n item adalah nlogn, yang lebih baik dari n2. Itulah mengapa pada awal bab ini, saya menyebutkan bahwa heap adalah implementasi efisien dari priority queue.
18.6 Definisi Heap Heap adalah salah satu jenis pohon. Ia memiliki dua sifat yang tidak selalu dimiliki oleh pohon lain: completeness : pohon komplet, yang berarti simpul ditambahkan dari atas ke
bawah,
kiri ke kanan, tanpa meninggalkan satu ruang pun. heapness: Item pada pohon dengan prioritas tertinggi ada pada puncak pohon, begitu juga pada setiap subpohon. Kedua sifat ini membutuhkan sedikit penjelasan. Gambar berikut memperlihatkan jumlah pohon yang komplet dan tidak komplet.
Pohon kosong selalu dianggap komplit. Kita bisa mendefinisikan completeness lebih dalam dengan membandingkan tinggi subpohon. Dari pelajaran sebelumnya kita ketahui bahwa tinggi pohon adalah banyak tingkatan yang ada. 397
Dimulai dari akar, jika pohon komplit, lalu tinggi subpohon kiri dan tinggi subpohon kanan harus sama, atau subpohon kiri bisa lebih tinggi satu. Pada kasus lain, pohon tidak disebut komplit. Dengan demikian jika pohon komplit, maka hubungan antara subpohon harus selalu benar untuk setiap simpul pada pohon.
Sifat heap adalah rekursif. Agar pohon disebut heap, nilai terbesar pada pohon harus ada pada akar, dan hal yang sama harus benar pada setiap subpohon. Latihan 18.1 Tulis metode yang mengambil pohon sebagai parameter dan mengecek apakah komplit atau tidak. Petunjuk: Anda dapat menggunakan metode height dari latihan 17.4 Latihan 18.2 Tulis metode yang mengambil pohon sebagai parameter dan mengecek apakah ia memiliki sifat heap.
18.7 Penghapusan pada heap Mungkin kelihatan ganjil kita akan menghapus sesuatu dari heap sebelum menambah apapun, tetapi saya pikir penghapusan lebih mudah untuk dijelaskan. Sekilas, kita mungkin berpikir mengapus item dari heap adalah operasi yang konstan, karena item dengan prioritas tertinggi selalu berada pada akar. Masalahnya adalah setiap kita menghapus simpul akar, kita ditinggalkan sesuatu yang bukan lagi heap. Sebelum kita dapat mengembalikan hasilnya, kita harus mempebaiki sifat heap. Kita menyebut operasi ini reheapify. Situasi ini digambarkan pada gambar berikut:
398
Simpul akar memiliki prioritas r dan dua subpohon, A dan B. nilai pada akar subpohon A adalah a dan nilai pada subpohon B adalah b.
Kita mengasumsikan sebelum kita menghapus r dari pohon, pohon adalah heap. Ini mengakibatkan r adalah nilai terbesar dalam heap dan bahwa a dan b adalah nilai terbesar dalam subpohon mereka. Ketika kita menghapus r, kita harus membuat pohon yang dihasilkan kembali menjadi heap. Dengan kata lain kita butuh meyakinkan ia memiliki sifat completeness dan heapness.
Cara terbaik untuk menjamin completeness adalah dengan menghapus elemen paling bawah, yang paling kanan, yang kita sebut c dan meletakkan nilai nya pada akar. Pada implementasi pohon, kita harus menjelajahi pohon untuk mencari simpul ini, tetapi pada implementasi array, kita dapat mencarinya pada waktu konstan karena ia akan selalu elemen non null terakhir pada array.
Tentu, nilai terakhir bukan yang tertinggi, jadi meletakkanyya pada akar akan merusak sifat heapness. Untungnya mudah untuk diperbaiki. Kita tahu bahwa nilai terbesar 399
dalam heap setelah r dihapus adalah a atau b. Itulah mengapa kita bisa memilih yang mana yang lebih besar dan menggantinya dengan nilai pada akar.
Mari kita anggap b adalah yang terbesar. Karena kita tahu ia adalah nilai tertinggi pada kiri heap, kita bisa meletakkannya pada akar dan meletakkan c pada puncak subpohon B. Sekarang situasinya akan seperti di bawah ini:
lagi, c adalah nilai yang kita kopi dari elemen terakhir dalam array dan b adalah nilai tertinggi dalam heap. Karena kita tahu kita tidak pernah mengganti subpohon A, kita mengetahui ia masih merupakan heap. Satu satunya masalah adalah kita tidak dapat mengetahui apakah subpohon B adalah heap, karena kita hanya menempel sebuah nilai (kemungkinan rendah) pada akarnya. Akan lebih baik jika kita memiliki metode yang bisa reheapify subpohon B? Tunggu.... kita punya
18.8 Penambahan Heap Menambahkan sebuah item baru pada heap adalah operasi yang mirip dengan penghapusan heap, perbedaanya bahwa kita tidak menghamburkan elemen dari atas 400
tetapi kita menghamburkan elemen ke atas dari bawah heap.
Untuk menjamin sifat completeness, kita menambah elemen baru pada elemen paling bawah pada pohon, yang merupakan ruang kosong berikutnya pada array. Lalu untuk mengembalikan sifat heap, kita membandingkan nilai baru dengan tetangganya. Situasi nya akan terlihat seperti ini:
Nilai baru adalah c. Kita bisa mengembalikan sifat heap pada subpohon ini dengan membandingkan c dengan a. Jika c lebih kecil, maka sifat heap telah dipenuhi. Jika c lebih besar maka kita mengganti c dan a. Proses swap akan memenuhi sifat heap karena kita tahu c harus lebih besar dari b, karena c>a dan a>b.
Sekarang subtree telah menjadi heap kembali, selanjutnya proses dilanjutkan ke simpul atas (a atau c) dengan membandingkan dengan orang tua nya demikian seterusnya sampai mencapai akar.
18.9 Kinerja Heap Untuk penambahan dan penghapusan, kita menggunakan operasi dengan waktu yang konstan untuk penambahan dan penghapusan yang sebenarnya, tetapi kemudian kita harus melakukan reheapify pada pohon. Pada satu kasus kita mulai dari akar dan
401
dilanjutkan ke bawah, membandingkan elemen dan secara rekursif melakukan reheapify pada subpohon. Pada kasus lain kita mulai pada daun dan bekerja ke atas, kembali kita membandingkan elemen pada tiap level pada pohon.
Seperti biasa, ada beberapa operasi yang akan kita hitung, seperti pembandingan dan pertukaran. Kedua pilihan akan bekerja; masalah sebenarnya adalah banyak level pada pohon yang kita periksa dan berapa banyak usaha yang kita lakukan setiap level. Pada kedua kasus kita tetap bekerja pada sebuah level pohon sampai kita mengembalikan sifat heap, yang berarti kita mungkin hanya berkunjung sekali, atau pada kasus terburuk kita harus mengunjungi semua elemen, sekarang mari kita lihat pada kasus terburuk.
Pada setiap level, kita melakukan hanya operasi dengan waktu yang konstan seperti pembandingan dan penggantian. Jadi total kerja sebanding dengan banyak tingkatan pada pohon, dengan kata lain tinggi. Jadi kita bisa berkata operasi operasi ini berbanding lurus denga tinggi pohon, tetapi “ukuran masalah” yang menarik bukan tinggi nya, tetapi banyak elemen pada heap.
Dalam fungsi n, tinggi pohon adalah log2n. Ini tidak selalu benar untuk semua pohon, tetapi hanya benar untuk pohon yang lengkap. Untuk mengetahui kenapa, pikirkan banyak simpul pada setiap level dari pohon. Level pertama berisi 1, level 2 berisi 2, level ketiga berisi 4, demikian seterus nya. Level ke i akan berisi 2i simpul dan jumlah elemen di semua level sampai i adalah 2i1. Dengan kata lain 2h = n, yang berarti h=log2n.
Sehingga, keduanya penambahan dan penghapusan menggunakan waktu yang logaritmik. Untuk menambah dan menghapus n elemen maka akan menggunakan waktu 402
nlog2n.
18.10 Heapsort Hasil dari bagian yang lalu bisa menjadi inspirasi untuk algoritma pengurutan. Terdapat n elemen, kita menambahkan mereka ke dalam Heap kemudian menghapusnya. Karena sifat heap, mereka akan keluar dalam keadaan terurut. Kita telah memperlihatkan algoritma ini, yang disebut heapsort, ia menggunakan waktu sebanding dengan nlog2n, yang lebih baik dari selection sort dan sama dengan mergesort.
Semakin besar nilai, kita berharap heapsort akan lebih cepat dari selection sort, tetapi analisis kinerja tidak menunjukkan cara untuk mengetahui apakah ia akan lebih cepat dari mergesort. Kita akan berkata kedua algoritma memiliki order of growth yang sama karena waktu kinerja mereka bertambah dengan bentuk fungsi yang sama. Cara lain untuk mengatakan hal yang sama adalah mereka memiliki kompleksitas yang sama.
Kompleksitas kadang kadang ditulis dalam “notasi bigO”. misalnya O(n^2), diucapkan dengan “oh of en squared” adalah kumpulan fungsi yang berkembang tidak lebih cepat dari n^2 untuk nilai n yang besar. Untuk mengatakan sebuah algoritma adalah O(n^2) sama dengan algoritma tersebut kuadratik. Untuk kasus kompleksitas lain yang telah kita lihat, dengan urutan kinerja menurun, adalah O(1) constant time O(log n) logarithmic O(n) linear O(n log n) “en log en” O(n2 ) quadratic O(2n ) exponential 403
Sejauh ini tidak ada algoritma yang telah kita lihat memiliki kompleksitas eksponensial. Untuk nilai n yang besar, algoritma ini cepat menjadi tidak praktis. Namun demikian frase “pertumbuhan eksponensial” muncul lebih sering bahkan pada bahasa yang tidak teknis. Ia sering digunakan tidak tepat jadi saya ingin memasukkan arti teknis nya. Orang sering menggunakan eksponensial untuk menjelaskan kurva yang bertambah dan berakselerasi (kurva yang memiliki kemiringan positif dan lekukan). Tentu saja ada banyak kurva lian yang sesuai dengan penjekasan ini, termasuk fungsi kuadrat (dan polinomilal tingkat yang lebih tinggi) dan bahkan fungsi seperti nlogn. Sebagian besar kurva ini tidak memiliki (bahkan merusak) sifat eksplosif dari eksponensial.
18.11 Daftar Kata Kata selection sort: Algoritma pengurutan sederhana pada bab 12.3. mergesort: Algoritma pengurutan yang lebih baik dari bab 12.6. heapsort: Algoritma pengurutan yang lain. kompleksitas: kumpulan algoritma yang kinerja nya (biasanya waktu eksekusi) memiliki order of growth yang sama. order of growth: Kumpulan fungsi dengan pangkat awal yang sama, dan karena itu sama dalam sifat kualitatif untuk nilai n yang besar. overhead: waktu atau sumber daya tambahan yang dikonsumsi oleh sebuah program dalam melakukan suatu operasi selain dari operasi abstrak yang diperhitungkan pada analisis kinerja.
404
18.12 Latihan latihan 18.3 a) Gambar Heap yang direpresentasikan pada array di bawah ini.
b) Perlihatkan susunan array ketika 68 ditambahkan ke dalam heap. Latihan 18.4 Asumsikan terdapat n elemen dalam heap. Untuk mencari nilai tengah dari elemen, kita dapat menghapus n/21 elemen dan mengembalikan nilai elemen ke n/2. Kemudian kita harus meletakkan n/2 elemen kembali ke dalam heap. Berapa order of growth dari algoritma ini? Latihan 18.5 Berapa kali loop di bawah ini dieksekusi? Kemukakan jawaban mu sebagai fungsi dari n: while (n > 1) { n = n / 2; } Latihan 18.6 Berapa banyak pemanggilan rekursif yang dilakukan zippo? Kemukakan jawabanmu sebagai fungsi dari x atau n atau keduanya. public static double zippo (double x, int n) { if (n == 0) return 1.0; return x * zippo (x, n1); }
405
Latihan 18.7 Tulis implementasi dari Heap berdasarkan implementasi pohon dengan array. Waktu eksekusi add dan remove harus sebanding dengan log n, diman n adalah banyak elemen dalam heap.
406
BAB 19 Maps 19.1 Array, Vector, Map Array adalah struktur data yang secara umum sangat berguna, tetapi array memiliki dua keterbatasan utama yaitu: ●
Ukuran array tidak bergantung terhadap jumlah elemen yang ada di dalamnya. Jika array lebih besar dibanding jumlah elemen di dalamnya maka akan menghabiskan tempat. Sedangkan jika terlalu kecil (Ukuran array lebih kecil) maka kemungkinan dapat menyebabkan kesalahan. Atau kita harus menulis kode untuk mengubah ukurannya.
●
Meskipun array dapat berisi tipe apapun, indeks array harus bertipe integer. Kita tidak dapat misalnya menggunakan String untuk digunakan sebagai indeks.
Pada bab 17.9 kita telah melihat bagaimana kelas Vector yang merupakan kelas yang disediakan oleh Java bisa menyelesaikan masalah pertama. Ketika sebuah kode menambah elemen, maka Vector akan membesar secara otomatis. Dan juga memungkinkan untuk memperkecil Vector sehingga kapasitasnya sama dengan jumlah elemen yang ada di dalamnya.
Tetapi Vector tidak dapat menolong pada masalah kedua. Indeks Vector masih harus bertipe integer. Masalah ini bisa diselesaikan dengan struktur data Map ADT. Map serupa dengan Vector, perbedaannya adalah Map dapat menggunakan tipe objek apapun sebagai indeks. Indeks pada Map disebut sebagai kunci.
407
Sebagaimana anda menggunakan indeks untuk mengakses nilai pada Vector, anda juga menggunakan kunci untuk mengakses nilai pada Map. Setiap kunci diasosiasikan dengan sebuah nilai, itulah mengapa Map kadang kadang disebut array assosiatif. Assosiasi antara sebuah kunci dengan sebuah nilai disebut entri.
Contoh umum dari struktur data Map adalah kamus, yang memetakan kata kata (kunci) pada definisinya (nilai). Karena hal ini Map juga kadang disebut kamus.
19.2 Map ADT
Seperti ADT lain yang telah kita lihat, Maps didefinisikan dengan himpunan operasi yaitu: contructor: Membuat sebuah Map kosong put: Menciptakan sebuah entri yang mengasosiasikan sebuah nilai dengan sebuah kunci get: Untuk sebuah kunci yang diberikan, cari nilai yang bersesuaian containsKey: Mengembalikan true jika terdapat entri pada Map dengan kunci yang diberikan keySet: Mengembalikan struktur data Set yang berisi semua kunci pada Map
19.3 HashMap builtin Java menyediakan implementasi dari Map ADT pada paket java.util.HashMap. Selanjutnya pada bab ini kita akan lihat mengapa disebut HashMap
408
Untuk mendemonstrasikan penggunaan HashMap kita akan menulis sebuah program sederhana yang menjelajahi sebuah String dan menghitung jumlah kemunculan suatu kata.
Kita akan membuat kelas baru yaitu WordCount yang akan membangun Map dan mencetak isinya. Setiap WordCount berisi HashMap sebagai variabel instance.
public class WordCount{ HashMap Map;
public WordCount(){ Map = new HashMap(); } }
Metode publik pada WordCount adalah processLine, yang mengambil sebuah String dan menambah kata katanya pada Map, dan print, yang bertugas mencetak hasil pemrosesan.
ProcessLine memecah String menjadi kata kata menggunakan StringTokenizer (untuk mennggunakan StringTokenizer anda harus mengimpornya terlebih dahulu, dengan menambahkan baris import java.util.* sebelum deklarasi class) dan melewatkan setiap kata pada processWord.
public void processLine(String s){
409
StringTokenizer st = new StringTokenizer(s,”,.”); while(st.hasMoreTokens()){ String word = st.nextToken(); processWord(word.toLowerCase()); } }
public void processWord (String word) { if (Map.containskunci (word)) { integer i = (integer) Map.get (word); integer j = new integer (i.intValue() + 1); Map.put (word, j); } else { Map.put (word, new integer (1)); } }
Jika kata telah ada dalam Map, maka kita mengambil variabel pencacahnya, menambahkan satu, dan memasukkannya (put) kembali pada Map. Jika tidak, kita hanya akan menaruh entri baru pada Map dengan pencacah di beri nilai satu.
Untuk mencetak semua entri pada Map, kita harus bisa menjelajahi seluruh kunci dalam Map. Untungnya, HashMap menyediakan metode keySet, yang mengembalikan sebuah objek Set, dan Set menyediakan sebuah metode iterator yang mengembalikan objek Iterator.
410
Berikut ini cara untuk menggunakan keySet untuk mencetak isi dari HashMap: //catatan agar kode bisa fungsional anda harus menambahkan baris import java.util.* sebelum //deklarasi kelas public void print () { Set set = Map.keySet(); Iterator it = set.iterator(); while (it.hasNext ()) { String kunci = (String) it.next (); integer value = (integer) Map.get (kunci); System.out.println ("{ " + kunci + ", " + value + " }"); } }
Setiap elemen pada iterator adalah sebuah Objek, tetapi karena kita tahu bahwa mereka adalah kunci, kita bisa mengcastingnya menjadi String. Ketika kita mengambil sebuah nilai pada Map, mereka juga bertipe Objek, tetapi kita tahu bahwa mereka adalah pencacah, jadi kita bisa mengcastingnya menjadi bertipe integer.
Berikut ini cara untuk menghitung kata kata pada sebuah kalimat:
WordCount wc = new WordCount (); wc.processLine ("you spin me right round baby " + "right round like a record baby " + "right round round round"); wc.print ();
411
Keluarannya adalah { you, 1 } { round, 5 } { right, 3 } { me, 1 } { like, 1 } { baby, 2 } { spin, 1 } { record, 1 } { a, 1 }
Elemen elemen pada Iterator tidak diurutkan berdasarkan aturan tertentu. Satu satunya jaminan adalah semua kunci pada Map akan muncul.
Berikut ini kode kelas WordCount secara keseluruhan: import java.util.*;
public class WordCount { HashMap Map;
public WordCount () {
Map = new HashMap (); }
public void processLine (String s) { StringTokenizer st = new StringTokenizer (s, " ,.");
412
while (st.hasMoreTokens()) { String word = st.nextToken(); processWord (word.toLowerCase ()); } }
public void processWord (String word) { if (Map.containsKey (word)) { integer i = (integer) Map.get (word); integer j = new integer (i.intValue() + 1); Map.put (word, j); } else { Map.put (word, new integer (1)); } }
public void print () { Set set = Map.keySet(); Iterator it = set.iterator(); while (it.hasNext ()) { String kunci = (String) it.next (); integer value = (integer) Map.get (kunci); System.out.println ("{ " + kunci + ", " + value + " }"); } }
public static void main(String [] args){ WordCount wc = new WordCount ();
413
wc.processLine ("you spin me right round baby " + "right round like a record baby " + "right round round round"); wc.print (); } }
19.4 Implementasi Dengan Vector Cara yang mudah untuk mengimplementasikan Map ADT adalah menggunakan entri dengan struktur data Vector, dimana setiap entri adalah sebuah objek yang berisi sebuah kunci dan sebuah nilai. Definisi kelas dari Entry seperti di bawah ini:
class Entry { Object kunci, value; public Entry (Object kunci, Object value) { this.kunci = kunci; this.value = value; } public String toString () { return "{ " + kunci + ", " + value + " }"; } }
Lalu implementasi dari Map seperti di bawah ini:
414
public class MyMap { Vector entries; public Map () { entries = new Vector (); } }
Untuk menaruh entri baru pada Map, kita hanya menambah sebuah objek Entry pada Vector: public void put (Object kunci, Object value) { entries.add (new Entry (kunci, value)); }
Kemudian untuk melihat sebuah kunci pada Map kita harus menjelajahi Vector dan mencari sebuah Entry dengan kunci yang sesuai.
public Object get (Object kunci) { Iterator it = entries.iterator (); while (it.hasNext ()) { Entry entry = (Entry) it.next (); if (kunci.equals (entry.kunci)) { return entry.value; } } return null;
415
} Cara untuk menjelajahi Vector telah kita lihat pada bagian 17.10. Ketika kita membandingakan kunci, kita menggunakan metode equal (deep quality) daripada operator == (shallow quality). Hal ini membuat diperbolehkannya tipe data kunci untuk menspesifikasikan definisi persamaan yang digunakan, Pada contoh di atas, tipe data kunci adalah String, jadi metode get akan menggunakan metode equals yang ada pada kelas String.
Karena equals adalah metode objek. Implementasi dari get tidak bekerja jika kunci berisi null, Untuk mengatasi kunci yang berisi null, kita harus menambahkan kasus khusus untuk mengambil dan menulis sebuah metode pada kelas yang membandingkan kunci dan mengurusi parameter null dengan aman. Tapi hal ini tidak akan kita bahas secara detail.
Sekarang kita telah memiliki metode get, kita bisa menulis versi dari put yang lebih lengkap. Jika telah terdapat entri pada Map dengan kunci yang diberikan, put akan memperbaharuinya( memberinya nilai yang baru), dan mengembalikan nilai yang lama( atau null jika belum ada). ini adalah implementasi metode yang menyediakan fitur ini: public Object put (Object kunci, Object value) { Object result = get (kunci); if (result == null) { Entry entry = new Entry (kunci, value); entries.add (entry); } else { update (kunci, value);
416
} return result; } private void update (Object kunci, Object value) { Iterator it = entries.iterator (); while (it.hasNext ()) { Entry entry = (Entry) it.next (); if (kunci.equals (entry.kunci)) { entry.value = value; break; } }
Metode update bukan merupakan bagian dari Map ADT, jadi ia dideklarasikan private. Ia menjelajahi Vector sampai mendapatkan objek Entry yang tepat lalu memparbaharui atribut nilai nya. Perlu diperhatikan bahwa kita tidak memodifikasi Vector, kita hanya salah satu objek yang ada di dalamnya.
Metode yang belum kita implementasikan adalah containsKey dan keySet. Metode containsKey hampir sama dengan metode get kecuali ia mengembalikan nilai true atau false sebagai ganti objek atau null.
Latihan 19.1 Implementasikan metode keySet dengan membuat dan mengembalikan objek TreeSet. Gunakan implementasi TreeSet anda pada bab 17.7 atau implementasi yang telah disediakan pada paket java.util.TreeSet
417
Implementasi lengkap Map menggunakan Vector: import java.util.*; public class MyMap { Vector entries;
public MyMap () { entries = new Vector (); }
public Object get (Object kunci) { Iterator it = entries.iterator (); while (it.hasNext ()) { Entry entry = (Entry) it.next (); if (kunci.equals (entry.kunci)) { return entry.value; } } return null; }
public Object put (Object kunci, Object value) { Object result = get (kunci); if (result == null) { Entry entry = new Entry (kunci, value); entries.add (entry); } else {
418
update (kunci, value); } return result; }
private void update (Object kunci, Object value) { Iterator it = entries.iterator (); while (it.hasNext ()) { Entry entry = (Entry) it.next (); if (kunci.equals (entry.kunci)) { entry.value = value; break; } } }
public Set keySet(){ Iterator it = entries.iterator ();
Set s = new TreeSet(); while (it.hasNext ()) { Entry entry = (Entry) it.next (); s.add(entry.kunci); } return s; }
public boolean containsKey(Object kunci){ if(get(kunci)==null)
419
return false; else return true; }
} class Entry {
Object kunci, value;
public Entry (Object kunci, Object value) {
this.kunci = kunci;
this.value = value; }
public String toString () {
return "{ " + kunci + ", " + value + " }"; }
}
Contoh kelas WordCount menggunakan implementasi Map dengan Vector import java.util.*;
public class WordCountVector { MyMap Map; public WordCountVector () {
Map = new MyMap (); }
420
public void processLine (String s) { StringTokenizer st = new StringTokenizer (s, " ,."); while (st.hasMoreTokens()) { String word = st.nextToken(); processWord (word.toLowerCase ()); } }
public void processWord (String word) { if (Map.containsKey (word)) { integer i = (integer) Map.get (word); integer j = new integer (i.intValue() + 1); Map.put (word, j); } else { Map.put (word, new integer (1)); } }
public void print () { Set set = Map.keySet(); Iterator it = set.iterator(); while (it.hasNext ()) { String kunci = (String) it.next (); integer value = (integer) Map.get (kunci); System.out.println ("{ " + kunci + ", " + value + " }"); } }
421
public static void main(String [] args){ WordCountVector wc = new WordCountVector (); wc.processLine ("you spin me right round baby " + "right round like a record baby " + "right round round round"); wc.print ();
} }
19.5 Kelas Abstrak List Paket java.util mendefinisikan sebuah kelas abstrak yang menspesifikasikan himpunan operasi yang sebuah kelas harus mengimplementasikan agar dapat disebut List, Ini tidak berarti bahwa setiap kelas yang mengimplementasikan List harus merupkan struktur data linked list.
Tidak aneh bahwa kelas bawaan LinkedList adalah implementasi dari List. Dan yang mengejutkan kelas Vector juga merupakan implementasi dari kelas List.
Metode metode pada definisi List merliputi add, get, dan iterator. Pada kenyataannya, semua metode dari kelas Vector yang digunakan untuk mengimplementasikan Map didefinisikan pada kelas abstrak List. Ini berarti sebagai ganti kelas Vector, kita dapat menggunakan implementasi kelas abstrak List apa pun. Pada implementasi Map yang telah kita buat kita dapat mengganti Vector dengan LinkedList dan program masih bisa
422
bekerja dengan baik.
Tipe umum seperti ini dapat berguna untuk meningkatkan performa sebuah program. Anda dapat membuat program dalam bentuk kelas abstrak seperti List lalu mengetes program tersebut dengan beberapa implementasi yang berbeda untuk melihat implementas mana yang menghasilkan performa terbaik.
19.6 Implementasi dengan HashMap Alasan implementasi bawaan Map ADT disebut HashMap karena ia menggunakan sebuah implementasi Map yang efisien menggunakan tabel hash. Untuk lebih memahami implementasi HashMap dan mengapa ia dianggap efisien. Kita akan memulai dengan menganalisa performa dari implementasi List.
Dengan melihat pada implementasi put, kita melihat bahwa terdapat dua kasus. Jika kunci belum terdapat dalam Map, kita lalu hanya akan membuat entri baru dan menambahkannya pada Map, kedua operasi ini merupakan operasi dengan waktu yang konstan.
Pada kasus lain, kita harus menjelajahi List untuk mencari suatu entri. Yang merupakan operasi dengan waktu linear. Untuk alasan yang sama get dan containsKey juga memiliki waktu yang linear.
Meskipun operasi linear sering sudah dianggap mencukupi , tetapi kita dapat melakukannya lebih baik. Ada sebuah cara untuk mengimplementasikan Map ADT sehingga put dan get dilakukan pada waktu yang konstan.
423
Kuncinya adalah pengetahuan tentang waktu penjelajahan List yang linear dengan panjang List. Jika kita dapat meletakkan batas atas pada panjang List maka kita dapat meletakkan batas atas pada waktu penjelajahan, maka apapun dengan batas atas yang pasti adalah waktu konstan yang bisa diperhitungkan.
Tetapi bagaimana kita dapat membatasi panjang List tanpa membatasi banyak item pada Map? Solusinya adalah dengan menambah jumlah List. Sebagai ganti satu List yang panjang kita akan menggunakan banyak List pendek. Sepanjang kita tahu List mana yang akan dicari, kita dapat meletakkan batas pada jumlah pencarian.
19.7 Fungsi Hash Dan itulah dimana fungsi hash bisa dipergunakan. Kita membutuhkan cara untuk melihat sebuah kunci dan tahu tanpa mencari, pada List mana ia berada. Kita akan mengasumsikan bahwa List ada di dalam array (atau Vector) jadi kita bisa mereferensinya dengan dengan index.
Solusi yang bisa dilakukan adalah dengan menggunakan teknik pemetaan. Untuk setiap kemungkinan kunci dipastikan terdapat sebuah indeks tunggal, tetapi ada kemungkinan banyak kunci yang dipetakan pada index yang sama.
Sebagai contoh, Sebuah array dengan 8 List dan sebuah Map yang dibuat dengan kunci bertipe integer dan nilai yang bertipe String. Untuk mendapatkan indeks digunakan metode intValue pada kelas integer sebagai indeks, yang merupakan tipe yang benar,
424
tetapi ada banyak bilangan integer (bulat) yang tidak berada antara 0 dan 7, padahal indeks yang legal harus berada antara 0 dan 7.
Operator modulus menyediakan cara yang sederhana dan efisien untuk memetakan semua bilangan bulat (integer) antara 0 dan 7. Perhitungan kunci.intValue % 8 dijamin menghasilkan nilai dalam rentang 7 sampai 7 (termasuk keduanya). Jika anda mengambil nilai absolut (menggunakan Math.abs) anda akan mendapatkan indeks yang sah. Untuk tipe yang lain, dapat diterapkan cara yang sama. Misalnya untuk mengkonversi variabel bertipe Character menjadi bilangan bulat, kita dapat menggunakan metode bawaan Character.getNumericValue dan untuk tipe Double dapat digunakan metode intValue.
Untuk String kita bisa mendapatkan nilai bilangan tiap karakter dan menambahkannya, atau kita mungkin bisa menggunakan operasi shifted sum. Untuk menghitung nilai shifted sum, anda secara bergiliran menambahkan nilai baru kedalam akumulator dan menggeser akumulator ke kiri. “geser ke kiri” berarti “menambahkan dengan bilangan konstan.”
Untuk melihat bagaimana ini bekerja, ambil daftar bilangan 1, 2, 3, 4, 5, 6 dan hitung shifted sum dengan cara sebagai berikut, pertama inisialisasi akumulator dengan 0, lalu 1. Kalikan akumulator dengan 10. 2. Tambahkan elemen berikut dari daftar pada akumulator. 3. Ulangi sampai semua bilangan pada daftar telah dikalikan semua.
425
Sebagai latihan, tulis sebuah metode yang menghitung shifted sum nilai bilangan semua karakter pada String menggunakan pengali 16.
Untuk setiap tipe, kita bisa membuat fungsi yang mengambil nilai dengan tipe tersebut dan menghasilkan nilai bulat yang bersesuaian. Fungsi ini dinamakan fungsi hash, karena mereka sering terlibat membuat sebuah hash pada komponen sebuah objek. Nilai hash dari sebuah objek disebut hash code.
Ada satu cara lain yang dapat dilakukan untuk mengasilkan hash code untuk objek Java. Setiap objek java menyediakan metode hashCode yang mengembalikan integer yang bersesuaian dengan objek tersebut. Untuk tipe bawaan java, metode hashCode diimplementasikan sehingga jika dua objek yang memiliki data yang sama, mereka akan memiliki hash code yang sama. Dokumentasi dari metode ini menjelaskan apa itu fungsi hash. Anda lebih baik mengeceknya untuk mengetahui lebih lanjut.
Untuk tipe yang didefinisikan pengguna, hal ini tergantung pada pengimplementasi untuk menyediakan fungsi hash yang sesuai. Fungsi hash default, yang disediakan pada kelas Object, Sering menggunakan lokasi objek untuk mengasilkan hash code, jadi notasi nya untuk suatu kesamaan adalah “shallow equality”. Sering ketika kita mencari sebuah kunci pada Map kesamaan yang menggunakan shallow equality bukan merupakan yang kita inginkan.
Terlepas dari bagaimana cara menghasilkan hash code, langkah terakhir adalah menggunakan modulus dan fungsi nilai absolut untuk memetakan hash code pada
426
rentang indeks yang legal.
19.8 Mengubah Ukuran Hash Map. Sebuah hash table berisi sebuah array (atau Vector) yang elemennya merupakan sebuah List. Dimana setiap List berisi jumlah entri yang kecil. Untuk menambah entri baru pada Map, kita menghitung hash code pada kunci yang baru dan menambah entri tersebut pada List yang sesuai. Untuk mencari sebuah kunci, kita menghitung hash lagi dan mencarinya pada List yang sesuai. Jika panjang List terbatas maka waktu pencarian juga terbatas.
Jadi bagaimana cara membuat List tetap pendek? Salah satu tujuan adalah bagaimana sebisa mungkin semua List tersebut tetap seimbang. Jadi tidak akan ada List yang sangat panjang dan pada saat yang sama List lain kosong. Hal ini bukan hal yang mudah untuk dilakukan secara sempurna, ia tergantung pada seberapa baik kita memilih fungsi hash.
Bahkan dengan keseimbangan yang sempurna, rata rata panjang List berkembang secara linear dengan jumlah entri. Dan kita harus meletakkan titik henti pada saat itu.
Solusinya adalah dengan mengikuti rata rata jumlah entri untuk setiap List, yang disebut load factor. Jika load factor terlalu tinggi kita harus mengubah ukuran hash table.
Untuk mengubah ukuran, kita membuat hash table baru, biasanya dua kali lebih besar dari yang asli, ambil semua entri yang ada pada hash table yang lama. Mengenainya dengan fungsi hash. Dan meletakkannya kembali pada hash table yang baru. Biasanya kita bisa menggunaka fungsi hash yang sama. Kita hanya menggunakan nilai yang
427
berbeda untuk operator modulus.
19.9 Kinerja dari pengubahan ukuran
Berapa lama waktu yang dibutuhkan untuk mengubah ukuran hash table? Yang jelas ia sebanding dengan banyak entri. Itu berarti sebagian besar waktu put membutuhkan waktu yang konstan, tetapi sesekali ketika kita melakukan pengubahan ukuran ia akan membutuhkan waktu yang linear.
Pada awalnya hal ini memang buruk. Bukankah ini menyalahi klaim saya bahwa kita melakukan put dengan waktu yang konstan? Terus terang ya. Tetapi dengan sedikit pengaitan, kita bisa memperbaikinya.
Beberapa operasi put membutuhkan waktu yang lebih lama dibandingkan dengan yang lain, mari kita gambarkan waktu rata rata dalam operasi put. Rata rata dilambangkan dengan c yaitu waktu konstan untuk put yang sederhana. Ditambah dengan p, prosentase waktu sebelum dilakukan pengubahan ukuran, di kali dengan kn, maka biaya untuk mengubah ukuran adalah. t(n) = c + p ∙ kn (19.1) Kita tidak tahu melambangkan apa c dan k, tetapi kita bisa menggambarkan apakah p itu. Bayangkan kita baru saja mengubah ukuran hash Map dengan memperbesar dua kali ukurannya. Jika terdapat n entri lalu kita dapat menambahkan tambahan n entri sebelum kita harus mengubah ukrannya lagi. Jadi persentasi waktu sebelum harus
428
dilakukan pengubahan ukuran kembali adalah 1/n.
Dengan dimasukkan pada persamaan maka kita dapatkan t(n) = c + 1/n ∙ kn = c + k (19.2) Dengan kata lain, t(n) adalah waktu yang konstan.
19.10 Daftar Kata map : Sebuah ADT(Abstract Data Type) yang mendefinisikan operasi pada kumpulan entri. entri : Elemen pada map yang berisi sebuah kunci dan sebuah nilai. kunci : Indeks , dapat berupa objek apapun, digunakan untuk mencari nilai dalam map. value : Elemen, dapat berupa objek apapun, disiman dalam map. dictionary : nama lain dari map. array assosiatif : nama lain dari dictionary. hash map : Sebuah implementasi efisien dari map. fungsi hash : fungsi yang memetakan nilai pada tipe tertentu kepada bilangan bulat. hash code: bilangan bulat yang berhubungan dengan sebuah nilai. shifted sum : fungsi hash sederhana yang sering digunakan untuk objek majemuk seperti String load factor : banyak entri dalam hashmap dibagi dengan banyak List dalam hashmap; sebagai contoh rata rata banyak entri tiap List.
429
19.11 Latihan latihan 19.2 a. Hitung shift sum dari bilangan bilangan 1, 2, 3, 4, 5, 6 menggunakan pengali 10 b. Hitung shift sum dari bilangan bilangan 11,1 2,1 3,1 4,15, 16 menggunakan pengali 100 c. Hitung shift sum dari bilangan bilangan 11,1 2,1 3,1 4,15, 16 menggunakan pengali 10
latihan 19.3 Bayangkan anda memiliki sebuah hash table yang berisi 100 List. Jika anda ingin mengambil sebuah nilai yang bersesuaian dengan kunci tertentu, dan kode hash dari kunci tersebut adalah 654321, pada indeks List keberapakah kunci tersebut akan berada (jika berada dalam map)?
Latihan 19.4 Jika terdapat 100 List dan terdapat 89 entri pada map dan List terpanjang berisi 3 entri, dan panjang list tengah (median) adalah 1 dan 19% list adalah kosong, berapakah load factor nya?
Latihan 19.5 Bayangkan terdapat banyak orang pada sebuah pesta. Anda ingin mengetahui apakah ada diantara mereka yang memiliki hari ulang tahun yang sama. Pertama anda mendesain sebuah objek Java yang mewakili hari ulang tahun dengan dua variabel instance: bulan yang merupakan bilangan bulat antara 1 dan 12, dan hari yang merupakan bilangan bulat antara 1 dan 31. 430
Selanjutnya, anda membuat sebuah array dari objek hariulangtahun yang berisi satu objek untuk tiap orang pada pesta. a. Tulis sebuah metode equals untuk hariulangtahun sehingga hari ulang tahun dengan bulan dan hari yang sama adalah sama (equal). b. Tulis sebuah metode bernama hasDuplicate yang mengambil sebuah array objek hariulangtahun dan mengembalikan true jika terdapat dua atau lebih orang yang memiliki hari ulang tahun sama. Algoritma anda harus hanya menjelajahi array objek hariulangtahun sekali. c. Tulis sebuah metode yang bernama randomBirthdays dengan parameter integer n dan mengembalikan array dengan n objek hariulangtahun yang atribut atribut nya diisi secara acak. Agar lebih sederhana, anda bisa mengganggap semua bulan adalah 30 hari. d. Bangkitkan 100 array acak, masing masing array 10 objek hariulangtahun, dan lihat berapa banyak array yang berisi hari ulang tahun yang sama. e. Jika terdapat 20 orang pada pesta, berapa kemungkinan satu atau lebih peserta pesta memiliki hari ulang tahun yang sama.
Latihan 19.6 Sebuah Map disebut invertible jika setiap kunci dan setiap nilai hanya muncul sekali. Pada HashMap selalu benar bahwa setiap kunci hanya muncul sekali, tetapi ada memnungkinkan nilai yang sama muncul beberapa kali. Itulah mengapa, beberapa HashMap invertible, dan beberapa tidak. Tulis sebuah metode yang bernama isInvertible dengan parameter HashMap dan mengembalikan true jika map invertible dan false jika tidak.
431
Latihan 19.7 Tujuan latihan ini adalah untuk mencari 20 kata kata yang paling sering dipakai pada buku ini (buku ini yang masih dalam bahasa inggris). Tulislah kode yang menggunakan HashMap untuk menghitung frekuensi kemunculan kata. Download teks buku pada alamat http://kleq.web.ugm.ac.id/images/thinkapjava.txt. Pikirkan bagaimana seharusnya program anda menangani tanda baca dan hal hal lain yang muncul pada berkas.
Tulis program untuk menjawab pertanyaan pertanyaan di bawah ini: a. Berapa banyak kata yang ada pada buku ini? b. Berapa banyak kata yang berbeda muncul pada buku ini? c. Berapa kali kata “encapsulation” muncul? d. Apa saja 20 kata kata yang paling sering muncul? Petunjuk: Ada struktur data lain yang mungkin bisa membantu untuk bagian latihan ini.
Latihan 19.8 Tulis sebuah metode untuk LinkedList yang mengecek apakah list mengandung loop. Anda tidak boleh mengasumsikan bahwa panjang List telah benar.
Latihan 19.9 Tulis implementasi Map ADT yang menggunaka hash table(seperti yang didefinisikan pada bagian 19.2). Ujilah dengan program program yang telah anda buat sebelumnya. a. Mulailah dengan implementasi Vector pada buku ini b. Ubah implementasi dengan menggunakan array dari LinkedList atau Vector yang berisi LinkedList. Latihan 19.10 432
Buat implementasi Set menggunakan HashMap
Latihan 19.11 Paket java.util menyediakan dua implementasi Map, yaitu HashMap dan TreeMap, HashMap dibangun dengan meniru hash table seperti yang dijelaskan pada bab ini. TreeMap dibuat berdasarkan redblack tree, yang mirip dengan pohon pencarian pada latihan 17.6. Meskipun kedua implementasi ini meyediakan antar muka yang sama, kita mengharapkannya mencapai kinerja yang berbeda. Sejalan dengan banyak entri n bertambah, kita berharap add dan contains menggunakan waktu konstan untuk implementasi hash table. Dan waktu yang logaritmik untuk implementasi dengan pohon.
Lakukan percobaan untuk mengkonfirmasi (atau membantah) prediksi ini, Buat sebuah program yang menambah n entri pada HashMap atau TreeMap, lalu panggil contains untuk setiap kunci pada entri tersebut. Catat waktu pengeksekusian program terhadap nilai n dan buat grafiknya. Apakah kinerja sesuai dengan perkiraan kita?
433
BAB 20 Kode Huffman 20.1 Variabellength codes Jika anda familiar dengan kode morse, anda mengetahui bahwa morse adalah sistem untuk mengkodekan huruf pada alfabet menjadi titik dan garis. Misalnya sinyal terkenal ...... mewakili huruf SOS yang termasuk kode panggilan internasional untuk meminta pertolongan. Tabel berikut memperlihatkan kode tersebut:
Perhatikan bahwa beberapa kode lebih panjang dibandingkan dengan yang lain. Pada perancangan, huruf yang paling sering digunakan memiliki kode terpendek. Karena terbatasnya kode pendek sehingga huruf huruf dan simbol yang lain memiliki kode yang lebih panjang. Suatu pesan akan memiliki lebih banyak kode pendek dibandingkan kode yang panjang, sehingga meminimalisasi rata rata transmisi per huruf.
434
Kode seperti ini disebut variablelength codes. Pada bab ini kita akan melihat algoritma untuk membangkitkan variablelength codes yang disebut kode Huffman. Huffman merupakan algoritma yang menarik, tetapi juga merupakan latihan yang berguna karena implementasinya menggunakan banyak struktur data yang telah kita pelajari. Di bawah ini garis besar untuk beberapa bagian berikutnya: ●
Pertama, kita akan menggunakan sebuah contoh teks dalam bahas inggris untuk menghasilkan tabel frekuensi. Tabel frekuensi mirip seperti histogram, ia menghitung berapa kali setiap huruf muncul pada teks contoh.
●
Inti dari kode huffman adalah pohon huffman. Kita akan menggunakan tabel frekuensi untuk membangun pohon huffman, lalu menggunakan pohon untuk mengkodekan dan memecah kode dari rangkaian yang ada.
●
Langkah terakhir, kita akan menjelajahi phon huffman dan memangun tabel kode, yang berisi rangkaian titik dan garis untuk setiap pohon.
20.2 Tabel frekuensi Karena tujuannya adalah memberikan kode pendek pada kata yang umum, kita harus tahu berapa kali setiap huruf muncul. Pada cerita pendek Edgar Allen Poe “The Gold Bug”, salah satu karakter menggunakan frekuensi huruf untuk memecahkan chiper. Dia menjelaskan
“Sekarang dalam bahasa inggris, huruf yang paling sering muncul adalah e, setelah itu a o i d h n r s t u y c f g l m w b k p q x z”.
Jadi misi pertama kita adalah melihat apakah Poe benar.
435
Latihan 20.12 Tulis sebuah kelas yang bernama FreqTab yang menghitung berapa kali setiap huruf muncul pada teks contoh. Download contoh teks cerita pendek favorit anda, dan analisa frekuensi masing masing huruf.
Saya menemukan bahwa lebih tepat membuat kelas FreqTab diturunkan dari HashMap. Lalu menulis metode increment yang mengambil huruf sebagai parameter dan menambah atau memperbaharui entri dalam HashMap untuk setiap huruf.
Anda dapat menggunakan keySet untuk mengambil entri pada HashMap, dan mencetak daftar huruf dan frekuensinya. Sayangnya, mereka tidak akan tampil secara berurutan. Latihan berikut menyelesaikan masalah ini.
Latihan 20.13 Tulis sebuah kelas yang bernama Pair yang mewakili pasangan huruf dan frekuensi. Objek pair harus berisi sebuah huruf dan frekuensi sebagai variabel instance. Pair harus mengimplementasikan Comparable sehingga objek Pair dengan frekuensi lebih tinggilah yang menang.
Sekarang urutkan pasangan huruf dan frekuensi dari HashMap dengan menjelajahi himpunan kunci, buat objek Pair, tambahkan Pair pada PriorityQueue, ambil Pair dari PriorityQueue dan cetak dengan urutan mundur berdasarkan frekuensi.
436
20.3 Pohon Huffman Langkah berikutnya adalah membangun pohon huffman. Setiap simpul pada pohon berisi sebuah huruf dan frekuensinya, serta penunjuk ke simpul kiri dan kanan. Untuk membangun pohon Huffman, kita mulai dengan membuat himpunan pohon tunggal, satu dari setiap entri pada tabel frekuensi. Lalu kita membangun pohon dari bawah ke atas, dimulai dari huruf dengan frekuensi terendah dan secara berulang menggabungkan subphon sampai mendapatkan pohon tunggal yang berisi semua huruf.
Di bawah ini algoritma untuk membangun pohon Huffman secara lebih detil. 1. Untuk setiap entri pada tabel frekuensi, buat pohon Huffman tunggal dan tambahkan pada PriorityQueue. Sehingga ketika kita mengambil pohon dari PriorityQueue, kita mendapatkan yang memiliki frekuensi terendah. 2. Ambil dua pohon dari PriorityQueue dan gabungkan dengan membuat sebuah simpul orang tua yang menunjuk pada pohon yang diambil tadi. Frekuensi simpul orang tua adalah jumlahan dari frekuensi anak anaknya. 3. Jika PriorityQueue kosong, maka algoritma selesai, jika tidak letakkan pohon baru pada PriorityQueue kembali ke langkah 2.
Sebagai contoh kita akan menggunakan teks di bawah untuk membentuk pohon Huffman.
Eastern Tennessee anteaters ensnare and eat red ants, detest ant antennae (a tart taste) and dread Antarean anteatereaters. Rare Andean deer eat tender sea reeds, aster seeds and rats’ ears. Dessert? Rats’ asses. 437
Teks di atas menghasilkan tebel frekuensi: e
40
a
32
t
24
s
22
n
20
r
19
d
13
Jadi setelah langkah 1, PriorityQueue akan terlihat seperti:
Pada langkah 2, kita mengambil dua pohon dengan frekuensi terendah (r dan d) dan menggabungkannya dengan membuat simpul orang tua dengan frekuensi 32. Nilai huruf untuk simpul internal kurang relevan, sehingga dihapus dari gambar. Ketika kita meletakkan pohon baru kembali ke PriorityQueue. Hasilnnya akan seperti di bawah:
Sekarang ulangi langkah sebelumnya, dengan menggabungkan s dan n:
Setelah iterasi berikutnya, kita memiliki kumpulan pohon seperti di bawah, Sebagai 438
informasi kumpulan pohon biasa disebut hutan.
Setelah dua iterasi lagi, hanya akan ada tiga pohon yang tertinggal:
Ini adalah pohon Huffman untuk teks contoh. Sebenarnya, ia bukan satu satunya karena setiap kali kita menggabungkan dua pohon, kita memilih yang mana menjadi simpul kanan dan yang mana di kiri, dan ketika ada pohon yang sama pada PriorityQueue, kita memilih tergantung selera. Jadi akan ada banyak kemungkinan pohon pada contoh di atas. Jadi bagaimana kita mengambil sebuah kode dari pohon Huffman? Kode untuk setiap huruf dihitung dengan menelusuri jalan dari akar pohon sampai daun yang berisi huruf yang dimaksud. Misalnya jalan dari akar sampai s adalah kirikanankiri. Jika kita mengganti kiri dengan “.” dan kanan dengan “” kita mendapatkan tabel kode di bawah: e
.
a
439
t
..
s
..
n
.
r
....
d
...
Perhatikan bahwa kita telah mencapai tujuan: huruf dengan frekuensi terbanyak memiliki kode paling pendek. Latihan 20.14 Dengan perhitungan manual, gambarkan pohon Huffman untuk tabel frekuensi berikut: e
93
s
71
r
57
t
53
n
49
i
44
d
43
o
37
20.4 Metode super Salah satu cara untuk mengimplementasikan pohon Huffman adalah dengan menurunkannya dari kelas Pair dari latihan 20.13 public class HuffTree extends Pair implements Comparable { HuffTree left, right; public HuffTree (int freq, String letter, HuffTree left, HuffTree right) { 440
this.freq = freq; this.letter = letter; this.left = left; this.right = right; } } HuffTree mengimplementasikan Comparable sehingga kita bisa meletakkan pohon Huffman ke dalam PriorityQueue. Untuk mengimplementasikan Comparable, kita harus menyediakan metode compareTo. Kita bisa menuliskannya dari awal, tetapi akan lebih mudah jika menggunakan versi compareTo dari kelas Pair.
Sayangnya, metode tersebut tidak melakukan apa tepatnya yang kita inginkan. Untuk Pair kita memberikan prioritas untuk frekuensi yang lebih tinggi. Sedang pada pohon Huffman, kita menginginkan memberikan prioritas pada frekuensi yang lebih rendah. Tentu saja, kita bisa menuliskan versi baru dari compareTo tetapi itu tentunya akan menutupi (override) versi pada kelas di atasnya (kelas orang tua), padahal kita ingin bisa memanggil versi metode yang ada pada kelas orang tua.
Kata kunci super mengijinkan kita untuk memanggil metode yang telah ditutup. Ia disebut super karena kadang kadang kelas orang tua juga disebut superclasses. Berikut contoh dari implementasi HuffTree: public int compareTo (Object obj) { return super.compareTo (obj); }
441
Ketika compareTo dipanggil pada HuffTree, ia akan memanggil versi compareTo pada kelas orang tua, dan menegatifkan hasilnya. Sehingga urutan prioritas menjadi terbalik.
Ketika kelas anak (juga disebut subclass) menutupi constructor, ia bisa memanggil konstruktor kelas orang tua dengan menggunakan super: public HuffTree (int freq, String letter, HuffTree left, HuffTree right) { super (freq, letter); this.left = left; this.right = right; } Pada contoh ini, konstruktor orang tua menginisialisasi freq dan letter, dan lalu konstruktor anak menginisialisasi left dan right.
Meskipun fitur ini berguna, ia juga memilliki kecenderungan untuk salah. Ada beberapa keterbatasan seperti misal konstruktor orang tua harus dipanggil pertama, sebelum variabel instance lain diinisialisasi, dan ada beberapa hal yang tidak ingin anda ketahui. Secara umum, mekanisme ini seperti perlengkapan p3k. Ketika anda mengalami masalah ia bisa membantu anda. Tetapi anda tahu apa yang lebih baik? Jangan masuk ke dalam masalah. Pada masalah ini, lebih mudah untuk menginisialisasi kesemua empat variabel instance pada konstruktor anak.
Latihan 20.15 Tulis definisi kelas HuffTree dari bagian ini dan sebuah kelas dengan metode build yang mengambil sebuah FreqTab dan mengembalikan sebuah HuffTree. Gunakan algoritma
442
pada bagian 20.3.
20.5 Pemecahan Kode Ketika kita menerima pesan yang dikodekan, kita menggunakan pohon Huffman untuk memecah kodenya. Di bawah ini algoritma untuk melakukan hal tersebut: 1. Mulai dari akar pohon Huffman 2. Jika simbol berikutnya adalah “.”, maka berlanjut ke anak sebelah kiri, jika tidak maka berlanjut ke anak sebelah kanan. 3. Jika anda berada pada simpul daun. Ambil hurufnya dan tambahkan pada hasil. Lalu kembali ke akar. 4. Ke langkah 2.
Perhatikan kode ...., sebagai contoh. Mulai dari puncak pohon, kita menuju kirikiri kanan dan mendapatkan huruf t. kemudian kita mulai dari akar lagi, kanankiri dan mendapatkan huruf e, kembali ke puncak, lalu kanankiri dan kita mendapatkan huruf a. jika kodenya dibentuk dengan baik, kita seharusnya ada pada simpul daun ketika kode berakhir. Pada kasus ini pesannnya adalah tea.
Latihan 20.16 Gunakan contoh pohon Huffman untuk memecahkan kode berikut: a. ..... b. ............ c. ......... d. .....
Perhatikan ketika anda mulai memecahkan kode, anda tidak dapat mengetahui berapa 443
banyak huruf pada kode atau berada di mana batas batas hurufnya.
Latihan 20.17 Tulis definisi kelas Huffman. Konstuktor memiliki parameter String yang mengandung contoh teks. Kelas harus bisa membuat tabel frekuensi dan pohon Huffman. Buat metode yang bernama decode. Metode ini menggunakan parameter dalam bentuk titik dan garis dan menggunakan pohon Huffman untuk memecahkan kode dan mengembalikan hasilnya.
Catatan: Walaupun anda menggunakan contoh kalimat pada bagian 20.2, anda tidak akan mendapatkan pohon Huffman yang sama seperti pada bagian 20.3, jadi kemungkinan anda tidak akan bisa menggunakan program anda untuk memecahkan kode pada contoh latihan sebelumnya.
20.6 Pengkodean Biasanya, pengkodean lebih sulit dibandingkan dengan pemecahan kode, karena untuk suatu huruf kita harus mencari simpul daun pada pohon yang berisi huruf tersebut dan dilanjutkan dengan menggambarkan lintasan dari akar sampai simpul tersebut.
Proses ini akan lebih efisien jika kita menjelajahi pohon sekali, memproses semua kode, dan membangun Map dari huruf ke kode (huruf sebagai kunci dan kode sebagai nilai).
Dari sekarang kita telah melihat banyak pohon penjelajahan, tetapi yang satu ini tidak biasa karena saat kita menjelajahi pohin, kita juga ingin menyimpan lintasan/jalan dimana kita berada. Awalnya ini memang kelihatan sulit, tetapi ada cara yang lebih alami untuk melakukan perhitungan secara rekursif. Kuncinya adalalah jika lintasan dari 444
akar ke suatu simpul disimbolkan dengan rangkaian titik dan garis yang disebut lintasan, maka lintasan ke anak kiri pada simpul adalah lintasan + '' dan lintasan ke anak sebelah kanan adalah lintasan + '.'.
Latihan 20.18 1. Tulis definisi kelas dari CodeTab, yang diturunkan dari HashMap. 2. Pada definisi CodeTab, tulis metode rekursif yang bernama getCodes. Metode ini menjelajahi pohon Huffman. Ketika ia mencapai simpul daun, ia akan mencetak huruf pada simpul dan kode yang menyimbolkan lintasan dari akar ke simpul tersebut. 3. Tulis konstruktor untuk CodeTab yang mengambil HuffTree sebagai parameter dan mengeksekusi getCodes untuk membangun tabel kode. 4. Pada kelas Huffman, tulis metode encode yang menjelajahi sebuah kalimat, mencari setiap karakter pada tabel kode dan mengembalikan kalimat yang telah dikodekan. Uji metode ini dengan melewatkan hasilnya pada decode dan lihat apakah ia mengembalikan kalimat yang asli.
20.7 Daftar Kata hutan : kumpulan pohon superclass : nama lain dari kelas orang tua subclass : nama lain dari kelas anak. super : kata kunci yang digunakan untuk memanggil metode yang ditutupi pada superclass.
445
Lampiran Foreach dan Generik Generik Generik adalah kemampuan baru pada Java 1.5 yang digunakan dalam pembentukan Collection (List, Set, dll). Para pengguna Java tentu mengetahui salah satu kelemahan penggunaan Collection sebelumnya adalah adanya keharusan untuk mengcasting Objek saat diambil dari Collection. Proses pengcastingan ini cukup berbahaya karena pengecekan apakah Casting bisa dilakukan dengan benar dilakukan saat runtime, sehingga berisiko menghasilkan RuntimeException.
Untuk mengatasi hal di atas maka saat pembentukan Collection, telah diberitahu lebih dahulu tipe apa yang akan ada di dalam Collection inilah yang disebut generik. Generik dilakukan dengan menambahkan tipe data/tipe objek saat pendeklarasian Collection. Contoh deklarasi lama: List l = new ArrayList(); Set s = new HashSet();
Contoh deklarasi dengan generik: List l = new ArrayList(); Set s = new HashSet();
Penggunaan Generik mengakibatkan dua hal: 1. Kompiler akan mengecek apakah elemen yang ditambahkan ke dalam Collection valid atau tidak sehingga saat anda menambahkan elemen yang bertipe data berbeda dengan yang bisa diterima Collection maka akan terjadi Compile Error.
446
import java.util.*;
class Test{ public static void main (String [] args){ List l = new ArrayList();
l.add(new Integer(1));
} }
Pada kode di atas Collection yang berupa List hanya akan menerima data yang bertipe Integer. Ketika anda mencoba menambahkan tipe data yang lain ke dalam List tersebut maka list tersebut akan error.
import java.util.*;
class Test{ public static void main (String [] args){ List l = new ArrayList(); l.add(new Double(1)); } }
Sebagai contoh saya mencoba memasukkan data bertipe Double. Jika anda menggunakan Java 1.5 ke atas, kode akan mengalami error saat kompilasi, tetapi tidak jika anda menggunakan versi sebelumnya.
2. Dalam pengambilan objek dari Collection anda tidak perlu lagi melakukan pengcastingan karena tipe data yang ada pada Collection telah didefinisikan lebih dahulu.
447
import java.util.*; class Test{ public static void main (String [] args){ List l = new ArrayList(); l.add(new Integer(1)); Integer i=l.get(0); } }
Foreach Foreach adalah syntax baru Java yang juga baru diperkenalkan semenjak Java 1.5. Foreach sangat berguna ketika anda ingin menjelajahi semua elemen dalam satu Collection atau array. Syntax baru ini dikeluarkan karena sebelumnya untuk melakukan penjelajahan pada Collection menggunakan metode Iterator yang cukup rumit dalam penggunaannya. import java.util.*; class Test{ public static void main (String [] args){ List l = new ArrayList(); l.add(1); l.add(2); Iterator iter=l.iterator(); while(iter.hasNext()){ Object i=iter.next(); System.out.println(i); } } }
448
Dalam penggunaan iterator seperti contoh di atas untuk menjelajahi Collection satu persatu diperlukan minimal 2 baris kode, yaitu mengambil objek iterator dan mengiterasinya menggunakan while. Dengan foreach hal di atas bisa disederhanakan import java.util.*;
class Test{ public static void main (String [] args){ List l = new ArrayList(); l.add(1); l.add(2); for(Integer i : l){ System.out.println(i); } } }
Jika sebelumnya untuk menjelajahi Collection dibutuhkan minimal dua baris kode, maka dengan foreach kita hanya membutuhkan satu baris kode. Foreach juga sepenuhnya menggunakan kemampuan generik sehingga kita tidak perlu pengcastingan lebih dahulu.
Secara umum syntax foreach adalah: for( : ) <statemen>
Dalam penggunaannya dalam array, foreach tidak terlalu banyak berguna karena dengan menggunakan perulangan for biasa penjelajahan array bisa dilakukan dengan 449
maksimal. Salah satu kelebihan foreach dibandingkan dengan for dalam penjelajahan array adalah syntax yang sederhana dan tidak perlunya menggunakan indeks saat mengakses elemen dalam array.
import java.util.*; class Test{ public static void main (String [] args){ int [] arr={1,4,5}; for(int i : arr) System.out.println(i); } }
Tetapi dalam menjelajahi array foreach memiliki keterbatasan, bahwa di dalam foreach kita tidak bisa melakukan pemanipulasian terhadap isi array. Karena foreach tidak mereferensi langsung pada isi array tersebut. import java.util.*; class Test{ public static void main (String [] args){ int [] arr={1,4,5}; for(int i :arr) i=i+1; for(int i : arr) System.out.println(i); } }
Kode di atas sama sekali tidak akan mengubah isi array array. Sehingga saat anda mencetaknya yang tampil tetap array yang asli.
450
Daftar Kata Collection : Tipe umum Struktur data Java, struktur data List, Set, Map, dan lain lain diturunkan
dari kelas ini. Biasanya digunakan untuk mengacu pada keseluruhan
struktur data pada Java. RuntimeException : Eksepsi yang dilempar Java ketika terjadi kesalahan saat program Java
dijalankan
451
Jawaban Latihan Bab 2 Latihan 2.1 class Date{
public static void main(String [] args){ String hari; hari="Sabtu"; int tanggal; tanggal=16; String bulan; bulan="September"; int tahun; tahun=2006; System.out.println("Format Amerika:"); System.out.println(hari+", "+bulan+" "+tanggal+", "+tahun); System.out.println("Format Eropa:"); System.out.println(hari+" "+tanggal+" "+bulan+", "+tahun); } }
Latihan 2.2 class Time{ public static void main(String [] args){
452
int jam,menit,detik; jam=6; menit =26; detik=20; int detik_saat_ini; detik_saat_ini=6*3600+26*60+20; System.out.println("Detik saat ini sejak tengah malam adalah "+detik_saat_ini+" detik"); int jumlah_detik_dalam_sehari; jumlah_detik_dalam_sehari=24*3600; int sisa_detik=jumlah_detik_dalam_seharidetik_saat_ini; System.out.println("Sisa detik saat ini = "+sisa_detik+" detik"); int prosentase_sisa_detik; prosentase_sisa_detik=sisa_detik*100/jumlah_detik_dalam_sehari; System.out.println("Prosentase sisa detik saat ini = "+prosentase_sisa_detik+" %"); } }
Bab 3 Latihan 3.1
453
Hour
11
main Minute
59
Hour
11
Minute
59
printTime
Latihan 3.2 No, Iwug. You wugga wug. I wug.
Latihan 3.3 class Latihan{ public static void zool(int a, String b, String c){ }
public static void main(String [] args){ zool(11,"chicken","gajah mada"); } }
454
Latihan 3.4 class Date{
public static void printAmerican(String hari, int tanggal, String bulan, int tahun){ System.out.println(hari+", "+bulan+" "+tanggal+", "+tahun); }
public static void printEuropean(String hari, int tanggal, String bulan, int tahun){ System.out.println(hari+" "+tanggal+" "+bulan+", "+tahun); }
public static void main(String [] args){ String hari; hari="Sabtu"; int tanggal; tanggal=16; String bulan; bulan="September"; int tahun; tahun=2006; System.out.println("Format Amerika:"); printAmerican(hari,tanggal,bulan,tahun); System.out.println("Format Eropa:"); printEuropean(hari,tanggal,bulan,tahun); 455
} }
Latihan 3.5 class Multadd{ public static void multadd(double a, double b, double c){ System.out.println(a*b+c); }
public static void main(String [] args){ multadd(1.0,2.0,3.0); //phi=180' //Math.log10 ada pada java versi 5(1.5) multadd(Math.cos(180/4),0.5,Math.sin(180/4)); multadd(1,Math.log10(10),Math.log10(20)); yikes(3); }
public static void yikes(double x){ multadd(x,Math.exp(x),+(Math.sqrt(1Math.exp(x)))); } }
Bab 4 Latihan 4.1 456
main nLines
n
4
nLines
n
3
n
2
n
1
nLines nLines
Latihan 4.2 public static boolean isTriangle(int a, int b, int c){ if(a>(b+c)) return false;
else if(b>(a+c)) return false;
else if(c>(a+b)) return false;
else return true; }
Bab 6
457
Latihan 6.1.a
Iterasi ke
i
n
1
10
10
2
5
10
3
6
10
4
3
10
5
4
10
6
2
10
7
1
10
8
2
10
9
1
10
b. Loop tak berhingga 10 5 6 4 3 2 1 2 1 . Latihan 6.2. a.public static int fooMethod (String s) { int len = s.length (); 458
int i = 0; int count = 0;
while (i < len) { char c = s.charAt(i);
if (c == '(') { count = count + 1; } else if (c == ')') { count = count 1; } i = i + 1; }
return count; } b. Metode yang jika terdapat kurung buka menambah 1 isi variabel counter dan jika terdapat kurung tutup akan mengurangi 1 nilai counter. Metode ini berguna untuk menentukan apakah suatu ekspresi yang menggunakan tanda kurung telah valid (masing masing memiliki kurung pasangan) atau tidak. c. Pasangan karakter yang akan dicek sebaiknya dapat diubah ubah sesuai keperluan class Test{ public static void main (String[] args) { System.out.println(fooMethod("((3 + 7) * 2)",'(',')'));
459
}
public static int fooMethod (String s, char kar1, char kar2) { int len = s.length ();
int i = 0; int count = 0;
while (i < len) { char c = s.charAt(i);
if (c == kar1) { count = count + 1; } else if (c == kar2) { count = count 1; } i = i + 1; }
return count; } } Latihan 6.3 public static double squareRoot (double number) { double x0=number/2;
460
double x2=100; double x1=90; while(Math.abs(x2x1)>=0.0001){ x1 = (x0 +number/x0)/2; x2= (x1 +number/x1)/2; x0=x2; } return x2; }
Latihan 6.4 public static double power(double x, int n){ double result=1; for(int i=1; i<=n; i++) result*=x; return result; }
Latihan 6.5 public static int faktorial(int x){ int result=1; while(x!=1){ result*=x; x=1; }
461
return result; } Latihan 6.6 a) public class Latihan66 {
public static int faktorial(int x){ int result=1; while(x!=1){ result*=x; x=1; } return result; }
public static double myexp(double x,int n){ double result=1; for(int i=1; i<=n; i++){ result+=power(x,i)/faktorial(i); } return result; }
public static double power(double x, int n){ double result=1; 462
for(int i=1; i<=n; i++) result*=x; return result; } } b. public class Latihan66 {
public static int faktorial(int x){ int result=1; while(x!=1){ result*=x; x=1; } return result; }
public static double myexp(double x,int n){ double result=1; double numerator=1; double denominator=1; for(int i=1; i<=n; i++){ numerator*=x; denominator*=i; result+=numerator/denominator; 463
} return result; }
public static double power(double x, int n){ double result=1; for(int i=1; i<=n; i++) result*=x; return result; } }
c) public static void check(double x){ System.out.println(x+"\t"+Math.exp(x)+"\t"+myexp(x, 100)); }
Latihan 6.7 public static double gauss(double x,int n){ double result=1; double xvalue=1; double denominator=1; int sign=1; for(int i=1; i<=n; i++){ sign=sign*(1); 464
xvalue*=x; denominator*=i; result+=sign*(i+1)*xvalue/denominator; } return result; }
Bab 7 Latihan Tambahan public static void printBackward(String s){ int index = s.length()1; while (index >= 0) { char letter = s.charAt (index); System.out.print(letter); index = index 1; } System.out.println(); }
Latihan 7.2 Metode bing mencetak setiap karakter pada kalimat beserta indeksnya mulai dari huruf paling akhir.
Latihan 7.3 Program tidak akan mencetak bilangan secara terbalik tetapi akan mencetak
465
penjumlahan digit pertama dan digit terakhir. Program harusnya seperti berikut: int number = 17; int lastDigit = number%10; int firstDigit = number/10; System.out.println (lastDigit +”“+firstDigit); Latihan 7.4 Program akan mencetak mengkonversi bilangan basis 10 menjadi bilangan biner. Latihan 7.5 class Palindrome{ public static char first(String str){ return str.charAt(0); } public static char last(String str){ return str.charAt(str.length()1); } public static String middle(String str){ int indeks=1; String result=""; while(indeks<str.length()1){ result+=str.charAt(indeks); indeks++; } return result; } public static boolean isPalindrome(String str){ if(str.length()<=1) return true; if(first(str)==last(str)){ String middle=middle(str); return isPalindrome(middle); }else{ return false; } 466
} public static boolean isPalindromeIter(String str){ if(str.length()<=1) return true; int length=str.length(); for(int i=0; i
Latihan 7.7 public static boolean isDupledrome(String str){ char [] charArr=str.toLowerCase().toCharArray(); for(int i=0; i=charArr.length || charArr[i]!=charArr[i+1]) return false;
467
} return true; }
Latihan 7.8 public class Name { public static boolean hasComma(String str){ char [] charArr=str.toCharArray(); for(int i=0; i
public static String convertName(String name){ if(hasComma(name)) return name; else if(name.split(" ").length==1) return name; else{ String backname=""; int i=0; for(i=name.length()1; i>=0; i){
468
if(name.charAt(i)==' ') break; else backname=name.charAt(i)+backname; } return backname+", "+name.substring(0,i); } }
public static int compareName(String name1, String name2){ name1=convertName(name1.toLowerCase()); name2=convertName(name2.toLowerCase()); for(int i=0; i<Math.min(name1.length(), name2.length()); i++){ if(name1.charAt(i)==name2.charAt(i)) continue; else if(name1.charAt(i) > name2.charAt(i)) return 1; else return 1; } if(name2.length()>name1.length()) return 1; else if(name2.length() < name1.length()) return 1;
469
else return 0; } } Latihan 7.9 public static char add(char c, int addition){
if(Character.isLowerCase(c)){ if(addition>26 || addition<26 ) addition%=26; int ascii=(char)c; ascii+=addition; if(ascii>122) ascii=96+(ascii122); else if(ascii<97) ascii=122+(96ascii); return (char)ascii;
} else if(Character.isUpperCase(c)){ if(addition>26 || addition<26 ) addition%=26; int ascii=(char)c; ascii+=addition; if(ascii>90) 470
ascii=64+(ascii90); else if(ascii<65) ascii=90+(64ascii); return (char)ascii;
} return c; }
Bab 8 latihan 8.1 b)
5 1 2
Latihan 8.2
5
Latihan 8.3 (5,8) (5,8) 471
p1 dan p2 bukan merupakan alias, karena masing masing objek dibuat saat pemanggilang "findCenter" import java.math.BigInteger;
Latihan 8.4
class Big{ public static int factorial(int n){ int result=1; for(int i=n; i>=1; i){ result*=i; } return result;
}
public static BigInteger bigfactorial(int n){ BigInteger result=BigInteger.valueOf(1); for(int i=n; i>=1; i){ result=result.multiply(BigInteger.valueOf(i)); } return result;
}
472
public static void main(String [] args){ for(int i=1; i<=20; i++){ System.out.println("factorial "+i+" = "+Big.bigfactorial(i)); } } }
Latihan 8.5 public static BigInteger pow(int x, int n){ if(n==0) return BigInteger.valueOf(1); BigInteger t =pow(x,n/2);
if(n%2 == 0) return t.multiply(t); else return t.multiply(t).multiply(BigInteger.valueOf(x)); }
Bab 9
Latihan 9.1 public class Tile{ 473
int value; char letter;
public Tile(char c, int v){ letter=c; value=v; }
public static void print(Tile t){ System.out.println("letter "+t.letter+" has value "+t.value); }
public static void testTile(){ Tile t = new Tile('z',10); Tile.print(t); } }
latihan 9.2 public class Date{ int date; int month; int year;
public Date(){}
474
public Date(int d, int m, int y){ date=d; month=m; year=y; }
public static void main(String [] args){ Date d = new Date(11, 7, 1986); Date d1 = new Date(); d1.date=11; d1.month=7; d1.year=1986; } }
Latihan 9.3 public class Rational{ int numerator; int denominator;
public Rational(){ numerator=0; denominator=0; }
public Rational(int a, int b){ 475
numerator=a; denominator=b; }
public static void printRational(Rational r){ System.out.println(r.numerator+"/"+r.denominator); }
public void negate(){ numerator=numerator; }
public void invert(){ int temp=numerator; numerator=denominator; denominator=temp; }
public static double toDouble(Rational r){ return (double)r.numerator/r.denominator; }
public static int gcd(int a, int b){ if(b>a){ int temp=a; a=b; 476
b=temp; } while(b>1){ int temp=b; b=a%b; a=temp; }
if(b==1) return 1; else return a;
}
public static Rational reduce(Rational r){ int gcd=gcd(r.numerator,r.denominator); System.out.println("gcd ="+gcd); Rational number = new Rational(); number.numerator=r.numerator/gcd; number.denominator=r.denominator/gcd; return number; }
public static Rational add(Rational r1, Rational r2){ 477
return new Rational(r1.numerator*r2.denominator+r2.numerator*r1.denominator,r2.denominator*r 1.denominator); }
public static void main(String [] args){ Rational r = new Rational(36,20); Rational.printRational(r); r.negate(); Rational.printRational(r); r.invert(); Rational.printRational(r); System.out.println(Rational.toDouble(r)); r = Rational.reduce(new Rational(36,20)); Rational.printRational(r);
}
}
Bab 10 Latihan 10.1 dan 10.2
class RandomClass { public static double randomDouble(double low, double high){ 478
return low+(Math.random()*(high1)); }
public static int randomInt(int low, int high){ return low+(int)(Math.random()*(high1)); }
public static void main(String [] args){ System.out.println(randomDouble(0.6,7.8)); System.out.println(randomInt(7,8)); } }
Latihan 10.3 public static int[] scoreHist(int [] scores){ int [] counts= new int[100]; for(int i=0; i<scores.length; i++){ int index=scores[i]; counts[index]++; } return counts; }
latihan 10.4 public static boolean areFactors(int number, int [] factors){ for(int i=0; i
if(number%factors[i]==0) continue; else return false; } return true; }
latihan 10.5 public static int findInRange(int [] arr, int bil, int low, int high){ if(low==high){ if( arr[low]==bil) return low; else return 1; } int mid=(low+high)/2; int result=findInRange(arr,bil,low,mid); if(result==1){ int result1=findInRange(arr,bil,mid+1,high); return result1; }else return result;
} 480
Latihan 10.6 public static int[] arrayHist(int [] scores){ int [] counts= new int[11]; for(int i=0; i<scores.length; i++){ if(scores[i]<=0) counts[0]++; else if(scores[i]>=10) counts[10]++; else counts[scores[i]]++; } return counts; }
Latihan 10.8 a. 30 b. bob ==> 0>2 1>4 2>6 3>8 4>10 c. menjumlahkan semua elemen array dan mengembalikan nilainya
481
Latihan 10.9 class Array { public static int maxInRange(int [] arr, int low, int high){ if(low==high) return arr[low]; if(low==high1) return Math.max(arr[low],arr[high]); int mid=(low+high)/2; return Math.max(maxInRange(arr,mid+1,high),maxInRange(arr,low,mid));
}
public static int max(int [] arr){ return maxInRange(arr,0,arr.length1); }
public static int findInRange(int [] arr, int bil, int low, int high){ if(low==high){ if( arr[low]==bil) return low; else return 1; } int mid=(low+high)/2; 482
int result=findInRange(arr,bil,low,mid); if(result==1){ int result1=findInRange(arr,bil,mid+1,high); return result1; }else return result;
}
public static void main(String [] args){ int [] arr={1,4,5,7}; System.out.println(findInRange(arr,71,0,3)); } }
Latihan 10.10 class Sorter { public static int indexOfMaxInRange(int [] arr, int low, int high){ int max=low; for(int i=low+1; i<=high; i++) if(arr[i]>arr[max]) max=i; return max; } 483
public static void swap(int [] arr, int index, int index1){ int swap=arr[index]; arr[index]=arr[index1]; arr[index1]=swap; }
public static int[] sortArrays(int [] arr){ for(int i=0; i<arr.length1; i++){ int index=indexOfMaxInRange(arr,i,arr.length1); swap(arr,i,index); } return arr; }
public static void main(String [] args){ int [] arr={4,5,6,0,23,12}; arr=sortArrays(arr); for(int i=0; i<arr.length; i++) System.out.println(arr[i]); } }
latihan 10.11 //blank tiles diwakili oleh spasi(' ') class LetterHist { 484
public static int [] letterHist(String kal){ int [] counts = new int[27]; char [] kar = kal.toCharArray(); for(int i=0; i
public static void main(String [] args){ int count[]=letterHist("afathkFFUhskldh"); for(int i=0; i
latihan 10.12 class Doubloon{ public static boolean isDoubloon(String word){ int count[]=LetterHist.letterHist(word); for(int i=0; i
if(count[i]==2 || count[i]==0) continue; else return false; return true; }
public static void main(String [] args){ System.out.println(isDoubloon("alalsxxsx")); } }
latihan 10.13 class Scrabble { public static boolean testWord(String tiles, String words){ int [] tilesCount=LetterHist.letterHist(tiles); int [] wordCount=LetterHist.letterHist(words); for(int i=0; i<wordCount.length; i++){ if(wordCount[i] <= tilesCount[i]) continue; else return false;
} return true; } 486
public static boolean testWordModif(String tiles, String words){ int [] tilesCount=LetterHist.letterHist(tiles); int [] wordCount=LetterHist.letterHist(words); int falseCount=0; for(int i=0; i<wordCount.length; i++){ if(wordCount[i] <= tilesCount[i]) continue; else falseCount++;
} if(falseCount<=tilesCount[26]) return true; else return false; }
public static void main(String [] args){ System.out.println(testWordModif("qijbo","jjib")); } }
487
Bab 11 class CardUtility{ public static Card[] buildDeck(){ Card deck[] = new Card[52]; int index=0; for(int suit=0; suit<=3; suit++){ for(int rank=1; rank<=13; rank++){ deck[index]= new Card(suit,rank); index++; } } return deck; }
public static int handScore(Card [] card){ int total=0; for(int i=0; i10) score=10; total+=score; } return total; }
488
public static int StringToSuit(String str){ if(str.equals("Clubs")) return 0; if(str.equals("Diamonds")) return 1; if(str.equals("Hearts")) return 2; if(str.equals("Spades")) return 3; else return 99; }
public static int StringToRank(String str){ int result=99; try{ result=Integer.parseInt(str); if(result>=2 && result<=10) return result; else return 99;
}catch(NumberFormatException e){ str=str.toLowerCase(); if(str.equals("ace")) 489
return 1; if(str.equals("jack")) return 11; if(str.equals("queen")) return 12; if(str.equals("king")) return 13; else return 99;
} }
public static Card parseCard(String str){ String [] token = str.split(" of "); int rank=StringToRank(token[0]); int suit=StringToSuit(token[1]); System.out.println(suit+" "+rank); if(rank==99 || suit==99) return null; else return new Card(suit,rank); }
public static int [] suitHist(Card [] card){ int [] hist = new int[4]; 490
for(int i=0; i
public static boolean isFlush(Card [] card) { int [] hist=suitHist(card); for(int i=0; i=5) return true; else continue; } return false; }
public static void main(String [] args){ parseCard("1 of Diamonds"); } }
class Card{ int suit; int rank;
public Card(){ 491
suit=0; rank=0; }
public Card(int s, int r){ suit=s; rank=r; }
public int getSuit(){ return suit; }
public int getRank(){ return rank; } }
Bab 12 latihan 12.1 class Search { public static int findBisect(Deck d, Card c, int low, int high){ System.out.println(low+" "+high); if(high
int mid=(high+low)/2; int comp=d.cards[mid].compareCard(c); if(comp==0) return mid; else if(comp>0){ Deck sub=Deck.subDeck(d,low,mid1); return findBisect(sub,c,low,mid1); }else{ Deck sub=Deck.subDeck(d,mid+1,high); return findBisect(sub,c,mid+1,high); } }
}
latihan 12.5 public class Poker{ public Deck[] getHands(){ Deck d= new Deck(); Deck.shuffleDeck(d); Deck[] arr = new Deck[4]; arr[0]=Deck.subdeck(d,0,4); arr[1]=Deck.subdeck(d,5,9); arr[2]=Deck.subdeck(d,10,14); arr[3]=Deck.subdeck(d,15,19); 493
return arr;
}
public static boolean isFlush(Deck d){ return CardUtility.isFlush(d.cards); }
public static boolean isThreeKind(Deck d){ Card[] cards=d.cards; int [] counter = new int[13]; for(int i=0; i
for(int i=0; i
return false; } public static boolean isFourKind(Deck d){ Card[] cards=d.cards; 494
int [] counter = new int[13]; for(int i=0; i
for(int i=0; i
return false; }
public static boolean isPair(Deck d){ Card[] cards=d.cards; int [] counter = new int[13]; for(int i=0; i
for(int i=0; i
continue; }
return false; }
public static boolean isTwoPair(Deck d){ Card[] cards=d.cards; int [] counter = new int[13]; for(int i=0; i
int pair=0; for(int i=0; i
return false; }
496
public static boolean isStraight(Deck d){ Card[] cards=d.cards; Card [] temp = new Card[cards.length]; for(int i=0; i=1){ if(temp[i].getRank()!=temp[i1].getRank()+1) return false; } } return true; }
public static boolean isFullHouse(Deck d){ Card[] cards=d.cards; int [] counter = new int[13]; for(int i=0; i
if(three && two) return true; if(counter[i]==3 ) three=true; else if(counter[i]==2) two=true; else continue; }
return false; }
private static int getLowestCard(Card[] c, int low, int high){ int min=low; for(int i=low+1; i<=high; i++){ if(c[i].getRank()
return min; }
} 498
latihan 12.2 s/d 12.7 kecuali 12.5 class Deck{ Card[] cards;
public Deck(int n){ cards = new Card[n]; }
public Deck(){ cards = new Card[52]; int index=0; for(int suit=0; suit<=3; suit++){ for(int rank=1; rank<=13; rank++){ cards[index]=new Card(suit,rank); index++; } } }
public static Deck subdeck(Deck deck, int low, int high){ Deck sub = new Deck(highlow+1); for(int i=0; i<sub.cards.length; i++) sub.cards[i]=deck.cards[low+i]; return sub; } 499
public static void swapCards(Card [] c, int i, int j){ Card temp=c[i]; c[i]=c[j]; c[j]=temp; }
public static void shuffleDeck(Card[] c){ for(int i=0; i=0) swapCards(c,i,random); } }
public static void shuffleDeck(Deck d){ for(int i=0; i=0)
swapCards(d.cards,i,random);
} }
public static int findLowestCard(Card [] c, int low, int high){ //if(low<0 || high >=c.length) //
return 99; 500
int min=low; for(int i=low+1; i<=high; i++){ if(c[min].compareCard(c[i])==1) min=i; } return min; }
public static void sort(Card[] c){ for(int i=0; i
public static void incrementalShuffle(Deck d, Card c){ int random=0+(int)(Math.random()*(d.cards.length1)); d.cards[random]=c; }
public static Deck merge(Deck d1, Deck d2){
Deck result = new Deck(d1.cards.length+d2.cards.length); int i=0; int j=0; 501
for(int k=0; k
//d1 empty if(i==d1.cards.length){ result.cards[k]=d2.cards[j]; j++; //d2 empty }else if(j==d2.cards.length){ result.cards[k]=d1.cards[i]; i++;
}else if(d1.cards[i].compareCard(d2.cards[j])==1){ result.cards[k]=d1.cards[i]; i++; }else{ result.cards[k]=d2.cards[j]; j++; }
} return result; }
502
public static Deck mergeSort1(Deck deck){
int mid=(deck.cards.length1)/2;
Deck sub1=subdeck(deck,0,mid); Deck sub2=subdeck(deck,mid+1,deck.cards.length1); sort(sub1.cards); sort(sub2.cards); Deck d = merge(sub1,sub2); return d;
}
public static Deck mergeSort2(Deck deck){ if(deck.cards.length<=1) return deck; int mid=(deck.cards.length1)/2;
Deck sub1=subdeck(deck,0,mid); Deck sub2=subdeck(deck,mid+1,deck.cards.length1); sub1=mergeSort2(sub1); sub2=mergeSort2(sub2); Deck d = merge(sub1,sub2); return d; }
503
public static void main(String [] args){ Deck d = new Deck(5); Card [] c = new Card[5]; c[0]= new Card(3,5); c[1]= new Card(0,5); c[2]= new Card(0,4); c[3]= new Card(4,4); c[4]= new Card(1,5); d.cards=c; Deck d1 = mergeSort2(d); for(int i=0; i
}
Bab 13 latihan 13.2
public double abs(){ return Math.sqrt(real*real+imag*imag); } 504
latihan 13.3 public static boolean equals(Complex a, Complex b){ return (a.real==b.real && a.imag==b.imag); }
latihan 13.4 class Complex{ double real; double imag;
public Complex(){ real=0; imag=0; }
public Complex(double r, double i){ real=r; imag=i; }
public void printComplex(){ System.out.println(real+"+"+imag+"i"); }
public void conjugate(){ 505
imag=imag; }
public double abs(){ return Math.sqrt(real*real+imag*imag); } public static Complex normalize(Complex num){ double d = num.abs(); double real=num.real/d; double imag=num.imag/d; Complex c = new Complex(real,imag); return c; }
public void add(Complex x){ real=real+x.real; imag=imag+x.imag; }
public static void main(String [] args){ Complex x = new Complex(); x.real=1; x.imag=2;
506
Complex y = new Complex(3,4);
System.out.println(y.abs());
x.conjugate(); x.printComplex(); y.printComplex();
x.add(y); x.printComplex(); } }
Bab 14 latihan 14.1 public class IntList { int length; Node head;
public IntList () { length = 0; head = null; }
public static Node removeSecond (Node list) { Node first = list; 507
Node second = list.next;
// make the first node refer to the third first.next = second.next;
// separate the second node from the rest of the list second.next = null; return second; }
public Node removeFirst(){ if(head!=null){ Node headLama=head; head=head.next; headLama.next=null; length; return headLama; } return null; }
public void set(int index,int cargo){ if(index>=1 && index<=length){ int i=1; Node list=head; 508
while(i
}
public void add(int index, int cargo){ if(index>=1 && index<=length){ Node newNode = new Node(); newNode.cargo=cargo; int i=1; Node list=head; while(i
public void addLast(int cargo){ Node newNode = new Node(); 509
newNode.cargo=cargo; int i=1; Node list=head; while(list.next!=null){ list=list.next;
} newNode.next=list.next; list.next=newNode; length++; }
public void reverse(){ Node list=head; Node temp=list.next; list.next=null; Node prev=list; while(temp!=null){ list=temp; temp=list.next; list.next=prev; prev=list; } head=list; }
510
public void append(IntList list){ Node node = list.head; while(node!=null){ addLast(node.cargo); node=node.next; }
}
public boolean checkLength(){ if(head==null) return false; Node node=head; int panjang=0; while(node!=null && node.next!=node){ panjang++; node=node.next; } System.out.println(panjang); System.out.println(length); if(panjang==length) return true; else return false; }
511
// print: print the list public void print () { Node node;
System.out.print ("(");
// start at the beginning of the list node = head;
// traverse the list, printing each element while (node != null) { System.out.print (node.cargo); node = node.next; if (node != null) { System.out.print (", "); } }
System.out.println (")"); }
public void printBackward () { System.out.print ("(");
if (head != null) { 512
Node tail = head.next; Node.printBackward (tail); System.out.print (head); } System.out.println (")"); }
public static void main (String[] args) {
// note: the following is a really bad way to build a list. // warning signs of badness: allocating two different kinds // of objects, accessing the instance variables of another class, // using the constant 3 to set the length
// create an empty list IntList list = new IntList ();
// create three new nodes, unlinked Node node1 = new Node (); node1.cargo = 1;
Node node2 = new Node (); node2.cargo = 2;
Node node3 = new Node (); node3.cargo = 3; 513
// next up the nodes node1.next = node2; node2.next = node3; node3.next = null;
list.head = node1; list.length = 3;
list.print(); Node removed=list.removeFirst(); System.out.println("Node yang dihapus"); Node.printList(removed);
list.print(); System.out.println("ubah cargo indeks 2 jadi 6"); list.set(2,6); list.print();
System.out.println("tambah node dengan kargo 5 di belakang node indeks ke 1"); list.add(1,5); list.print();
System.out.println("tambah node 7 di belakang list"); list.addLast(7); 514
list.print();
System.out.println("Reserve"); list.reverse(); list.print();
IntList list2 = new IntList ();
// create three new nodes, unlinked Node node4 = new Node (); node4.cargo = 4;
Node node5 = new Node (); node5.cargo = 5;
Node node6 = new Node (); node6.cargo = 6;
// next up the nodes node4.next = node5; node5.next = node6; node6.next = null;
list2.head = node4; list2.length = 3;
515
System.out.println("append list 4 5 6"); list.append(list2); list.print(); System.out.println("Check length ="+list.checkLength());
} }
class Node { int cargo; Node next;
// default constructor public Node () { cargo = 0; next = null; }
// other constructor public Node (int cargo, Node next) { this.cargo = cargo; this.next = next; }
public String toString () { return cargo + ""; 516
}
public static void printBackward (Node list) { if (list == null) return;
Node head = list; Node tail = list.next;
printBackward (tail); System.out.print (head + ", "); }
public static void printList (Node list) { Node node = list; while (node != null) { System.out.print (node); node = node.next;
}
System.out.println (); } }
// This program is part of "How to think like a computer scientist," // by Allen B. Downey, which is available from thinkAPjava.com
// This program is licensed under the GNU Free Software License, 517
// the terms of which are available from www.gnu.org
latihan 14.2 class PembandingAngka{ public static void main(String [] args){ IntList list = new IntList(); Node node1 = new Node (); node1.cargo = 3;
Node node2 = new Node (); node2.cargo = 2;
Node node3 = new Node (); node3.cargo = 1;
// next up the nodes node1.next = node2; node2.next = node3; node3.next = null;
list.head = node1; list.length = 3;
IntList list2 = new IntList();
Node node4 = new Node (); 518
node4.cargo = 2;
Node node5 = new Node (); node5.cargo = 6;
Node node6 = new Node (); node6.cargo = 1;
// next up the nodes node4.next = node5; node5.next = node6; node6.next = null;
list2.head = node4; list2.length = 3;
System.out.println("hasil = "+compare(list,list2));
}
public static int compare(IntList list1, IntList list2){ if(list1.length>list2.length) return 1; else if(list1.length<list2.length) return 1; else{ 519
Node node1=list1.head; Node node2=list2.head; int result=0; while(node1!=null){ if(node1.cargo > node2.cargo) result=1; else if(node1.cargo < node2.cargo) result=1; else if(node1.cargo == node2.cargo) result=result; node1=node1.next; node2=node2.next; } return result; } } }
Bab 15 15.1 import java.util.*;
class Latihan15_1{
public static void reverse(int [] arr){ 520
Stack stack = new Stack(); for(int i=0; i<arr.length; i++){ stack.push(Integer.valueOf(arr[i])); } int indeks=0; while(!stack.isEmpty()){ Integer i=(Integer)stack.pop(); arr[indeks]=i.intValue(); indeks++; }
}
public static void main(String [] args){ int arr[]={3,4,5,6}; Latihan15_1.reverse(arr); for(int i=0; i<arr.length; i++){ System.out.print(arr[i]+" "); } } }
Latihan 15.2 public class LinkedList { int length; Node head; 521
public LinkedList () { length = 0; head = null; }
public static Node removeSecond (Node list) { Node first = list; Node second = list.next;
// make the first node refer to the third first.next = second.next;
// separate the second node from the rest of the list second.next = null; return second; }
// print: print the list public void print () { Node node;
System.out.print ("(");
// start at the beginning of the list node = head; 522
// traverse the list, printing each element while (node != null) { System.out.print (node.cargo); node = node.next; if (node != null) { System.out.print (", "); } }
System.out.println (")"); }
public void printBackward () { System.out.print ("(");
if (head != null) { Node tail = head.next; Node.printBackward (tail); System.out.print (head); } System.out.println (")"); }
public Node removeFirst(){ if(head!=null){ 523
Node headLama=head; head=head.next; headLama.next=null; length; return headLama; } return null; }
public void addFirst(Object cargo){ Node newNode = new Node(); newNode.cargo=cargo; if(head==null){ head=newNode; newNode.next=null; length++;
} else{ newNode.next=head; head=newNode; length++; }
}
524
public void add(int index, Object cargo){ Node newNode = new Node(); newNode.cargo=cargo; if(head==null){ head=newNode; newNode.next=null; length++; }
else if(index>=1 && index<=length){ int i=1; Node list=head; while(i
public static LinkedList split(String sentence){ String [] word=sentence.split(" "); Node [] node = new Node[word.length]; for(int i=0; i<word.length; i++){ 525
node[i] = new Node(); node[i].cargo=word[i]; }
LinkedList list = new LinkedList(); list.head=node[0]; for(int i=1; i<node.length; i++) node[i1].next=node[i]; node[node.length1]=null; list.length=word.length; return list; }
public static String join(LinkedList list){ Node node = list.head; String result=""; while(node!=null){ result+=node.cargo+" "; node=node.next; } return result; }
public String toString(){ return LinkedList.join(this); } 526
public static void main (String[] args) {
// note: the following is a really bad way to build a list. // warning signs of badness: allocating two different kinds // of objects, accessing the instance variables of another class, // using the constant 3 to set the length
// create an empty list LinkedList list = new LinkedList ();
// create three new nodes, unlinked Node node1 = new Node (); node1.cargo = new Integer(1);
Node node2 = new Node (); node2.cargo = new Integer(2);
Node node3 = new Node (); node3.cargo = new Integer(3);
// next up the nodes node1.next = node2; node2.next = node3; node3.next = null;
527
list.head = node1; list.length = 3;
list.print (); list.printBackward ();
LinkedList linkedList=LinkedList.split("aaa akbfkjfb lij ndnd"); linkedList.print(); System.out.println(LinkedList.join(linkedList)); System.out.println("Test toString method"); System.out.println(linkedList.toString()); } }
class Node { Object cargo; Node next;
// default constructor public Node () { cargo = new Integer(0); next = null; }
// other constructor public Node (Object cargo, Node next) { 528
this.cargo = cargo; this.next = next; }
public String toString () { return cargo + ""; }
public static void printBackward (Node list) { if (list == null) return;
Node head = list; Node tail = list.next;
printBackward (tail); System.out.print (head + ", "); } }
Latihan 15.3 class NewStack { private LinkedList list;
public NewStack(){ list = new LinkedList(); list.head=null; 529
list.length=0; }
public boolean isEmpty(){ return list.head==null; }
public void push(Object o){ list.addFirst(o); }
public Object pop(){ Node node = list.removeFirst(); return node.cargo; }
public static void main(String [] args){ NewStack stack = new NewStack(); stack.push(Integer.valueOf(1)); stack.push(Integer.valueOf(2)); stack.push(Integer.valueOf(3)); while(!stack.isEmpty()){ Object o = stack.pop(); System.out.println(o); } } 530
}
latihan 15.4 import java.util.*; import java.io.*;
class Balance { private Stack parenthesStack; private Stack bracketStack; private Stack squigStack;
public Balance(){ parenthesStack = new Stack(); bracketStack = new Stack(); squigStack = new Stack();
}
public boolean read(String filename){
BufferedReader reader=null; try{ reader = new BufferedReader(new FileReader(filename)); while(true){ String s = reader.readLine(); 531
if(s==null) break; char [] kar = s.toCharArray(); for(int i=0; i
else if(kar[i]=='[') bracketStack.push(new Character('[')); else if(kar[i]==']'){ if(bracketStack.isEmpty()) return false; else bracketStack.pop(); } else if(kar[i]=='{'){ squigStack.push(new Character('{')); } else if(kar[i]=='}'){ if(squigStack.isEmpty()) 532
return false; else squigStack.pop(); } } }syst if(squigStack.isEmpty() && bracketStack.isEmpty() && parenthesStack.isEmpty()) return true; else return false;
}catch(Exception e){ e.printStackTrace(); return false; }finally{ try{ reader.close(); }catch(IOException ioe){ ioe.printStackTrace(); } }
}
public static void main(String [] args){ 533
System.out.println(new Balance().read("balance")); } }
latihan 15.5 dan 15.6 import java.util.*; import java.io.*;
class EvalPostfix{ public static double evalPostFix(String postfix){ StringTokenizer token = new StringTokenizer(postfix); Stack stack = new Stack(); while(token.hasMoreTokens()){ String operand=token.nextToken(); System.out.println(operand); try{ Double angka=Double.valueOf(operand); stack.push(angka);
}catch (NumberFormatException e){ double angka1=((Double)stack.pop()).doubleValue(); double angka2=((Double)stack.pop()).doubleValue(); if(operand.equals("+")) stack.push(Double.valueOf(angka1+angka2)); else if(operand.equals("")) stack.push(Double.valueOf(angka1angka2)); 534
else if(operand.equals("*")) stack.push(Double.valueOf(angka1*angka2)); else if(operand.equals("/")) stack.push(Double.valueOf(angka1/angka2)); } } return ((Double)stack.pop()).doubleValue(); }
public static String inputLoop() throws IOException{ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String result=""; while(true){ System.out.print("=>"); String s = reader.readLine(); if(s==null) break; else if(isOperator(s) || isOperand(s)){ System.out.println(s); result+=s+" "; } else if(s.equals("quit")) break; } return result; 535
}
public static boolean isOperator(String operand){ if(operand.equals("+")) return true; else if(operand.equals("")) return true; else if(operand.equals("*")) return true; else if(operand.equals("/")) return true; else return false;
}
public static boolean isOperand(String s){ try{ Integer.parseInt(s); return true; }catch (NumberFormatException e){ return false; }
}
536
public static void main(String [] args){ String s="";
try{ s = inputLoop(); }catch(IOException e){ e.printStackTrace(); } System.out.println(s); System.out.println(evalPostFix(s)); } }
Bab 16 latihan 16.1 class Queue16_1 { public Object[] array; public int first,next;
public Queue16_1(){ array = new Object[128]; first=0; next=0; }
537
public boolean isEmpty(){ return first==next; }
public void add(Object item){ array[next]=item; next++; }
public Object remove(){ if(isEmpty()) return null; else{ Object result=array[first]; first++; return result; } } }
latihan 16.2 class Queue16_2 { public Object[] array; public int first,next;
public Queue16_2(){ 538
array = new Object[3]; first=0; next=0; }
public boolean isEmpty(){ return first==next; }
public void add(Object item){ array[next]=item; if(isFull()) resize_array(); next=(next+1)%array.length; }
private boolean isFull(){ return ((next+1)%array.length==first); }
private void resize_array(){ Object [] temp=array; array = new Object[2*temp.length]; for(int i=0; i
}
public Object remove(){ if(isEmpty()) return null; else{ Object result=array[first]; first++; return result; } }
public static void main(String [] args){ Queue16_2 queue = new Queue16_2(); queue.add("a"); queue.add("b"); queue.add("b"); queue.remove(); queue.add("a"); queue.add("c"); } }
latihan 16.4 import java.util.*; 540
class SortedList extends LinkedList{ public SortedList(){ super(); } public void add(Comparable o){ Node node=super.head; int indeks=1; while(node!=null){ if(o.compareTo(node)!=1) break; indeks++; node=node.next; } Node n = (Node)o; //jika indeks satu maka gunakan method addFirst if(indeks==1) super.addFirst(n.cargo); else super.add(indeks1,n.cargo); }
public Node removeLast(){ return super.removeLast(); }
541
public static void main(String [] args){ SortedList list = new SortedList(); list.add(new Node(6)); list.add(new Node(4)); list.add(new Node(5)); list.print();
}
}
latihan 16.5 public Object maximum(){ Node node=head; Node max=head; while (node != null) { if(max.compareTo(node) == 1) max=node; node = node.next; } return max; }
latihan 16.6 //descending priority class PriorityQueue extends SortedList{ 542
public boolean isEmpty(){ return super.isEmpty(); }
public void add(int a){ Node n = new Node(a); super.add(n); }
public int remove(){ Node node=super.removeLast(); return ((Integer)node.cargo).intValue(); }
public static void main(String [] args){ PriorityQueue queue = new PriorityQueue(); queue.add(6); queue.add(4); queue.add(7); while(!queue.isEmpty()){ System.out.println(queue.remove()); }
} 543
}
latihan 16.7 import java.util.PriorityQueue; import java.util.Date;
class Event{ private PriorityQueue queue;
public boolean isEmpty(){ return queue.size()==0; } public Event(){ queue = new PriorityQueue(); }
public void add(Date d){ queue.add(d); }
public Date nextTime(){ if(queue.isEmpty()) return null; else{ return (Date)queue.peek(); 544
} }
public Date nextEvent(){ if(queue.isEmpty()) return null; else{ return (Date)queue.poll(); } }
public static void main(String [] args){ Event event = new Event(); event.add(new Date(2006,10,6)); event.add(new Date(2006,11,6)); event.add(new Date(2006,10,7)); while(!event.isEmpty()){ System.out.println(event.nextTime().toString()); System.out.println(event.nextEvent().toString()); }
} }
545
Bab 17 latihan 17.1 public static void visitPreorder(Tree tree){ if(tree==null) return; tree.node.visit(); visitPreorder(tree.left); visitPreorder(tree.right); }
latihan 17.2 import java.util.*;
class PreIterator implements Iterator{
private Iterator iter;
public PreIterator(Tree t){ iter=buildVector(t).iterator();
}
public void remove(){ iter.remove(); }
546
public boolean hasNext(){ return iter.hasNext(); }
public Object next(){ return iter.next(); }
private Vector buildVector(Tree t){ Vector v = new Vector(); v.add(t.getNode().getCargo()); if(t.left!=null) v.addAll(buildVector(t.left)); if(t.right!=null) v.addAll(buildVector(t.right)); return v; } }
import java.util.*;
class Tree{
public Node node; public Tree left; 547
public Tree right;
public Tree(Node node, Tree left, Tree right){ this.node=node; this.left=left; this.right=right; }
public Node getNode(){ return node; }
public static void visitPreorder(Tree tree){ if(tree==null) return; tree.node.visit(); visitPreorder(tree.left); visitPreorder(tree.right); }
public PreIterator preorderIterator(){ return new PreIterator(this); }
public static int longestPath(Tree tree){ 548
if(tree==null) return 0; return 1+Math.max(longestPath(tree.left),longestPath(tree.right)); }
public static void main(String [] args){
Tree t1 = new Tree(new Node(2),null,null); Tree t2 = new Tree(new Node(3),null,null); Tree t = new Tree(new Node(1),t1,t2); Tree t3 = new Tree(new Node(4),null,null); Tree root = new Tree(new Node(5),t,t3); visitPreorder(root);
PreIterator iter=root.preorderIterator(); System.out.println(); while(iter.hasNext()){ System.out.println(iter.next()); }
System.out.println("panjang tree "+longestPath(root));
} }
549
class Node implements Visitable,Comparable{ private Object cargo; public Node(Object o){ cargo=o; }
public void visit(){ System.out.print(cargo+" "); }
public Object getCargo(){ return cargo; }
public int compareTo(Object obj){ Node node=(Node)obj; int a=((Integer)this.cargo).intValue(); int b=((Integer)node.cargo).intValue();
if(a>b) return 1; if(a
interface Visitable{ public void visit(); }
latihan 17.3 a.9 b.2 3 * 1 + c.3
latihan 17.4 public static int longestPath(Tree tree){ if(tree==null) return 0; return 1+Math.max(longestPath(tree.left),longestPath(tree.right)); }
latihan 17.5 import java.util.*;
class ComparableTree{ Comparable node; public Tree left; public Tree right;
public ComparableTree(Comparable node, Tree left, Tree right){ 551
this.node=node; this.left=left; this.right=right; }
public static Node findMax(Tree tree){
Node max=tree.node; if(tree.left!=null){ Node left=findMax(tree.left); if(max.compareTo(left)==1) max=left; } if(tree.right!=null){ Node right=findMax(tree.right); if(max.compareTo(right)==1) max=right; } return max;
}
public static void main(String [] args){
Tree t1 = new Tree(new Node(2),null,null); Tree t2 = new Tree(new Node(78),null,null); 552
Tree t = new Tree(new Node(1),t1,t2); Tree t3 = new Tree(new Node(4),null,null); Tree root = new Tree(new Node(5),t,t3);
System.out.println("max= "+findMax(root).getCargo());
}
}
latihan 17.6 import java.util.*;
class SearchTree { Comparable node; public SearchTree left; public SearchTree right;
public SearchTree(Comparable node, SearchTree left, SearchTree right){ this.node=node; this.left=left; this.right=right; }
public boolean contains(Object o){ return contains(o,this); 553
}
public Comparable getNode(){ return node; }
public static boolean contains(Object o, SearchTree t){ Node n =(Node)o; if(n.compareTo(t.getNode())==0) return true; if(n.compareTo(t.getNode())==1){ if(t.left==null) return false; return contains(o,t.left); } else{ if(t.right==null) return false; return contains(o,t.right); } }
public static void main(String [] args){ SearchTree t1 = new SearchTree(new Node(2),null,null); SearchTree t2 = new SearchTree(new Node(4),null,null); SearchTree t = new SearchTree(new Node(3),t1,t2); 554
SearchTree t3 = new SearchTree(new Node(6),null,null); SearchTree root = new SearchTree(new Node(5),t,t3); System.out.println(root.contains(new Node(5))); } }
latihan 17.817.9
import java.util.*;
class TreeSetModify extends TreeSet { public static TreeSet union(Set set1, Set set2){ Iterator iter1 = set1.iterator(); Iterator iter2 = set2.iterator();
TreeSet newset = new TreeSet(); while(iter1.hasNext()){ newset.add(iter1.next()); } while(iter2.hasNext()){ newset.add(iter2.next()); }
return newset;
} 555
public static TreeSet intersection(Set set1, Set set2){ Iterator iter1 = set1.iterator();
TreeSet newset = new TreeSet(); while(iter1.hasNext()){ Object o =(Object)iter1.next(); if(set2.contains(o)) newset.add(o); }
return newset; } }
Bab 18 latihan 18.1 18.2 dan 18.7 import java.util.*;
class Heap { int [] array; int size;
public Heap(){ 556
array = new int[128]; size=0; }
public void print(){ for(int i=0; i<array.length; i++){ if(array[i]!=0) System.out.println(array[i]); } }
public Object getCargo(int i){ return array[i]; }
public void setCargo(int i, int o){ array[i]=o; }
public int getLeft(int i){ return 2*i; }
public int getRight(int i){ return 2*i+1; } 557
public int getParent(int i){ if(i % 2 == 1) return i/2; else return (i1)/2; }
public void add(int o){ array[size]=o; int indeks=size; int parent=getParent(size); while(indeks!=0){ if(array[parent]<array[indeks]){ int temp=array[parent]; array[parent]=array[indeks]; array[indeks]=temp; indeks=parent; parent=getParent(indeks); } else break; } size++;
} 558
public void remove(int indeks){ int right=getRight(indeks); int left=getLeft(indeks); //indeks is tree's leaf if(array[right]==0)
array[indeks]=array[right]; array[right]=array[size1]; if(array[indeks]<array[left]){ int temp=array[indeks]; array[indeks]=array[left]; array[left]=temp; } reheap(left); reheap(right);
}
private void reheap(int indeks){ if(array[indeks]==0) return; int right=getRight(indeks); int left=getLeft(indeks); if(array[indeks]<array[left]){ int temp=array[indeks]; 559
array[indeks]=array[left]; array[left]=temp; } reheap(left); if(array[indeks]<array[right]){ int temp=array[indeks]; array[indeks]=array[right]; array[right]=temp; } reheap(right); }
public static boolean isComplete(Tree tree){ if(tree.left==null && tree.right==null) return true; int leftHeight=Tree.longestPath(tree.left); int rightHeight=Tree.longestPath(tree.right); if(leftHeight==1 && rightHeight==1) return true; if(leftHeight==rightHeight || leftHeight==rightHeight+1){ if(isComplete(tree.left)){ if(isComplete(tree.right)){ return true; } } 560
} return false; }
public static boolean isHeapProperty(Tree tree){ if(tree.left==null || tree.right==null) return true; Node cargo=tree.getNode(); Node left=tree.left.getNode(); Node right=tree.right.getNode(); if(cargo.compareTo(left)!=1 && cargo.compareTo(right)!=1) if(isHeapProperty(tree.left)) if(isHeapProperty(tree.right)) return true;
return false;
}
public static void main(String [] args){ Heap heap = new Heap(); heap.add(79); heap.add(60); heap.add(65); 561
heap.add(56); heap.add(57); heap.add(18); heap.add(20); heap.add(14); heap.add(28); heap.add(20); heap.print(); heap.add(68); heap.print(); heap.remove(68); } }
import java.util.*;
class Tree{
public Node node; public Tree left; public Tree right;
public Tree(Node node, Tree left, Tree right){ this.node=node; this.left=left; this.right=right; 562
}
public Node getNode(){ return node; }
public static void visitPreorder(Tree tree){ if(tree==null) return; tree.node.visit(); visitPreorder(tree.left); visitPreorder(tree.right); }
public static int longestPath(Tree tree){ if(tree==null) return 0; return 1+Math.max(longestPath(tree.left),longestPath(tree.right)); }
public static void main(String [] args){
Tree t1 = new Tree(new Node(2),null,null); 563
Tree t2 = new Tree(new Node(3),null,null); Tree t = new Tree(new Node(1),t1,t2); Tree t3 = new Tree(new Node(4),null,null); Tree root = new Tree(new Node(5),t,t3); visitPreorder(root);
System.out.println("panjang tree "+longestPath(root));
} }
class Node implements Visitable,Comparable{ private Object cargo; public Node(Object o){ cargo=o; }
public void visit(){ System.out.print(cargo+" "); }
public Object getCargo(){ return cargo; }
564
public int compareTo(Object obj){ Node node=(Node)obj; int a=((Integer)this.cargo).intValue(); int b=((Integer)node.cargo).intValue();
if(a>b) return 1; if(a
interface Visitable{ public void visit(); }
latihan 18.3
79
60
65
56 57 18 20 14 28 20
79 68 65 56 60 18 20 14 28 20 57
565
latihan 18.5 n div 2
latihan 18.6 n+1
Bab 19
Latihan 19.1
import java.util.*;
public class MyMap { Vector entries;
public MyMap () { entries = new Vector (); }
public Object get (Object key) { Iterator it = entries.iterator (); while (it.hasNext ()) { Entry entry = (Entry) it.next (); if (key.equals (entry.key)) {
566
return entry.value; } } return null; }
public Object put (Object key, Object value) { Object result = get (key); if (result == null) { Entry entry = new Entry (key, value); entries.add (entry); } else { update (key, value); } return result; }
private void update (Object key, Object value) { Iterator it = entries.iterator (); while (it.hasNext ()) { Entry entry = (Entry) it.next (); if (key.equals (entry.key)) { entry.value = value; break; }
567
} }
public Set keySet(){ Iterator it = entries.iterator ();
Set s = new TreeSet(); while (it.hasNext ()) { Entry entry = (Entry) it.next (); s.add(entry.key); } return s; }
public boolean containsKey(Object key){ if(get(key)==null) return false; else return true; }
}
class Entry {
Object key, value;
568
public Entry (Object key, Object value) {
this.key = key; this.value = value; }
public String toString () {
return "{ " + key + ", " + value + " }"; } }
Penggunaan TreeSet pada metode keySet cukup mudah. Prinsipnya adalah anda tinggal mengambil semua kunci yang ada pada Vector dan memasukkannya satu persatu ke dalam TreeSet. Untuk melakukan hal ini Vector memiliki metode iterator yang bisa digunakan untuk menjelajahi semua elemen pada Vector.
Latihan 19.2 a 123456 b 111213141516 c 1234566
Latihan 19.3 21
Latihan 19.5 import java.util.*;
569
class BirthDay{ private int day; private int month;
public BirthDay(int d, int m){ day=d; month=m; } public int getDay(){ return day; }
public int getMonth(){ return month; }
public boolean equals(BirthDay b){ if(this.day==b.getDay() && this.month==b.getMonth()){ return true; } return false; }
570
public static boolean hasDuplicate(BirthDay [] arr){ List list = new ArrayList(); for(int i=0; i<arr.length; i++){ String str=arr[i].getDay()+""+arr[i].getMonth(); if(list.contains(str)) return true; else list.add(arr[i]); } return false; }
public static BirthDay[] randomBirthdays(int n){ BirthDay [] bdays = new BirthDay[n]; for(int i=0; i
public static void main(String [] args){ for(int i=1; i<100; i++){ BirthDay[] bday = randomBirthdays(10);
571
System.out.println(hasDuplicate(bday)); }
} } Latihan 19.6
import java.util.*;
class Invertible{ public static boolean invertible(HashMap map){ Set set=map.keySet(); Iterator iter=set.iterator(); List list = new ArrayList(); while(iter.hasNext()){ Object o=map.get(iter.next()); if(list.contains(o)) return false; else list.add(o); } return true; } } Latihan 19.7
572
import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.PriorityQueue; import java.util.Set; import java.util.StringTokenizer;
public class Excercise197 {
private BufferedReader reader; private Map map = new HashMap(); private int numOfWord;
public Excercise197(String filename){ try{ reader = new BufferedReader(new InputStreamReader(new FileInputStream(filename))); }catch(Exception e){ e.printStackTrace();
573
} while(true){ try{ String content=reader.readLine(); if(content==null) break; StringTokenizer st = new StringTokenizer(content); while(st.hasMoreTokens()){ numOfWord++; String str=st.nextToken(); if(str.length()==1) continue; if(map.containsKey(str)){ int counter=((Integer)map.get(str)).intValue(); map.put(str, Integer.valueOf(counter+1)); }else map.put(str, Integer.valueOf(1));
} }catch(Exception e){ e.printStackTrace(); try{ reader.close(); }catch(IOException ioe){ 574
ioe.printStackTrace(); } } } }
public int getNumOfDifferentWord(){ return map.size(); }
public int getNumOfWord(){ return numOfWord; }
public int getNumOfWord(String s){ Object o=map.get(s); if(o==null) return 0; return ((Integer)o).intValue(); }
public String[] get20MostCommonWord(){ Set s=map.keySet(); Iterator iter=s.iterator(); PriorityQueue queue = new PriorityQueue(); 575
while(iter.hasNext()){ String key=(String)iter.next(); Pair p = new Pair(key,((Integer)map.get(key)).intValue()); queue.add(p); } String [] arr = new String[20]; for(int i=0; i<20; i++){ arr[i]=((Pair)queue.poll()).getWord(); } return arr;
} /** * @param args */ public static void main(String[] args) { // TODO Autogenerated method stub Excercise197 e = new Excercise197("../../thinkapjava.txt"); //jawaban a System.out.println(e.getNumOfWord()); //jawaban b System.out.println(e.getNumOfDifferentWord()); //jawaban c System.out.println(e.getNumOfWord("encapsulation")); //jawaban d 576
String [] arr=e.get20MostCommonWord(); for(int i=0; i<arr.length; i++) System.out.println(arr[i]); }
}
class Pair implements Comparable{ String word; int frequency;
public Pair(String w, int freq){ word=w; frequency=freq; }
public int getFrequency(){ return frequency; }
public String getWord(){ return word; }
public int compareTo(Object o){ 577
Pair p=(Pair)o; if(this.getFrequency() > p.getFrequency()) return 1; else if(this.getFrequency() < p.getFrequency()) return 1; else return 0; } }
Latihan 19.8 import java.util.*;
public class LinkedList { int length; Node head;
public LinkedList () {
length = 0;
head = null;
} //metode yang mengecek apakah suatu list mengandung loop public boolean containLoop(){ Node node=head; 578
Map map = new HashMap(); while(node != null){ if(map.containsKey(node)) return true; else map.put(node, new Integer(1)); node=node.next; }
return false; }
public static void main(String [] args){ LinkedList list = new LinkedList ();
// create three new nodes, unlinked Node node1 = new Node (); node1.cargo = new Integer(1);
Node node2 = new Node (); node2.cargo = new Integer(5);
Node node3 = new Node (); node3.cargo = new Integer(3); 579
// next up the nodes node1.next = node2; node2.next = node3; node3.next = node1;
list.head = node1; list.length = 3;
System.out.println(list.containLoop()); }
}
class Node { Object cargo; Node next;
// default constructor public Node () { cargo = new Integer(0); next = null; } } 580
Latihan 19.9 import java.util.*;
class NewMap{ Vector entries;
public NewMap(){ entries = new Vector(); }
public Object get(Object key){ Iterator it = entries.iterator(); while(it.hasNext()){ java.util.LinkedList entry =(java.util.LinkedList)it.next(); if(key.equals(entry.getFirst())){ return entry.getLast(); } }
return null; }
public Object put(Object key, Object value){ Object result=get(key); 581
if(result==null){ List l = new java.util.LinkedList(); l.add(key); l.add(value); entries.add(l); }else{ update(key,value); } return result; }
private void update(Object key, Object value){ Iterator it = entries.iterator(); while(it.hasNext()){ java.util.LinkedList entry =(java.util.LinkedList)it.next(); if(key.equals(entry.getFirst())){ entry.set(1, value); break; } } }
public Set keySet(){ Iterator it = entries.iterator(); TreeSet set = new TreeSet(); 582
while(it.hasNext()){ java.util.LinkedList entry =(java.util.LinkedList)it.next(); set.add(entry.getFirst()); } return set; }
public static void main(String [] args){ NewMap map = new NewMap(); map.put("aa","dsdd"); map.put("saa","sdfdd"); map.put("saa","test"); Set set=map.keySet(); Iterator it = set.iterator(); while(it.hasNext()){ String key=(String)it.next(); System.out.println(key); System.out.println(map.get(key)); } } } Pada implementasi di atas digunakan Vector yang berisi LinkedList. Dimana isi dari Map disimpan dalam LinkedList. LinkedList yang digunakan adalah LinkedList bawaan dari Java yang pada paket java.util. Elemen pertama LinkedList adalah kunci entri
583
sedang Elemen terakhir LinkedList adalah nilai yang disimpan dalam Map.
Latihan 19.10 import java.util.*;
class NewSet { HashMap map ; public NewSet(){ map = new HashMap(); }
public void clear(){ map = new HashMap(); }
public boolean remove(Object o){ if(map.remove(o) != null) return true; else return false; }
public boolean removeAll(Collection c){ Iterator it = c.iterator();
584
while(it.hasNext()){ if(remove(it.next())) continue; else return false; } return true;
}
public void add(Object o){ map.put(o,null); } }
Latihan 19.11 Untuk melakukan pengujian saya menyediakan kelas BenchMark yang mengambil argumen banyak data (n) yang akan di test dan mencetak waktu yang digunakan oleh HashMap dan TreeMap dalam melakukan operasi add dan contains. Untuk menggunakan kelas anda harus memberi argumen saat anda mengeksekusi kelas pada konsole atau commandprompt
Contoh penggunaan: [fuad@fuad bab19]$ java Bencmark 200 TES METODE ADD
585
waktu hash map 1 waktu tree map 2
TES METODE CONTAINS waktu hash map 1 waktu tree map 1 Eksekusi di atas akan mengetes HashMap dan TreeMap dengan data sebanyak 200. waktu yang dihasilkan adalah waktu eksekusi masing masing metode baik add maupun contains.
import java.util.*;
public class Bencmark { HashMap hashMap = new HashMap(); TreeMap treeMap = new TreeMap(); public void add(int n){ System.out.println("TES METODE ADD"); long time=System.currentTimeMillis(); for(int i=1; i<=n; i++) hashMap.put(new Integer(i),"test"); long time1=System.currentTimeMillis(); System.out.println("waktu hash map "+(time1time));
time=System.currentTimeMillis();
586
for(int i=1; i<=n; i++) treeMap.put(new Integer(i),"test"); time1=System.currentTimeMillis(); System.out.println("waktu tree map "+(time1time)); }
public void testContains(){ System.out.println(); System.out.println(); System.out.println("TES METODE CONTAINS"); Iterator keys = hashMap.keySet().iterator(); long time=System.currentTimeMillis(); while(keys.hasNext()){ hashMap.containsKey(keys.next()); } long time1=System.currentTimeMillis(); System.out.println("waktu hash map "+(time1time));
keys = treeMap.keySet().iterator(); time=System.currentTimeMillis(); while(keys.hasNext()){ treeMap.containsKey(keys.next()); } time1=System.currentTimeMillis(); System.out.println("waktu tree map "+(time1time));
587
}
public static void main(String [] args){ int n = Integer.parseInt(args[0]); Bencmark b = new Bencmark(); b.add(n); b.testContains(); } }
BAB 20
Latihan 20.12
import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Iterator; import java.util.Map;
public class FreqTab extends HashMap { 588
BufferedReader reader; Map map = new HashMap(); public FreqTab(String filename){ try{ reader = new BufferedReader(new InputStreamReader(new FileInputStream(filename))); }catch(Exception e){ e.printStackTrace(); } }
private void buildFrequencyTable(){ String content=""; while(true){ try{ content=reader.readLine(); if(content==null) break; char [] arr=content.toCharArray(); for(int i=0; i<arr.length; i++){ if(Character.isLetter(arr[i])){ increment(arr[i]); } } 589
}catch(Exception e){ e.printStackTrace(); try{ reader.close(); }catch(IOException ioe){ ioe.printStackTrace(); } } } }
private void increment(char c){ Character key = Character.valueOf(Character.toLowerCase(c)); Object o=map.get(key); if(o==null) map.put(key,new Integer(1)); else{ int oldValue=((Integer)o).intValue(); map.put(key,new Integer(++oldValue));
}
}
public void print(){ 590
Iterator iter=map.keySet().iterator(); while(iter.hasNext()){ Object key=iter.next(); System.out.println(key+" "+map.get(key)); }
} public static void main(String [] args){ FreqTab ft = new FreqTab("FreqTab.java"); ft.buildFrequencyTable(); ft.print();
}
} Kelas di atas mengambil parameter nama file pada konstruktor nya, membaca dan menghitung frekuensi kemunculan tiap tiap huruf. Sebagai contoh saya membaca file kode sumber kelas ini lagi. Anda bisa mengubahnya dengan teks lain yang anda inginkan.
Latihan 20.13 import java.util.*;
public class pair implements Comparable{ int frequency;
591
char karakter;
public pair(char c, int a){ karakter=c; frequency=a; }
public pair(){ }
public int getFrequency(){ return frequency; }
public char getKarakter(){ return karakter; }
public int compareTo(Object o){ pair p=(pair)o; if(this.getFrequency() > p.getFrequency()) return 1; else if(this.getFrequency() < p.getFrequency()) return 1; else
592
return 0; } } Definisi FreqTab yang menggunakan pair import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.PriorityQueue;
public class FreqTab extends HashMap {
BufferedReader reader; Map map = new HashMap(); public FreqTab(String filename){ try{ reader = new BufferedReader(new InputStreamReader(new FileInputStream(filename))); }catch(Exception e){ e.printStackTrace(); } 593
}
public FreqTab(){
}
public void buildFrequencyTable(String teks){ char [] arr=teks.toCharArray(); for(int i=0; i<arr.length; i++){ if(Character.isLetter(arr[i])){ increment(arr[i]); } } }
private void buildFrequencyTable(){ String content=""; while(true){ try{ content=reader.readLine(); if(content==null) break; char [] arr=content.toCharArray(); for(int i=0; i<arr.length; i++){ if(Character.isLetter(arr[i])){ 594
increment(arr[i]); } } }catch(Exception e){ e.printStackTrace(); try{ reader.close(); }catch(IOException ioe){ ioe.printStackTrace(); } } } }
private void increment(char c){ Character key = Character.valueOf(Character.toLowerCase(c)); Object o=map.get(key); if(o==null) map.put(key,new Integer(1)); else{ int oldValue=((Integer)o).intValue(); map.put(key,new Integer(++oldValue));
}
595
}
public void print(){ Iterator iter=map.keySet().iterator(); PriorityQueue queue = new PriorityQueue(); while(iter.hasNext()){ Character key=(Character)iter.next(); pair p = new pair(key.charValue(),((Integer)map.get(key)).intValue()); queue.add(p); } while(!queue.isEmpty()){ Object o = queue.remove(); pair pairobj = (pair)o; System.out.println(pairobj.getKarakter()+" "+pairobj.getFrequency()); } }
public static void main(String [] args){ FreqTab ft = new FreqTab("FreqTab.java"); ft.buildFrequencyTable(); ft.print();
596
}
} Latihan 20.14
447 261
186
151 110 e 93
s 71
r 57
93 t 53
n 49
80 i 44
d 43
o 37
latihan 20.15
import java.io.*; import java.util.*;
public class HuffTree extends pair { HuffTree left,right;
public HuffTree(char c, int a,HuffTree left, HuffTree right){ super(c,a); 597
this.left=left; this.right=right; }
public int compareTo(Object o){ return super.compareTo(o); }
public HuffTree getLeft(){ return left; }
public HuffTree getRight(){ return right; }
public static void print(HuffTree tree){ System.out.println(tree.getFrequency()); if(tree.left!=null) print(tree.left); if(tree.right!=null) print(tree.right); }
public static HuffTree build(FreqTab ft){ 598
Map map=ft.getMap(); Iterator iter=map.keySet().iterator(); PriorityQueue queue = new PriorityQueue(); while(iter.hasNext()){ Character key=(Character)iter.next(); HuffTree ht = new HuffTree(key.charValue(),((Integer)map.get(key)).intValue(),null,null); queue.add(ht); } while(!queue.isEmpty()){ Object o = queue.remove(); HuffTree ht= (HuffTree)o; o = queue.remove(); HuffTree ht1= (HuffTree)o; HuffTree join = new HuffTree(' ',ht.getFrequency()+ht1.getFrequency(),ht,ht1); if(queue.isEmpty()){ return join; }else{ queue.add(join);
} }
return null; 599
} public static void main(String [] args){ FreqTab ft = new FreqTab("FreqTab.java"); ft.buildFrequencyTable(); HuffTree tree = HuffTree.build(ft); HuffTree.print(tree);
} }
Latihan 20.16 a. santa b. steere c. deer d. east
Latihan 20.17 public class HuffMan { private HuffTree tree; public HuffMan(String text){ FreqTab ft = new FreqTab(); ft.buildFrequencyTable(text); tree= HuffTree.build(ft); }
600
public String decode(String teks){ char [] arr= teks.toCharArray(); HuffTree tree= this.tree; String result=""; for(int i=0;i<arr.length; i++){ if(arr[i]=='.'){ tree=tree.left; } else if(arr[i]==''){ tree=tree.right; } if(tree.left==null && tree.right==null){ result+=tree.karakter; tree=this.tree; } } return result; } }
Latihan 20.18 import java.util.*;
class CodeTab extends HashMap{ 601
private HuffTree root; public CodeTab(HuffTree r){ root=r; }
public void getCodes(){ traverseTree(root,""); }
private void traverseTree(HuffTree t, String lastpath){ if(t.left == null && t.right== null){ put(Character.valueOf(t.getKarakter()),lastpath); return; } traverseTree(t.getLeft(),lastpath+"."); traverseTree(t.getRight(),lastpath+""); } }
public class HuffMan { private HuffTree tree; public HuffMan(String text){ FreqTab ft = new FreqTab(); 602
ft.buildFrequencyTable(text); tree= HuffTree.build(ft); }
public String decode(String teks){ char [] arr= teks.toCharArray(); HuffTree tree= this.tree; String result=""; for(int i=0;i<arr.length; i++){ if(arr[i]=='.'){ tree=tree.left; } else if(arr[i]==''){ tree=tree.right; } if(tree.left==null && tree.right==null){ result+=tree.karakter; tree=this.tree; } } return result; }
public String encode(String s){ char[] charArr=s.toCharArray(); 603
CodeTab ct= new CodeTab(tree); ct.getCodes(); String code=""; for(int i=0; i
public static void main(String [] args){ String teks=""; for(int i=1; i<=40; i++) teks+="e"; for(int i=1; i<=32; i++) teks+="a"; for(int i=1; i<=24; i++) teks+="t"; for(int i=1; i<=22; i++) teks+="s"; for(int i=1; i<=20; i++) teks+="n"; for(int i=1; i<=19; i++) teks+="r"; 604
for(int i=1; i<=13; i++) teks+="d"; HuffMan hm = new HuffMan(teks); String code=hm.encode("desta"); System.out.println(code); System.out.println(hm.decode(code));
}
}
605
History 1994, Tulisan Original Oleg Allen Downey 2007, Penerjemahan dan Penambahan lampiran “For each dan Generik” oleh Wim Permana, Muhammad Fuad Dwi Rizki, dan Agus Juliardi
GNU Free Documentation License
Version 1.2, November 2002 Copyright (C) 2000,2001,2002
Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA
02110-1301
USA
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
0. PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because 606
free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. 1. APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a worldwide, royaltyfree license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A "Secondary Section" is a named appendix or a frontmatter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The "Invariant Sections" are certain Secondary Sections whose titles are designated,
607