How To Think Like Computer Scientist (bahasa Indonesia)

  • Uploaded by: fuad muhammad
  • 0
  • 0
  • May 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View How To Think Like Computer Scientist (bahasa Indonesia) as PDF for free.

More details

  • Words: 85,783
  • Pages: 607
BAGAIMANA CARA BERPIKIR SEBAGAI ILMUWAN KOMPUTER Versi Java 

Pengarang  Allen B. Downey 

Penerjemah  Wim Permana, [email protected] (Bab 6­17) Muhammad Fuad Dwi Rizki, [email protected] (Bab 5, 18, 19, 20)  (Lampiran Foreach Dan Generik) (Jawaban Latihan) Agus Juliardi (Bab 1­4)

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 Front­Cover Texts, and with no Back­Cover 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 02111­1307, 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 Latihan­latihan....................................................................................................46 3. Method........................................................................................................................49 3.1 Floating­point........................................................................................................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 Kata­Kata...............................................................................................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 Run­Time 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 Kata­Kata...............................................................................................158 7.12 Latihan..............................................................................................................159 8. Interesting Objects....................................................................................................168 8.1 Apanya yang menarik?.......................................................................................168 8.2 Paket­paket..........................................................................................................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 diubah­ubah (Mutable)...................................................................173 8.9 Aliasing...............................................................................................................175 8.10 Null...................................................................................................................176 8.11 Garbage Collection...........................................................................................177 8.12 Objek dan Primitif.............................................................................................178 8.13 Daftar Kata­Kata...............................................................................................180 8.14 Latihan..............................................................................................................181 9. Membuat Objek Anda Sendiri..................................................................................188 9.1 Definisi­definisi kelas dan tipe­tipe 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 Fungsi­fungsi murni............................................................................................197 9.9 Modifier..............................................................................................................200 9.10 Fill­in 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 Kata­Kata...............................................................................................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 Angka­Angka Acak (Random) ........................................................................221 10.7 Array dari Angka­angka Random (Acak) ........................................................223 10.8 Pencacahan........................................................................................................225 10.9 Histogram..........................................................................................................227 10.10 Solusi Single­Pass...........................................................................................228 10.11 Daftar Kata­Kata.............................................................................................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 Kata­Kata.............................................................................................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 Kata­Kata...............................................................................................274 12.8 Latihan..............................................................................................................274 13.Pemrograman Berorientasi Objek............................................................................280 13.1 Bahasa Pemrograman dan Teknik­tekniknya...................................................280 12.3 Metode Objek dan Metode Kelas......................................................................281 13.3 Objek Terkini....................................................................................................281 8

13.4 Bilangan Kompleks...........................................................................................282 13.5 Fungsi­fungsi dalam Bilangan Kompleks.........................................................283 13.6 Fungsi­fungsi 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 Kata­Kata.............................................................................................295 13.17 Latihan............................................................................................................296 14.Linked List................................................................................................................298 14.1 Acuan­acuan 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 Node­Node.....................................................................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 Kata­Kata.............................................................................................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 Nilai­Nilai.................................................................................322 15.8 Metode­Metode 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 Kata­Kata.............................................................................................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 Kata­Kata.............................................................................................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 Kata­Kata.............................................................................................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 built­in..............................................................................................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 Variabel­length 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  (Problem­Solving). 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.  Kadang­kadang 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 meng­compile 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   perintah­perintah   tertentu   untuk   melaksanakan   proses  komputasi. Komputasi dapat berupa beberapa perhitungan matematika, seperti halnya  menyelesaikan suatu sistem persamaan untuk menemukan akar­akar polinomial. Akan  17

tetapi juga bisa berupa sebuah simbol komputasi seperti searching, dan replacing teks  pada sebuah dokumen atau cara lain (meng­compile 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   meng­eksekusi   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   pernyataan­pernyataan   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 operasi­operasi 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 RUN­TIME 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 men­generate 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 berangsur­angsur 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   unsur­unsur   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: Kata­kata digunakan untuk bunyinya juga untuk maksud/artinya , dan syair/puisi  yang utuh bersama­sama menciptakan suatu efek atau tanggapan emosional. Kerancuan  sering merupakan kesengajaan.

Prosa:Arti   kata­kata   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.   Hal­Hal   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   orang­orang   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 tiap­tiap 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 Problem­solving:   Proses   merumuskan   suatu   masalah,menemukan   suatu   solusi,   dan  menyatakan solusi itu. High­level language: Suatu bahasa program seperti bahasa Java yang dirancang untuk  memudahkan manusia untuk membaca dan menulis. Low­level   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 high­level language  menterjemahkannya perbaris pada waktu yang sama. Compile: Untuk menterjemahkan suatu program di dalam suatu high­level language ke  dalam suatu low­level­language, untuk kemudian di eksekusi. Source   program:   Suatu   program   di   dalam   suatu   high­level   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   low­level   language,   tetapi   portable   sama   seperti   high­level  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 run­time error. Debugging: Proses untuk menemukan dan membuang ke tiga macam kesalahan.

1.7. Latihan Latihan 1.1 Ilmuwan   Komputer   mempunyai   kebiasaan   menggunakan   kata­kata   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 berbeda­beda. Daftar kata pada akhir masing­masing bab dimaksudkan untuk menyoroti kata­kata dan  ungkapan yang mempunyai arti khusus didalam ilmu pengetahuan komputer. Ketika  anda melihat kata­kata 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.  Kadang­kadang compiler akan memberi tahu anda  tepatnya apa yang salah, dan anda  harus  membereskan   kesalah  itu.   Kadang­kadang,  walaupun  demikian,   compiler   juga  akan   memberi   tahu   banyak   kesalahan   yang   tidak   semestinya.   Anda   akan   sedikit  memakai nalar ketika anda mempercayai compiler dan anda harus hati­hati. 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, angka­angka, tanda baca, dan karekter khusus lainnya.

println   adalah   singkatan   dari   “print   line"   sebab   setelah   masing­masing   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.   Masing­masing   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 benar­benar perlu untuk berhati­hati. 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   menerka­nerka   tentang   nilai   apa   yang   akan   disimpan   oleh   tipe  tersebut. Contoh ini memperlihatkan sintak untuk medemonstrasikan berbagai variabel  dengan   tipe   yang   sama:  hour  dan  minute  kedua­duanya   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   masing­masing  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   nilai­nilai   dari   satu   tipe   ke   tipe   lain,   dan   Java   kadang­kadang  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   hati­hati   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 kata­kata 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. Kata­kata 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,   bagian­bagian   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 

hour­1 

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

Lagi­lagi 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  floating­point,  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*3­1  hasilnya  5,   bukan  4,   dan  2/3­1  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 ketentuan­ketentuan nilai precende  (   atau   anda   tidak   tahu   pasti   apa   hasilnya)   anda   dapat   menggunakan   tanda  kurung. Ungkapan didalam tanda kurung dievaluasi terlebih dahulu, maka 2* 

41

( 3­1)  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   angka­angka.   Yang   berikut   adalah   tidak   benar   (jika   kita  mengetahui bahwa fred mempunyai tipe string)

fred ­ 1 

"Hello"/123 

fred * "Hello"

Ngomong­ngomong,   dengan   memperhatikan   ekspresi   tersebut   dapatkan   anda  menunjukkan bahwa  fred  adalah suatu  integer  atau  string? Tidak. Satu­Satunya 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 unsur­unsur 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 angka­angka dan kita mengetahui bagaimana  cara mencetaknya, itu dapat kita sayakan kedua­duanya 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 angka­angka, 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. Tiap­Tiap 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 nilai­nilai 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 nilai­nilai dimana diatasnya suatu operator beroperasi.

Precedence: Kedudukan suatu operator ketika suatu operasi dievaluasi

Concatenate: Untuk menggabungkan ujung­ujung dua operand.

Komposisi: Kemampuan untuk mengkombinasikan statemen dan ekspresi sederhana ke  dalam statemen campuran dan ekspresi­ekspresi untuk mempresentasikan perhitungan  kompleks dengan singkat. 45

2.11 Latihan­latihan

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   masing­masing   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   nilai­nilai   dengan   tipe   berbeda   (   int   dan   string),   dan   untuk   praktek  mengembangkan   program   secara   berangsur­angsur   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   nilai­nilai   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 nilai­nilai 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 nilai­nilai  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 Floating­point

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   angka­angka   floating­point,   yang   dapat   mempresentasikan   pecahan  seperti halnya bilangan bulat. Didalam Java, tipe floating­point 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   angka­angka   floating­point   bermanfaat,   namunn   sering   suatu   source  membingungkan karena sepertinya ada tumpang­tindih antara bilangan bulat dan angka­ angka floating­point. Sebagai contoh, jika anda mempunyai nilai 1, yang merupakan  suatu bilangan bulat, bilangan floating­point, atau kedua­duanya?

Pada   dasarnya,   Java   menilai  integer  itu   1   dan  floating­point  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  floating­point hasilnya 0.0. Satu   cara   untuk   memecahkan   masalah   ini   (   saat   anda   menggambarkan   apa   artinya)  adalah untuk ekspresi floating­point 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 floating­point, walaupun anda mungkin tertarik untuk  mengetahui   bahwa   mekanisme   dasarnya   berbeda   sepenuhnya.   Pada   kenyataanya,  prosesor mempunyai perangkat keras khusus hanya untuk mekan operasi floating­point.

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 floating­point 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 kira­kira 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 berulang­kali 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   built­in   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   nilai­nilai 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 built­in:

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  floating­point 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 built­in :

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 berulang­kali:

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 berulang­kali. Sebenarnya, itu umumnya  berguna bagi mekannya.

Anda   bisa   mempunyai   satu   method   memanggil   metoda   lain.   Dalam   hal   ini,   main  meminta   threeLine   dan   threeLine   meminta   newLine.   Lagi­lagi   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   masing­masing   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   kode­kode   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 masing­masing method built­in 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 kira­kira 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 built­in, 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 built­in 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 masing­masing. 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 apa­apa 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 masing­masing variabel didefinisikan pada suatu  stack diagram.

Stack Diagram untuk contoh yang sebelumnya kelihatan seperti ini:

65

Untuk masing­masing method ada suatu kotak abu­abu 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 tiap­tiap 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 assign­nya 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 floating­point: 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 nilai­nilai 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 kata­kata 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   angka­angka  floating­point.

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 nilai­nilai 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 hati­hati 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 pattern­matching: 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  kondisi­kondisi  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) tanda­kurung dieksekusi. Jika kondisi tidaklah benar, tidak dilakukan apapun. Kondisi dapat berisi operator perbandingan apapun, kadang­kadang 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 Kadang­kadang anda ingin melihat kemungkinan sejumlah kondisi­kondisi 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   kedua­duanya   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   mula­mula   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 (n­1); } }

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 n­1  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 (n­1); } }

Program ini sangat serupa; sepanjang n adalah lebih besar dari nol, ia mencetak satu  newline,   dan   kemudian   memanggil   dirinya   sendiri   untuk   mencetak   n­1   tambahan  newlines. Sehingga, total jumlah newlines yang dicetak adalah 1+ ( n­1), 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,   masing­masing  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   sewenang­wenang   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 segi­empat. 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   masing­masing   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 ("boo­wa­ha­ha");

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 x2­x1 dan y2­y1.  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!  Kadang­kadang  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 n­1. 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 n­1, dan kemudian mengalikannya dengan n. public static int factorial (int n) {   if (n == 0) {     return 1;   } else {     int recurse = factorial (n­1);     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 n­1.... Karena   2   tidak   sama   dengan   0,   kita   mengambil   cabang   kedua   dan   menghitung faktorial dari n­1.... Karena 1 tidak sama dengan 0, kita mengambil cabang  kedua dan menghitung faktorial dari n­1....

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   n­1,   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 (n­1);   } } 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 (n­1) + fibonacci (n­2);   } } 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, n­1);             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, n­1);   } 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,n­10,  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 variabel­variabel  dalam Java merupakan penampung (container) untuk  nilai­nilai. 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  hati­hati   dalam  menggunakannya. Jika nilai dari suatu variabel selalu berubah­ubah pada lokasi/bagian  yang   berbeda­beda   di   dalam   program,   maka   Anda   akan   menemui     kesulitan   ketika  hendak men­debug dan membaca kodenya. 122

6.2

Iterasi 

Salah   satu   fungsi   komputer   yang   paling   sering   dimanfaatkan   manusia   adalah   untuk  melakukan sesuatu (baca: tugas) yang sifatnya berulang­ulang. Mengulang­ulang 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   program­program   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 program­program 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  sehari­hari 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   kasus­kasus   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. Nilai­nilai 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, orang­orang harus menghitung logaritma, sinus, cosinus, serta fungsi­fungsi  matematika lainnya menggunakan tangan (baca: manual). Untuk membuat pekerjaan­pekerjaan di atas menjadi lebih mudah, dibuatlah beberapa  buku yang berisi tabel­tabel panjang yang dapat membantu kita untuk melihat nilai­nilai  dari beberapa fungsi. Untuk membuat tabel­tabel 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, tabel­tabel tersebut menjadi barang kuno.  Setidaknya bisa dikatakan hampir demikian. Karena untuk beberapa operasi, komputer  juga  masih menggunakan tabel­tabel 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 floating­point. 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  nilai­nilai 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 nilai­nilai 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   bilangan­bilangan  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 nilai­nilai  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   (wrap­it­up)   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 

berulang­ulang dengan argumen yang berbeda­beda. Tidak hanya itu, di sini kita juga  akan menggunakan loop yang berbeda untuk melakukan iterasi ke dalam baris­baris  tersebut. int i = 1;     while (i <= 6) {     printMultiples (i);     i = i + 1; } Pertama­tama, 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

agak­agak miring sedikit. Jika bentuk­bentuk seperti ini mengganggu Anda, Java telah  menyediakan   metode­metode   yang   dapat   memberi   Anda   kendali   lebih   baik   atas  masalah­masalah 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 bertanya­tanya, apa saja sih kebaikan­ kebaikan yang dimaksud? Berikut ini adalah beberapa alasan mengapa metode­metode  tersebut mempunyai posisi yang sangat penting: •

Dengan   memberi   sebuah   nama   untuk   satu   set   perintah­perintah/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   bagian­bagian   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  program­program 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. Variabel­variabel yang dideklarasikan di dalam sebuah metode disebut  variabel lokal  karena   mereka   terlokalisasi   dalam   metodenya   masing­masing.   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 nama­nama variabel yang  berbeda meskipun variabel­variabel itu tidak berada dalam satu metode yang  sama. Ini  semata­mata 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 nama­nama variabel seperti i, j, dan k sebagai nama­nama 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 parameter­parameter dengan nama yang sama (persis seperti  variabel­variabel 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 Kata­Kata Istilah Loop

Arti Sebuah pernyataan yang dieksekusi berulang­ulang 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  komponen­komponen kecil (seperti metode) untuk kemudian  mengisolasinya dari komponen­komponen 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 metode­metode 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 bab­bab 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, kira­kira 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 mentah­mentah  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 (built­in) 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 ke­i 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  predecessor­nya  (sebelumnya) dikali dengan x, sementara denominatornya adalah sama dengan nilai  predecessor­nya  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 kolom­kolom dalam tabel tersebut. d) Ubah­ubahlah   (variasikan)   nilai­nilai   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 tak­hingga berikut: e^(­x^2)  =   1­2 x  +   3x 2/ 2!   ­  4x 3 /3!   +   5x 4 /4!­   ......   (6.4) Dengan kata lain, kita perlu menambahkan beberapa term dimana term yang ke­i 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  n­term   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.       Metode­metode  digunakan   untuk   melakukan   operasi­operasi   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 “metode­metode apa saja yang dapat kita  panggil untuk memfungsikan objek­objek yag berupa  String?” Data yang terkandung dalam objek String adalah huruf­huruf dari string itu sendiri.  Sebenarnya ada cukup banyak metode yang dapat  digunakan  untuk mengoperasikan  atau   beroperasi   dalam   objek­objek   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   huruf­huruf   yag   berada   dalam   sebuah   String.   Untuk  menyimpan hasil pengambilan tersebut, kita membutuhkan sebuah variabel yang dapat  menyimpan huruf­huruf tersebut sebagai sebuah satuan tersendiri (kebalikan dari String  itu sendiri yang merupakan sekumpulan item­item 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 data­tipe 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  ke­0 dalam string “banana” adalah b. sedangkan urutan yang ke­1 adalah a, urutan ke­2  adalah n, dst. Jadi, jika Anda menginginkan huruf yang berada pada urutan ke­0 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   hati­hati   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 ke­6 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   bentuk­bentuk   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   meng­enkode   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  men­traverse  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

Run­Time 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, run­time error disebut juga dengan exceptions. Sejauh ini, Anda mungkin belum terlalu sering melihat run­time 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 length­1, 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 parameter­parameter  dan nilai pengembalian. Dalam  kasus   ini,   penjelasan   yang   diberikan   mungkin   terkesan   berulang­ulang,   tapi  dokumentasi seperti inilah yang merupakan bentuk standar dokumentasi dalam Java.  Baris   terakhir   akan   menjelaskan   kepada   kita   mengenai   exceptions,   kalau   memang  benar­benar 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   indeks­mulai   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   indeks­mulai,   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  menguji­cobanya. Berikut hasil uji coba saya: ●

Jika   indeks­mulai   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 indeks­mulai bernilai negatif, hasilnya adalah 1. hasil ini mengindikasikan  bahwa kemunculan pertama huruf yang dicari berada dalam indeks yang lebih  besar daripada nilai indeks­mulai.

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 program­program 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 argumen­argumen. Untuk   latihan   yang   kedua,   tulis­ulanglah   metode   itu   dengan   memasukkan   indexOf 

153

untuk   mencari   letak   huruf   yang   diinginkan   ketimbang   melakukan   pemeriksaaan  karakter­karakter tersebut satu­per­satu.

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 berulang­ulang sampai syarat yang ditetapkan bernilai false.



Decrement berarti  menurunkan/mengurangi  nilai suatu variabel dengan nilai  satu (1) secara berulang­ulang 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   meng­increment   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 ekspresi­ekspresi 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 sungguh­sungguh hendak mengetahuinya, coba saja sendiri! 154

Menggunakan operator increment, kita dapat menulis ulang letter­counter: 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 melihat­lihat kembali dokumentasi untuk metode String, Anda mungkin  akan memperhatikan metode toUpperCase dan toLowerCase. Metode ini sering menjadi  biang masalah. Kok bisa? Karena keduanya tampak seolah­olah 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

diubah­ubah.  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   saat­saat   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   operator­operator   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 karakter­karakter 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 bagian­bagian  berikutnya. Sabar ya .....

7.11 Daftar Kata­Kata 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   berulang­ulang  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 satu­satu secara bertahap (increment).

Increment

menaikkan/menambahkan  nilai suatu variabel dengan nilai  satu (1) secara berulang­ulang sampai syarat yang ditetapkan  bernilai false. Operatornya adalah ++

Decrement 

menurunkan/mengurangi  nilai suatu variabel dengan nilai  satu (1) secara berulang­ulang 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   ekspresi­ekspresi   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   data­tipe   data   ini   dengan   informasi   yang  memberitahu legal­tidaknya 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 teka­teki: 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 kasus­kasus  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  huruf­huruf  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 (lower­case). 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  kata­kata   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 nama­nama telah disimpan dalam komputer, terkadang nama­nama 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 nama­nama 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   komputer­komputer   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 nama­nama yang mengandung beberapa kata  sekaligus seperti Van Houten

juga   nama­nama   dengan   huruf   kapital   yang   non­

standar,   seperti   desJardins.   Coba   lihat,   apakah   Anda   sanggup   untuk   merancang  aturan/rambu­rambu pengurutannya. Jika Anda memiliki akses ke buku telepon negara­ negara di eropa, coba juga itu, dan telitilah kalau­kalau aturan yang digunakan juga  berbeda dengan standar Amerika. Hasil dari semua bentuk non­standar ini adalah bahwa terkadang tidaklah tepat untuk  mengurutkan nama­nama tersebut menggunakan aturan String yang biasa. Satu solusi  yang umum adalah dengan cara menyimpan dua versi sekaligus untuk tiap­tiap 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 (bottom­up), 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 masing­masing 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   nama­depan   duluan.   Selain   itu,  metode ini  juga harus mengembalikan  sebuah string baru yang berisi nama yang  telah dikonversikan ke format nama­belakang 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 case­insensitive. Artinya, metode ini tidak akan memedulikan apakah huruf­ huruf penyusun nama­nama yang Anda berikan nantinya merupakan huruf kapital  (upper case) atau huruf non­kapital (lower case).

Latihan 7.9 a) Cincin Decoder Kapten Crunch bekerja dengan cara mengambil setiap huruf dalam  sebuah string, lalu menambahkan ke masing­masing huruf tersebut nilai 13. Sebagai  contoh, ' a ' menjadi ' n ' dan ' b ' menjadi ' o '. Huruf­huruf 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 benar­benar menarik.  Maklum, String itu: ●

Tidak bisa “diubah­ubah” 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  objek­objek gambar yang akan  tampil di  layar. Keduanya merupakan variabel yang mengandung data, sama halnya dengan int  dan double. Layaknya variabel­variabel lain, keduanya digunakan secara internal untuk  mengerjakan perhitungan­perhitungan. Definisi­definisi mengenai kelas Point dan kelas Rectangle berada dalam paket java.awt,  jadi kita harus mengimport keduanya. 

8.2

Paket­paket

Kelas­kelas  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 kelas­kelas 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 kelas­kelas  yang   Anda   inginkan   secara   eksplisit,   tapi   sayangnya   tidak   terlalu   menguntungkan.  Kelas­kelas   dalam   java.lang   sudah   diimport   secara   otomatis.   Hal   inilah   yang  menyebabkan kenapa program­program 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,   titik­titik   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   argumen­argumen.   Mungkin   ini   bukanlah   sesuatu   yang  mengejutkan Anda bahwa argumen­argumen 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 nilai­nilainya 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 variabel­variabel instan. Jika disatukan/digabungkan, maka semua komponen, variabel, nilai, dan objek dalam  program   tersebut   akan   disebut   dengan   state.   Diagram­diagram   seperti   ini   yang  menunjukkan state dari suatu program disebut juga dengan diagram­diagram state (state   diagram). Ketika program berjalan (running), state­nya 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 masing­masing. 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 meng­assign 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  nama­nama dan nilai­nilai 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] Lagi­lagi, 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   koordinat­koordinat   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 diubah­ubah (Mutable)

Anda   dapat   mengubah­ubah   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 masing­masing 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 meng­assign suatu variabel dengan objek, ini berarti  Anda   sedang   meng­assign   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 sehati­hati 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   membuang­buang   ruang   (space).   Oleh   karena   itulah,   selagi  program Anda berjalan, sistem Java akan mencari objek­objek 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 tipe­tipe objek adalah null yang  berarti tidak ada objek.



Variabel­variabel 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. Variabel­variabel  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 hati­hati  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 Kata­Kata Istilah

Arti Sebuah kumpulan dari kelas­kelas (classes). Kelas­kelas 

paket (package)

yang dibawa (built­in) oleh Java dikelola dalam bentuk  paket­paket.

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   item­item   data   yang   membentuk   sebuah 

variabel instan

objek. Setiap objek (instan) memiliki salinan variabel­ variabel instan untuk kelasnya masing­masing. 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   objek­objek   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   nilai­nilai   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  variabel­variabel lokal dan parameter­parameter dari main dan fred, lalu tunjukkan  suatu objek yang diacu oleh variabel­variabel 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   benar­benar  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 variabel­variabel 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 objek­objek 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   benar­benar   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   15­an,   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   batas­atas   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   algoritma­algoritma   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. Tulis­ulanglah metode ini sehingga  hasilnya   merupakan   bentuk   BigInteger.   Meskipun   begitu,   parameter­paramater   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

Definisi­definisi kelas dan tipe­tipe 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 objek­objek 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 benar­benar telah membuatnya. Dalam bab ini, 

kita 

akan melihat beberapa contoh definisi­definisi kelas yang mampu menunjukkan kepada  kita tipe objek baru yang bermanfaat.  Berikut ini adalah hal­hal 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 metode­metode 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 variabel­variabel instan. ●

Semua metode yang beroperasi dalam sebuah tipe akan masuk dalam definisi  kelas untuk tipe tersebut.

Berikut ini merupakan isu­isu sintaks mengenai definisi­definisi kelas: ●

Nama kelas  (atau  tipe objek) selalu  dimulai  dengan huruf kapital  yang akan  membedakannya dengan tipe­tipe primitif dan nama­nama 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   topik­topik   di   atas,   sekarang   kita   akan   melihat   sebuah   contoh   kelas   yang  merupakan hasil buatan sendiri (user­defined 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 detik­detik 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 apa­apa 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   nama­nama   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   meng­assign­nya  dengan sesuatu apapun. this merupakan produk sistem Java secara otomatis; yang harus  Anda lakukan hanyalah menyimpan nilai­nilai 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

Konstruktor­konstruktor  dalam  Java juga dapat  berstatus  overloading seperti metode  pada  lazimnya.  Akibatnya,  Anda   dapat  menyediakan  beberapa  konstruktor  sekaligus  dalam sebuah tipe objek dengan parameter­parameter yang berbeda. Java mengetahui  konstruktor mana yang dipanggil dengan cara mencocokkan argumen yang digunakan  oleh   perintah   new   dengan   parameter­parameter   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 variabel­variabel 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 variabel­variabel 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 kekurangan­kekurangannya, 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 variabel­variabel instannya saja.

9.5

Membuat Sebuah Objek yang Baru

Meskipun konstruktor­konstruktor 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 variabel­variabel  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 meng­assign nilai­nilai ke setiap  variabel­variabel instan.  194

Pada   kesempatan   kedua   ketika   kita   memanggil   perintah   new,   kita   memberikan  argumen­argumen   yang   cocok   dengan   parameter­parameter   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 nilai­nilai apa yang akan di­assign 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 (user­defined 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 penjaga­tempat (place­keeper).  Selain   itu,  kita  mungkin  ingin  membuang  angka­angka yang  termasuk  dalam angka  desimal/pecahan dari detik. Dengan kata lain, kita ingin sesuatu yang tampak seperti  11:08:03. Dalam lingkungan bahasa­bahasa pemrograman lainnya, biasanya sudah terdapat cara  sederhana untuk mengendalikan format output yang berupa angka­angka. 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   input­input   terformat   (formatted   input).   Sayangnya,   tools   ini  tidaklah terlalu mudah untuk digunakan, jadi saya tidak akan membahasnya di dalam  buku ini. Tetapi jika Anda benar­benar ingin menggunakannya, silahkan melihat­lihat  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   bagian­bagian  berikutnya,   saya   akan   menunjukkan   beberapa   antarmuka   (interface)   yang   mungkin  dapat   digunakan   untuk   metode­metode   yang   beroperasi   melalui   objek­objek.   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 objek­objek/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.



Fill­in­method: salah satu argumennya adalah sebuah objek 'kosong' yang diisi  oleh metode. Secara teknis, sebenarnya ini merupakan salah satu bentuk dari  modifier. 

9.8

Fungsi­fungsi murni

Sebuah metode dianggap sebagai fungsi murni jika hasilnya hanya bergantung kepada  argumen­argumen, dan pada saat yang bersamaan dia tidak memberikan efek samping;  seperti memodifikasi sebuah argumen atau mencetak sesuatu. Satu­satunya 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   pembuat­roti   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 kasus­kasus 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 masalah­masalah 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 variabel­variabel. 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.   Lagi­lagi,   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 bentuk­bentuk perulangan (iteration)?

9.10 Fill­in 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   berulang­ulang   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 men­debug kode program yang panjang.

9.11 Mana yang terbaik? Semua yang dapat dilakukan dengan menggunakan modifier dan fill­in­method   dapat  juga dikerjakan menggunakan fungsi murni. Dalam kenyataannya, ada sebuah bahasa  pemrograman,   yang   disebut   sebagai   bahasa   pemrograman   fungsional,   yang   hanya  mengizinkan   penggunaan   fungsi­fungsi   murni   saja.   Beberapa   pemrogram   percaya  bahwa program yang dibangun dengan menggunakan fungsi murni akan lebih cepat  untuk dikembangkan dan lebih kecil risiko error­nya ketimbang program yang dibangun  dengan menggunakan modifier. Meskipun begitu, tetap ada saat­saat dimana modifier  lebih sesuai untuk diterapkan, dan saat­saat 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 high­level programming (pemrograman tingkat­ tinggi). Dalam teknik ini, Anda harus benar­benar mengandalkan  deep­thinking  Anda  terhadap   suatu   kasus.   Untuk   kasus   Time   di   atas,   deep­thinking   ini   berarti   Anda  menganggap bahwa Time merupakan angka tiga­digit dalam basis 60! Artinya, second  adalah “kolom satuan”, minute adalah “kolom 60­an”, 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 variabel­variabel 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 metode­metode 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 di­debug, 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   kasus­kasus   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,   teknik­teknik   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 Kata­Kata Istilah

Arti Sebelumya, saya telah mendefinisikan kelas sebagai sebuah  koleksi/kumpulan dari metode­metode 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 variabel­variabel 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” 

Fill­in­method

sebagai sebuah parameter lalu mengisi variabel­variabel  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 petak­petak  Scrabble. Variabel­variabel insatannya haruslah sebuah karakter dengan nama letter  dan sebuah integer dengan nama value. b) Tulislah sebuah konstruktor yang menggunakan parameter­parameter dengan nama  letter dan value lalu inisialisasilah variabel­variabel instannya.  c) Tulislah sebuah metode dengan nama printTile yang menggunakan satu objek Tile  209

sebagai parameter lalu mencetak variabel­variabel 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 sisi­sisi 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 variabel­variabel 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 variabel­variabel instannya dengan nilai­nilai tertentu, lalu  cetaklah objek tersebut. f) Sampai   sejauh   ini,   Anda   telah   membuat   program­program   yang   dapat   diuji  (debuggable), meski dalam jumlah yang tidak terlalu banyak. Sekarang ujilah semua  212

program itu, lalu debug­lah jika memang perlu. g) Tulislah sebuah konstruktor kedua untuk kelas Anda. Kelas ini menggunakan dua  argumen yang sekaligus berfungsi untuk menginisialisasi variabel­variabel 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 baris­baris 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. Ingat­ingatlah 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  floating­point)   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  variabel­variabel 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 fungsi­fungsi murni.  213

214

Bab 10 Array Array   adalah   suatu   kumpulan   nilai   yang   diidentifikasi   dengan   sebuah   indeks.   Anda  dapat membuat sebuah array berisi nilai­nilai 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 variabel­variabel bertipe ini dengan cara yang sudah lazim  digunakan: int[] count; double[] values; Sampai Anda menginisialisasi variabel­variabel 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  nomor­nomor   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 nilai­nilai di dalam array, gunakanlah operator []. Sebagai contoh,  count [0] akan mengacu kepada elemen ke­0 dari array tersebut. Sedangkan count [1]  akan mengacu kepada elemen ke­1 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  Perulangan­perulangan   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 bertingkah­laku 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 nilai­nilai 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 elemen­elemen (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 variabel­variabel instan dengan tipe data yang berbeda­beda. 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   elemen­elemen   dari  array pertama ke array yang baru, lalu mengembalikan nilai kembalian yang berupa  acuan ke array baru tersebut.

10.6 Angka­Angka Acak (Random)  Program­program komputer pada umumnya selalu mengerjakan hal yang sama setiap  kali mereka dieksekusi. Sifat seperti ini membuat program­program 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).  Permainan­permainan (games) yang dimainkan dengan sebuah komputer adalah contoh  yang jelas, tapi masih banyak lagi contoh yang lain tentunya. Membuat sebuah program yang benar­benar bersifat non­deterministik adalah perkara  yang sangat tidak mudah. Namun tetap ada cara untuk membuat sebuah program agar  tampak bersifat non­deterministik. Salah satu cara adalah dengan menggunakan angka­ angka acak lalu memanfaatkannya untuk menentukan keluaran/hasil sebuah program.  Java menyediakan metode bawaan yang dapat membuat angka­angka  pseudorandom  (imitasi terhadap definisi angka random sesungguhnya). Angka­angka ini sebenarnya  tidaklah sungguh­sungguh 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; angka­angka  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 Angka­angka 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 angka­angka 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   elemen­elemen   bertipe   integer.  Kemudian   mengisinya   dengan   bilangan­bilangan   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 sungguh­sungguh 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  penghitung­penghitung  (counter)   yang  menyimpan   jejak  mengenai jumlah kemunculan suatu nilai dalam setiap kesempatan. Untuk nilai­nilai 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 masalah­masalah 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 ide­ide 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 pola­pola  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”. Elemen­elemen 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   nilai­nilai   yang   ada   ke  dalam interval­interval nilai yang ada. Berikut ini adalah metode inRange yang menghitung jumlah suatu elemen dalam array  yang  masuk ke  dalam  interval  nilai  yang ada.  Parameter­parameter   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  berulang­ulang. 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); } Satu­satunya '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 Single­Pass 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   single­pass   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 nilai­nilai (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 90­an, 80­an, dst. 

10.11 Daftar Kata­Kata Istilah

Arti Kumpulan nilai­nilai 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 hal­hal yang sama setiap  kali digunakan/dipanggil. Suatu deretan angka­angka yang tampak seperti angka­angka 

pseudorandom

yang diacak, tetapi sebenarnya tidak. Angka­angka 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   angka­angka   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 buah­buahan. 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, elemen­elemen 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   satu­satunya  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) Metode­metode   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.length­1); Tulislah   metode   max   yang   menggunakan   sebuah   array   sebagai   parameter   dan  maxInRange untuk menemukan dan mengembalikan nilai terbesar. Metode­metode  seperti   max   terkadang   disebut   juga   metode   pembungkus   (wrapper   methods).  Disebut   demikian   karena   metode­metode   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 wrapper­helper.  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 elemen­elemen 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 elemen­elemen 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 huruf­huruf yang berada di dalam String. Elemen  ke­0 dari histogram harus berisi jumlah huruf a yang berada dalam string, baik huruf  kecil maupun kapital, sementara elemen ke­26 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, kata­kata 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 kotak­kotak yang berisi huruf di  dalamnya. Tujuan permainan ini adalah untuk membuat huruf­huruf yang pada awalnya  tidak beraturan menjadi teratur sehingga dapat digunakan untuk   mengeja kata­kata.  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 masing­masing 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   fitur­fitur   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 array­array  (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 sia­sia. Ada 52 kartu dalam sebuah tumpukan kartu  (deck).   Masing­masing   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 kartu­kartu 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   angka­angka   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   kartu­kartu   yang  menggunakan simbol­simbol: 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 objek­objek. Saya akan memulainya dengan  metode printCard.  Untuk mencetak objek Card yang dapat dibaca dengan mudah oleh manusia, kita akan  memetakan   kode­kode   integer   ke   bentuk   yang   berupa   kata­kata.   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 elemen­elemen array ini dengan nilai­nilai 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,   sampai­sampai   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:

Elemen­elemen   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 objek­objek, 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) peringkat­peringkat dalam kartu: String[]   ranks   =   {   "narf",   "Ace",   "2",   "3",   "4",   "5",   "6", "7",  "8", "9",  "10",  "Jack",  "Queen",  "King"  };

Elemen   “narf”   digunakan   sebagai   penjaga­tempat   (place­keeper)   untuk   elemen   ke­0  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 ke­0, seperti biasa, tapi  akan lebih baik bagi kita untuk mengkode 2 dengan 2, 3 dengan 3, 4 dengan 4, dst. Dengan   array­array   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 maksud­maksud 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 berbeda­beda 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 data­tipe data primitif, terdapat operator­operator kondisional yang dapat  membandingkan nilai­nilai sekaligus menentukan nilai mana yang lebih besar daripada  nilai   lainnya.   Operator­operator   ini   (<,  rel="nofollow">,   dll)   tidak   bisa   digunakan   untuk   tipe­tipe 

245

objek.   Khusus   untuk   String,   ada   sebuah  metode  built­in  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.   Angka­angka   yang   termasuk   ke   dalam   kelompok   tipe   data   integer   dan  floating­point 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, buah­buahan 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  kartu­kartu  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 masing­masing individu. Namun untuk  kasus   ini,   saya   berpendapat   bahwa   variabel  suit  lebih   penting,   karena   ketika   Anda  membeli  satu set kartu,  kartu­kartu  itu tersusun dalam  susunan  di  mana kartu­kartu  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 kartu­kartu 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 kelompok­kelompok  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 objek­objek Card itu sendiri. Nilai­nilai dari  elemen   array   diinisialisasi   dengan  null.   Anda   bisa   mengakses   elemen­elemen   array  dengan cara yang sudah lazim dipakai: if (deck[3] == null) { System.out.println ("No cards yet!"); } Tapi jika Anda mencoba mengakses variabel­variabel 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 ke­0 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 objek­objek 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 kelompok­kelompok 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; } Argumen­argumen  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 kartu­kartu 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 benar­benar 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   kata­kata   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. Satu­satunya 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 kata­kata sudah tersusun dalam keadaan terurut alfabetis. Dalam kasus tumpukan kartu ini, jika kita tahu bahwa kartu­kartu 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 mid­1. 4. Jika kartu yang berada dalam mid lebih rendah daripada kartu yang Anda cari,  carilah kartu tersebut di daerah dalam interval antara mid­1 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, mid­1);     } 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   benar­benar  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, mid­1); } 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 benar­tidaknya 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 program­program 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   bagian­bagian   dari   array   yang  berada   di   luar   batas.   Jadi,   Anda   sebenarnya   tidak   mengirim   sebuah   subbagian   dari  tumpukan;   sebaliknya,   Anda   benar­benar   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  menyebut­nyebut   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   variabel­variabel   tersebut,   semua   sudah   diberi   dengan   nilai­nilai  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,   sampai­sampai   ia   sering   kehilangan   maknanya   dalam  berbagai   konteks   kalimat.   Walaupun   begitu,   abstraksi   merupakan   ide   utama   dalam  dunia ilmu komputer (sebagaimana juga terjadi di bidang­bidang 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 detail­detail yang tidak penting pada saat   menangkap  perilaku­perilaku yang relevan.”

11.10 Daftar Kata­Kata 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  bersama­sama  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  kartu­kartu 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  masing­masing kartu  adalah sebagai  berikut: kartu  As  bernilai  1, semua  kartu yang  bergambar (Jack, Queen, King) bernilai 10, sementara kartu­kartu 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 kartu­kartu sebagai  argumen.   Nilai   yang   akan   dikembalikan   adalah   skor   total   dari   kartu­kartu   tersebut.  Peringkat­peringkat 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   mem­parse  suatu   string   yang  ditentukan. Latihan 11.4 Tulislah sebuah metode, suitHist, yang menggunakan sebuah array berisi kartu­kartu  sebagai   parameter   dan   mengembalikan   sebuah   histogram   dari   kelompok­kelompok  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   kartu­kartu  sebagai sebuah parameter dan mampu mengembalikan nilai True jika kartu­kartu 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 objek­objek 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   benar­benar   didefinisikan   di   dalam   kelas  Card.      Untuk metode­metode 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   permainan­permainan   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   benar­benar   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   pernyataan­pernyataan   dalam   Java  dengan kata­kata dalam bahasa alamiah (baca: bahasa yang kita gunakan sehari­hari).  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 hati­hati terhadap indeks yang dihasilkan, karena mungkin  saja indeks  tersebut keluar dari batasan interval. Anda juga bisa membuat metode  swapCard  sendiri tentunya. Satu­satunya 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 kartu­kartu 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   mengacak­acak   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   berulang­ulang   lalu   memilih   kartu  terendah dalam setiap perulangan.  Pada perulangan pertama, kita menemukan kartu terendah (the lowest) lalu menukarnya  dengan kartu yang berada di posisi ke­0. Pada perulangan berikutnya, yaitu yang ke­i,  kita akan mendapatkan kartu terendah di sebelah kanan i lalu menukarnya dengan kartu  di indeks yang ke­i. 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   ke­i   dengan   kartu   terendah } Lagi­lagi,  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  kartu­kartu  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 kartu­kartu 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 sifat­sifat 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 kartu­kartu yang telah ditentukan: public   static   Deck   subdeck   (Deck   deck,   int   low,   int   high) {    Deck sub = new Deck (high­low+1);       for (int i = 0; i<sub.cards.length; i++) {       sub.cards[i] = deck.cards[low+i];    }    return sub; }

Ukuran  subdeck  adalah  high­low+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  off­by­one   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, subtumpukan­subtumpukan  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 objek­objek 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 bagi­bagi kartu (dealing), apakah Anda juga  berpikir mengenai pembagian kartu yang harus dilakukan secara satu per satu untuk  setiap pemain, dengan teknik round­robin, 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  round­robin  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 hal­hal tidak perlu yang memang berada di luar kemampuannya,  atau mengharapkan kemampuan­kemampuan dari komputer yang  sebenarnya memang  tidak dimilikinya. Ini semua terjadi karena kita telah melebih­lebihkan sebuah metafora  jauh di atas ambang batasnya sendiri. Berhati­hatilah 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 masing­masing terdiri dari 10 kartu kemudian  urutkanlah keduanya sehingga ketika keduanya saling berhadap­hadapan, kartu  terendah akan berada di posisi teratas. Letakkan kedua subtumpukan tersebut  tepat di depan muka Anda. 2. Bandingkan kartu teratas dari masing­masing 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   kartu­kartu   di   kedua   subtumpukan   itu   habis.  Kartu­kartu yang sudah dikenai proses pada langkah kedua adalah kartu­kartu  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, hal­hal  yang menarik akan segera terjadi! Sesuatu yang ajaib dalam  mergesort  adalah fakta  bahwa teknik ini melakukan proses yang bersifat rekursif. Di saat Anda mengurutkan  subtumpukan­subtumpukan itu, Anda tidak perlu lagi repot­repot 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 dipilah­pilah menjadi bagian­bagian kecil.  Saya   telah   mencontohkan   contoh­contoh   di   atas   agar   Anda   bisa   mempraktikkan  paradigma kedua. Ketika Anda menggunakan  sortDeck  untuk mengurutkan subtumpukan­subtumpukan,  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   program­program   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 Kata­Kata 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 sehari­hari) 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 kira­kira 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  kartu­kartu   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 metode­metode 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 di­compile  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 benar­benar  yakin   dengan   apa   yang   Anda   lakukan,   Anda   juga   bisa   menggunakan   perintah  search­and­replace. 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  kartu­kartu) dan dua indeks. Metode ini berfungsi untuk menukar kartu­kartu 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   kartu­kartu  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   kartu­kartu   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 (card­counter) semakin sulit,  sekarang   banyak   kasino   yang   menggunakan   mesin   pengacak   yang   mengacak   kartu  secara   secara   naik­satu   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  masing­masing  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 masing­masing 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 hand­hand tersebut termasuk Flush ataukah Three Of A Kind.  e) Tulislah   beberapa   metode   yang   bisa   menguji   status­status  hand  poker   lainnya.  Beberapa diantaranya mungkin lebih mudah ketimbang lainnya. Mungkin akan lebih  baik jika Anda menulis beberapa helper­method yang bisa digunakan secara umum  untuk beberapa metode penguji sekaligus.  f) Dalam beberapa permainan poker, masing­masing 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  hand­hand  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   benar­benar   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 Teknik­tekniknya Ada begitu banyak bahasa pemrograman di dunia ini, begitu juga dengan teknik­teknik  pemrograman   (sering   disebut   sebagai   paradigma).   Tiga   teknik   yang   sudah   muncul  dalam buku ini antara lain; prosedural, fungsional, dan object­oriented. Meskipun Java  sering disebut­sebut 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. Program­program Java yang sudah ada  beserta   paket­paket   bawaan   Java   ditulis   menggunakan   gabungan   dari   ketiga   teknik  tersebut. Bedanya, program­program dan paket­paket 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 kelas­kelas) biasanya berkaitan dengan objek­objek  yang berada di dunia  nyata.  Contoh; di  subbab  12.1, pembuatan kelas  Deck  merupakan   langkah   menuju   penggunaan   konsep   pemrograman   berorientasi  objek. 



Metode­metode 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   metode­metode   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.

Akhir­akhir ini, pemrograman berorientasi objek menjadi sangat populer, dan bahkan  ada   orang   yang   menganggapnya   sebagai   teknik   pemrograman   yang   lebih   hebat  (superior) ketimbang teknik­teknik 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   metode­metode   objek  seringkali terlihat lebih singkat dan padat ketimbang metode­metode 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 konstruktor­konstruktor 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. Variabel­variabel  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 meng­assign  nilai­nilai  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 variabel­variabel 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 Fungsi­fungsi dalam Bilangan Kompleks Mari   kita   lihat   beberapa   operasi   yang   mungkin   akan   kita   kerjakan   untuk   mengolah  bilangan­bilangan 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   variabel­variabel   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 Fungsi­fungsi lainnya dalam Bilangan Kompleks Operasi lain yang mungkin akan kita perlukan adalah fungsi penjumlahan. Anda bisa  menjumlahkan   bilangan­bilangan   kompleks   dengan   menjumlahkan   masing­masing  bilangan dengan pasangannya  masing­masing; 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) ; }

Lagi­lagi,   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 contoh­contoh 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 contoh­contoh 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   metode­metode   objek   (pun   jika   metode­metode   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 meng­override 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 berulang­ulang  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 Tulis­ulang lah  normalize agar menjadi fungsi murni. Lalu tulis­ulang 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 kelas­kelas built­in). Manfaat utama dari fitur ini adalah agar Anda dapat menambahkan metode­metode atau  variabel­variabel   instan   baru   ke   dalam   kelas   yang   sudah   ada   tanpa   harus  memodifikasinya. Hal ini sangat berguna jika Anda hendak melakukan sesuatu terhadap  kelas­kelas built­in yang memang tidak bisa diubah­ubah 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,   kelas­kelas   yang   sudah   ada/eksis   terlebih   dahulu   itu 

291

sering disebut dengan kelas­kelas 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); } Parameter­parameter   dalam  draw  adalah   sebuah   objek  Graphics  dan   kotak   untuk  menampilkan gambar lingkaran (bukan koordinat­koordinat lingkaran tersebut). Lagi­lagi, 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   men­set  variabel   instan   milik  dr  lalu   memanggil   metode­metode   melalui  dirinya   sendiri   dengan   cara   yang   sudah   biasa   kita   lakukan   sebelumnya.   Ketika   kita  memanggil  grow  atau   metode­metode   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   kelas­kelas  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   berturut­turut  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 kelas­kelas built­in   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 jangan­jangan dokumentasinya terdapat di  induk dari induk dari induk dari induk Slate. Dan jangan lupa, banyak hal­hal yang bisa dikerjakan dengan konsep inheritance juga  bisa dikerjakan sama baiknya (bahkan lebih) jika dikerjakan tanpanya.

13.16 Daftar Kata­Kata 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   objek­objek   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   metode­metode   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 hal­hal mana yang legal dan mana yang tidak.  Selain itu,  pelajari juga pesan­pesan 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 Acuan­acuan dalam Objek Di  dalam bab sebelumnya kita sudah melihat bahwa variabel­variabel 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 node­nya 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 variabel­variabel instan sudah mengikuti spesifikasi yang ada secara  alami,   sementara   sisanya   tinggal   menyesuaikan   dengan   variabel   instan.   Ekspresi  cargo   +   ""   sebenarnya  agak­agak  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 node­node  tersebut  belum   kita  hubungkan   (linked).   Diagram   keadaannya   akan   tampak   sebagai  berikut:

Untuk   menghubungkan   node­node   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 node­node 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   objek­objek   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   node­node   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 masing­masing elemen, misalnya (1, 2, 3).  Untuk   latihan   Anda,   ubahlah  printList  sedemikian   sehingga   ia   bisa   menghasilkan  output seperti format di atas. Untuk   tambahan,   tulis­ulanglah  printList  menggunakan   perulangan  for  ketimbang  memakai while. 

14.4 List dan Rekursi Rekursi dan list berjalan seiring­seirama 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   digit­digit,   lalu  menggunakan infinite list untuk merepresentasikan bagian atau angka­angka (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 program­program  dengan list justru sulit dibaca. Saya sering menggunakan nama­nama 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 Node­Node 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 jelas­jelas 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 node­node 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

Lagi­lagi, 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   metode­metode   seperti   ini   untuk  melihat apakah mereka benar­benar berfungsi dengan kondisi­kondisi 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 berubah­ubah, saya  akan memperlihatkan sebuah cara alternatif untuk mengimplementasikan listnya dahulu  baru   kemudian   menjelaskan   kepada   Anda   semua   masalah   apa   yang   dapat  diselesaikannya. Pertama­tama, 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. Objek­objek IntList bertindak sebagai manipulator list milik objek­objek 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   fungsi­fungsi   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   “well­formed”,  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 Kata­Kata Istilah

Arti Struktur   data   yang   mengimplementasikan   sebuah   koleksi 

list

menggunakan   sekumpulan   node­node   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 definisi­definisi 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 masing­masing metode, tambahkan kode itu ke dalam main lalu  ujilah.   Yakinlah   untuk   menguji   beberapa   kasus   khusus   seperti   list­list   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 ke­i 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 memutar­balik  urutan node­node yang ada di dalamnya.   314

f) Tulislah   metode  append  yang   menggunakan  sebuah   IntList   sebagai   parameter.  Metode ini bisa menambahkan salinan dari node­node 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   digit­digit,   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   data­tipe   data   tersebut  diimplementasikan.   Sebagai   contoh,   kelas  Card  merepresentasikan   sebuah   kartu  menggunakan   dua   integer.   Sebagaimana   yang   telah   saya   kemukakan   sebelumnya,  teknik   di   atas   bukanlah   satu­satunya   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   operasi­operasi   tersebut   (apa   yang  dilakukan   oleh   operasi)   tapi   dia   tidak   menentukan   implementasi   operasi­operasi   itu  sendiri. Hal inilah yang membuat mereka abstrak.  Kenapa ADT berguna? ●

ADT   menyederhanakan   tugas   Anda   ketika   hendak   menentukan   sebuah  algoritma jika Anda bisa mengidentifikasi operasi­operasi 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.



Varian­varian   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.  ●

Operasi­operasi   ADT   menyediakan   sebuah   level   bahasa   tingkat­tinggi   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 layanan­layanan.

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 operasi­operasi. Stack bisa melakukan operasi­operasi 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 tipe­tipe 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  benar­benar mengetahui tipe­tipe objek yang dikandungnya. Ketika kita memasukkan  objek­objek Node, mereka dikonversikan secara otomatis menjadi Object. Ketika kita  mengambil objek­objek tersebut dari stack, kita harus meng­cast mereka menjadi Node  kembali. Node node = (Node) obj; System.out.println (node); Sayangnya, programmer justru akan mengalami kesulitan besar untuk melacak jejak  objek­objek dalam stack dan juga ketika hendak meng­cast  mereka kembali ke tipe  yang benar sebelum mereka diambil. Jika Anda mencoba untuk meng­cast 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 elemen­elemen 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  run­time stack untuk melacak keberadaan node­node 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 run­time 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 objek­objek yang  berisi nilai­nilai 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   nilai­nilai   khusus   (seperti   nilai   minimum   da  maximum   untuk   sebuah   tipe),   dan   metode­metode   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 Nilai­Nilai Java mengetahui cara mencetak objek­objek 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   metode­metode   yang   bisa   digunakan   untuk   mengubah   objek­objek  322

pembungkus ke tipe primitif yang berbeda­beda. Anda harus memeriksa dokumentasi  untuk masing­masing kelas pembungkus untuk melihat apa saja yang bisa diubah dan  apa yang tidak.

15.8 Metode­Metode Bermanfaat dalam Kelas Pembungkus Seperti   yang   telah   saya   sebutkan,   kelas­kelas   pembungkus   berisi   metode­metode  bermanfaat   yang   berkaitan   dengan   masing­masing   tipe.   Sebagai   contoh,   kelas  Character  berisi metode­metode, 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 metode­metode 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   metode­metode   untuk  menginterpretasikan dan mencetak integer­integer ke bentuk yang berbeda­beda. 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,   ekspresi­ekspresi   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 operan­operannya, 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) operan­operan 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 token­token. Untuk menggunakan kelas  ini, Anda harus mengimpornya dari java.util. Dalam bentuknya yang paling sederhana,  StringTokenizer  menggunakan spasi­spasi  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  token­token   dari  StringTokenizer. while (st.hasMoreTokens ()) { System.out.println (st.nextToken()); } Hasilnya adalah: Here are

325

four tokens. Untuk menguraikan ekspresi­ekspresi, 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 operator­operatornya. Untungnya, masih ada satu  opsi lagi dari StringTokenizer. StringTokenizer

  st   =   new   StringTokenizer

  ("11  

22+33*", " +­*/", true); Argumen ketiga berkata, “Ya, kami ingin memperlakukan delimiter­delimiter sebagai  token­token.” sekarang hasilnya adalah 11 22 + 33 326

* Ini hanyalah token­token yang akan kita gunakan untuk mengevaluasi ekspresi­ekspresi  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  benar­tidaknya 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 Variabel­variabel   instan   untuk   mengimplementasikan   kasus   ini   adalah   sebuah   array  yang berisi sekumpulan Object. Array ini sendiri nantinya akan berisi item­item 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 di­set 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 men­decrement 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 variabel­variabel 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 kosong­tidaknya 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 sana­sini. 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   satu­satunya   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. Fakta­fakta  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 Kata­Kata 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   objek­objek   mengandung   tipe­tipe  primitif,   dan   metode­metode   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   ekspresi­ekspresi   matematika  infix

dengan   posisi   operator   yang   berada   di   antara   operan­ operannya. Suatu   cara   dalam   menulis   ekspresi­ekspresi   matematika 

postfix

dengan   posisi   operator   yang   berada   di   belakang   operan­ operannya. Membaca sebuah string yang terdiri dari karakter­karakter 

parse

atau token­token untuk menganalisa struktur tata bahasanya  (grammatical structure). Sekumpulan   karakter­karakter   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 item­item ke dalam stack, lalu mengambil item­item itu dari stack, 

333

meletakkan   kembali   ke   dalam   array   dalam   posisi   yang   terbalik   dari   keadaan  sebelumnya. Tujuan   latihan   ini   adalah   untuk   mempraktikkan   mekanisme   pembuatan   objek­objek  pembungkus, memasukkan dan mengeluarkan objek­objek, dan meng­typecast  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 kata­kata (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  benar­benar   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 baris­baris 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 String­Tokenizer untuk menguraikan String  dan sebuah Stack berisi Double untuk mengevaluasi ekspresi tersebut. Langkah­langkah  berikut ini adalah saran saya untuk pengembangan program milik Anda. a) Tulislah   sebuah   program   yang   bisa   meminta   user   untuk   memasukkan   string  sekaligus   mencetaknya,   berulang­ulang,   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  operasi­operasi 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 operasi­operasi 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 kelas­kelas tersebut? Parameter­ parameter apa yang harus dilewatkan? h) Agar rancangan Anda tambah elegan, Anda juga harus bisa membuat kode Anda  bersifat  bullet­proof,   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 sehari­hari, 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  masing­masing   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 masing­masing 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   operasi­operasinya:   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 koleksi­koleksi tersebut, termasuk array dan list. Di  sini kita akan memilih teknik yang unjuk kerjanya paling bagus – berapa lama waktu  yang dibutuhkannya untuk melaksanakan operasi­operasi yang kita inginkan – dan tentu  saja dalam hal kemudahan penggunaannya.

16.1 ADT Antrian ADT antrian akan didefinisikan oleh operasi­operasi 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  metode­metode 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 meng­override 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 di­debug. Hal ini terjadi karena  metode­metode 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   metode­metode   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   metode­metode   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   metode­metode  LinkedList  ke   dalam  metode­metode   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 item­item yang berada dalam  list (daftar)? Untuk menjawab pertanyaan di atas, kita harus mengetahui lebih banyak hal mengenai  implementasi.   Jika   kita   mengasumsikan   bahwa  LinkedList  benar­benar   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 node­nya berisi kargo serta sebuah acuan yang mengarah ke node berikutnya  341

dalam list.  Tidak ada pemanggilan perulangan atau fungsi di sini. Akibatnya, run­time 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, lagi­lagi, 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 objek­objek  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 biasa­biasa 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 diagram­diagram yang menunjukkan operasi­operasi ini, baik  dalam   kasus   normal   maupun   kasus   khusus,   dan   yakinkan   diri   Anda   sendiri   bahwa  diagram­diagram 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. Item­item 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 titik­titik).

Ada dua cara untuk memikirkan variabel­variabel 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 indeks­indeks 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;     } Variabel­variabel instan dan konstruktornya tampak normal dan biasa­biasa saja, tapi  kita   lagi­lagi   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 array­nya 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 item­item 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:

array­nya   sekarang   telah   penuh.   Tidak   ada   lagi   “ruang   kosong   berikutnya”.   Jadi  sekarang next tidak bisa menunjuk ke mana­mana lagi. Satu kemungkinan adalah kita  bisa   mengubah   ukuran  array,  seperti  yang   telah  kita  lakukan   dengan  impelementasi  untuk  stack. Tapi di kasus  itu, array­nya 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 ruang­ruang 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   benar­benar   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 array­nya penuh? Dalam kasus ini, mengubah  ukuran array mungkin adalah satu­satunya 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 item­item apa yang berada di dalam antrian tersebut. Sebagai contoh, jika item­item 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 objek­objek 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 metaclass­metaclass 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 kelas­kelas bawaan Java merupakan anggota dari metaclass  Comparable,  termasuk kelas­kelas 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 item­item yang  berada   dalam   antrian,   kita   menentukan   metaclass  Comparable.   Sebagai   contoh,  variabel­variabel 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. Variabel­variabel instan dideklarasikan dengan  private  agar kelas­kelas  lainnya tidak bisa mengakses mereka secara langsung. Konstruktor   dan  isEmpty  masih   sama   dengan   yang   pernah   kita   lihat   sebelumnya.  Ukuran awal dari array­nya 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++; }

Satu­satunya 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  compile­time  yang   berbunyi   “java.lang.Comparable   is   an  interface. It can't be instantiated”.  Di Java, metaclass­metaclass 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 metaclass­metaclass itu tidak bisa diinstansiasi? Karena sebuah metaclass hanya  menentukan   apa­apa   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  compile­time  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  run­time  akan   memeriksa   apakah   cara   ini   memenuhi   semua  persyaratan, lalu menolaknya. Jadi, itulah yang akan kita dapatkan jika kita mengikuti  saran dari compiler. Untuk mengeluarkan item­item 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 item­item   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 meng­compile  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 meng­cast 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   kelas­kelas   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 Kata­Kata 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   faktor­faktor   eksternal.  Anggota yang mempunyai prioritas tertinggi adalah anggota  yang pertama kali kita hapus. Sebuah   ADT   yang   mendefinisikan   operasi­operasi   yang 

Priority Queue

mungkin   akan   dilakukan   oleh   seseorang   terkait   dengan  antrian berprioritas. Suatu   definisi   kelas   yang   mengimplementasikan   sebuah  ADT dengan definisi­definisi metode yang merupakan hasil 

veneer

pemanggilan dari metode­metode 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  run­time  yang   tidak   bergantung  kepada ukuran struktur datanya. Operasi yang run­time­nya 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   kelas­kelas.   Spesifikasi   metaclass   berisi 

metaclass

persyaratan­persyaratan   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, elemen­elemen SortedList  361

harus menjadi anggota Comparable, dan list­nya 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 list­nya 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 list­nya 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 ADT­nya: 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 compareTo­nya 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 simpul­simpul. 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 simpul­simpul dalam list, simpul­simpul pohon juga berisi kargo: dalam hal ini  berupa  Object  yang generik. Variabel­variabel 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 istilah­istilah dalam dunia perpohonan (baca: metafora pohon), simpul­simpul  lain akan disebut cabang­cabang (branches). Sementara simpul yang berada di posisi  bagian bawah yang mengacu ke null disebut daun­daun (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   simpul­simpul   yang   diacunya   disebut  children  (anak).   Simpul­simpul   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 simpul­simpul pohon mirip dengan proses ketika kita merangkai  list.   Kita   mempunyai   sebuah   konstruktor   untuk   simpul­simpul   pohon   yang  menginisialisasi variabel­variabel 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 simpul­simpul 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   objek­objek  Integer.   Jika   kita  membuat asumsi ini dalam Tree.java maka kita akan kehilangan manfaat­manfaat yang  bisa kita ambil dari penggunaan struktur data generik. Di sisi lain, kode ini mengakses variabel­variabel instan simpul­simpul 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

Simpul­simpul di atas bisa saja merupakan sebuah operan seperti 1 dan 2 atau berupa  operator­operator   seperti   +   dan   *.   Operan­operan   merupakan   simpul­simpul   daun;  sementara simpul­simpul operator berisi acuan yang mengacu ke operan­operan 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). Pohon­pohon 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 anak­anaknya 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,   operator­operator   muncul   sebelum  operan­operan 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   operasi­operasinya.  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   pohon­pohon:   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 pohon­pohon ekspresi. Sementara kode yang menghasilkan  sekaligus   menelusuri   pohon­pohon   ekspresi   tidak   perlu   tahu   segala   sesuatu   tentang  implementasi   pohon­pohon.   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. Item­item yang disimpan dalam sebuah pohon disyaratkan  agar   bisa   dikunjungi   (visitable).   Ini   berarti   bahwa   item­item   tersebut   akan  mendefinisikan sebuah metode – visit – yang akan melakukan apapun yang diinginkan  oleh klien terhadap masing­masing simpul. Dengan cara ini, Pohon kita akan mampu  melakukan penelusuran sementara klien mampu mengerjakan operasi­operasi simpul. Berikut ini adalah langkah­langkah yang harus kita kerjakan untuk membuat metaclass  yang bisa digunakan oleh kedua kubu sekaligus, klien dan provider: 1. Definisikanlah sebuah metaclass yang mampu mengidentifikasi metode­metode  yang   akan   dibutuhkan   oleh   kode  provider  untuk   melakukan   pemanggilan  melalui komponen­komponennya. 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 metode­metode yang memang dibutuhkan oleh klien. 4. Tulislah kode klien untuk menggunakan kelas yang baru tersebut. 371

Subbab berikutnya akan mendemonstrasikan langkah­langkah 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   metode­metode   yang   harus   diimplementasikan   oleh  sebuah kelas agar bisa menjadi anggota metaclass. Syarat­syarat yang harus dipenuhi  antara lain; nama, tipe­tipe 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 isi­isinya. 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   meng­compile  definisi   kelas   ini   (yang   berada   dalam  file  Token.java),  compiler  akan   memeriksa   apakah   metode­metode   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 objek­objek 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 meng­cast­nya 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 metode­metode 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 capek­capek melakukannya. Sebelum   menggunakan   kelas  Vector  ini,   Anda   harus   memahami   beberapa   konsep  terlebih dahulu. Setiap Vector mempunyai kapasitas masing­masing, yakni ruang yang  sudah dialokasikan untuk menyimpan nilai­nilai 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 elemen­elemen yang berada dalam vektor.  Metode­metode tersebut menyediakan semantik dan kemampuan  error­checking  yang  berbeda­beda. Hati­hati, 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   elemen­elemen   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 metode­metode 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.   Vektor­vektor   menyediakan   satu   metode,  yakni  iterator,  yang   bisa   mengembalikan   objek­objek  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  elemen­elemen 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.  Iterator­iterator   menyediakan   cara   lain   bagi   kita   untuk   melakukan   hal   yang   sama.  Dalam kasus yang pertama,  provider  melakukan perulangannya lalu memanggil kode  klien untuk “mengunjungi” masing­masing 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 elemen­elemen Tree secara preorder. PETUNJUK:  Cara termudah untuk membuat Iterator adalah dengan meletakkan elemen­elemen ke  dalam   sebuah   vektor   yang   urutannya   sesuai   dengan   apa   yang   Anda   inginkan.  Kemudian Anda bisa memanggil metode iterator melalui vektor tersebut.

17.11 Daftar Kata­Kata 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 masing­masing memiliki jarak yang  sama ke root. Sebuah   teknik   penelusuran   pohon   di   mana   setiap   simpul  dikunjungi lebih dahulu sebelum simpul anak­anaknya. 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   objek­objek  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 parameter­parameternya 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 tipe­tipe 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 nama­nama dan skor­ skor  dari  kumpulan (Set)  pegolf. Program ini harus bisa membuat objek  Golfer,  meletakkan objek­objek 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 baris­baris 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 masalah­masalah 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 n­i, sehingga jumlah total perbandingan adalah n­i­1. 

Selanjutnya   kita   akan   melilhat   berapa   kali  findLowest  dipanggil   serta   nilai   i   nya.  Terakhir kali dipanggil, i bernilai  n­2 sehingga jumlah perbandingan adalah 1. Iterasi  sebelumnya   melakukan   dua   perbandingan   dan   demikian   seterusnya.   Selama   iterasi  pertama, i bernilai 0 dan jumlah perbandingan adalah n­1.

Jadi jumlah perbandingan total adalah 1+2+...+n­1. 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 m­1. 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 n­1 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 2i­1. 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 big­O”. 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/2­1 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, n­1); }

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 built­in 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 red­black 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 Variabel­length 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 variable­length codes. Pada bab ini kita akan melihat algoritma  untuk membangkitkan variable­length 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 anteater­eaters. Rare Andean deer eat tender sea reeds, aster seeds and rats’ ears. Dessert? Rats’ asses. 437

Teks di atas menghasilkan tebel frekuensi:  e 

40



32



24



22



20



19



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 kiri­kanan­kiri. Jika kita  mengganti kiri dengan “.” dan kanan dengan “­” kita mendapatkan tabel kode di bawah: e 

­.



­­ 439



..­



.­.



.­­



....



...­

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



71



57



53



49



44



43



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 kiri­kiri­ kanan dan mendapatkan huruf t. kemudian kita mulai dari akar lagi, kanan­kiri dan  mendapatkan huruf e, kembali ke puncak, lalu kanan­kiri 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_sehari­detik_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(1­Math.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(x2­x1)>=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+(ascii­122); else if(ascii<97) ascii=122+(96­ascii); 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+(ascii­90); else if(ascii<65) ascii=90+(64­ascii); 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()*(high­1)); }

public static int randomInt(int low, int high){ return low+(int)(Math.random()*(high­1)); }

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==high­1) 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.length­1); }

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.length­1; i++){ int index=indexOfMaxInRange(arr,i,arr.length­1); 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,mid­1); return findBisect(sub,c,low,mid­1); }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[i­1].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(high­low+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.length­1)); 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.length­1)/2;

Deck sub1=subdeck(deck,0,mid); Deck sub2=subdeck(deck,mid+1,deck.cards.length­1); 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.length­1)/2;

Deck sub1=subdeck(deck,0,mid); Deck sub2=subdeck(deck,mid+1,deck.cards.length­1); 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[i­1].next=node[i]; node[node.length­1]=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(angka1­angka2)); 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(indeks­1,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.8­17.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 (i­1)/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[size­1]; 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 Auto­generated 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 "+(time1­time)); 

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 "+(time1­time));  }

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 "+(time1­time));

keys = treeMap.keySet().iterator(); time=System.currentTimeMillis(); while(keys.hasNext()){ treeMap.containsKey(keys.next()); } time1=System.currentTimeMillis(); System.out.println("waktu tree map "+(time1­time));  

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 world­wide, royalty­free 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 front­matter 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

Related Documents


More Documents from "M Idrees"

Ta
May 2020 25
May 2020 13
May 2020 16
May 2020 17
Mod3.docx
December 2019 23