BAGAIMANA CARA BERPIKIR SEBAGAI ILMUWAN KOMPUTER
Versi Java
Pengarang
Allen B. Downey
Penerjemah
Wim Permana, [email protected]
(Bab 617)
Muhammad Fuad Dwi Rizki, [email protected]
(Bab 5, 18, 19, 20)
(Lampiran Foreach Dan Generik)
(Jawaban Latihan)
Agus Juliardi
(Bab 14)
1
Copyright 2003 Allen Downey.
Permission is granted to copy, distribute, and/or modify this document under
the terms of the GNU Free Documentation License, Version 1.1 or any later ver
sion published by the Free Software Foundation; with Invariant Sections being
“Preface”, with no FrontCover Texts, and with no BackCover Texts. A copy
of the license is included in the appendix entitled “GNU Free Documentation
License.”
The GNU Free Documentation License is available from www.gnu.org or by
writing to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 021111307, USA.
Copyright (c) Terjemahan, Lampiran Foreach dan Generik, dan
Jawaban Latihan © 2006 oleh Wim Permana, Muhammad Fuad Dwi
Rizki, dan Agus Juliardi. Permission is granted to copy,
distribute and/or modify this document under the terms of the
GNU Free Documentation License, Version 1.2 or any later version
published by the Free Software Foundation; with no Invariant
Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the section entitled
"GNUFree Documentation License".
2
Daftar Isi
1. Alur Program..............................................................................................................14
1.1Apa itu Bahasa Pemrograman?.............................................................................14
1.2 Apa itu program ?.................................................................................................17
1.4 Bahasa Formal dan Natural...................................................................................21
1.5 Program Pertama...................................................................................................24
1.6 Daftar Kata............................................................................................................27
1.7. Latihan.................................................................................................................28
2. Variabel dan Tipenya..................................................................................................31
2.1 Mencetak lebih banyak.........................................................................................31
2.2 Variabel.................................................................................................................33
2.3 Assignment...........................................................................................................34
2.4 Mencetak Variabel................................................................................................36
2.5 Keywords..............................................................................................................38
2.6 Operator................................................................................................................39
2.7 Operasi..................................................................................................................41
2.8 Operator pada String.............................................................................................42
2.9 Komposisi.............................................................................................................43
2.10 Daftar kata...........................................................................................................44
2.11 Latihanlatihan....................................................................................................46
3. Method........................................................................................................................49
3.1 Floatingpoint........................................................................................................49
3.2 Mengkonversi double ke int..................................................................................51
3.3 Method Matematika..............................................................................................52
3.4 Composition..........................................................................................................54
3
3.5 Menambahkan metode baru..................................................................................55
3.6 Class dan method..................................................................................................59
3.7 Program dengan banyak method...........................................................................61
3.8 Parameter dan argumen........................................................................................62
3.9 Stack diagram.......................................................................................................65
3.10 Metode Dengan Banyak Parameter....................................................................66
3.11 Method dan hasilnya...........................................................................................67
3.12 Daftar kata...........................................................................................................68
3.13 Latihan................................................................................................................69
4. Kondisional dan rekursi..............................................................................................74
4.1 Operator modulus.................................................................................................74
4.2 Percabangan..........................................................................................................74
4.3 Eksekusi Alternatif...............................................................................................76
4.4 Percabangan Berantai...........................................................................................77
4.5 Percabangan Bersarang.........................................................................................78
4.6 The return statement.............................................................................................79
4.7 Konversi Tipe........................................................................................................80
4.8 Recursion..............................................................................................................81
4.9 Diagram Stack Untuk Metode Rekursif................................................................84
4.10 Konvensi dan Divine Law...................................................................................85
4.11 Daftar Kata .........................................................................................................86
4.12 Latihan................................................................................................................87
5. Fruitful methods..........................................................................................................93
5.1 Nilai Kembalian....................................................................................................93
5.2 Pengembangan Program.......................................................................................96
5.3 Komposisi...........................................................................................................100
4
5.4 Overloading........................................................................................................101
5.5 Ekspresi Boolean................................................................................................103
5.6 Operator Logika .................................................................................................104
5.7 Metode Boolean..................................................................................................105
5.8 Lagi tentang Rekursi ..........................................................................................106
5.9 Leap of faith........................................................................................................109
5.10 Satu Contoh Lagi..............................................................................................111
5.11 Daftar Kata........................................................................................................112
5.12 Latihan .............................................................................................................113
6. Iterasi.........................................................................................................................121
6.1 Multiple Assignment...........................................................................................121
6.2 Iterasi ..................................................................................................................123
6.3 Pernyataan While ...............................................................................................123
6.4 Tabel...................................................................................................................126
6.5 Tabel Dua Dimensi.............................................................................................129
6.6 Enkapsulasi dan Generalisasi..............................................................................131
6.7 Metode................................................................................................................133
6.8 Enkapsulasi .... sekali lagi!..................................................................................133
6.9 Variabel Lokal....................................................................................................134
6.10 Generalisasi. Sekali lagi dengannya ................................................................135
6.11 Daftar KataKata...............................................................................................139
6.12 Latihan..............................................................................................................140
7. String.........................................................................................................................145
7.1 Memanggil metode dalam objek.........................................................................145
7.2 Length.................................................................................................................147
7.3 Traversal.............................................................................................................148
5
7.4 RunTime errors.................................................................................................149
7.5 Membaca Dokumentasi......................................................................................149
7.6 Metode indexOf..................................................................................................150
7.7 Looping dan Counting........................................................................................153
7.8 Operator Increment dan Decrement....................................................................154
7.9 Strings are Immutable.........................................................................................155
7.10 Strings are Incomparable..................................................................................156
7.11 Daftar KataKata...............................................................................................158
7.12 Latihan..............................................................................................................159
8. Interesting Objects....................................................................................................168
8.1 Apanya yang menarik?.......................................................................................168
8.2 Paketpaket..........................................................................................................168
8.3 Objek Titik (Point)..............................................................................................169
8.4 Variabel Instan...................................................................................................170
8.5 Objek Sebagai Parameter....................................................................................171
8.6 Rectangle.............................................................................................................172
8.7 Objek sebagai tipe kembalian (Return Type).....................................................173
8.8 Objek dapat diubahubah (Mutable)...................................................................173
8.9 Aliasing...............................................................................................................175
8.10 Null...................................................................................................................176
8.11 Garbage Collection...........................................................................................177
8.12 Objek dan Primitif.............................................................................................178
8.13 Daftar KataKata...............................................................................................180
8.14 Latihan..............................................................................................................181
9. Membuat Objek Anda Sendiri..................................................................................188
9.1 Definisidefinisi kelas dan tipetipe objek..........................................................188
6
9.2 Time ...................................................................................................................189
9.4 Konstruktor Lagi.................................................................................................192
9.5 Membuat Sebuah Objek yang Baru....................................................................193
9.6 Mencetak Sebuah Objek.....................................................................................195
9.7 Operasi Pada Objek.............................................................................................196
9.8 Fungsifungsi murni............................................................................................197
9.9 Modifier..............................................................................................................200
9.10 Fillin Methods..................................................................................................202
9.11 Mana yang terbaik?...........................................................................................203
9.12 Pengembangan secara bertahap vs perencanaan...............................................204
9.13 Generalisasi.......................................................................................................206
9.14 Algoritma..........................................................................................................207
9.15 Daftar KataKata...............................................................................................208
9.16 Latihan .............................................................................................................209
10.Array.........................................................................................................................215
10.1 Mengakses Elemen...........................................................................................216
10.2 Menyalin Array.................................................................................................217
10.3 Bentuk Perulangan for .....................................................................................218
10.4 Array dan Objek................................................................................................220
10.5 Panjang Array...................................................................................................220
10.6 AngkaAngka Acak (Random) ........................................................................221
10.7 Array dari Angkaangka Random (Acak) ........................................................223
10.8 Pencacahan........................................................................................................225
10.9 Histogram..........................................................................................................227
10.10 Solusi SinglePass...........................................................................................228
10.11 Daftar KataKata.............................................................................................229
7
10.12 Latihan............................................................................................................230
11.Array berisi Objek....................................................................................................238
11.1 Komposisi.........................................................................................................238
11.2 Objek Card........................................................................................................238
11.3 Metode printCard..............................................................................................241
11.4 Metode sameCard.............................................................................................243
11.5 Metode compareCard........................................................................................245
11.6 Array berisi Card..............................................................................................248
11.7 Metode printDeck.............................................................................................250
11.8 Searching..........................................................................................................251
11.9 Tumpukan dan subtumpukan............................................................................257
11.10 Daftar KataKata.............................................................................................258
11.11 Latihan............................................................................................................259
12.Objek berisi Array....................................................................................................261
12.1 Kelas Deck........................................................................................................261
12.2 Shuffling...........................................................................................................264
12.3 Pengurutan (Sorting).........................................................................................265
12.4 Subtumpukan (Subdecks).................................................................................266
12.5 Pengacakan dan Pembagian Kartu (Shuffling and Dealing).............................268
12.6 Mergesort..........................................................................................................270
12.7 Daftar KataKata...............................................................................................274
12.8 Latihan..............................................................................................................274
13.Pemrograman Berorientasi Objek............................................................................280
13.1 Bahasa Pemrograman dan Tekniktekniknya...................................................280
12.3 Metode Objek dan Metode Kelas......................................................................281
13.3 Objek Terkini....................................................................................................281
8
13.4 Bilangan Kompleks...........................................................................................282
13.5 Fungsifungsi dalam Bilangan Kompleks.........................................................283
13.6 Fungsifungsi lainnya dalam Bilangan Kompleks............................................285
13.7 Modifier............................................................................................................286
13.8 Metode toString................................................................................................287
13.9 Metode equals...................................................................................................288
13.10 Memanggil Satu Metode Objek dari Metode Objek Lain..............................289
13.11 Keganjilan dan Error.......................................................................................290
13.12 Inheritance.......................................................................................................291
13.13 Lingkaran yang bisa digambar .......................................................................292
13.14 Hierarki Kelas.................................................................................................293
13.15 Rancangan Berorientasi Objek.......................................................................294
13.16 Daftar KataKata.............................................................................................295
13.17 Latihan............................................................................................................296
14.Linked List................................................................................................................298
14.1 Acuanacuan dalam Objek................................................................................298
14.2 Kelas Node........................................................................................................298
14.3 List sebagai Collection......................................................................................301
14.4 List dan Rekursi................................................................................................302
14.5 Infinite List........................................................................................................304
14.6 Teorema Ambiguitas Fundamental...................................................................305
14.7 Metode Objek Untuk NodeNode.....................................................................306
14.8 Memodifikasi List.............................................................................................307
14.9 Wrappers dan Helpers.......................................................................................308
14.10 Kelas intList....................................................................................................310
14.11 Invariants.........................................................................................................312
9
14.12 Daftar KataKata.............................................................................................313
14.13 Latihan............................................................................................................313
15.Stacks........................................................................................................................316
15.1 Tipe Data Abstrak ............................................................................................316
15.2 The Stack ADT.................................................................................................317
15.3 Objek Stack dalam Java....................................................................................318
15.4 Kelas Pembungkus............................................................................................320
15.5 Membuat Kelas Pembungkus............................................................................321
15.6 Membuat Kelas Pembungkus Lagi...................................................................321
15.7 Mengeluarkan NilaiNilai.................................................................................322
15.8 MetodeMetode Bermanfaat dalam Kelas Pembungkus...................................323
15.9 Ekspresi Postfix................................................................................................324
15.10 Parsing............................................................................................................325
15.11 Mengimplementasikan ADT...........................................................................327
15.12 Implementasi Array untuk Stack ADT...........................................................327
15.13 Mengubah Ukuran Array................................................................................329
15.14 Daftar KataKata.............................................................................................332
15.15 Latihan............................................................................................................333
16.Antrian dan Antrian Prioritas...................................................................................337
16.1 ADT Antrian.....................................................................................................338
16.2 Veneer...............................................................................................................340
16.3 Linked Queue....................................................................................................343
16.4 Circular Buffer..................................................................................................345
16.5 Priority Queue...................................................................................................351
16.6 Metaclass...........................................................................................................352
16.7 Implementasi Array untuk Antrian Prioritas....................................................353
10
16.8 Klien Antrian Prioritas......................................................................................355
16.9 Kelas Golfer......................................................................................................357
16.10 Daftar KataKata.............................................................................................360
16.11 Latihan............................................................................................................361
17.Pohon (Trees)...........................................................................................................364
17.1 Simpul Pohon....................................................................................................364
17.2 Membuat Pohon................................................................................................365
17.3 Menelusuri Pohon.............................................................................................366
17.4 Pohon Ekspresi.................................................................................................367
17.5 Penelusuran.......................................................................................................368
17.6 Enkapsulasi.......................................................................................................370
17.7 Mendefinisikan metaclass.................................................................................372
17.8 Mengimplementasikan Metaclass.....................................................................373
17.9 Kelas Vector......................................................................................................374
17.10 Kelas Iterator...................................................................................................377
17.11 Daftar KataKata.............................................................................................378
17.12 Latihan............................................................................................................380
18.Heap..........................................................................................................................384
18.1 Implementasi Pohon menggunakan Array........................................................384
8.2 Analisis Kinerja .................................................................................................389
18.3 Analisis Merge Sort..........................................................................................392
18.4 Overhead...........................................................................................................394
18.5 Implementasi PriorityQueue.............................................................................395
18.6 Definisi Heap ...................................................................................................397
18.7 Penghapusan pada heap....................................................................................398
18.8 Penambahan Heap ............................................................................................400
11
18.9 Kinerja Heap ....................................................................................................401
18.10 Heapsort..........................................................................................................403
18.11 Daftar Kata Kata.............................................................................................404
18.12 Latihan............................................................................................................405
19.Maps.........................................................................................................................407
19.1 Array, Vector, Map...........................................................................................407
19.2 Map ADT..........................................................................................................408
19.3 HashMap builtin..............................................................................................408
19.4 Implementasi Dengan Vector...........................................................................414
19.5 Kelas Abstrak List.............................................................................................422
19.6 Implementasi dengan HashMap........................................................................423
19.7 Fungsi Hash......................................................................................................424
19.8 Mengubah Ukuran Hash Map...........................................................................427
19.9 Kinerja dari pengubahan ukuran.......................................................................428
19.10 Daftar Kata......................................................................................................429
19.11 Latihan............................................................................................................430
20.Kode Huffman..........................................................................................................434
20.1 Variabellength codes.......................................................................................434
20.2 Tabel frekuensi ................................................................................................435
20.3 Pohon Huffman.................................................................................................437
20.4 Metode super.....................................................................................................440
20.5 Pemecahan Kode...............................................................................................443
20.6 Pengkodean.......................................................................................................444
20.7 Daftar Kata........................................................................................................445
21.Lampiran Foreach dan Generik................................................................................446
Generik......................................................................................................................446
12
Foreach......................................................................................................................448
Daftar Kata ...............................................................................................................451
22.Jawaban Latihan.......................................................................................................452
Bab 2.........................................................................................................................452
Bab 3.........................................................................................................................453
Bab 4.........................................................................................................................456
Bab 6.........................................................................................................................457
Bab 7.........................................................................................................................465
Bab 8.........................................................................................................................471
Bab 9.........................................................................................................................473
Bab 10.......................................................................................................................478
Bab 11.......................................................................................................................488
Bab 12.......................................................................................................................492
Bab 13.......................................................................................................................504
Bab 14 ......................................................................................................................507
Bab 15.......................................................................................................................520
Bab 16.......................................................................................................................537
Bab 17.......................................................................................................................546
Bab 18.......................................................................................................................556
Bab 19.......................................................................................................................566
BAB 20.....................................................................................................................588
23.History .....................................................................................................................606
24.GNU Free Documentation License..........................................................................606
13
BAB 1
Alur Program
Tujuan dari buku ini dan kelas ini adalah untuk mengajarkan anda untuk berfikir seperti
ahli komputer. Saya menyukai cara berfikir ahli komputer karena mereka
menggabungkan beberapa fitur matematika, engineering, dan ilmu alam. Seperti ahli
matematika, ilmu komputer menggunakan bahasa formal untuk mengutarakan ide
(secara spesifik komputasi). Seperti seorang engineer, komputer mendesain sesuatu,
mengumpulkan komponen kedalam sistem dan mengevaluasi alternatif antar pertukaran
data. Seperti seorang ilmuwan, komputer meneliti sifat kompleksitas sistem, hipotesa
form dan prediksi tes.
Salah satu dari kemampuan penting dari komputer adalah pemecahan masalah
(ProblemSolving). Dengan pengertian kemampuan untuk memformulasikan masalah,
berfikir secara kreatif pemecahannya dan mengekspresikan solusi secara jelas dan
sayarat. Dengan demikian, proses dari pembelajaran ke program merupakan
kesempatan sempurna untuk mempraktekkan kemampuan pemecahan masalah. Oleh
karena itu bab ini dinamakan “Alur Program”.
Di level pertama, Anda akan belajar program yang merupakan kemampuan berguna
untuk diri sendiri. Di level lainnya Anda akan menggunakan pemrograman yang berarti
suatu berakhir. Jika kita pelan maka akhirnya akan menjadi jelas.
1.1Apa itu Bahasa Pemrograman?
Bahasa Pemrograman yang akan Anda pelajari adalah Java, yang relatif masih baru
14
(Sun merilis versi pertama pada bulan mei 1995). Java merupakan contoh bahasa
tingkat tinggi; bahasa tingkat tinggi lainnya yang mungkin pernah Anda dengar adalah
pascal, C, C++, dan FORTRAN.
Seperti yang Anda duga selain Bahasa Tingkat Tinggi ada juga Bahasa Tingkat Rendah.
Kadangkadang dikenal juga dengan bahasa mesin atau bahasa asembly. Dengan kata
lain komputer hanya dapat mengeksekusi program dalam bahasa tingkat rendah.
Sehingga program yang ditulis menggunakan bahasa tingkat harus diterjemahkan
terlebih dahulu sebelum dapat di Jalankan. Terjemahan ini akan memakan waktu dan
itulah sedikit kekurangan dari bahasa tingkat tinggi.
Akan tetapi keuntungannya sangat banyak, pertama, sangatlah mudah membuat
program dengan bahasa tingkat tinggi, mudah dalam arti program dapat dibuat dalam
waktu singkat, cepat dan mudah dibaca dan mudah untuk dikoreksi. Kedua, bahasa
tingkat tinggi bersifat portable, yang berarti bahwa bahasa tingkat tinggi dapat berjalan
diberbagai komputer yang berbeda dengan sedikit ataupun tanpa modifikasi. Bahasa
tingkat rendah hanya dapat berjalan pada satu macam komputer saja dan harus ditulis
ulang jika dijalankan pada komputer lainnya.
Dengan beberapa keuntungan ini, hampir semua program yang ditulis dengan
menggunakan bahasa tingkat tinggi. Bahasa tingkat rendah hanya digunakan untuk
aplikasi tertentu saja. Ada dua cara dalam menterjemahkan program; diterjemahkan
(intrepeter) atau di compile. Penterjemah (intrepeter) adalah sebuah program yang
membaca program tingkat tinggi dan menerjemahkan perintahnya. Sebagai efeknya,
program diterjemahkan per baris. Secara berurutan membaca baris dan melaksanakan
perintahnya.
15
Compiler adalah sebuah program yang membaca suatu bahasa tingkat tinggi dan
menterjemahkan semua sekaligus sebelum mengeksekusi perintah. Biasanya Anda
sering membuat program secara terpisah lalu mengeksekusi kodenya diakhir. Dalam
kasus ini program tingkat tinggi dinamakan source code dan terjemahan program
dinamakan object code atau executable.
Sebagai contoh sekiranya Anda menulis program dalam bahasa C. Anda mungkin
menggunakan teks editor untuk menulis program (teks editor adalah editor sederhana).
Ketika program selesai, Anda mungkin menyimpannya dalam sebuah file dengan nama
program.c, dimana “program” adalah nama file. Dan akhiran .c adalah convensi yang
menandai kode program dalam bahasa C.
Lalu, berdasarkan programming environment yang Anda sukai, Anda bisa
meninggalkan text editor dan menjalankan compilernya. Compiler akan membaca
source code anda, menterjemahkannya, dan membuat file baru yang bernama program.o
yang berisi kode objek atau program.exe yang executable.
16
Pada bahasa Java tidak seperti biasa karena langsung dikompilasi dan di interpreted.
Sebagai ganti terjemahan bahasa Java ke bahasa mesin, Java Compiler men generate
java byte code. Byte code sangat mudah diinterpretasikan seperti bahasa tingkat tinggi.
Seperti itu, dimungkinkan untuk mengkompile program java pada satu mesin, lalu
mentransfer byte code ke mesin lain dengan jaringan. lalu menginterpretasikan byte
code ke mesin lainnya. Kemampuan ini adalah salah satu keunggulan Java diantara
banyak keunggulan lainnya sebagai bahasa tingkat tinggi.
Walaupun proses ini tampak rumit, pada kebanyakan lingkungan pemrograman (kadang
disebut development environment), langkah ini langsung secara otomatis. Biasanya
Anda hanya menulis program dan menekan tombol compile untuk mengcompile dan
untuk menjalankannya. Disisi lain hal ini berguna untuk mengetahui langkahnya dan
apa yang terjadi pada layar. Sehingga jika terjadi kesalahan Anda dapat langsung
memperbaikinya.
1.2 Apa itu program ?
Program adalah urutan dari perintahperintah tertentu untuk melaksanakan proses
komputasi. Komputasi dapat berupa beberapa perhitungan matematika, seperti halnya
menyelesaikan suatu sistem persamaan untuk menemukan akarakar polinomial. Akan
17
tetapi juga bisa berupa sebuah simbol komputasi seperti searching, dan replacing teks
pada sebuah dokumen atau cara lain (mengcompile sebuah program).
Perintah yang kita sebut sebagai suatu pernyataan, kelihatan berbeda pada setiap bahasa
pemrograman yang berbeda tetapi terdapat beberapa operasi dasar yang kebanyakan
bahasa menyediakan:
Input :memasukkan data dari keyboard, atau sebuah file, atau beberapa
perangkat lain.
Output :menampilkan data dari layar dan membuat sebuah file atau perangkat
lain.
Math :menyelesaikan operasi dasar matematika seperti penambahan, dan
perkalian.
Testing :mencek kondisi tertentu dan mengeksekusi urutan dari pernyataan
yang sesuai.
Repetition :melakukan suatu aksi secara berulang biasanya dengan beberapa variasi.
Semua hal diatas sangatlah baik. Setiap program yang pernah Anda gunakan, tak
masalah itu rumit, terdiri dari pernyataanpernyataan yang melaksanakan beberapa
operasi. Lalu satu cara untuk menggambarkan pemrograman yaitu sebuah proses dari
Task yang besar dan kompleks menjadi subtask yang kecil bahkan sampai pada subtask
yang cukup sederhana yang dilakukankan dengan satu operasioperasi dasar.
Sebagai contoh, dalam bahasa inggris, sebuah kalimat harus dimulai dengan huruf besar
dan diakhiri dengan periode. Kalimat mengandung syntax error. Begitupun dengan yang
satu ini. Untuk kebanyakan pembaca , beberapa syntax yang error bukanlah masalah
yang signifikan. Yang mana sama halnya kenapa kita membaca puisi yang mengabaikan
apabila ada kesalahan.
18
Compiler tidak memperbolehkan satu kesalahan.Jika ada satu kesalahan pada program
Anda , compiler akan menampilkan pesan kesalahan dan lalu keluar. Sehingga Anda
tidak bisa menjalankan program Anda.
Untuk mempersulit persoalan, ada beberapa peraturan syntax pada Java yang dalam
bahasa inggris, dan pesan kesalahan yang anda terima dari compiler sering tidak banyak
membantu. Selama beberapa minggu pertama pembuatan program anda, kemungkinan
Anda akan menghabiskan waktu untuk mencari kesalahan syntax. Semakin anda
mendapatkan pengalaman maka anda semakin sedikit melakukan kesalahan dan
semakin cepat menemukan error.
1.3.2 RUNTIME ERROR
Tipe kedua dari error adalah Runtime error. Disebut demikian karena error yang terjadi
tidak muncul sampai program dijalankan. Pada Java runtime errror terjadi ketika
interpereter menjalankan byte code dan terjadi suatu kesalahan.
Untungnya, Java cenderung merupakan bahasa yang aman. Yang berarti runtime
errornya jarang, terutama pada program singkat yang sederhana yang akan kita tulis
untuk beberapa minggu selanjutnya kedepan.
Kemudian dalam semester nanti, Anda kemungkinan akan melihat lebih jelas tentang
run time error. Terutam ketika kita membicarakan tentang objek dan reference. (Bab 8).
Pada Java runtime error dinamakan exceptions, dan pada kebanyakan environment itu
muncul pada windows dengan kotak dialog yang mengandung informasi tentang apa
yang terjadi dan apa yang program kan ketika itu terjadi. Informasi ini berguna untuk
19
proses Debugging.
1.3.3 Logics error dan semantics
Tipe ketiga dari error adalah logical dan semantics errror. Jika ada logical error pada
program Anda, maka akan di compile dan berhasil di run dengan pengertian komputer
tidak akan mengenerate setiap pesan kesalahan. Akan tetapi hal itu bukan hal yang
benar. Ada hal lain yang akan dilakukan yaitu program melakukan apa yang anda
suruh.
Masalahnya program yang anda tulis bukanlah program yang anda inginkan. Artinya
programnya (semantiknya) salah. Mengindentifikasi logical error sangat sulit. Karena
itu mengharuskan anda untuk kembali melihat output program dan mencoba untuk
memperbaiki apa yang telah dilakukan.
1.3.4 Experimental debugging
Salah satu dari kemampuan penting yang akan Anda dapatkan dikelas ini adalah
debugging. Walaupun itu dapat menimbulkan frustasi , debugging adalah salah satu dari
hal yang beralasan, menantang dan menarik dalam sebuah pemrograman.
Dengan kata lain debugging seperti pekerjaan detektif. Anda dihadapkan pada petunjuk,
dan anda harus menyimpulkan proses dan kejadian yang mengarah pada hasil yang anda
lihat.
Debugging juga seperti ilmu percobaan. Sesekali Anda memiliki ide apa yang akan
salah, Anda memperbaiki program dan mencobanya lagi. Jika hipotesis Anda benar,
20
lalu Anda dapat memprediksi hasil dari modifikasi, lalu Anda mengambil langkah yang
terdekat untuk mengerjakan program. Jika hipotesis Anda salah Anda harus mengulang
lagi membuat program yang baru. Sherlock Holmes mengatakan: ”jika Anda telah
mengeliminasi ketidakmungkinan, maka apa saja yang tersisa haruslah benar” (dari
Conan Doyle's The Sign of Four).
Kebanyakan orang mengatakan, programming dan debugging adalah sama. Bahwa
programming merupakan suatu proses yang berangsurangsur dari debugging sebuah
program sampai akhirnya menjadi program yang diinginkan. Artinya bahwa Anda
memulai program kerja dengan melakukan sesuatu, membuat modifikasi kecil lalu
melakukan debugging sehingga Anda selalu memiliki program kerja.
Sebagai contoh, Linux adalah sistem operasi yang terdiri dari beribu baris kode akan
tetapi itu awalnya berasal dari program sederhana Linus Tovards yang digunakan untuk
mengeksplorasi Intel 3086 Chips. Menurut Larry Grandfield ” Satu dari Linus project
adalah sebuah program yang berasal dari cetakan AAAA dan BBBBB. Ini nantinya
akan menjadi Linux”.(Panduan Pemakai Linux Beta versi 1).
Di bab selanjutnya Saya akan lebih menyarankan untuk mempelajari tentang debugging
dan latihan program lainnya.
1.4 Bahasa Formal dan Natural
Bahasa Natural adalah bahasa yang diucapkan oleh orang, seperti bahasa Inggris,
Spanyol, dan perancis. Bahasa Natural tidak dibuat oleh manusia (walaupun orang
mencoba mengatakan demikian), bahasa natural tercipta secara alami.
21
Bahasa Formal adalah bahasa yang di desain orang untuk aplikasi tertentu. Sebagai
contoh, notasi matematika menggunakan bahasa formal yang umumnya baik untuk
menjelaskan hubungan antara angka dan simbol. Ahli kimia menggunakan bahasa
formal untuk merepresentasikan struktur molekul kimia dan banyak lagi.
Bahasa pemrograman adalah bahasa formal yang di desain untuk
mengekpresikan suatu komputasi
Seperti yang disebutkan diawal, bahasa formal mempunyai aturan yang tegas pada
syntax. Sebagai contoh, 3+3= 6 adalah pernyataan matematika yang syntaxnya benar.
Tetapi 3= 6$ bukan. Juga H2O adalah syntax yang benar secara kimia. Tapi 2Zz tidak
benar.
Peranan syntax ada dua, sebagai tanda dan struktur. Tanda adalah elemen dasar dari
suatu bahasa. Seperti kata dan angka dan unsurunsur kimia. Satu masalah dengan
3=+6$ bahwa $ bukanlah tanda yang benar secara matematika (sejauh yang saya tahu).
2Zz salah karena tidak ada unsur dengan tanda Zz.
Yang kedua jenis aturan sintaksis menyinggung kepada struktur suatu statemen,
maksudnya suatu tanda diatur. Statemen 3=+6$ dari sudut bangunannya tidak benar,
sebab Anda tidak bisa mempunyai suatu tanda pertambahan setelah suatu tanda sama
dengan. Dengan cara yang sama, rumusan molekular harus mempunyai tulisan di bawah
baris setelah nama unsur, yang sebelumnya tidak.
Ketika Anda membaca suatu kalimat di dalam Bahasa Inggris atau suatu statemen di
dalam suatu bahasa formal, Anda harus menggambarkan ke luar apa struktur kalimat
22
tersebut (walaupun di dalam suatu bahasa alami yang Anda lakukan ini tanpa disadari).
Proses ini disebut parsing.
Sebagai contoh, ketika Anda mendengar kalimat, “Sepatu lain jatuh," Anda memahami
bahwa “sepatu lain" adalah subjek dan “jatuh" adalah kata kerja. Sekali anda sudah
menguraikan suatu kalimat, anda dapat menggambarkan apa maknanya, itu adalah
makna semantik dari sebuah kalimat. Asumsikan bahwa Anda mengetahui sepatu apa
itu, dan apa makna jatuh, anda akan memahami implikasi yang umum dari kalimat ini.
Bagaimanapun bahasa alami dan formal mempunyai banyak fitur pada tanda, struktur,
sintaksis dan semantik ada banyak perbedaan.
Ambiguitas: Bahasa alami adalah penuh dengan kerancuan, yang mana orang
berhadapan dengan dengan penggunaan tanda dan informasi lainnya. Bahasa formal
dirancang dengan hampir atau sama sekali tidak bersifat ambigu, yang berarti bahwa
semua statemen persis mempunyai satu arti dengan mengabaikan konteks.
Redudancy: Dalam rangka mengejar kerancuan dan mengurangi salah paham, bahasa
alami banyak mengandung redudancy. Sebagai hasilnya, bahasa alami sering bertele
tele. Bahasa formal adalah lebih sedikit lebih ringkas dan tidak berlebih lebihan.
Harfiah: Bahasa alami penuh dengan idiom dan kiasan. Jika Saya mengatakan,sepatu
lain jatuh," kemungkinan tidak ada sepatu dan tidak ada apapun yang jatuh.Bahasa
formal persis seperti apa yang dikatakan. Orang berbicara tentang suatu bahasa alami
sering mempunyai kesulitan pada waktu menyesuaikan ke bahasa formal. Dalam
beberapa hal perbedaan antara bahasa formal dan bahasa alami seperti perbedaan antara
23
sebuah puisi dan prosa, tetapi:
Puisi: Katakata digunakan untuk bunyinya juga untuk maksud/artinya , dan syair/puisi
yang utuh bersamasama menciptakan suatu efek atau tanggapan emosional. Kerancuan
sering merupakan kesengajaan.
Prosa:Arti katakata secara harfiah lebih penting dan pemberdayaan struktur lebih
berarti. Prosa jadilah lebih bisa dianalisa dibanding puisi, namun sering rancu.
Program: Arti dari suatu program komputer tidak ambigu dan jelas, dan dapat
dipahami seluruhnya oleh analisa tanda dan struktur.
Di sini adalah beberapa usul untuk membaca program ( dan bahasa formal lain).
Pertama, ingat bahwa bahasa formal jauh lebih tebal/padat dibanding bahasa alami,
sehingga memakan waktu lebih untuk memahaminya. Strukturnya juga sangat penting,
jadi bukanlah gagasan yang baik jika membaca dari atas sampai ke bawah, dari kiri ke
kanan. Sebagai gantinya belajar menguraikan kalimat program dapat
mengidentifikasikan tanda dan menginterpretasikan struktur. Yang akhirnya, ingat
bahwa perihal detil. HalHal kecil seperti mengeja kesalahan dan tanda baca tidak
baik,dapat Anda hindari dari bahasa alami, dapat membuat suatu perbedaan besar di
dalam suatu bahasa formal.
1.5 Program Pertama
Biasanya program pertama orangorang pada suatu bahasa baru dinamakan dengan
“Hello World." sebab semuanya itu menampilkan “Hello world" Di dalam bahasa Java,
programnya seperti :
24
class Hello {
// main: generate some simple output
public static void main (String[] args) {
System.out.println ("Hello, world.");
}
}
Sebagian orang menilai mutu suatu bahasa program terlihat dari kesederhanaannya
“Hello World" Program. Dengan standard ini, bahasa Java tidak melakukan seluruhnya
dengan baik. Bahkan program yang paling sederhana berisi sejumlah code yang sulit
dijelaskan pada para programmer pemula. Mari kita abaikan semuanya terlebih dahulu.
Sekarang, saya akan menjelaskan beberapa.
Semua program terdiri dari definisi kelas, yang mempunyai format:
class CLASSNAME {
public static void main (String[] args) {
STATEMENTS
}
}
Di sini CLASSNAME menunjukkan bahwa suatu nama dapat anda tentukan sendiri.
Nama kelas pada contoh ini adalah Hello.
25
Pada baris yang kedua, kata public static void anda abaikan dulu, tetapi perhatikan kata
main. main adalah suatu nama khusus yang menandai tempat dimana program akan
mulai. Ketika program berjalan, program akan mulai dengan mengeksekusi statemen di
dalam main dan itu berlanjut sampai sampai akhir statemen, dan kemudian keluar.
Tidak ada batas jumlah dari statemen yang terdapat di dalam main, pada contoh hanya
berisi satu. Ini merupakan suatu statemen cetakan, maksudnya bahwa itu mencetak
suatu pesan pada layar, dengan kata lain berarti mengirimkan sesuatu ke printer. Dalam
buku ini saya tidak akan banyak mengatakan tentang mengirimkan berbagai hal kepada
printer. Tetapi akan mencetaknya pada layar.
Statemen yang mencetak berbagai hal pada layar adalah System.Out.Println, Dan
diantara tanda kurung adalah hal yang akan dicetak. Pada bagian akhir statemen ada
suatu titik koma (;) , yang mana diperlukan pada ujung tiaptiap statemen.
Ada beberapa lain yang anda perlu perhatikan tentang sintaksis dari program ini.
Pertama, bahasa Java menggunakan kurung kurawal ( { dan }) untuk mengolongkan
sesuatu secara bersama. Kurung kurawal yang paling jauh ( baris 1 dan 8) berisi definisi
kelas , dan bagian dalam berisi definisi dari main.
Perhatikan baris 3 mulai dengan //. Ini menandakan pada baris ini berisi komentar pada
teks Bahasa Inggris anda dapat menaruhnya pada pertengahan program, biasanya
menjelaskan apa yang program kerjakan. Ketika compiler melihat tanda //, maka akan
mengabaikan segalanya dari sana sampai akhir baris.
26
1.6 Daftar Kata
Problemsolving: Proses merumuskan suatu masalah,menemukan suatu solusi, dan
menyatakan solusi itu.
Highlevel language: Suatu bahasa program seperti bahasa Java yang dirancang untuk
memudahkan manusia untuk membaca dan menulis.
Lowlevel language: Suatu bahasa program yang dirancang untuk memudahkan
komputer untuk melaksanakannya. Juga disebut ”bahasa mesin" atau ”bahasa
assembly”.
Bahasa formal: Bahasa yang sudah dirancang untuk mempresentasikan suatu gagasan
matematika atau program komputer. Semua bahasa program adalah bahasa formal.
Bahasa alami: Bahasa orang yang manapun itu sudah terbentuk secra alami.
Portabilitas: Suatu properti dari suatu program yang dapat berjalan lebih dari satu
macam komputer.
Interpreted: Untuk mengeksekusi suatu program di dalam suatu highlevel language
menterjemahkannya perbaris pada waktu yang sama.
Compile: Untuk menterjemahkan suatu program di dalam suatu highlevel language ke
dalam suatu lowlevellanguage, untuk kemudian di eksekusi.
Source program: Suatu program di dalam suatu highlevel language sebelum di
compile.
Object Code: Keluaran compiler, setelah menterjemahkan program itu.
Executable: Nama lain untuk object code yang siap untuk dieksekusi.
ByteCode: Semacam object code khusus digunakan untuk Program Java. Byte Code
serupa dengan suatu lowlevel language, tetapi portable sama seperti highlevel
language.
27
Statement: Bagian dari suatu program yang merupakan sesuatu yang akan dikan ketika
program berjalan. Suatu cetakan statemen menyebabkan keluaran pada layar.
Comment: Bagian dari suatu program yang berisi informasi tentang program, tetapi itu
tidak mempunyai efek ketika program berjalan.
Algorithm: Suatu proses umum untuk memecahkan suatu kategori permasalahan.
Bug: Suatu kesalahan di dalam suatu program.
Syntax: Struktur suatu program.
Semantic: Arti dari suatu program.
Parse: Untuk menguji dan menganalisis suatu struktur syntax sebuah program.
Syntax error: Suatu kesalahan di dalam suatu program yang membuatnya mustahil
untuk di parsing.
Exception: Suatu kesalahan di dalam suatu program yang membuatnya gagal pada
waktu dijalankan. Biasa disebut runtime error.
Debugging: Proses untuk menemukan dan membuang ke tiga macam kesalahan.
1.7. Latihan
Latihan 1.1
Ilmuwan Komputer mempunyai kebiasaan menggunakan katakata Bahasa Inggris
untuk mengartikan sesuatu yang berbeda dengan maksud Bahasa Inggris secara umum.
Sebagai contoh, didalam Bahasa Inggris, suatu pernyataan dan suatu komentar adalah
hal hampir sama bagusnya, tetapi ketika kita sedang berbicara sekitar suatu program,
mereka umumnya berbedabeda.
Daftar kata pada akhir masingmasing bab dimaksudkan untuk menyoroti katakata dan
ungkapan yang mempunyai arti khusus didalam ilmu pengetahuan komputer. Ketika
anda melihat katakata yang telah dikenal, jangan berasumsi bahwa anda mengetahui
28
apa artinya!
a. Didalam komputer jargon, apa perbedaan antara suatu statemen dan suatu komentar?
b. Apa artinya bahwa suatu program dikatakan portable ?
c. Apa yang dimaksud dengan executable?
Latihan 1.2
Sebelum anda melakukan yang lain, cari tahu bagaimana cara menyusun dan
menjalankan Program Java didalam environtment yang anda gunakan. Beberapa
environtment menyediakan contoh program yang serupa dengan contoh pada bagian
1.5.
a. Ketik program ”Hello World”, kemudian compile dan Run.
b.Tambahkan suatu statemen yang dapat mencetak pesan kedua setelah “Hello World".
Sesuatu yang lucu seperti, Anda apa kabar? Compile dan Run lagi.
c.Tambahkan suatu baris komentar pada program dimana saja dan Compile. Lalu Run
lagi. Komentar yang baru mestinya tidak mempunyai effect pada program setelah di
Run.
Latihan ini mungkin tampak sepele, tetapi itu adalah awal dari banyak program yang
akan kita kerjakan nanti. Dalam mendebug dengan perceaa diri, anda harus mempunyai
percaya diri pada environment pemrograman anda. Dalam beberapa environment,
sangat mudah untuk kehilangan jejak program yang mana yang sedang dieksekusi, dan
anda mungkin menemukannya diri berusaha mendebug suatu program ketika anda
menjalankan program yang lain. Menambahkan (dan mengubah) suatu cetakan statemen
merupakan hal yang sederhana untuk menetapkan hubungan antara program yang
sedang anda perhatikan dan keluarannya ketika program diRun.
29
Latihan 1.3
Adalah suatu hal baik jika anda melakukan banyak kesalahan, sehingga anda dapat
melihat apa yang salah dengan cara melihat pemberi tahu kesalahan pada compiler.
Kadangkadang compiler akan memberi tahu anda tepatnya apa yang salah, dan anda
harus membereskan kesalah itu. Kadangkadang, walaupun demikian, compiler juga
akan memberi tahu banyak kesalahan yang tidak semestinya. Anda akan sedikit
memakai nalar ketika anda mempercayai compiler dan anda harus hatihati.
a. Hapus salah satu dari kurung kurawal pembuka.
b. Hapus salah satu dari kurung kurawal yang terdekat.
c. Sebagai ganti main, tulis mian.
d. Hapus kata static.
e. Hapus kata public.
f. Hapus kata System.
g. Gantikan println dengan pintln.
h.Gantikan println dengan print. Yang ini adalah suatu hal ketat sebab merupakan suatu
kesalahan logika, bukan kesalahan sintaksis. Statemen ”System.Out.Print” adalah
legal tetapi mungkin iya atau mungkin bukan apa yang anda harapkan.
i. Hapus salah satu dari tanda kurung. Tambahkan lagi lebih dari itu.
30
Bab II
Variabel dan Tipenya
2.1 Mencetak lebih banyak
Seperti yang telah saya sebutkan pada bab yang lalu, Anda dapat meletakkan sebanyak
apapun statemen pada main. Sebagai contoh, untuk mencetak lebih dari satu baris :
class Hello {
// main: generate some simple output
public static void main (String[] args) {
System.out.println ("Hello, world."); // print one line
System.out.println ("How are you?"); // print another
}
}
Juga, seperti yang anda lihat, adalah legal untuk menaruh komentar pada akhir suatu
baris, seperti halnya sejajar dengan baris itu. Ungkapan yang nampak tanda kutipnya
disebut “string”, sebab karena terdiri dari suatu urutan string huruf. sebetulnya, string
bisa berisi kombinasi huruf, angkaangka, tanda baca, dan karekter khusus lainnya.
println adalah singkatan dari “print line" sebab setelah masingmasing baris itu
ditambahkan suatu khusus khusu yang dinamakan newline, yang menyebabkan cursor
untuk bergerak ke baris yang berikutnya. Setelah itu Println dilibatkan, teks yang baru
muncul pada baris berikutnya.
Menampilkan output dari banyak cetakan statemen dalam satu baris sangatlah
31
bermanfaat. satu baris. Anda dapat mekannya command print berikut:
// main: generate some simple output
public static void main (String[] args) {
System.out.print ("Goodbye, ");
System.out.println ("cruel world!");
}
}
Dalam hal ini keluaran akan nampak pada satu baris seperti Goodbye, cruel world!.
Perhatikan bahwa ada suatu spasi antara kata “Goodbye” dan tanda kutip yang kedua.
Spasi ini muncul pada Output, dan mempunyai efek pada program. Spasi yang muncul
diluar tanda kutip biasanya tidak mempunyai efek pada program. Sebagai contoh, saya
bisa sudah menulis :
class Hello {
public static void main (String[] args) {
System.out.print ("Goodbye, ");
System.out.println ("cruel world!");
}
}
Program ini akan dicompile dan diRun sama halnya sebelumnya. Tulisan pada bagian
akhir ( newlines) juga tidak mempunyai efek pada program, maka saya bisa sudah
menulis :
32
class Hello { public static void main (String[] args) {
System.out.print ("Goodbye, "); System.out.println
("cruel world!");}}
Ini juga bisa, walaupun anda mungkin melihat bahwa program menjadi semakin sulit
dan lebih sulit untuk membaca. Baris baru dan spasi bermanfaat untuk mengorganisir
program anda secara visual, membuat program lebih mudah untuk membaca program
itu dan menempatkan kesalahan sintax.
2.2 Variabel
Salah satu keunggulan fitur suatu bahasa pemrograman adalah kemampuan untuk
memanipulasi variabel. Suatu variabel adalah lokasi yang menyimpan suatu nilai. Nilai
adalah berbagai hal yang dapat dicetak dan disimpan (kita akan lihat kemudian) yang
dioperasikan.String yang telah kita cetak (" Hello, World.", " Goodbye", dll.) adalah
nilai.
Dalam menyimpan suatu nilai, anda harus menciptakan suatu variabel. Karena nilai
yang ingin kita simpan adalah string, maka kita akan mendeklarasikan variabel baru
sebagai suatu string :
String fred;
Statemen ini merupakan suatu deklarasi, karena mendklarasikan variabel bernama
fred yang bertipe string. Masingmasing variabel mempunyai suatu tipe yang
menentukan tipe nilai apa yang akan disimpan. Sebagai contoh, tipe int dapat
menyimpan bilangan bulat, dan tidak mungkin akan menyimpan tipe string.
33
Anda akan melihat bahwa beberapa tipe dimulai dengan huruf besar dan beberapa
dengan huruf kecil.. Kita akan belajar arti dari perbedaan ini nanti, tetapi untuk
sekarang anda benarbenar perlu untuk berhatihati. Ada tipe selain Int atau string, dan
compiler akan menolak jika anda mencoba membuat sendiri tipe data.
Untuk membuat suatu variabel bilangan bulat, sintaknya seperti int bob; , dimana bob
adalah nama yang dapat anda tulis sembarang. Secara umum, anda akan menulis suatu
nama variabel yang mengindikasikan variabel yang ingin anda bentuk. Sebagai contoh,
jika anda melihat deklarasi variabel ini:
String firstName;
String lastName;
int hour, minute;
Anda mungkin bisa menerkanerka tentang nilai apa yang akan disimpan oleh tipe
tersebut. Contoh ini memperlihatkan sintak untuk medemonstrasikan berbagai variabel
dengan tipe yang sama: hour dan minute keduaduanya merupakan bilangan bulat
( tipe int).
2.3 Assignment
Sejak kita sudah menciptakan beberapa variabel, kita ingin menyimpan suatu nilai pada
variabel tersebut. Kita akan mekannya dengan Assignmen Statement.
fred = "Hello."; // give fred the value "Hello."
34
hour = 11; // assign the value 11 to hour
minute = 59; // set minute to 59
Contoh ini menunjukkan tiga assignment, dan komentar menunjukkan tiga perbedaan
cara orang menyatakan tentang assignment statement. Kosa katanya jadi meragukan di
sini, tetapi gagasan secara langsung:
ketika anda mendeklarasikan suatu variabel, anda menciptakan suatu nama
untuk lokasi penyimpanan.
Ketika anda mekan assignment terhadap suatu variabel, anda memberinya suatu
nilai.
Suatu cara umum untuk mempresentasikan suatu variabel secara tertulis adalah dengan
menggambarkan suatu kotak dengan nama variabel diluarnya dan nilai variabel pada
didalamnya. Gambar ini menunjukkan efek dari ke tiga assignment statemen :
Untuk masingmasing variabel, nama variabel nampak di luar kotak itu dan nilai
nampak di dalam.
Pada umumnya, suatu variabel harus mempunyai tipe yang sama seperti meng assign
nilai. Anda tidak bisa menyimpan suatu string didalam minute atau suatu bilangan bulat
didalam fred.
Di sisi lain, aturan itu bisa meragukan, karena ada banyak cara yang dapat anda kan
35
dalam mengkonversi nilainilai dari satu tipe ke tipe lain, dan Java kadangkadang
mengkonversi berbagai hal secara otomatis. Maka sekarang anda perlu ingat aturan
yang umum, dan kita akan memperbicangkannya secara khusus nanti.
Hal lain yang meragukan adalah bahwa beberapa string kelihatan seperti bilangan
bulat, tetapi sesungguhnya bukan. Sebagai contoh, fred mengandung string " 123",
yang mana terdiri dari karakter 1, 2 dan 3, tapi itu tidak sama halnya seperti nomor 123.
fred = "123"; // legal
fred = 123; // not legal
2.4 Mencetak Variabel
Anda dapat mencetak nilai suatu variabel menggunakan perintah yang sama seperti
yang kita gunakan untuk mencetak Strings.
class Hello {
public static void main (String[] args) {
String firstLine;
firstLine = "Hello, again!";
System.out.println (firstLine);
}
}
Program ini menciptakan suatu variabel yang bernama firstLine, meng assign nya ke
36
dalam nilai " Hello, Again!" dan kemudian mencetak nilainya. Ketika kita
memperbicangkan tentang “mencetak suatu variabel," kita berarti mencetak nilai
variabel itu. Untuk mencetak nama suatu variabel, anda harus menaruhnya didalam
tanda kutip. Sebagai contoh: System.Out.Println (" Firstline");
Jika anda ingin lebih detail , anda bisa menulis :
String firstLine;
firstLine = "Hello, again!";
System.out.print ("The value of firstLine is ");
System.out.println (firstLine);
Keluaran dari program ini adalah :
The value of firstLine is Hello, again!
Saya senang untuk mengatakan bahwa sintak untuk mencetak suatu variabel adalah
sama halnya dengan mengabaikan jenis variabel itu.
int hour, minute;
hour = 11;
minute = 59;
System.out.print ("The current time is ");
System.out.print (hour);
System.out.print (":");
System.out.print (minute);
37
System.out.println (".");
Keluaran dari program ini adalah : The current time is 11:59.
PERINGATAN: Ini adalah praktek umum untuk menggunakan beberapa perintah print
yang diikuti oleh println, untuk meletakkan berbagai nilai pada Baris yang sama.
Tetapi anda harus hatihati perintah println pada bagian akhir. Pada banyak
environment keluaran dari print disimpan tanpa dipertunjukkan sampai perintah
println dilibatkan, di mana keseluruhan baris dipertunjukkan dengan segera. Jika anda
menghilangkan println, program bisa berakhir tanpa pernah mempertunjukkan keluaran
yang disimpan!
2.5 Keywords
Beberapa bagian yang lalu, saya berkata bahwa anda dapat membuat nama apapun yang
anda butuhkan untuk variable, tetapi itu tidak selalu benar. Ada katakata tertentu yang
tersedia pada Java sebab itu digunakan oleh compiler untuk menguraikan struktur dari
program anda, dan jika anda menggunakannya sebagai nama variabel, akan menjadi
kacau. Katakata ini disebut keywords, meliputi public, class, void, int, dan banyak
lagi yang lain.
Daftar yang lengkap ada tersedia pada :
http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html
Lokasi ini, yang disediakan oleh Sun, meliputi Dokumentasi Java yang akan mengacu
38
pada keseluruhan isi buku.
Dengan menghafal daftar itu, saya jamin anda mendapat keuntungan dari fitur yang
disediakan pada banyak Java Development environment : Kode yang di tampilkan.
Dalam mengetik, bagianbagian berbeda dari program anda perlu tampak berbeda
warnanya. Sebagai contoh, kata kunci boleh jadi biru, string merah, dan lain kode
hitam. Jika anda mengetik suatu nama variabel dan warnya biru, awas! Anda mungkin
menemukan hal yang aneh dari compiler itu.
2.6 Operator
Operator adalah lambang khusus yang digunakan untuk mempresentasikan perhitungan
sederhana seperti penambahan dan perkalian. Kebanyakan dari operator didalam Java
persisnya seperti apa yang anda inginkan, sebab itu adalah lambang matematika secara
umum. Sebagai contoh, operator untuk menambahkan dua bilangan bulat adalah +.
Yang berikut adalah semua ekpresi yang legal menurut Java yang mana artinya
kelihatan lebih jelas:
1+1 hour1 hour*60 + minute minute/60
Ungkapan dapat berisi nama dan nomor variabel. Pada setiap kasus nama variabel
digantikan dengan nilai sebelum perhitungan nya dikan. Penambahan, pengurangan
dan perkalian semua yang anda inginkan, tetapi anda mungkin dikagetkan oleh
pembagian. Sebagai contoh, program yang berikut:
int hour, minute;
hour = 11;
minute = 59;
39
System.out.print ("Number of minutes since midnight: ");
System.out.println (hour*60 + minute);
System.out.print ("Fraction of the hour that has passed: ");
System.out.println (minute/60);
akan menghasilkan keluaran yang berikut:
Number of minutes since midnight: 719
Fraction of the hour that has passed: 0
Baris yang pertama adalah apa yang kita harapkan, tetapi baris yang kedua adalah aneh.
Nilai menit variabel adalah 59, dan 59 yang dibagi oleh 60 adalah 0.98333, bukan 0.
Pertentangan ini disebabkan bahwa Java mekan pembagian dalam bilangan bulat.
Ketika kedua operand adalah bilangan bulat (operand adalah suatu operasi pada
operator), hasilnya harus jadi suatu bilangan bulat, dan bilangan bulat bersifat selalu
membulatkan kebawah, bahkan pada kasus seperti ini dimana bilangan bulat yang
berikutnya menjadi sangat dekat.
Suatu alternatif yang mungkin dalam hal ini adalah untuk mengkalkulasi suatu
persentase dibanding pecahan:
System.out.print ("Percentage of the hour that has passed: ");
System.out.println (minute*100/60);
Hasilnya :
40
Percentage of the hour that has passed: 98
Lagilagi hasil dibulatkan bawah, tetapi setidaknya sekarang jawabannya sudah benar.
Dalam memperoleh jawaban yang lebih sayarat lagi, kita bisa menggunakan suatu tipe
variable berbeda, yang disebut floatingpoint, yang mampu untuk menyimpan nilai
yang kecil.Kita akan mempelajari itu pada bab berikutnya.
2.7 Operasi
Ketika lebih dari satu operator muncul pada suatu ekspresi, evaluasi bergantung pada
nilai precedence. Suatu penjelasan lengkap tentang nilai precedence sangat rumit,
namun untuk mengetahuinya anda dapat memulai:
• Perkalian dan pembagian mempunyai kedudukan lebih tinggi ( dikan sebelum)
penambahan dan pengurangan. Maka 2*31 hasilnya 5, bukan 4, dan 2/31
hasilnya 1, bukan 1 ( ingat bahwa pembagian bilangan bulat 2/3 adalah 0).
• Jika operator mempunyai kedudukan yang sama maka akan dievaluasi dari kiri
ke kanan. Maka didalam ungkapan ini menit*100/60, perkalian dikan terlebih
dulu, hasil dari 5900/60 adalah 98. Jika operasi dikan dari kanan ke kiri, hasil
59*1 adalah 59, dan itu salah.
• Kapanpun Jika anda ingin mengesampingkan ketentuanketentuan nilai precende
( atau anda tidak tahu pasti apa hasilnya) anda dapat menggunakan tanda
kurung. Ungkapan didalam tanda kurung dievaluasi terlebih dahulu, maka 2*
41
( 31) 4. Anda dapat juga menggunakan tanda kurung untuk membuat suatu
ungkapan lebih mudah untuk membaca, seperti di ( menit* 100) / 60,walaupun
itu tidak mengubah hasil.
2.8 Operator pada String
Secara umum anda tidak bisa melaksanakan operasi matematika pada String, sekalipun
string kelihatan seperti angkaangka. Yang berikut adalah tidak benar (jika kita
mengetahui bahwa fred mempunyai tipe string)
fred 1 "Hello"/123 fred * "Hello"
Ngomongngomong, dengan memperhatikan ekspresi tersebut dapatkan anda
menunjukkan bahwa fred adalah suatu integer atau string? Tidak. SatuSatunya cara
mengetahui jenis suatu variabel adalah dengan melihat tempat dimana variabel itu
dideklarasikan.
Menariknya, operator + dapat dikan pada string, walaupun itu tidak kan persis dengan
apa yang anda harapkan. Untuk string, operator + menghasilkan penggabungan, yang
berarti menggabungkan dua operand. Sehingga " Hello,"+ " World." menghasilkan
string Hello,World. dan fred+ " ism" berarti menambahkan akhiran ism itu kepada
ujung kata fred apapun tipe fred ini dipakai sebagai format baru.
42
2.9 Komposisi
Sejauh ini kita sudah melihat di unsurunsur suatu variabel bahasa program, ungkapan,
dan statements tertutup, tanpa membicarakan tentang bagaimana cara
menggabungkannya.
Salah satu cirri dari bahasa program paling bermanfaat adalah kemampuan mereka
untuk mengambil satu bagian blok kecil dan menyusunnya. Sebagai contoh, kita
mengetahui bagaimana cara mengalikan angkaangka dan kita mengetahui bagaimana
cara mencetaknya, itu dapat kita sayakan keduaduanya pada waktu yang sama:
System.out.println (17 * 3);
Sebenarnya, saya seharusnya tidak mengatakan “pada waktu yang sama," karena pada
kenyataannya perkalian harus terjadi sebelum pencetakan, tetapi intinya adalah bahwa
ungkapan manapun menyertakan angkaangka, string, dan variabel, dapat digunakan di
dalam suatu statemen cetakan. Kita telah melihat satu contoh:
System.out.println (hour*60 + minute);
Tetapi anda dapat juga menaruh ungkapan pada sembarang tempat pada sisi kanan dari
suatu statemen tugas:
int percentage;
percentage = (minute * 100) / 60;
43
Kemampuan ini tidak lagi nampak sangat mengesankan sekarang, tetapi kita akan lihat
contoh lain dimana komposisi memungkinkan untuk menyatakan perhitungan kompleks
dengan rapi dan dengan singkat.
PERINGATAN: Ada batas dimana anda dapat menggunakan ungkapan tertentu;
khususnya, dari sisi kiri dari suatu tugas statemen haruslah nama suatu variabel, bukan
suatu ekspresi. Itu disebabkan pada sisi yang kiri menandai adanya tempat penyimpanan
hasil nantinya. Ungkapan tidak mempresentasikan tempat penyimpanan, hanya
nilai. Sehingga yang berikut adalah tidak benar: minute+1= hour;
2.10 Daftar kata
Variabel: Suatu nama tempat dimana suatu nilai disimpan. Semua variabel mempunyai
suatu tipe, yang dideklarasikan ketika suatu variable dibuat.
Value: Suatu angka atau string (atau hal alin yang dinamai kemudian) yang dapat
disimpan pada suatu variabel. TiapTiap nilai kepunyaan satu tipe.
type: Suatu set nilai. Tipe suatu variabel menentukan dalam penyimpanan nilainya.
Sejauh ini, tipe yang sudah kita lihat adalah integer ( int didalam Java) dan ( string
didalam Java.
Keyword: Suatu kata digunakan oleh compiler untuk menguraikan (kalimat) program.
anda tidak bisa menggunakan kata kunci sepert public, class dan void sebagai nama
variabel.
44
Statement: Satu baris kode yang mempresentasikan suatu perintah atau tindakan.
Sejauh ini, statemen yang sudah kita lihat adalah deklarasi, tugas, dan statemen cetakan.
Deklarasi: Suatu statemen yang menciptakan suatu variabel baru dan menentukan
tipenya
Assigment: Suatu statemen yang menugaskan suatu nilai kepada suatu variabel.
Expression: Suatu kombinasi variabel, operator dan nilainilai yang mempresentasikan
hasil nilai tunggal. Ungkapan juga mempunyai tipe yang ditentukan oleh operator dan
operand.
Operator: Suatu lambang khusus yang mempresentasikan suatu perhitungan sederhana
seperti addition,multiplication atau penggabungan string.
Operands: Salah satu dari nilainilai dimana diatasnya suatu operator beroperasi.
Precedence: Kedudukan suatu operator ketika suatu operasi dievaluasi
Concatenate: Untuk menggabungkan ujungujung dua operand.
Komposisi: Kemampuan untuk mengkombinasikan statemen dan ekspresi sederhana ke
dalam statemen campuran dan ekspresiekspresi untuk mempresentasikan perhitungan
kompleks dengan singkat.
45
2.11 Latihanlatihan
Latihan 2.1
a. Ciptakan suatu program baru nama Date.Java. Copy atau ketik sesuatu seperti Hello,
World" yakinkan anda dapat meng compile dan Run nya.
b. Ikuti contoh didalam Bagian 2.4, tulis suatu program yang menciptakan variabel
nama hari, tanggal/date, bulan dan tahun. hari akan berisi hari minggu dan
tanggal/date akan berisi hari pada bulan itu. Apa tipe masingmasing variabel?
Berikan nilai untuk variabel yang mempresentasikan tanggal/date sekarang.
c. Cetak nilai dari tiap variabel sejajar dengan dirinya. Ini adalah suatu langkah
intermediate yang bermanfaat untuk mengecek yang segalanya yang telah dikan
sejauh ini.
d. Modifikasi program itu sedemikian rupa sehingga dapat mencetak tanggal/date itu
didalam Format standar Amerika:
Wednesday, February 17, 1999.
e. Modifikasi lagi program itu sedemikian rupa sehingga total keluarannya adalah:
American format:
Wednesday, February 17, 1999
46
European format:
Wednesday 17 February, 1999
Inti dari latihan ini adalah untuk menggunakan penggabungan string untuk
menampilkan nilainilai dengan tipe berbeda ( int dan string), dan untuk praktek
mengembangkan program secara berangsurangsur dengan menambahkan beberapa
statemen pada waktu yang sama.
Latihan 2.2
a. Ciptakan suatu program baru yang dinamakan Time.Java. Mulai sekarang, saya tidak
akan mengingatkan anda untuk mulai dengan suatu yang kecil, bekerja dengan
program, tetapi anda sendiri yang mekannya.
b. Ikuti contoh didalam Bagian 2.6, untuk menciptakan variabel nama jam, menit dan
detik/second, dan meng assign nilainya dengan waktu yang sekarang. Gunakan
format 24 jam, sedemikian rupa sehingga pada 2pm nilai jam adalah 14.
c. Buat program untuk mengkalkulasi dan mencetak banyaknya detik mulai dari tengah
malam.
d. Buat program untuk mengkalkulasi dan mencetak banyaknya detik yang tersisa pada
hari itu.
e. Buat program untuk mengkalkulasi dan mencetak persentase dari hari yang telah
lewat.
47
f. Ubah nilainilai jam, menit dan detik untuk mencerminkan waktu yang sekarang
( saya berasumsi bahwa waktu telah berlalu), dan periksa untuk meyakinkan bahwa
program dapat bekerja dengan tepat dengan nilainilai yang berbeda.
Inti dari latihan ini adalah untuk menggunakan sebagian dari operasi perhitungan, dan
untuk memulai berpikir tentang campuran variabel seperti jam yang dipresentasikan
dengan banyak nilai. Juga, anda mungkin menemukan permasalahan dalam menghitung
persentase dengan ints, yang mana adalah motivasi untuk belajar floating point pada
yang berikutnya.
SYARAT: anda boleh menggunakan variabel tambahan untuk menyimpan nilainilai
yang untuk sementara sepanjang perhitungan itu. Variabel seperti ini, digunakan pada
komputasi tetapi tidak pernah dicetak yang terkadang disebut dengan intermediate atau
variabel temporer.
48
BAB III
Method
3.1 Floatingpoint
Di dalam bab yang terakhir kita mempunyai beberapa permasalahan dengan angka
angka yang bukanlah bilangan bulat. Kita disibukkan dengan masalah untuk mengukur
persentase sebagai pecahan, tetapi suatu solusi yang lebih umum adalah untuk
menggunakan angkaangka floatingpoint, yang dapat mempresentasikan pecahan
seperti halnya bilangan bulat. Didalam Java, tipe floatingpoint disebut double.
Anda dapat menciptakan variabel floating dan memberikan nilai dengan menggunakan
Sintak yang sama yang]kita gunakan untuk tipe lain . Sebagai contoh:
double pi;
pi = 3.14159;
Ini juga benar untuk mempresentasikankan suatu variabel dan menugaskan suatu nilai
kepadanya pada waktu yang sama:
int x = 1;
String empty = "";
double pi = 3.14159;
Sebenarnya, sintak ini adalah secara umum. Suatu kombanisi assigment dan deklarasi
terkadang disebut suatu initialisasi.
49
Walaupun angkaangka floatingpoint bermanfaat, namunn sering suatu source
membingungkan karena sepertinya ada tumpangtindih antara bilangan bulat dan angka
angka floatingpoint. Sebagai contoh, jika anda mempunyai nilai 1, yang merupakan
suatu bilangan bulat, bilangan floatingpoint, atau keduaduanya?
Pada dasarnya, Java menilai integer itu 1 dan floatingpoint 1.0, sungguhpun
sepertinya adalah jumlahnya sama. Tetapi tipenya berbeda, dan pada dasarnya, anda
tidaklah diijinkan untuk meng assign antara tipe. Sebagai contoh, yang berikut adalah
tidak benar:
int x = 1.1;
Sebab variabel pada sisi kiri adalah suatu int dan nilai pada sisi kanan adalah suatu
double. maka mudah untuk melupakan aturan ini, kususnya karena ada tempat dimana
java akan secara otomatis mengkonversi dari satu tipe ke lain. Sebagai contoh:
double y = 1;
seharusnya secara teknis tidak benar, tetapi Java mengijinkan dengan mengubah int itu
kepada suatu double secara otomatis. Kemudahan ini sangat menyenangkan, tetapi itu
dapat menyebabkan permasalahan;
sebagai contoh:
double y = 1 / 3;
50
Anda mungkin mengharapkan variabel y diberi nilai 0.333333, benar, tetapi
sesungguhnya akan menghasilkan nilai 0.0. Alasan adalah bahwa ekspresi pada sisi
kanan nampak seperti perbandingan dua bilangan bulat, maka Java mengerjakan
pembagian bilangan bulat, yang menghasilkan bilangan bulat 0. yang dikonversi Untuk
floatingpoint hasilnya 0.0.
Satu cara untuk memecahkan masalah ini ( saat anda menggambarkan apa artinya)
adalah untuk ekspresi floatingpoint dari kanan.
double y = 1.0 / 3.0;
disini y diset menjadi 0.333333, yang diharapkan.
Semua operasi yang telah kita lihat sejauh ini penambahan, pengurangan, perkalian,dan
pembagian juga ada pada nilai floatingpoint, walaupun anda mungkin tertarik untuk
mengetahui bahwa mekanisme dasarnya berbeda sepenuhnya. Pada kenyataanya,
prosesor mempunyai perangkat keras khusus hanya untuk mekan operasi floatingpoint.
3.2 Mengkonversi double ke int
Seperti saya sebutkan, Java mengkonversi int ke double secara otomatis jika perlu,
sebab tidak ada informasi hilang dari konversi itu. Pada sisi lain, mengubah suatu
double ke suatu int memerlukan penyelesaian. Java tidak melaksanakan operasi ini
secara otomatis, sebagai programmer, anda menyadari hilangnya sebagian jumlah dari
angka itu.
Cara yang paling sederhana untuk mengkonversi suatu floatingpoint ke suatu bilangan
51
bulat adalah menggunakan typecast. Dinamakan typecasting karena mengijinkan anda
untuk mengambil suatu tipe nilai dan mengubahnya kedalam tipe lain (dalam pengertian
mengubah tidak melempar).
Sayangnya, sintaks untuk typecasting jelek: anda menaruh nama tipe didalam tanda
kurung dan menggunakannya sebagai suatu operator. Sebagai contoh,
int x = (int) Math.PI;
Operator int mempunyai efek mengikuti bilangan bulat, maka x menghasilkan nilai 3.
Typecasting harus didahulukan sebelum operasi perhitungan, maka didalam contoh
yang berikut, nilai PI (22:7) menghasilkan pertama dikonversi ke suatu bilangan bulat ,
dan hasilnya adalah 60, bukan 62.
int x = (int) Math.PI * 20.0;
Mengubah ke suatu bilangan bulat selalu dibulatkan kebawah, sekalipun pecahannya
0.99999999.
Dua properties ini (precedence dan pembulatan) dapat membuat typecast kurang bagus.
3.3 Method Matematika
Di dalam matematika, anda mungkin sudah melihat fungsi seperti sin dan log, dan anda
52
sudah mempelajari ekspresi seperti sin(∏/2) dan log(1/x). Pertama, anda mengevaluasi
ekspresi didalam tanda kurung, yang mana disebut argumentasi fungsi. Sebagai contoh,
∏/2 kirakira 1.571, dan 1/x adalah 0.1 ( diasumsikan bahwa x 10).
Kemudian anda dapat mengevaluasi fungsi itu dengan menggunakan tabel atau dengan
mekan berbagai perhitungan. Sin 1.571 adalah 1, dan log 0.1 adalah 1 ( diasumsikan
bahwa log mengindikasikan bilangan pokok 10).
Proses ini dapat diterapkan berulangkali untuk mengevaluasi ekspresi yang lebih rumit
seperti log(1/ sin( /∏ 2)). pertama kita mengevaluasi argumentasi function, lalu
mengevaluasi fungsi yang paling dalam, dan seterusnya.
Java menyediakan satu set builtin fungsi yang mengandung kebanyakan operasi
matematikan yang anda tahu. Fungsi ini disebut methods. Kebanyakan method
matematika beroperasi pada double.
Method matemmatika dilibatkan menggunakan suatu sintaksis yang serupa dengan
perintah print yang kita lihat:
double root = Math.sqrt (17.0);
double angle = 1.5;
double height = Math.sin (angle);
Contoh yang pertama akar 17. Contoh yang kedua sinus 1.5, yang mana adalah nilai
sudut variabel. Java berasumsi bahwa nilainilai yang anda gunakan dengan sin dan
53
fungsi trigonometric lain ( cos, Tan) adalah didalam radian. Untuk mengkonversi dari
derajat ke radian, anda dapat membagi dengan 360 dan mengalikan dengan 2.
Untungnya, Java menyediakan ∏ sebagai nilai yang builtin:
PI (22:7) adalah dalam huruf besar. Java tidak mengenali Pi (22:7), Pi (22:7), atau pie.
Metoda bermanfaat lain didalam Claas adalah putaran, yang mengelilingi suatu nilai
floatingpoint kepada bilangan bulat yang paling dekat dan kembalikan ke suatu int.
double degrees = 90;
double angle = degrees * 2 * Math.PI / 360.0;
int x = Math.round (Math.PI * 20.0);
Dalam hal ini perkalian dikan terlebih dulu, sebelum method dilibatkan. hasilnya adalah
63 ( yang dibulatkan dari 62.8319).
3.4 Composition
Sama halnya dengan fungsi matematika, Method Java dapat composed, maksudnya
anda menggunakan satu ekspresi sebagai bagian dari yang lain. Sebagai contoh, anda
dapat menggunakan ekspresi manapun sebagai suatu argumentasi ke suatu metoda:
double x = Math.cos (angle + Math.PI/2);
Statemen ini mengambil nilai Math.Pi, membaginya dengan dua dan menambahkan
hasilnya kepada nilai variabel angle. Penjumlahan kemudian melewatkan suatu
54
argumentasi untuk method cos.(ingat PI (22:7) itu adalah nama suatu variabel, bukan
method , maka tidak ada argumentasi, bukan pula argumentasi yang kosong()).
Anda dapat juga mengambil satu method dan melewatkannya pada argumentasi ke lain:
double x = Math.exp (Math.log (10.0));
Didalam Java, log berfungsi selalu menggunakan dasar e, maka statemen ini
mengartikan dasar log e 10 dan kemudian menaikkan e. Hasilnya di assign ke x; saya
berharap anda mengetahui apa artinya.
3.5 Menambahkan metode baru
Sejauh ini kita hanya menggunakan metoda yang dibangun dalam Java, tetapi
memungkinkan untuk menambahkan metoda baru. Sebenarnya kita melihat satu metoda
definisi: main. Method dinamakan main khusus menandai di mana pelaksanaan
program dimulai, tetapi sintak untuk yang main adalah sama dengan definisi metoda
lain:
public static void NAME ( LIST OF PARAMETERS ) {
STATEMENTS
}
Anda dapat membuat nama apapun yang anda butuhkan untuk metoda anda, kecuali
nama main atau keyword java lainnya. Daftar yang menginformasikan tipe parameter,
55
bila ada, anda harus menyediakan untuk penggunaan fungsi yang baru.
Parameter tunggal untuk main adalah String[] args, yang menunjukkan bahwa siapapun
menggunakan main harus menyediakan suatu array String ( akan dipelajari pada Bab
10). Sepasang metoda yang pertama yang akan kita tulis tidak punya
parameters,sehingga sintaknya kelihatan seperti ini:
public static void newLine () {
System.out.println ("");
}
Method ini dinamai newLine, dan tanda kurung yang kosong menunjukkan bahwa ia
tidak mengambil parameter apapun. Method itu hanya berisi statemen tunggal, yang
mencetak suatu string kosong yang ditandai oleh"". Pencetakan suatu string dengan
tidak ada huruf didalamnya tidak akan nampak bermanfaat, kecuali ingat bahwa println
melompati baris yang berikutnya setelah itu mencetak, maka statemen ini mempunyai
efek melompati baris yang berikutnya.
Didalam main kita dapat membuat method yang baru ini menggunakan sintak yang
serupa dengan memakai perintah Java yang sudah builtin :
public static void main (String[] args) {
System.out.println ("First line.");
newLine ();
System.out.println ("Second line.");
}
56
Keluaran dari program ini adalah
First line.
Second line.
Perhatikan spasi antara kedua bentuk. Akibatnya bagaimana jika kita ingin menambah
spasi antara dua baris? Kita bisa memanggil method yang sama berulangkali:
public static void main (String[] args) {
System.out.println ("First line.");
newLine ();
newLine ();
newLine ();
System.out.println ("Second line.");
}
Atau kita bisa menulis suatu metoda baru yang dinamakan threeLine yang mencetak
tiga bentuk baru:
public static void threeLine () {
newLine (); newLine (); newLine ();
}
public static void main (String[] args) {
System.out.println ("First line.");
threeLine ();
57
System.out.println ("Second line.");
}
Anda perlu memperhatikan beberapa hal sekitar program ini:
Anda dapat memanggil prosedur yang sama berulangkali. Sebenarnya, itu umumnya
berguna bagi mekannya.
Anda bisa mempunyai satu method memanggil metoda lain. Dalam hal ini, main
meminta threeLine dan threeLine meminta newLine. Lagilagi ini umumnya
bermanfaat.
Didalam threeLine saya menulis tiga statemen semua pada baris yang sama, yang mana
secara yg sintak benar (ingat bahwa spasi dan baris baru pada umumnya tidak
mengubah arti dari suatu program). Di sisi lain, itu suatu gagasan yang baik untuk
menaruh statemen masingmasing sejajar dengan barisnya, untuk membuat program
anda mudah dibaca. Saya terkadang menggunakan aturan dalam buku ini untuk
menghemat ruang.
Sejauh ini, mungkin belum begitu jelas kenapa begitu berharga ini untuk menciptakan
semua metoda baru ini . sebenarnya, ada banyak pertimbangan, tetapi contoh ini hanya
mempertunjukkan dua hal:
1. Menciptakan suatu method baru memberi anda suatu kesempatan untuk memberi
suatu nama kepada satu kelompok statemen. Method dapat menyederhanakan suatu
program dengan menyembunyikan suatu perhitungan kompleks di belakang perintah
58
tunggal, dan dengan penggunaan kata Bahasa Inggris sebagai pengganti kode. Lebih
jelasnya , newLine atau System.Out.Println("")?
2. Menciptakan suatu method baru yang dapat membuat suatu program yang lebih kecil
dengan menghilangkan kode berulang. Sebagai contoh, bagaimana cara anda mencetak
sembilan baris baru berurutan? Anda bisa memanggil method threeLine tiga kali.
3.6 Class dan method
Dari pengelompokkan kodekode dari bagian yang sebelumnya, maka keseluruhan
definisi kelas kelihatan seperti ini:
class NewLine {
public static void newLine () {
System.out.println ("");
}
public static void threeLine () {
newLine (); newLine (); newLine ();
}
public static void main (String[] args) {
System.out.println ("First line.");
threeLine ();
System.out.println ("Second line.");
}
}
59
Baris yang pertama menunjukkan bahwa ini adalah definisi untuk suatu kelas yang baru
yang dinamakan Newline. Suatu Class adalah suatu kumpulan dari suatu method yang
berelasi. Dalam hal ini, kelas yang bernama Newline berisi tiga metoda, yang
dinamakan newLine, threeLine, dan main.
Class lain yang telah kita lihat adalah Math Kelas. Class itu berisi method yang bernama
sqrt, sin, dan banyak yang lain. Ketika kita memanggil suatu fungsi matematika, kita
harus menetapkan nama Class (Math) dan nama fungsinya. Itulah mengapa sintak
berbeda untuk masingmasing method builtin dan metoda yang kita tulis:
Math.pow (2.0, 10.0);
newLine ();
Statemen yang pertama memanggil method pow didalam Class Math (Yang menaikkan
kekuatan argumentasi yang pertama ke argumentasi yang kedua ). Statemen yang kedua
meminta method newLine metoda, Java mengasumsikan (dengan tepat) didalam Class
Newlines, yang mana sedang kita tulis.
Jika anda mencoba untuk memanggil suatu method dari Class yang salah, compiler akan
menghasilkan suatu kesalahan. Sebagai contoh, jika anda ketik:
pow (2.0, 10.0);
Compiler kirakira akan mengatakan, Tidak bisa menemukan suatu method bernama
pow didalam Class Newline." Jika anda sudah melihat pesan ini, anda mungkin heran
60
mengapa compiler mencari pow didalam Class yang didefinisikan. Sekarang anda sudah
tahu.
3.7 Program dengan banyak method
Ketika anda memperhatikan suatu Class yang didefinisikan yang berisi beberapa
method, ketika dicoba untuk membacanya dari atas sampai ke bawah, namun
nampaknya membingungkan, sebab itu bukanlah Order Of Execution dari program.
Eksekusi selalu dimulai pada awal dari statemen dari main, dengan mengabaikan
dimana programnya (dalam hal ini saya dengan bebas menaruhnya pada bagian bawah).
Statemen dieksekusi satu per satu secara berurutan, sampai anda menjangkau suatu
method yang dituju. Method seperti suatu belokan didalam alur eksekusi. Setelah itu
akan berlanjut ke statemen yang berikutnya, anda pertama akan kebaris yang pertama
dari metoda yang dilibatkan, mengeksekusi semua statemen disana, dan kemudian
kembali dan mengambil lagi dimana anda berhenti.
Kelihatannya cukup sederhana, namun anda harus ingat bahwa satu method dapat
memanggil method yang lain. Seperti itu, selagi masih pada main, kita mungkin harus
berhenti dan melaksanakan statemen pada threeLine. Tetapi ketika kita sedang
melaksanakan threeLine, kita mendapatkan interupsi tiga kali untuk berhenti dan
melaksanakan newLine.
Pada bagian ini, newLine memanggil method println yang sudah builtin, yang berbelok
ke hal lain. Kebetulan, Java sungguh ahli pada mengawasi dimana itu, maka ketika
println selesai, lalu mengambil kembali eksekusi dimana newLine berhenti, dan
61
kemudian kembali lagi kepada threeLine, dan kemudian akhirnya kembali lagi kepada
main sehingga program dapat berakhir.
Sebenarnya, secara teknis program tidak berakhir pada ujung main. Sebagai gantinya,
eksekusi mengambil dimana program itu berhenti mengeksekusi yang melibatkan main,
yang merupakan Interpreter Java. Interpreter Java mengasuh macam hal suka
menghapus window dan cleanup umum, dan kemudian program berakhir.
Apa hal yang tidak baik untuk dikan dalam mempelajari buku ini? Ketika anda
membaca suatu program, jangan membaca dari atas sampai ke bawah. Sebaiknya
mengikuti alur pelaksanaan.
3.8 Parameter dan argumen
Sebagian dari method builtin yang sudah kita gunakan mempunyai parameter, yang
bernilai bahwa anda membiarkan method mekan pekerjaan itu. Sebagai contoh, jika
anda ingin menetukan 30 Method sinus suatu angka, anda harus menandai apa
angkanya. lalu, sin mengambil suatu nilai double sebagai parameter. Untuk mencetak
suatu string, anda harus menyediakan string, yang mana println mengambil suatu String
sebagai parameter.
Beberapa method mengambil parameter lebih dari satu, seperti pow, yang mengambil
dua double, yaitu base dan eksponen. Perhatikan bahwa pada setiap kasus ini kita harus
menetapkan tidak hanya berapa banyak parameter yang ada, tetapi juga tipe paremater.
Maka seharusnya itu tidak mengejutkan anda ketika anda menulis suatu Class
62
definition, daftar parameter menandai tipe parameter masingmasing. Sebagai contoh:
public static void printTwice (String phil) {
System.out.println (phil);
System.out.println (phil);
}
Metoda ini mengambil parameter tunggal yang dinamai phil, yang mempunyai tipe
String.
Apapun parameternya (dalam posisi ini kita tidak menyatakan apa parameternya), akan
dicetak dua kali. Saya memilih nama phil untuk menyatakan pada anda bahwa nama
yang anda beri suatu parameter terserah anda, tetapi secara umum anda sebaiknya
memilih sesuatu yang lebih ilustratif dibanding phil.
Untuk memanggil method ini, kita harus menyediakan suatu String. Sebagai contoh,
kita mungkin mempunyai suatu method main seperti ini:
public static void main (String[] args) {
printTwice ("Don't make me say this twice!");
}
String yang anda buat disebut suatu argument, dan kita mengatakan bahwa argument
diberikan pada method. Dalam hal ini kita akan menciptakan suatu nilai string yang
berisi teks "Don't make me say this twice!" dan melewatkan string sebagai suatu
63
argumenti ke printTwice dimana, berlawanan dengan yang diinginkan, dan akan dicetak
dua kali.
Sebagai alternatif, jika kita mempunyai suatu Variabel String, kita bisa
menggunakannya sebagai ganti suatu argument:
public static void main (String[] args) {
String argument = "Never say never.";
printTwice (argument);
}
Perhatikan sesuatu yang sangat penting disini: nama variabel yang kita lewatkan sebagai
suatu argument ( argument) tidak mekan apaapa terhadap parameter ( phil).
Saya katakan lagi bahwa :
Nama variabel yang kita lewatkan sebagai suatu argument tidak mekan apapun
terhadap nama parameter.
Itu bisa sama atau bisa berbeda, tetapi penting untuk menyadari bahwa itu bukanlah hal
yang sama, kecuali jika mempunyai nilai yang sama ( dalam hal ini string " ("Don't
make me say this twice!")
Nilai yang anda nyatakan sebagai suatu argumenti harus mempunyai tipe yang sama
dengan parameter method yang anda panggil. Aturan ini sangat penting, tetapi hal ini
64
sering membuat kita bingung pada Java untuk dua pertimbangan:
Ada beberapa metoda yang dapat menerima argumentasi dengan banyak tipe berbeda.
Sebagai contoh, anda dapat mengirimkan tipe manapun untuk print dan println, dan itu
akan selalu benar. Hal semacam ini merupakan suatu exception,though.
Jika anda membuat kesalahan pada aturan, compiler sering menghasilkan suatu hal yang
kacau pada pemberitahu kesalahan. sehingga, Anda sering mendapati bermacam
kesalahan argument pada method ini ," itu mungkin karena efek dari tidak
ditemukannya suatu metoda dengan nama dan tipe itu. Sekali anda menemukan
pemberitahu kesalahan ini maka anda akan menggambarkan tahu bagaimana cara
menginterpretasikannya.
3.9 Stack diagram
Parameter dan variabel lain hanya ada di dalam methodnya sendiri. Di dalam
membatasi main, tidak ada hal seperti phil. Jika anda mencoba untuk menggunakan itu,
compiler akan menganggapnya salah. Dengan cara yang sama, di dalam printTwice
tidak ada hal seperti argument.
Satu cara untuk mengetahui dimana masingmasing variabel didefinisikan pada suatu
stack diagram.
Stack Diagram untuk contoh yang sebelumnya kelihatan seperti ini:
65
Untuk masingmasing method ada suatu kotak abuabu yang dinamakan frame yang
berisi parameter method dan variabel lokal. Nama method nampak diluar frame. Seperti
biasanya, nilai dari tiap variabel digambark di dalam suatu kotak dengan nama variabel
di sampingnya.
3.10 Metode Dengan Banyak Parameter
Sintak untuk mendeklarasikan dan memanggil method dengan berbagai parameter
adalah suatu sumber kesalahan pada umumnya. Pertama, ingat bahwa anda harus
mendeklarasikan tipe tiaptiap parameter. Sebagai contoh
public static void printTime (int hour, int minute) {
System.out.print (hour);
System.out.print (":");
System.out.println (minute);
}
Itu bisa jadi terkecoh untuk menulis int jam, menit, tetapi format ituhanya benar untuk
mendeklarasikan variabel, bukan untuk parameter.
Sumber kesalahan umum yang lain adalah bahwa anda tidak punya sesuatu untuk
66
mendeklarasikan tipe argument. Yang berikut adalah salah!
int hour = 11;
int minute = 59;
printTime (int hour, int minute); // WRONG!
Dalam hal ini, Java dapat mengatakan bahwa tipe dari hour dan minute dengan
memperhatikan deklarasinya. Juga tidak legal dan tidak benar memasukkannya ketika
anda melewatkannya sebagi argument. Sintaksis yang benar adalah printTime (hour,
minute).
Latihan 3.1 Gambar suatu frame stack yang menunjukkan status program ketika main
memanggil printTime dengan argumentasi 11 dan 59.
3.11 Method dan hasilnya
Anda mungkin sudah memperhatikan sebagian dari method yang telah kita gunakan,
seperti method Math, menghasilkan hasil. Method lain, seperti println dan newLine,
meng eksekusi beberapa action tetapi mereka tidak mengembalikan suatu nilai. itu
memunculkan beberapa pertanyaan:
• Apa yang terjadi jika anda meminta suatu method dan yang anda tidak mekan
apapun dengan hasil (yaitu.anda tidak meng assignnya pada suatu variabel atau
menggunakannya sebagai bagian dari ekspresi yang besar)?
67
• Apa yang terjadi jika anda menggunakan suatu method print sebagai bagian dari
suatu ekspresi seperti System.Out.Println (" boo!")+ 7?
• Dapatkah kita menulis method yang menghasilkan hasil, atau kita tetap
menggunakan newLine dan printTwice?
Jawaban bagi pertanyaan ketiganya adalah ya, anda dapat menulis method yang
mengembalikan nilai," dan itu akan kita pelajari dalam 2 bab. Saya akan
meninggalkannya untuk anda coba jawab. Sebenarnya, kapan saja anda mempunyai
suatu pertanyaan tentang apa yang legal dan illegal lebih baik jika mencobanya pada
compiler.
3.12 Daftar kata
floatingpoint: Suatu jenis variabel ( atau nilai) yang dapat berisi pecahan seperti halnya
bilangan bulat. Didalam Jawa tipe ini disebut double.
Class: Suatu koleksi yang dinamai method. Sejauh ini, kita sudah menggunakan Class
Math dan Class System, dan kita sudah menulis Classs dengan nama Hello dan
Newline.
method: Penamaan suatu urutan statemen yang melaksanakan beberapa fungsi
bermanfaat. Metoda boleh atau boleh tidak mengambil parameter, dan boleh atau boleh
tidak menghasilkan suatu hasil.
Parameter: Suatu potongan informasi yang anda menyediakan untuk memanggil suatu
68
metohd.
Parameter adalah seperti variabel didalamnya nilainilai dan mempunyai tipe.
Argument: Suatu nilai yang anda sediakan ketika anda memanggil suatu method. Nilai
ini harus mempunyai tipe yang sama parameter yang bersangkutan.
invoke: Menyebabkan suatu method untuk dieksekusi
3.13 Latihan
Latihan 3.2
Latihan ini adalah untuk mempraktekkan membaca kode dan untuk meyakinkan bahwa
anda memahami alur eksekusi melalui suatu program dengan berbagai method.
a. Apa keluaran program yang berikut? Perhatikan dimana ada spasi dan dimana ada
baris baru.
SYARAT: mulai dengan menguraikan katakata apa ping dan baffle yang kan ketika itu
dilibatkan.
b. Gambar suatu diagram stack yang menunjukkan status program pertama kali ping
dilibatkan.
69
public static void zoop () {
baffle ();
System.out.print ("You wugga ");
baffle ();
}
public static void main (String[] args) {
System.out.print ("No, I ");
zoop ();
System.out.print ("I ");
baffle ();
}
public static void baffle () {
System.out.print ("wug");
ping ();
}
public static void ping () {
System.out.println (".");
}
Latihan 3.3
Latihan ini adalah untuk meyakinkan bahwa anda memahami bagaimana cara menulis
dan memanggil method yang mengambil parameter.
a. Tulis baris yang pertama suatu metoda nama zool yang mengambil tiga parameter:
suatu int dan dua String.
70
b. Tulis satu baris kode yang memanggil zool, ketika melewatkan suatu argument
dengan nilai 11, nama binatang kesayangan anda yang pertama, dan nama jalan yang
ada ditempat anda ketika anda dibesarkan.
Latihan 3.4
Tujuan latihan ini adalah untuk mengambil kode dari suatu latihan sebelumnya dan
mengemasnya didalam suatu method yang mengambil parameter. Anda perlu mulai
bekerja menemukan solusi dari Latihan 2.1.
a. Tulis suatu method untukmemanggil printAmerican yang mengambil day, date,
month dan years sebagai parameter dan dicetak didalam Format Amerika.
b. Uji method anda dengan memanggilnya dari main dan melewatkan argument yang
sesuai.
Keluarannya harus terlihat ( kecuali date boleh berbeda):
Wednesday, September 29, 1999
c. Anda sudah mempunyai debug printAmerican, tulis method lain untuk memanggil
printEuropean yang dicetak didalam Format Eropa.
Latihan 3.5
Banyak perhitungan dapat dinyatakan dengan singkat menggunakan “multadd" operasi,
71
yang mengambil tiga operand dan menghitung a*b+ c. Beberapa prosessor
menyediakan implementasi perangkat keras dari operasi ini untuk angkaangka
floatingpoint.
a. Buat suatu program baru dengan nama Multadd.Java.
b. Tulis suatu method dengan nama multadd yang mengambil tiga double sebagai
parameter dan cetak multadditionation nya
.
c. Tulis suatu method main yang menguji multadd dengan memanggilnya dengan
beberapa parameter sederhana, seperti 1.0, 2.0, 3.0, dan kemudian cetak hasilnya, dan
harus 5.0.
d. Juga didalam main, gunakan multadd untuk menghitung nilainilai yang berikut:
e. Tulis suatu method yang bernama yikes yang mengambil double sebagai parameter
dan gunakan multadd untuk mengkalkulasi dan cetak
SYARAT: Method Math untuk menaikkan nilai e adalah Math.Exp.
72
Didalam bagian yang terakhir, anda mendapat kesempatan untuk menulis suatu method
yang memanggil suatu method yang anda tulis.Kapan saja anda mekannya, merupakan
suatu gagasan baik untuk menguji method yang pertama secara hatihati sebelum anda
memulai menulis yang kedua. Selain itu, anda mungkin menemukan cara mekan
debugging dua method pada waktu yang sama,yang bisa jadi sangat sulit. Salah satu
tujuan dari latihan ini adalah untuk mempraktekkan patternmatching: kemampuan
untuk mengenali suatu masalah spesifik sebagai suatu kategori permasalahan umum.
73
BAB 4
Kondisional dan rekursi
4.1 Operator modulus
Operator Modulus bekerja pada bilangan bulat dan menghasilkan sisa ketika operan
yang pertama dibagi oleh yang kedua. Di (dalam) Java, operator modulus adalah suatu
tanda persen,%. Sintaksis persisnya sama halnya untuk lain operator:
int quotient = 7 / 3;
int remainder = 7 % 3;
Operator yang pertama, pembagian bilangan bulat, menghasilkan 2. Operator yang
kedua menghasilkan 1. Dengan demikian, 7 yang dibagi oleh 3 adalah 2 dengan sisa 1.
Operator modulus ternyata adalah anehnya bermanfaat. Sebagai contoh, anda dapat
memeriksa apakah satu bilangan; jumlah adalah dapat dibagi oleh lain: jika x% y adalah
nol, kemudian x dapat dibagi dengan y. Juga, anda dapat menggunakan operator
modulus untuk mengetahui digit paling kanan dari suatu bilangan. Sebagai contoh, x%
10 menghasilkan digit x paling kanan (di (dalam) basis 10). Dengan cara yang sama x%
100 menghasilkan dua digit terakhir.
4.2 Percabangan
Dalam menulis program yang betul betul fungsionalt, kita hampir selalu memerlukan
kemampuan untuk memeriksa kondisikondisi tertentu dan merubah aliran program.
Pernyataan percabangan memberi [kita/kami] kemampuan ini. Format yang paling
74
sederhana adalah if statemen:
if (x > 0) {
System.out.println ("x is positive");
}
Ungkapan di (dalam) tanda kurung disebut kondisi. Jika benar, kemudian statemen di
(dalam) tandakurung dieksekusi. Jika kondisi tidaklah benar, tidak dilakukan apapun.
Kondisi dapat berisi operator perbandingan apapun, kadangkadang disebut relational
operator:
x == y // x equals y
x != y // x is not equal to y
x > y // x is greater than y
x < y // x is less than y
x >= y // x is greater than or equal to y
x <= y // x is less than or equal to y
Walaupun operasi ini mungkin dikenal baik oleh anda, sintaks Java menggunakannya
sedikit berbeda dari lambang matematika seperti=, ≠ dan ≤. Suatu kesalahan umum
adalah untuk menggunakan = tunggal sebagai ganti suatu ==. Ingat bahwa = adalah
operator tugas, dan == adalah suatu operator perbandingan. Juga, tidak ada hal seperti
=< atau =>.
Dua sisi suatu kondisi operator harus memiliki tipe yang sama. Anda hanya dapat
membandingkan int ke int dan double dengan double . Sungguh sial, dalam posisi ini
75
anda tidak bisa bandingkan string sama sekali! Ada suatu [jalan/cara] untuk bandingkan
string tetapi kita tidak takkan membahasnya pada bab ini.
4.3 Eksekusi Alternatif
Bentuk kedua dari percabangan adalah pelaksanaan alternatif, di mana ada dua
kemungkinan, dan kondisi menentukan yang mana yang akan dieksekusi.
Sintaks nya seperti di bawah ini:
if (x%2 == 0) {
System.out.println ("x is even");
} else {
System.out.println ("x is odd");
}
Jika sisa ketika x dibagi oleh 2 adalah nol, kita mengetahui bahwa x menjadi genap, dan
kode ini mencetak suatu pesan. Jika kondisi adalah salah, statemen cetakan yang kedua
akan dieksekusi. Karena kondisi harus salah atau benar, maka salah satu dari alternafif
akan dieksekusi.
Jika anda berpikir mungkin akan sering mengecek genap atau ganjil, anda mungkin
ingin “membungkus" kode atas dalam suatu method, sebagai berikut:
public static void printParity (int x) {
if (x%2 == 0) {
System.out.println ("x is even");
} else {
76
System.out.println ("x is odd");
}
}
Sekarang anda mempunyai suatu metoda nama printParity yang akan mencetak suatu
pesan sesuai untuk bilangan bulat apapun. Di (dalam) metode “main” anda akan
memanggil metoda ini sebagai berikut:
printParity (17);
Selalu ingat bahwa ketika anda mengeksekusi suatu metoda, anda tidak harus
mendeklarasikan tipe argumen yang anda sediakan. Java dapat mengetahui tipe data
tersebut secara otomatis. Anda seharusnya penulisan seperti:
int number = 17;
printParity (int number); // WRONG!!!
4.4 Percabangan Berantai
Kadangkadang anda ingin melihat kemungkinan sejumlah kondisikondisi terkait dan
memilih salah satu dari beberapa tindakan. Satu cara untuk melakukan ini adalah
dengan rangkaian if dan else seperti berikut:
if (x > 0) {
System.out.println ("x is positive");
} else if (x < 0) {
system.out.println ("x is negative");
77
} else {
System.out.println ("x is zero");
}
Rantai ini dapat sepanjang yang anda inginkan, walaupun pada akhirnya akan sulit
dibaca. Satu cara untuk membuat [mereka/nya] lebih mudah untuk dibaca adalah untuk
menggunakan standard indentasi [seperti/ketika] dipertunjukkan contoh ini. Jika anda
meletakkan semua statement dan kurung kurawal dalam satu baris, anda memiliki lebih
sedikit kemungkinan untuk membuat kesalahan sintaks dan anda dapat menemukan nya
dengan lebih cepat.
4.5 Percabangan Bersarang
Sebagai tambahan terhadap yang percabangan berantai, anda dapat juga membuat
percabangan bersarang di dalam yang lain. Kita bisa menulis contoh yang sebelumnya
dengan:
if (x == 0) {
System.out.println ("x is zero");
} else {
if (x > 0) {
System.out.println ("x is positive");
} else {
System.out.println ("x is negative");
}
}
78
Sekarang ada suatu percabangan luar yang berisi dua cabang. Cabang yang pertama
berisi suatu statemen cetakan sederhana, tetapi cabang yang kedua berisi yang
pernyataan bersyarat lain, yang mempunyai dua cabang. Kebetulan, dua cabang itu
adalah keduaduanya statemen cetakan, walaupun mereka mungkin bisa juga
pernyataan bersyarat yang lain.
Perhatikan lagi indentasi membuat struktur lebih enak dibaca, tetapi meskipun
demikian, percabangan bersarang lebih susah dibaca dengan cepat. Secara umum,
merupakan suatu ide yang baik untuk menghindari nya ketika anda bisa.
Di lain pihak , struktur tersarang macam ini adalah umum, dan kita akan segera
melihatnya lagi.
4.6 The return statement
Statemen kembalian mengijinkan anda untuk mengakhiri pelaksanaan suatu metoda
sebelum anda menjangkau akhir pernyataan. Satu alasan untuk menggunakan nya
adalah jika anda mendeteksi suatu kondisi kesalahan:
public static void printLogarithm (double x) {
if (x <= 0.0) {
System.out.println ("Positive numbers only, please.");
return;
}
double result = Math.log (x);
System.out.println ("The log of x is " + result);
}
79
Terdapat suatu metoda bernama printLogarithm yang mengambil suatu tipe data double
bernama x sebagai parameter. Hal yang mulamula ia kerjakan adalah memeriksa
apakah x kurang dari atau sama dengan nol, dalam hal ini ia mencetak suatu
pemberitahu kesalahan dan kemudian menggunakan pernyataan return untuk keluar dari
metode. Alir pelaksanaan dengan seketika kembali ke pemanggil dan sisa metoda
tidaklah dieksekusi.
Saya menggunakan suatu nilai floating point pada sisi kanan kondisi sebab ada suatu
variabel floating point pada sisi kiri.
4.7 Konversi Tipe
Anda mungkin ingin tahu bagaimana anda suatu pernyataan seperti "The log of x is " +
result, bisa dieksekusi tanpa kesalahan padahal salah satu operan adalah suatu String
dan lain adalah double. Dalam hal ini Java secara cerdas mengubah double menjadi
String sebelum mengerjakan penggabungan String.
Hal macam ini adalah contoh suatu masalah umum di (dalam) merancang suatu bahasa
program, bahwa ada suatu konflik antar[a] formalisme, yaitu kebutuhan bahasa formal
untuk mempunyai aturan sederhana dengan sedikit eksepsi , dan kenyamanan, yang
merupakan kebutuhan bahasa program agar mudah digunakan dalam praktek.
Yang lebih sering terjadi adalah kenyamanan lah yang menang, yang biasnaya baik
untuk para programmer ahli, tetapi tidak baik untuk para programmer pemula. Dalam
buku ini saya sudah mencoba untuk menyederhanakan berbagai hal dengan
menekankan aturan dan penghilangan banyak perkecualian.
80
Meskipun demikian, ada baiknya untuk mengetahui bahwa kapan saja anda mencoba
menambahkan dua variabel, jika salah satu dari mereka adalah suatu String, maka Java
akan mengkonversi yang lain menjadi String dan melakukan penggabungan String. Apa
pendapat anda jika anda
melakukan suatu operasi antara suatu bilangan bulat dan suatu floating point?
4.8 Recursion
Saya menyebutkan pada bab yang terakhir bahwa diperbolehkan satu metoda untuk
memanggil metode yang lain, dan kita sudah melihat beberapa contoh menyangkut itu.
Saya lupa untuk menyebutkan bahwa juga diperbolehkan suatu metoda untuk
memanggil dirinya sendiri. Mungkin tidak jelas mengapa itu suatu hal baik, tetapi
ternyata itu adalah salah satu dari hal yang yang menarik dan ajaib yang suatu program
dapat lakukan.
Sebagai contoh, lihat di metoda yang berikut:
public static void countdown (int n) {
if (n == 0) {
System.out.println ("Blastoff!");
} else {
System.out.println (n);
countdown (n1);
}
}
81
Nama metoda adalah countdown dan mengambil bilangan bulat tunggal sebagai
parameter. Jika parameter adalah nol, ia encetak kata “Blasto." jika tidak ia mencetak
bilangan bulat tersebut dan memanggil suatu metoda countdown kembali dengan n1
sebagai argumen.
Apa yang terjadi jika kita memanggil metoda ini, di (dalam) main:
countdown (3);
Eksekusi countdown dimulai dengan n=3, dan karena n bukanlah nol, ia mencetak nilai
3, dan kemudian memanggil dirinya sendiri...
Pelaksanaan countdown dimulai dengan n=2, dan karena n bukanlah nol,
ia mencetak nilai 2, dan kemudian memanggil dirinya sendiri...
Pelaksanaan countdown dimulai dengan n=1, dan
karena n bukanlah nol, ia mencetak nilai 1, dan kemudian
memanggil [dirinya] sendiri...
Pelaksanaan countdown dimulai dengan n=0, dan
karena n adalah nol, ia mencetak kata “Blastoff"
kemudian kembali pada metode yang memanggilnya.
Countdown yang mendapat n=1 kembali (return).
Countdown yang mendapat n=2 kembali (return).
Countdown yang mendapat n=3 kembali (return).
Dan kemudian anda adalah ke main. Sehingga total keluaran kelihatan seperti:
3
2
82
1
Blastoff!
Sebagai contoh kedua, mari kita lihat lagi di metoda newLine dan threeLine.
public static void newLine () {
System.out.println ("");
}
public static void threeLine () {
newLine (); newLine (); newLine ();
}
Walaupun metode di atas dapat bekerja , mereka tidak akan banyak berguna jika saya
ingin mencetak 2 baris atau 106. Suatu alternatif lebih baik adalah dengan
public static void nLines (int n) {
if (n > 0) {
System.out.println ("");
nLines (n1);
}
}
Program ini sangat serupa; sepanjang n adalah lebih besar dari nol, ia mencetak satu
newline, dan kemudian memanggil dirinya sendiri untuk mencetak n1 tambahan
newlines. Sehingga, total jumlah newlines yang dicetak adalah 1+ ( n1), yang adalah
sama dengan n.
83
Proses suatu pemanggilan metoda terhadap dirinya sendiri disebut recursion, dan .
Metode seperti itu disebut recursive.
4.9 Diagram Stack Untuk Metode Rekursif
Di (dalam) bab yang sebelumnya kita menggunakan suatu diagram stack untuk
menampilkan status suatu program selama pemanggilan suatu metoda. Diagram yang
sama dapat membuat lebih mudah untuk
menginterpretasikan suatu metoda rekursif.
Ingat bahwa setiap kali suatu metoda dipanggil ia menciptakan instance baru yang
berisi variabel lokal dan parameter baru. Gambar berikut adalah diagram stack untuk
countdown, dengan n= 3:
Terdapat satu instance main dan empat instance countdown, masingmasing dengan
suatu nilai berbeda untuk parameter n. Stack terbawah, countdown dengan
n=0 adalah kasus dasar. Sehingga ia tidak membuat suatu panggilan rekursif, jadi tidak
ada lagi instance countdown. Main adalah kosong sebab main tidak mempunyai
84
parameter atau variabel lokal apapun.
Latihan 4.1 Gambar suatu diagram stack yang menunjukkan status program setelah
main memanggil nLines dengan parameter n=4, tepat sebelum instansce nLines terakhir
dikembalikan.
4.10 Konvensi dan Divine Law
Di dalam bagian terakhir, saya menggunakan ungkapan “by convention“ beberapa kali
untuk menandai (adanya) keputusan disain sewenangwenang tanpa pertimbangan
penting, tetapi hanya dikatakan berdasarkan konvensi.
Di (dalam) kasus ini, adalah suatu keuntungan untuk terbiasa dengan konvensi dan
menggunakannya, sehingga akan membuat program anda lebih mudah untuk dipahami
oleh orang lain. Dan penting pula untuk membedakan antara ( sedikitnya) tiga macam
aturan:
Divine Law: Ini adalah ungkapan saya untuk menandai aturan yang benar karena
beberapa prinsip logika atau matematika, maka itu adalah benar juga untuk bahasa
program (atau sistem formal) apapun. Sebagai contoh, tidak ada cara untuk menentukan
ukuran dan penempatan suatu kotak menggunakan lebih sedikit dari empat potongan
informasi. Contoh lain adalah bahwa menambahkan bilangan bulat adalah komutatif.
Hal ini adalah bagian dari definisi penambahan dan sama hal nya dengan Java.
Rules Of Java: Ini adalah peraturan tentang semantik dan yang syntactic Java yang
tidak bisa anda langgar, sebab program yang dihasilkan tidak akan bisa dikompilasi atau
dijalankan. Sebagai contoh, fakta bahwa lambang “+” menghadirkan penambahan dan
85
penggabungan String.
Gaya Dan Konvensi: Ada banyak aturan yang tidaklah dipaksa oleh compiler, tetapi itu
adalah penting untuk penulisan program yang benar, sehingga dapat didebug dan
dimodifikasi, dan dapat dibaca orang lain. Contoh nya adalah indentasi dan penempatan
kurung kurawal, dan konvensi untuk menamai variabel, metoda dan kelas.
Selagi saya adalah pada topik, anda sudah mungkin mengerti mulai sekarang bahwa
nama kelas selalu mulai dengan huruf besar, tetapi variabel dan metoda mulai dengan
huruf kecil. Jika nama lebih dari satu kata, maka anda biasanya membuat huruf pertama
setiap kata dengan huruf besar, seperti di newLine dan printParity.
4.11 Daftar Kata
modulus: Suatu operator yang bekerja pada bilangan bulat dan menghasilkan sisa
ketika satu bilangan dibagi oleh lain. Di (dalam) Java hal ini ditandai dengan
tanda persen(%).
conditional: Suatu blok statemen yang boleh atau tidak boleh dieksekusi tergantung
pada suatu kondisi.
chaining: Suatu cara untuk menggabungkan beberapa pernyataan bersyarat dalam
suatu urutan.
nesting: Meletakkan pernyataan bersyarat di dalam satu atau kedua cabang dari
pernyataan bersyarat lain.
coordinate: Suatu variabel atau nilai yang menunjukkan lokasi dari jendela grafis
dua dimensi.
pixel: satuan koordinat dalam komputer
86
bounding box: cara umum untuk menentukan koordinat suatu area segiempat.
typecast : operator yang mengkonversi dari satu tipe data ke tipe data yang lain.
Di dalam Java dengan tipe data dalam tanda kurungs seperti (int).
interface: Deskripsi tentang parameter yang dibutuhkan oleh suatu metode dan tipe
data nya
prototype: Suatu cara untuk menggambarkan metode menggunakan sintaks yang
mirip Java.
recursion: Proses yang memanggil metoda yang sama dengan yang sekarang
dieksekusi.
infinite recursion: Suatu metoda yang memanggil dirinya sendiri secara berulang tanpa
pernah berhenti. Hal seperti ini biasanya menghasilkan StackOver FlowException.
fractal: Semacam gambaran yang adalah didefinisikan secara berulang, sedemikian
sehingga masingmasing bagian dari gambar adalah suatu versi yang lebih kecil
keseluruhan.
4.12 Latihan
Latihan 4.2
Jika anda diberi tiga tongkat, anda mungkin bisa atau tidak bisa menyusun nya dalam
suatu segi tiga. Sebagai contoh, jika salah adalah 12 inci dan yang lain adalah satu inci
,jelas bahwa anda tidak akan bisa membuat ketiganya menjadi segitiga. Berapun
panjangnya, ada suatu test sederhana untuk melihat apakah mungkin untuk membentuk
suatu segi tiga:
“Jika terdapat dari ketiga tongkat tersebut yang panjang nya lebih besar dari jumlah dua
87
yang lain, maka anda tidak dapat membentuknya menjadi segitiga, jika tidak maka
mereka dapat dibentuk sebagai segitiga"
Tulis suatu metoda bernama isTriangle yang mengambil tiga bilangan bulat sebagai
argumen, dan itu mengembalikan true/false, tergantung apakah anda bisa membentuk
nya menjadi segitiga
Tujuan latihan ini adalah untuk menggunakan pernyataan bersyarat untuk menulis suatu
metoda yang mengembalikan suatu nilai.
Latihan 4.3
Latihan ini meninjau aliran eksekusi program dengan berbagai metoda. Baca kode yang
berikut dan jawab pertanyaan di bawah.
public class Buzz {
public static void baffle (String blimp) {
System.out.println (blimp);
zippo ("ping", 5);
}
public static void zippo (String quince, int flag) {
if (flag < 0) {
System.out.println (quince + " zoop");
} else {
System.out.println ("ik");
baffle (quince);
System.out.println ("boowahaha");
88
}
}
public static void main (String[] args) {
zippo ("rattle", 13);
}
}
a. Tulis angka 1 di sebelah statement program yang akan pertama kali dieksekusi.
Perhatikan untuk membedakan hal yang merupakan statement dari hal yang bukan.
b. Tulis nomor 2 disamping statement yang dieksekusi pada kali kedua , dan seterusnya
sampai akhir program [itu]. Jika suatu statemen dieksekusi lebih dari sekali, itu bisa
berakhir dengan billangan lebih dari satu disampingnya.
c. berapa nilai blimp ketika baffle dieksekusi?
d. Apa yang keluaran program ini?
Latih 4.4
Sajak pertama nyanyian “ 99 Bottles of Beer” adalah:
99 bottles of beer on the wall, 99 bottles of beer, ya’ take one down, ya’
pass it around, 98 bottles of beer on the wall.
Sajak berikutnya serupa kecuali banyak botol lebih sedikit satu setiap sajak, sampai
sajak terakhir:
No bottles of beer on the wall, no bottles of beer, ya’ can’t take one down,
ya’ can’t pass it around, ’cause there are no more bottles of beer on the
wall!
Dan kemudian nyanyian berakhir.
89
Tulis suatu program yang mencetak keseluruhan lirik lagu “ 99 Bottles of Beer”.
Program anda harus berisi metode rekursif yang bukan merupakan hal yang susah,
mungkin anda juga ingin menulis metode tambahan untuk memisahkan fungsi fungsi
utama program.
Ketika anda menulis kode, anda akan mungkin ingin mengujinya dengan ejumlah
bilangan kecil seperti “3 Bottles Of Beer."
Tujuan latihan ini adalah untuk mengambil suatu masalah dan memecahkannya ke
dalam permasalahan lebih kecil, dan untuk memecahkan permasalahan kecil dengan
menulis metode sederhana yang dapat didebug dengan mudah.
Latihan 4.5
Apa yang program yang berikut?
public class Narf {
public static void zoop (String fred, int bob) {
System.out.println (fred);
if (bob == 5) {
ping ("not ");
} else {
System.out.println ("!");
}
}
public static void main (String[] args) {
90
int bizz = 5;
int buzz = 2;
zoop ("just for", bizz);
clink (2*buzz);
}
public static void clink (int fork) {
System.out.print ("It's ");
zoop ("breakfast ", fork) ;
}
public static void ping (String strangStrung) {
System.out.println ("any " + strangStrung + "more ");
}
}
Latihab 4.6
Teorema Fermat yang menyatakan bahwa tidak ada bilangan bulat a, b, dan c sehingga
kecuali kasus ketika n= 2.
Tulis suatu metoda bernama checkFermat yang mengambil empat bilangan bulat
sebagai parameter a, b, c dan n yang memeriksa untuk melihat apakah teori Fermat
benar. Jika n adalah lebih besar dari 2 dan
ternyata adalah benar an+ bn= cn, program akan mencetak “Teori,
Fermat adalah salah!" Jika tidak program akan mencetak “Tidak ia betul."
91
Anda perlu berasumsi bahwa terdapat suatu metoda bernama raiseToPow yang
mengambil dua bilangan bulat sebagai argumen dan argumen kedua sebagai pangkat
argumen pertama. Sebagai contoh:
int x = raiseToPow (2, 3);
akan memasukkan nilai 8 ke dalam x, karena 23=8
92
Bab 5
Fruitful methods
5.1 Nilai Kembalian
Beberapa metode bawaan yang telah kita gunakan, seperti fungsi Math, telah
menghasilkan hasil. Tujuan dari pemanggilan metode adalah untuk menghasilkan
sebuah nilai baru, yang biasanya kita masukkan pada sebuah variabel yang merupakan
bagian dari sebuah ekspresi. Misalnya
double e = Math.exp (1.0);
double height = radius * Math.sin (angle);
Tetapi sejauh ini semua metode yang telah kita tulis adalah metode dengan kembalian
void; yaitu metode yang tidak mengembalikan nilai. Ketika anda memanggil metode
void, ia hanya terdiri atas satu baris, tanpa pemberian nilai ke dalam satu variabel.
nLines (3);
g.drawOval (0, 0, width, height);
Pada bab ini, kita akan menulis metode yang mengembalikan nilai, yang saya sebut
sebagai “fruitful methods”, untuk nama yang lebih baik. Contoh yang pertama adalah
area, yang mengambil double sebagai parameter, dan mengembalikan luas lingkaran
dengan radius yang diberikan:
public static double area (double radius) {
double area = Math.PI * radius * radius;
return area;
}
Hal pertama yang harus anda perhatikan adalah awal dari definisi metode adalah
93
berbeda. Sebagai ganti public static void, yang menunjukkan metode void, kita
menggunakan public static double, yang menunjukkan nilai kembalian dari metode ini
adalah double. Saya masih belum menjelaskan apa itu public static tapi tenanglah.
Juga perhatikan bahwa baris terakhir adalah bentuk alternatif dari pernyataan return
yang juga terdapat nilai yang akan dikembalikan. Pernyataan tersebut berarti, “kembali
secepatnya dari metode ini dan gunakan nilai berikut sebagai nilai kembalian.” Nilai
yang disediakan bisa lebih kompleks, jadi kita bisa menulis metode lebih singkat:
public static double area (double radius) {
return Math.PI * radius * radius;
}
Di lain pihak, variabel sementara seperti area lebih membuat debugging jadi lebih
mudah. Pada kedua kasus, tipe nilai kembalian pada pernyataan return harus sesuai
dengan tipe kembalian metode. Dengan kata lain, ketika anda mendeklarasikan bahwa
nilai kembalian adalah double, anda membuat janji bahwa metode ini betul betul akan
menghasilkan double. Jika anda mencoba return dengan tanpa nilai, atau nilai dengan
nilai yang berbeda, kompiler akan memaksa anda mengubahnya (menampilkan
kesalahan).
Kadang kadang berguna juga untuk memiliki beberapa statemen return, satu di setiap
cabang kondisi:
public static double absoluteValue (double x) {
if (x < 0) {
return x;
94
} else {
return x;
}
Karena statemen return ada pada kondisi alternatif, maka hanya satu yang akan
dieksekusi. Meskipun sah untuk memiliki lebih dari satu statemen return dalam satu
metode, harus selalu anda ingat bahwa segera setelah dieksekusi, metode akan berhenti
tanpa mengeksekusi pernyataan yang ada di bawah nya lagi.
Kode yang ada setelah sebuah pernyataan return, atau dimana pun dimana tidak akan
pernah dieksekusi, disebut dead code. Beberapa kompiler akan memperingatkan anda
jika ada bagian kode yang mengalami hal ini.
Jika anda meletakkan statemen return di dalam percabangan, anda harus menjamin
bahwa setiap jalan yang mungkin pada program akan mencapai statemen return.
Misalnya
public static double absoluteValue (double x) {
if (x < 0) {
return x;
} else if (x > 0) {
return x;
} // WRONG!!
}
progam di atas tidak sah karena jika x sama dengan 0, maka tidak ada kondisi yang
benar dan metode akan kembali tanpa mencapai statemen return. Pesan kompiler untuk
hal seperti ini adalah “return statement required in absoluteValue,” yang adalah pesan
95
memusingkan mengingat telah ada dua statemen return.
5.2 Pengembangan Program
Pada titik ini anda seharusnya telah bisa untuk melihat pada metode Java yang lengkap
dan melaporkan apa yang mereka kerjakan. Tetapi mungkin belum jelas bagaimana cara
menulis nya. Saya akan menyarankan satu teknik yang saya sebut “incremental
development”.
Sebagai contoh, bayangkan anda ingin mencari jarak antara dua titik, dengan koordinat
(x1,y1) dan (x2,y2). Dengan rumus berikut
Langkah pertama adalah memikirkan bagaimana metode distance akan terlihat pada
Java. Dengan kata lain apa saja input dan apa saja output nya.
Pada kasus ini, dua titik adalah parameter, dan sudah jamak untuk mewakili nya dengan
4 double, meskipun kita akan lihat nanti terdapat objek Point pada Java yang dapat kita
gunakan. Nilai kembalian adalah jarak, yang akan berupa double juga.
Selanjutnya kita dapat menulis garis besar metode sebagai berikut:
public static double distance
(double x1, double y1, double x2, double y2) {
return 0.0;
}
96
Nilai kembalian return 0.0; dibutuhkan agar program dapat dikompilasi. Pada tahap
ini program tidak melakukan apapun yang berguna, tapi akan lebih baik untuk mencoba
mengkopilasi sehingga kita bisa mengetahui jika terdapat syntaks yang error sebelum
kita membuatnya menjadi lebih rumit.
Untuk menguji metode yang baru, kita harus menggunakannya dengan nilai contoh
pada metode main:
double dist = distance (1.0, 2.0, 4.0, 6.0);
Saya memilih nilai nilai ini sehingga jarak horizontal adalah 3 dan jarak vertikal adalah
4. maka hasilnya akan menjadi 5. Ketika anda menguji sebuah metode akan berguna
jika mengetahui jawaban yang benar.
Ketika kita telah mengecek sintaks pada definisi metode, kita bisa memulai menambah
baris kode. Setelah setiap tahap perubahan , kita mengkompilasi lagi dan menjalankan
program. Dengan cara ini, kita mengetahui dengan tepat dimana masalah yang terjadi
(jika terdapat error) yang pasti pada baris terakhir yang kita tambahkan.
Langkah berikut pada komputasi adalah untuk mencari jarak antara x2x1 dan y2y1.
Saya akan menyimpan nilai nilai itu pada variabel sementara bernama dx dan dy.
public static double distance
(double x1, double y1, double x2, double y2) {
double dx = x2 x1;
double dy = y2 – y1;
System.out.println ("dx is " + dx);
System.out.println ("dy is " + dy);
97
return 0.0;
}
Saya menambahkan pernyataan cetak yang membuat saya bisa melihat nilai yang ada
sebelum diproses (jika anda menggunakan IDE (Integrated Development Environment)
anda bisa melakukan hal ini tanpa System.out.println dengan fitur debugger, catatan
penerjemah). Sebagaimana yang telah saya sebutkan, saya telah mengetahui bahwa nilai
nilai tersebut adalah 3 dan 4.
Ketika metode telah diselesaikan saya akan menghapus statemen cetak tersebut. Kode
seperti ini disebut scaffolding, karena akan membantu dalam membuat program, tapi
bukan merupakan bagian produk akhir. Kadang kadang merupakan hal yang baik untuk
tidak menghapus kode scaffold, tetapi hanya dibuat komentar, hanya jika kira kira anda
akan membutuhkannya lagi.
Langkah berikut pada pengembangan adalah memangkatkan dx dan dy. Kita bisa
menggunakan metode Math.pow, tetapi akan lebih mudah dan cepat jika hany
mengalikannya dengan dirinya sendiri.
public static double distance
(double x1, double y1, double x2, double y2) {
double dx = x2 x1;
double dy = y2 y1;
double dsquared = dx*dx + dy*dy;
System.out.println ("dsquared is " + dsquared);
return 0.0;
}
98
Lagi, saya akan mengkompilasi dan menjalankan program pada tahap ini dan mengecek
nilai (yang seharusnya 25).
Langkah terakhir adalah menggunakan Math.sqrt yaitu metode untuk menghitung dan
mengembalikan hasil.
public static double distance
(double x1, double y1, double x2, double y2) {
double dx = x2 x1;
double dy = y2 y1;
double dsquared = dx*dx + dy*dy;
double result = Math.sqrt (dsquared);
return result;
}
lalu pada metode main, kita lalu akan mengecek nilai dari hasil metode di atas.
Semakin anda berpengalaman dalam pemrograman, anda akan makin mahir dan bisa
menulis dan mendebug program lebih dari satu baris setiap kali. Namun demikian
proses pengembangan secara bertahap ini bisa menghemat banyak waktu debugging.
Hal kunci pada proses adalah:
● Mulai dengan program yang bisa bekerja dan buat perubahan bertahap yang
kecil. Jika terjadi kesalahan, anda akan mengetahui dengan tepat di aman ia
terjadi.
● Gunakan variabel sementara untuk menyimpan nilai (nilai yang bukan hasil
akhir) jadi anda bisa mencetak dan mengeceknya.
99
● Ketika program bekerja, anda mungin ingin menghapus beberapa baris
scaffolding atau menggabungkan beberapa baris/pernyattan menjadi satu tetapi
hanya jika itu tidak membuat program sulit untuk dibaca.
5.3 Komposisi
Seperti yang telah anda ketahui, ketika anda mendefinisikan sebuah metode, anda bisa
menggunakannya sebagai bagian dari ekspresi, dan anda bisa membangun metode baru
menggunakan metode yang telah ada. Misalnya, bagaimana jika seseorang memberikan
anda dua titik, titik pusat dari lingkaran dan sebuah titik dari keliling, dan bertanya
tentang luas lingkaran.
Jika titik tengah disimpan pada variabel xc dan yc, dan titik keliling adalah xp dan yp.
Langkah pertama adalah mencari jari jari lingkaran, yang merupakan jarak antara dua
titik. Untungnya, kita memiliki sebuah metode, distance yang melakukan:
double radius = distance (xc, yc, xp, yp);
Langkah kedua adalah mencari luas lingkaran dengan jari jari yang ada, dan
mengembalikannya
double area = area (radius);
return area;
Dengan menggabungkan semua nya ke dalam sebuah metode, maka kita dapatkan:
public static double fred
(double xc, double yc, double xp, double yp) {
double radius = distance (xc, yc, xp, yp);
double area = area (radius);
return area;
100
}
Nama metode ini adalah fred, yang mungkin kelihatan ganjil. Saya akan menjelaskan
pada bagian berikutnya.
Variabel sementara radius dan area berguna untuk pengembangan dan debugging, tetapi
ketika program telah bekerja kita bisa membuat nya lebih singkat dengan
menggabungkan pemanggilan metode.
public static double fred
(double xc, double yc, double xp, double yp) {
return area (distance (xc, yc, xp, yp));
}
5.4 Overloading
Pada bagian sebelumnya anda mungkin menyadari bahwa fred dan area melakukan hal
yang sama, mencari luas lingkaran tetapi dengan dua parameter yang berbeda. Untuk
area, kita harus menyediakan radius; untuk fred kita harus menyediakan dua titik.
Jika kedua metode melakukan hal yang sama, merupakan hal yang lazim untuk
memberikan nama yang sama. Dengan kata lain, akan lebih baik jika fred bernama
area.
Memiliki lebih dari satu metode dengan nama yang sama, yang disebut overloading,
adalah sah dalam Java. Jadi kita bisa menamai fred dengan:
public static double area(double x1, double y1, double x2, double y2) {
return area (distance (xc, yc, xp, yp));
}
101
Ketika anda memanggil metode yang dioverloading, Java mengetahui versi yang mana
yang anda inginkan dengan melihat pada argumen yang anda sediakan. Jika anda
menulis
double x = area (3.0);
Java akan mencari metode yang bernama area yang menggunakan satu parameter
double sebagai argumen, jadi ia akan menggunakan versi yang pertama. Yang
mengartikan argumen sebagai radius. Jika anda menulis:
double x = area (1.0, 2.0, 4.0, 6.0);
Maka Java akan menggunakan versi yang kedua.
Banyak perintah bawaan Java yang dioverloading, yang berati ada versi yang berbeda
beda yang menerima jumlah dan tipe parameter yang berbeda. Misalnya, ada versi print
dan println yang menerima satu parameter dengan tipe apapun. Pada kelas Math, ada
versi abs yang bekerja pada double dan ada juga yang bekerja untuk int.
Meskipun overloading adalah metode yang berguna, ia seharusnya dipakai dengan hati
hati. Anda mungkin menemukan diri anda pusing sendiri jika anda mencoba mendebug
satu versi metode padahal sebenarnya anda menggunakan versi yang berbeda.
Sebenarnya. Hal ini mengingatkan saya salah satu aturan dari debugging: yakinkan
bahwa versi program yang anda perbaiki adalah versi program yang sedang
berjalan! Kadangkadang anda mungkin menemukan diri anda membuat perubahan
setelah perubahan yang lain pada suatu program, dan melihat hal yang sama setiap kali
anda menjalankannya. Ini adalah tanda peringatan untuk satu alasan atau yang lain
bahwa anda tidak menjalankan versi program yang sedang anda perbaiki. Untuk
102
mengeceknya, letakkan print (tidak menjadi masalah apa yang anda cetak) dan
yakinkan bahwa program mencetak pernyataan print tersebut.
5.5 Ekspresi Boolean
Sebagian besar operasi yang telah kita lihat menghasilkan nilai dengan tipe yang sama
dengan operan nya. Misalnya + operator mengambil dua int dan menghasilkan sebuah
int atau dua double yang menghasilkan sebuah double.
Pengecualian yang telah kita lihat adalah operator relasional, yang membandingkan int
dan float dan mengembalikan apakah true atau false. True dan false adalah nilai khusus
dalam Java. Keduanya merupakan tipe data boolean.
Ekspresi boolean dan variabel bekerja seperti hal nya tipe yang lain:
boolean fred;
fred = true;
boolean testResult = false;
Contoh pertama adalah deklarasi variabel sederhana; contoh kedua adalah sebuah
penugasan, dan contoh ketiga adalah kombinasi dari deklarasi dan penugasan, yang
kadang kadang disebut inisialisasi. Nilai true dan false adalah kata kunci dalam Java,
jadi mereka mungkin tampil dengan warna yang berbeda, tergantung pada lingkungan
pengembangan anda.
Sebagaimana yang telah saya sebutkan, hasil dari operator kondisional adalah booean,
jadi anda bisa menyimpan hasil pembandingan pada sebuah variabel:
boolean evenFlag = (n%2 == 0); // true if n is even
103
boolean positiveFlag = (x > 0); // true if x is positive
dan menggunakannya sebagai bagian dari pernyataan kondisional:
if (evenFlag) {
System.out.println ("n was even when I checked it");
}
Variabel yang digunakan pada cara ini sering disebut sebagai flag, karena ia
menunjukkan ada atau tidak adanya suatu kondisi.
5.6 Operator Logika
Ada tiga operatot logika pada Java: AND, OR, NOT, yang memiliki simbol &&, ||,
dan !. Sematik (arti) dari operator operator ini sama dengan arti nya dalam bahasa
Inggris. Misalnya x>0 d && x<10 berarti benar hanya jika x lebih besar dari 0 dan lebih
kecil dari 10.
evenFlag || n%3 ==0 benar jika salah satu dari kedua kondisi benar, jika evenFlag
bernilai benar atau n habis dibagi 3.
NOT operatir memiliki efek untuk menegasikan atau menginvers ekspresi boolean, jadi
!evenFlag bernilai benar jika evenFlag bernilai false, atau jika n bernilai ganjil.
Operator logika sering menyediakan cara untuk menyederhanakan pernyataan kondisi
bersarang. Misalnya bagaimana anda menulis kode di bawah ini menggunakan satu
kondisional.
if (x > 0) {
if (x < 10) {
System.out.println ("x is a positive single digit.");
104
}
}
5.7 Metode Boolean
Metode dapat mengembalikan nilai boolean seperti hal nya tipe yang lain, yang sering
menyembunnyikan pengetesan kompleks di dalam metode. Misalnya
public static boolean isSingleDigit (int x) {
if (x >= 0 && x < 10) {
return true;
} else {
return false;
}
}
Nama metode adalah isSingleDigit. Cara yang umum untuk memberi nama metode
boolean seperti pertanyaan ya/tidak (yes/no question). Tipe kembalian adalah boolean
yang berarti setiap pernyataan kembalian harus menyediakan ekspresi boolean.
Kode nya sendiri sama saja, meskipun akan lebih panjang sedikit dari seharusnnya.
Ingat bahwa ekspresi x>=0 && x<10 memiliki tipe boolean, jadi tidak ada yang salah
dengan mengembalikannya secara langsung, dan menghindari pernyataan menggunakan
if.
public static boolean isSingleDigit (int x) {
return (x >= 0 && x < 10);
}
Pada metode main anda bisa memanggil metode dengan cara yang biasa:
105
boolean bigFlag = !isSingleDigit (17);
System.out.println (isSingleDigit (2));
Baris pertama menugaskan nilai true pada bigFlag dengan alasan 17 bukan angka satu
digit. Baris kedua mencetak true karena 2 adalah bilangan satu digit. Benar bahwa
println dioverloading untuk juga menangani boolean.
Cara yang paling umum untuk menggunakan metode boolean adalah di dalam
pernyataan kondisional
if (isSingleDigit (x)) {
System.out.println ("x is little");
} else {
System.out.println ("x is big");
}
5.8 Lagi tentang Rekursi
Sekarang kita memiliki metode yang mengembalikan nilai, anda mungkin tertarik untuk
mengetahui bahwa kita telah memiliki bahasa pemrograman yang lengkap, yang saya
maksudkan bahwa segala hal yang dapat dihitung dapat diekspresikan dalam bahasa ini.
Semua program yang telah ditulis dapat ditulis kembali menggunakan fitur fitur bahasa
yang telah kita gunakan sejauh ini (sebenarnya, kita juga membutuhkan beberapa
perintah untuk mengontrol alat seperti keyboard, mouse, dan diks).
Membuktikan klaim ini bukanlah pekerjaan yang mudah . Hal ini pertama kali
diselesaikan oleh Alan Turing, salah satu computer scientisct pertama(beberapa orang
akan beargumen bahwa ia adalah seorang matematikawan, tetapi banyak computer
scientist yang memulai sebagai matematikawan). Hal ini disebur sebagai thesis Turing.
106
Jika anda belajar tentang teori komputasi anda akan memiliki kesempatan untuk melihat
pembuktian ini.
Untuk memberikan anda ide apa yang bisa anda lakukan dengan alat yang telah kita
pelajari sejauh ini, mari kita melihat pada beberapa metode untuk mengevaluasi definisi
fungsi rekursif matematika. Definisi rekursif sama dengan definisi melingkar, yaitu
definisi yang berisi apa yang didefinisikan.
Jika anda melihat pada definisi faktorial, anda mungkin mendapatkan seperti:
0! = 1
n! = n ∙ (n − 1)!
(Faktorial biasanya disimbolkan dengan !, jangan bingung dengan operaror ! Pada Java
yang berarti NOT.) definisi ini menyatakan bahwa faktorial 0 adalah 1, dan faktorial
nilai yang lain, adalah n dikalikan dengan faktorial dari n1. Jadi 3! adalah 3 kali 2!,
dimana 2! faktorial adalah 2 kali 1!, yang merupakan 1 kali 0!. Sehingga didapatkan 3!
sama dengan 3 kali 2 kali 1 kali 1 yang menghasilkan 6.
Jika anda bisa menulis definisi rekursif dari sesuatu, anda biasanya dapat menulis
program Java untuk menghitungnya. Langkah pertama adalah untuk membuktikan
parameter apa yang akan dimiliki fungsi tersebut, dan apa nilai kembaliannya. Dengan
sedikit pemikiran, anda bisa menyimpulkan bahwa faktorial menggunakan bilangan
bulat sebagai parameter dan juga mengembalikan bilangan bulat:
public static int factorial (int n) {
}
Jika argumen sama dengan 0, maka akan mengembalikan 1:
107
public static int factorial (int n) {
if (n == 0) {
return 1;
}
}
Jika tidak, ini adalah bagian yang menarik, kita harus membuat pemanggilan rekursif
untuk mencari nilai faktorial n1, dan kemudian mengalikannya dengan n.
public static int factorial (int n) {
if (n == 0) {
return 1;
} else {
int recurse = factorial (n1);
int result = n * recurse;
return result;
}
}
Jika kita melihat pada aliran eksekusi pada program, maka akan serupa dengan metode
nLines pada bab sebelumnya, jika kita memanggil faktorial dengan nilai 3:
Karena 3 tidak sama dengan 0, kita mengambil cabang kedua dan menghitung faktorial
dari n1....
Karena 2 tidak sama dengan 0, kita mengambil cabang kedua dan
menghitung faktorial dari n1....
Karena 1 tidak sama dengan 0, kita mengambil cabang
kedua dan menghitung faktorial dari n1....
108
Karena 0 sama dengan 0, kita mengambil
cabang pertama dan mengembalikan nilai 1 tanpa
memanggil metode rekursif.
Nilai kembalian 1 dikalikan dengan n, yaitu 1, dan hasilnya
dikembalikan
Nilai kembalian 1 dikalikan dengan n, yaitu 2, dan hasilnya
dikembalikan.
Nilai kembalian 2 dikalikan dengan n, yaitu 3, dan hasilnya adalah 6. dikembalikan
pada main, yang memanggil faktorial(3).
Berikut ini diagram stack untuk urutan pemanggilan fungsi di atas:
Nilai kembalina dipelihatkan dilewatkan kembali pada stack.
Perhatikan bahwa pada instance terakhir dari faktorial, variabel lokal recurse dan
result tidak ada karena ketika n=0 cabang yang menciptakan mereka tidak dieksekusi.
5.9 Leap of faith
Mengikuti aliran eksekusi adalah salah satu cara untuk membaca program, tetapi
109
sebagaimana telah anda lihat pada bagian sebelumnya, hal ini akan sulit untuk beberapa
jenis program. Sebagai alternatif kita bisa menggunakan cara “leap of faith.” Ketika
anda melihat pemanggilan metode, sebagai ganti mengikuti aliran eksekusi, anda
mengasumsikan bahwa metode bekerja dengan benar dan mengembalikan nilai yang
cocok.
Sebenarnya, anda telah mempraktekkan “leap of faith” ketika anda menggunakan
metode bawaan. Ketika anda menggunakan metode Math.cos atau drawOval. Anda
tidak perduli dengan implementasi metode ini. Anda hanya berasumsi bahwa metode ini
bekerja, karena orang yang menulis metode ini adalah programmer yang baik. Ketika
kita telah yakin bahwa metode ini benar dengan mencoba dan menguji kode—kita bisa
menggunakan metode ini tanpa melihat kode itu kembali.
Hal yang sama berlaku ketika anda memanggil salah satu metode kepunyaan anda.
Misalnya pada bagian 5.7 kita menulis metode isSingleDigit yang menghitung apakah
sebuah bilangan ada diantara 0 dan 9.
Hal yang sam dengan program rekursif. Ketika anda melihat pada pemanggilan rekursif.
Sebagai ganti melihat pada aliran program, anda harus berasumsi bahwa pemanggilan
rekursif bekerja dengan baik (menghasilkan nilai yang benar), dan lalu tanyakan pada
diri anda, “Asumsikan bahwa saya bisa mencari faktorial n1, dapatkah saya
menghitung faktorial n?” Pada kasus ini, jelas bahwa anda bisa, dengan mengalikannya
dengan n.
Tentu, merupakan hal yang aneh untuk mengasumsikan metode bekerja dengan benar
ketika kita belum selesai menuliskannya, tetapi itulah mengapa kita menyebutnya “leap
110
of faith”
5.10 Satu Contoh Lagi
Pada contoh sebelumnya saya menggunakan variabel sementara untuk mengetahui
langkah, dan untuk membuat kode lebih mudah untuk didebug, tetapi saya bisa
menghemat beberapa baris:
public static int factorial (int n) {
if (n == 0) {
return 1;
} else {
return n * factorial (n1);
}
}
Dari sekarang saya akan menggunakan versi yang lebih singkat, tetapi saya
merekomendasikan anda untuk menggunakan versi yang lebih eksplisit ketika
mengembangkan kode. Ketika anda telah membuat nya bekerja, anda bisa
menciutkannya
Setelah faktorial, contoh klasik tentang definisi rekursif fungsi matematika adalah
fibonacci. Yang memiliki definisi berikut:
f ibonacci(0) = 1
f ibonacci(1) = 1
f ibonacci(n) = f ibonacci(n − 1) + f ibonacci(n − 2);
Diterjemahkan ke Java sehingga menjadi
public static int fibonacci (int n) {
111
if (n == 0 || n == 1) {
return 1;
} else {
return fibonacci (n1) + fibonacci (n2);
}
}
Jika anda mencoba untuk mengikuti aliran eksekusi, bahkan dengan nilai yang kecil,
kepala anda akan meledak. Tetapi menurut prinsip “leap of faith”, jika kita berasumsi
bahwa dua pemanggilan rekursif (ya, anda bisa melakukan dua pemanggilan rekursif)
bekerja dengan benar, maka dengan jelas kita bisa mendapatakan hasil yang benar
dengan menambahkan keduanya.
5.11 Daftar Kata
return type : Bagian deklarasi metode yang menunjukkan nilai dengan tipe apa yang
akan dikembalikan
return value : Nilai yang disediakan sebagai hasil dari pemanggilan metode.
dead code : Bagian program yang tidak akan pernah dieksekusi, sering terjadi arena
diletakkan setelah return
scaffolding : kode yang digunakan selama pengembangan program tetapi bukan
merupakan bagian dari versi akhir
void : tipe Kembalian khusu yang menunjukkan metode void; yaitu metode yang
tidak mengembalikan nilai.
overloading : Memiliki lebih dari satu metode dengan nama yang sama tetapi
parameter yang berbeda. Ketika anda memanggil metode yang dioverloading.
Java mengetahui versi mana yang digunakan dengan melihat pada argumen yang
112
anda sediakan
boolean : Tipe variabel yang hanya bisa berisi dua nilai true dan false
operator kondisional : operator yang membandingkan dua nilai dan menghasilkan
nilai boolean yang menunjukkan hubungan antara kedua operan.
Operator logika : operator yang menggunakan nilai boolean dan menghasilkan nilai
boolean.
Inisialisasi : pernyataan yang mendeklarsikan variabel baru dan menugaskan nilai
ke dalam nya pada waktu yang sama.
5.12 Latihan
latihan 5.1
Tulis sebuah metode yang bernam isDivisible yang mengambil dua bilangan bulat n dan
m mengembalikan nilai true jika n habis dibagi m dan false jika tidak.
Latihan 5.2
apakah keluaran dari program berikut? Tujuan dari latihan ini adalah untuk melatih
anda mengerti tentang operator logika dan aliran eksekusi pada metode.
public static void main (String[] args) {
boolean flag1 = isHoopy (202);
boolean flag2 = isFrabjuous (202);
System.out.println (flag1);
System.out.println (flag2);
if (flag1 && flag2) {
System.out.println ("ping!");
}
if (flag1 || flag2) {
113
System.out.println ("pong!");
}
}
public static boolean isHoopy (int x) {
boolean hoopyFlag;
if (x%2 == 0) {
hoopyFlag = true;
} else {
hoopyFlag = false;
}
return hoopyFlag;
}
public static boolean isFrabjuous (int x) {
boolean frabjuousFlag;
if (x > 0) {
frabjuousFlag = true;
} else {
frabjuousFlag = false;
}
return frabjuousFlag;
}
latihan 5.3
Jarak antara dua titik (x1,y1) dan (x2,y2) adalah
114
Tulislah metode bernama distance yang mengambil empat double sebagai parameter
x1,y1, x2, y2 dan mencetak jarak antara titik.
Anda harus berasumsi terdapat metode yang bernam sumSquares yang menghitung dan
mengembalikan jumlah dari kuadrat argumen nya sebagai contoh:
double x = sumSquares(3.0, 4.0);
akan menugaskan 25 ke x.
Tujuan dari latihan ini adalah menulis metode baru dengan menggunakan metode yang
sudah ada. Anda harus hanya menulis satu metode : distance. Anda tidak harus menulis
sumSquares atau main dan anda tidak harus memanggil distance
Latihan 5.4
Tujuan dari latihan ini adalah untuk mempraktekkan sintaks dari “fruitful methods”
a) Cari solusi anda sendiri untuk latihan 3.5 dan pastikan anda masih bisa
mengkompilasi dan menjalankannya.
b) Ubahlah multadd menjadi metode yang mengembalikan nilai, jadi sebagai ganti
mencetak hasilnya, ia akan mengembalikannya.
c) Di mana saja di dalam program multadd dipanggil, ganti pemanggilannya jadi
ia menyimpan hasilnya dalam variabel dan/atau mencetak hasil
d) Ubah juga yikes dengan cara yang sama.
Latihan 5.5
Tujuan dari latihan ini adalah untuk menggunakan diagram stack untuk mengerti
tentang pengeksekusian dari program rekursif.
public class Prod {
115
public static void main (String[] args) {
System.out.println (prod (1, 4));
}
public static int prod (int m, int n) {
if (m == n) {
return n;
} else {
int recurse = prod (m, n1);
int result = n * recurse;
return result;
}
}
}
a. gambar diagram stack yang menunjukkan keadaan program sesaat sebelum instance
terakhir prod selesai. Apakah keluaran dari program?
b. Jelaskan dengan beberapa kata apa yang sebenaranya dilakukan prod.
c. tulis kembali prod tanpa menggunakan variabel sementara recurse dan result.
Latihan 5.6
Tujuan latihan ini adalah untuk menerjemahkan definisi rekursif ke dalam metode Java.
Fungsi Ackerman didefinisikan untuk bilangan bulat non negatif sebagai berikut:
116
Tulis metode bernama ack yang mengambil dua int sebagai parameter, menghitung dan
mengembalikan nilai dari fungsi Ackerman.
Uji implementasi anda dengan memanggilnya dari metode main dan mencetak nilai
kembaliannya.
Peringatan: nilai keluaran akan bertambah besar dengan cepat. Anda harus mencobanya
untuk nilai m dan n (tidak lebih dari 2).
Latihan 5.7
a. Buat program bernama Recurse.java dan tulis metode metode berikut:
// first: kembalikan karakter pertama dari String yang diberikan
public static char first (String s) {
return s.charAt (0);
}
// last: kembalikan String baru yang berisi semua kecuali huruf pertama dari
// String yang diberikan
public static String rest (String s) {
return s.substring (1, s.length());
}
// length: kembalikan panjang dari string yang diberikan
public static int length (String s) {
return s.length();
}
b. Tulis beberap kode pada main yang mengetes setiap metode metode ini. Anda harus
117
yakin bahwa mereka bisa bekerja dengan baik dan anda bisa mengerti apa yang mereka
kerjakan.
c. Tulis metode yang bernama printString yang mengambil String sebagai parameter
dan mencetak huruf huruf dari String tersebut satu setiap baris. Metode ini harus lah
merupakan metode void.
d. Tulis metode yang bernama reverseString yang mengambil sebuah String sebagai
parameter dan mengembalikan String baru. String yang baru harus berisi huruf huruf
yang sama dengan parameter nya tetapi dengan urutan yang terbalik. Misalnya keluaran
kode di bawah
String backwards = reverseString ("Allen Downey");
System.out.println (backwards);
adalah
yenwoD nellA
Latihan 5.8
a. Buat program bernama Sum.java tulis dua metode di bawah
public static int methOne (int m, int n) {
if (m == n) {
return n;
} else {
return m + methOne (m+1, n);
}
}
public static int methTwo (int m, int n) {
if (m == n) {
118
return n;
} else {
return n * methTwo (m, n1);
}
b. Tulis beberapa baris pada main untuj menguji metode metode ini. Panggil mereka
beberapa kali dengan nilai yang berbedam dan lihat apa yang anda dapatkan. Dengan
beberapa kombinasi dari pengetesan, cari tahu apa yang dilakukan metode ini dan beri
mereka nama yang sesuai. Tambahkan komentar yang menjelaskan fungsi nya
c. Tambahkan pernyataan println pada awal metode sehingga mereka mencetak
argumen setiap kali dipanggil. Teknik ini berguna untuk mendebug program rekursif,
karena ia mendemontrasikan aliran eksekusi.
Latihan 5.9
Tulis metode rekursif bernama power yang mengambil double x dan sebuah integer n
dan mengembalikan xn.. Petunjuk: definisi rekursif dari power(x,n)=x*power(x,n10,
juga ingat bahwa apapun yang dipengkatkan 0 adalah 1.
Latihan 5.10
(latihan ini berdasarkan pada halaman 44 buku Structure and Intepetation of Computer
Programs karangan Abelson dan Sussman)
Algoritma berikut dikenal sebagai algoritma Euclid. Algoirtma ini berdasarkan dari
observasi bahwa, jika r adalah sisa ketika a dibagi dengan b maka faktor pembagi
terbesar(fpb) dari a dan b sama dengan faktor pembesar dari b dan r. sehingga bisa
dinyatakan dengan persamaan
gcd(a,b)=gcd(b.r)
119
Yang sukses untuk mengurangi masalah penghitungan GCD ( Greatest Common
Divisor) contoh penerapan algoritma,
gcd(36, 20) = gcd(20, 16) = gcd(16, 4) = gcd(4, 0) = 4
Yang menghasilkan GCD dari 36 dan 20 adalah 4. Dapat diperlihatkan bahwa untuk
berapapun angka awal, pengurangan berulang menghasilkan pasangan dimana bilangan
yang kedua adalah 0. dan GCD adalah nomor yang lain pada pasangan itu.
Tulis sebuah metode bernama gcd yang mengambil dua parameter bilangan bulat dan
menggunakan algoritma Euclid untuk menghitung dan mengembalikan greatest
common divisor (gcd)/Faktor Persekutuan Terbesar (FPB) dari kedua bilangan.
120
Bab 6
Iterasi
6.1 Multiple Assignment
Saya belum pernah mengatakan hal ini sebelumnya, tapi adalah sesuatu yang legal
dalam Java untuk membuat lebih dari satu assignment terhadap satu variabel yang
sama. Akibat yang akan timbul dengan adanya assignment berganda ini adalah
terjadinya penggantian nilai lama dari suatu variabel dengan nilai yang baru.
Perhatikan contoh berikut ini:
int fred = 5;
System.out.print(fred);
fred = 7;
System.out.println(fred);
Output dari program ini adalah 57. Kok bisa? Ini terjadi karena pada kesempatan
pertama kita mencetak nilai 5 menggunakan metode print sedangkan pada
kesempatan berikutnya kita mencetak nilai 7 menggunakan metode println .
Karena sifat multiple assigment inilah saya mendeksripsikan bahwa variabelvariabel
dalam Java merupakan penampung (container) untuk nilainilai. Ketika Anda memberi
(assign) sebuah nilai untuk suatu variabel, Anda berarti mengubah isi dari container
tersebut, perhatikan gambar di bawah ini:
int fred = 5; fred
fred = 7; fred
Ketika terjadi multiple assignment terhadap suatu variabel, adalah penting untuk
121
5
5 7
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 dan b sekarang sama
a = 3; // a dan b sekarang tidak lagi sama
Baris terakhir kode di atas memang mengubah nilai variabel a , tapi tidak dengan
variabel b . Nilai b masih tetap sama dengan nilai variabel a yang pertama, yaitu 5 .
Dalam lingkungan bahasa pemrograman lain, sebuah simbol alternatif lain sering
digunakan sebagai simbol pernyataan assignment. Misalnya: “ < ” atau " := ".
Hal ini dilakukan sebagai langkah antisipasi untuk mencegah kerancuan.
Meskipun multiple assignment seringkali berguna, Anda harus tetap hatihati dalam
menggunakannya. Jika nilai dari suatu variabel selalu berubahubah pada lokasi/bagian
yang berbedabeda di dalam program, maka Anda akan menemui kesulitan ketika
hendak mendebug dan membaca kodenya.
122
6.2 Iterasi
Salah satu fungsi komputer yang paling sering dimanfaatkan manusia adalah untuk
melakukan sesuatu (baca: tugas) yang sifatnya berulangulang. Mengulangulang tugas
yang sama atau identik tanpa melakukan satu kesalahan pun adalah sesuatu yang dapat
dikerjakan dengan sangat baik oleh komputer, tapi tidak oleh manusia.
Kita telah melihat programprogram yang menggunakan teknik rekursi untuk
melakukan perulangan, seperti nLines dan countdown . Bentuk perulangan
seperti inilah yang disebut dengan iterasi, dan Java menyediakan beberapa fitur bahasa
yang dapat memudahkan kita untuk membuat programprogram yang iteratif.
Dua fitur yang akan kita lihat pertama adalah pernyataan while dan pernyataan for .
6.3 Pernyataan While
Dengan pernyataan while , kita dapat menulis ulang countdown dengan cara
sebagai berikut:
public static void countdown (int n)
{
while (n > 0)
{
System.out.println (n);
n = n 1;
}
System.out.println ("Blastoff!");
}
Anda dapat menulis pernyataan while ini laiknya menulis kalimat dalam Bahasa
Inggris. Maksudnya, kode di atas dapat dibaca dengan kalimat seperti ini, "While n is
123
greater than zero, continue printing the value of n and then reducing the value of n by 1.
When you get to zero, print the word Blastoff!". Dalam Bahasa Indonesia berarti
kalimatnya akan menjadi seperti ini, "Selama n masih lebih besar daripada 0, teruslah
mencetak nilai n, kemudian kurangi nilai n dengan 1. Ketika Anda sudah sampai pada
nilai 0, cetaklah kata Blastoff!".
Untuk lebih jelasnya, berikut ini merupakan alur eksekusi yang terjadi dalam kode di
atas:
1. Cek/Evaluasi syarat (condition) yang berada dalam tanda kurung, apakah
bernilai true atau false .
2. Jika syarat itu bernilai false , Java akan keluar (exit) dari pernyataan while
tersebut lalu melanjutkan eksekusi ke pernyataan selanjutnya.
3. Jika syarat tersebut bernilai true , Java akan mengeksekusi setiap pernyataan
yang berada dalam kurung kurawal, lalu kembali ke langkah nomor 1.
Bentuk aliran program seperti inilah yang disebut loop (baca: putaran atau siklus).
Disebut demikian karena pernyataan pada langkah yang ketiga akan membuat program
kembali lagi ke bagian atas dari program itu sendiri. Perhatikan bahwa jika syarat
langsung bernilai false pada saat masuk ke dalam loop, pernyataan yang berada di
dalam loop tersebut tidak akan dieksekusi. Pernyataan yang berada di dalam loop
biasanya disebut body (badan) dari loop.
Badan loop harus merubah nilai dari satu atau lebih variabel sehingga syarat yang
dievaluasi akan sampai pada nilai false dan loop pun akan berakhir. Jika tidak, loop
tersebut akan melakukan putaran eksekusi selamanya. Keadaan inilah yang disebut
dengan infinite loop (baca: putaran tanpa batas). Contoh infinite loop dalam kehidupan
seharihari yang mungkin sering kita ditemui adalah instruksi dalam kemasan shampoo,
"Cuci rambut Anda, bilas dengan air, lalu ulangi."
124
Dalam kasus countdown , kita dapat membuktikan bahwa loop pasti akan dihentikan
karena kita tahu bahwa nilai dari variabel n adalah nilai yang terbatas (finite). Di situ
kita dapat melihat bahwa nilai n akan semakin mengecil setiap kali melewati loop (tiap
iterasi). Keadaan ini tentu akan membawa kita kepada nilai n yang sama dengan 0.
Namun dalam kasuskasus loop lainnya, terkadang tidaklah mudah untuk
mengatakannya. Perhatikan contoh berikut ini:
public static void sequence (int n) {
while (n != 1) {
System.out.println (n);
if (n%2 == 0) { // n is even
n = n / 2;
} else { // n is odd
n = n*3 + 1;
}
}
}
Syarat loop di atas adalah n != 1 , jadi loop akan terus diproses sampai nilai variabel
n = 1 . Kondisi ini akan menyebabkan syarat bernilai false .
Dalam tiap iterasinya, program akan mencetak nilai dari n sekaligus memeriksa
apakah nilai n tersebut termasuk genap atau ganjil. Jika n termasuk genap, maka nilai
n akan dibagi dengan 2 . Tapi jika termasuk ganjil, maka nilai n akan diganti dengan
nilai dari n*3 + 1 . Sebagai contoh, jika nilai awal (argumen yang dilewatkan dalam
metode sequence ) sama dengan 3 , maka hasil yang akan dicetak adalah 3, 10,
5, 16, 8, 4, 2.
Karena nilai n kadang bertambah dan kadang menurun, tidak ada bukti yang kuat
bahwa n akan sama dengan 1 , atau dengan kata lain agar proses dalam program dapat
125
dihentikan. Untuk beberapa nilai n, kita dapat membuktikan bahwa terminasi
(penghentian) terhadap program akan terjadi. Sebagai contoh, jika nilai awal dari
metode tersebut adalah bilangan yang termasuk kelipatan 2, maka nilai n akan selalu
genap setiap kali melintasi loop, sampai kita sampai ke nilai n = 1 . Dalam contoh
sebelumnya kita dapat melihat manakala nilai n telah sama dengan 16 maka nilai n
berikutnya pasti akan membawa variabel n ke 1 (16, 8, 4, 2, 1). catatan:
angka 1 tidak dicetak dalam output.
Nilainilai lain (selain kelipatan 2) tentunya tidak dapat dimasukkan ke dalam bentuk
umum seperti di atas. Pertanyaan menariknya adalah; dapatkah kita membuktikan
bahwa program di atas pasti akan dapat dihentikan (terminate) untuk semua nilai n?
Sampai sejauh ini, belum ada yang dapat membuktikan kebenaran pertanyaan di atas!
6.4 Tabel
Salah satu manfaat loop yang paling baik adalah untuk membuat (generate) dan
mencetak tabel data. Sebagai contoh, sebelum komputer dapat digunakan sebaik seperti
sekarang, orangorang harus menghitung logaritma, sinus, cosinus, serta fungsifungsi
matematika lainnya menggunakan tangan (baca: manual).
Untuk membuat pekerjaanpekerjaan di atas menjadi lebih mudah, dibuatlah beberapa
buku yang berisi tabeltabel panjang yang dapat membantu kita untuk melihat nilainilai
dari beberapa fungsi. Untuk membuat tabeltabel ini dibutuhkan waktu yang lama. Dan
membosankan! Akibatnya, hasilnya pun terkadang akan dipenuhi banyak error
(kesalahan).
Ketika komputer telah muncul beserta monitornya, salah satu reaksi awal yang muncul
adalah, "Hebat! Kita dapat menggunakan komputer untuk membuat tabel tabel,
sehingga tidak akan ada kesalahan lagi". Euforia ini secara umum memang akhirnya
terbukti, tapi ini hanyalah secuil kemampuan saja dari begitu banyak hal yang dapat
126
dilakukan dengan komputer. Setelah komputer (juga kalkulator digital) digunakan
secara luas oleh banyak kalangan, tabeltabel tersebut menjadi barang kuno.
Setidaknya bisa dikatakan hampir demikian. Karena untuk beberapa operasi, komputer
juga masih menggunakan tabeltabel untuk melihat jawaban yang paling mendekati,
untuk kemudian kembali lagi menggunakan komputer ketika hendak memperbaiki nilai
pendekatan tersebut. Dalam beberapa kasus, pernah terjadi kesalahan fatal dalam tabel
tabel yang dibuat menggunakan komputer. Kasus populer menyangkut ini adalah
kesalahan prosessor Intel Pentium generasi awal dalam melakukan operasi pembagian
(division) untuk nilai floatingpoint.
Meskipun sebuah "tabel logaritma" tidaklah terlalu penting lagi seperti beberapa tahun
yang lalu, tapi kita masih dapat menggunakannya sebagai contoh yang bagus untuk
menunjukkan bagaimana cara iterasi bekerja. Program berikut ini akan mencetak urutan
nilainilai di kolom bagian kiri dan logaritmanya di kolom bagian kanan:
double x = 1.0;
while (x < 10.0) {
System.out.println (x + " " + Math.log(x));
x = x + 1.0;
}
Output dari program ini tampak sebagai berikut:
1.0 0.0
2.0 0.6931471805599453
3.0 1.0986122886681098
4.0 1.3862943611198906
5.0 1.6094379124341003
6.0 1.791759469228055
7.0 1.9459101490553132
127
8.0 2.0794415416798357
9.0 2.1972245773362196
Dengan melihat nilainilai di atas, bisakah Anda memberi tahu default nilai basis dari
fungsi log yang digunakan oleh Java?
Karena nilai kelipatan 2 memiliki peran yang penting dalam ilmu komputer, terkadang
kita ingin menemukan logaritma dengan nilai basis 2. Untuk menemukannya, kita bisa
menggunakan formula berikut ini:
log 2x = log ex/log e2
Ubahlah pernyataan print dalam kode di atas sehingga menjadi seperti berikut ini:
System.out .print ln(x + " " +
Math.log(x)/M ath.lo g(2.0) );
pernyataan di atas akan menghasilkan:
1.0 0.0
2.0 1.0
3.0 1.5849625007211563
4.0 2.0
5.0 2.321928094887362
6.0 2.584962500721156
7.0 2.807354922057604
8.0 3.0
9.0 3.1699250014423126
Dari hasil di atas kita dapat melihat bahwa 1, 2, 4, dan 8 merupakan bilangan kelipatan
2. Ini karena logaritma mereka yang berbasis 2 menghasilkan bilangan dengan nilai
pembulatan (rounded). Jika kita ingin menemukan logaritma dari bilanganbilangan
lainnya selain 2, kita dapat memodifikasi program tersebut dengan cara seperti di bawah
128
ini;
double x = 1.0;
while (x < 100.0) {
System.out.p rintln (x + " " + Math.log(x) /
Math.log(2.0) );
x = x * 2.0;
}
Ketimbang selalu menambahkan sesuatu ke variabel x setiap kali melewati loop yang
akan menghasilkan deret aritmatika, sekarang kita akan mengalikan variabel x dengan
sesuatu sehingga menghasilkan deret geometri. Berikut ini merupakan hasil dari kode
di atas:
1.0 0.0
2.0 1.0
4.0 2.0
8.0 3.0
16.0 4.0
32.0 5.0
64.0 6.0
Tabel logaritma mungkin sudah tidak terlalu berguna untuk saat ini, tapi untuk seorang
ilmuwan komputer (computer scientist), mengetahui bilangan kelipatan 2 atau pangkat 2
adalah wajib! Jika kamu sedang dalam keadaan idle (baca: malas atau bete),
berusahalah utuk mengingat bahwa bilangan berpangkat atau kelipatan 2 dalam
komputer hanya akan sampai pada angka 65536 (ini adalah hasil dari 216).
6.5 Tabel Dua Dimensi
Sebuah tabel berdimensi dua adalah sebuah tabel yang di dalamnya terdapat nilainilai
129
yang berada dalam perpotongan (interseksi) antara satu baris dan satu kolom. Contoh
yang baik untuk memperlihatkan tabel ini adalah tabel perkalian (multiplication).
Anggap saja Anda ingin mencetak tabel perkalian untuk nilai 1 sampai dengan 6.
Cara yang bagus untuk memulaiya adalah dengan menulis sebuah loop sederhana yang
mencetak kelipatan 2, semua dalam 1 baris.
int i = 1;
while (i <= 6) {
System.out.print (2*i + " ");
i = i + 1;
}
System.out.println ("");
Baris pertama menginisialisasi sebuah variabel, yakni i . Variabel ini akan bertugas
sebagai counter, atau variabel untuk melakukan loop. Ketika loop dieksekusi, nilai dari
i akan terus naik mulai dari 1 hingga 6. Ketika i sama dengan 7, loop akan dihentikan.
Setiap melewati loop, kita mencetak nilai dari 2 * i diikuti dengan tiga spasi.
Karena kita menggunakan perintah print ketimbang println , semua output
akan dicetak dalam satu baris yang sama.
Seperti yang sudah saya singgung dalam bagian 2.4, dalam beberapa lingkungan
pemrograman, output dari perintah print akan terus disimpan dalam memori
sampai perintah println dipanggil. Jika program sudah berhenti, tapi Anda lupa
memanggil perintah println , Anda mungkin tidak akan pernah melihat nilai yang
disimpan tersebut.
Output dari program di ini adalah:
2 4 6 8 10 12
So far, so good. Tahap berikutnya adalah membuat suatu bentuk enkapsulasi dan
130
generalisasi.
6.6 Enkapsulasi dan Generalisasi
Enkapsulasi (encapsulation) berarti mengambil beberapa bagian dari kode yang sudah
ditulis untuk kemudian mengemasnya (wrapitup) dalam sebuah metode. Kita
sebelumnya sudah melihat dua contoh enkapsulasi. Pertama, ketika kita menulis metode
printParity dalam bagian 4.3 dan isSingleDigit dalam bagian 5.7.
Generalisasi berarti mengambil satu hal yang spesifik dari suatu bagian kode dalam
metode, misalnya mencetak kelipatan 2, untuk kemudian membuatnya menjadi lebih
umum. Dalam kasus ini berarti dengan cara membuat kode tersebut agar dapat
mencetak nilai kelipatan dari n .
public static void printMultiples (int n) {
int i = 1;
while (i <= 6) {
System.out.print (n*i + " ");
i = i + 1;
}
System.out.println ("");
}
Untuk mengenkapsulasi, yang saya lakukan hanyalah menambah sesuatu dalam baris
pertama. Dalam hal ini saya menambahkan nama metode, parameter, dan tipe
pengembalian. Sedangkan untuk menggeneralisasi, yang saya lakukan adalah
mengganti nilai 2 dengan parameter n .
Jika saya memanggil metode ini dengan argumen 2 maka saya akan mendapatkan hasil
yang sama dengan program sebelumnya. Dengan argumen 3, hasil yang akan kita lihat
131
adalah sebagai berikut:
3 6 9 12 15 18
dan dengan argumen 4, outputnya adalah sebagai berikut:
4 8 12 16 20 24
Mulai sekarang, Anda mungkin sudah dapat menerka bagaimana cara kita untuk
membuat tabel perkalian: kita akan memanggil printMultiples secara
berulangulang dengan argumen yang berbedabeda. Tidak hanya itu, di sini kita juga
akan menggunakan loop yang berbeda untuk melakukan iterasi ke dalam barisbaris
tersebut.
int i = 1;
while (i <= 6) {
printMultiples (i);
i = i + 1;
}
Pertamatama, perhatikan betapa miripnya loop ini dengan yang berada dalam metode
printMultiples . Saya hanya mengganti perintah print dengan sebuah
pemanggilan metode. Cool!
Output dari program ini adalah sebagai berikut:
1 2 3 4 5 6
2 4 6 8 10 12
3 6 9 12 15 18
4 8 12 16 20 24
5 10 15 20 25 30
6 12 18 24 30 36
Bagaimana? Tampak seperti tabel perkalian bukan? Meskipun bentuknya juga kelihatan
132
agakagak miring sedikit. Jika bentukbentuk seperti ini mengganggu Anda, Java telah
menyediakan metodemetode yang dapat memberi Anda kendali lebih baik atas
masalahmasalah output, tapi saya belum mau bertamasya ke sana sekarang. Tunggu ya!
6.7 Metode
Dalam bagian sebelumnya saya menyebutkan, "semua kebaikan yang dapat diberikan
oleh metode." Sebelumnya, mungkin Anda akan bertanyatanya, apa saja sih kebaikan
kebaikan yang dimaksud? Berikut ini adalah beberapa alasan mengapa metodemetode
tersebut mempunyai posisi yang sangat penting:
• Dengan memberi sebuah nama untuk satu set perintahperintah/pernyataan
tertentu, Anda telah membuat program Anda semakin mudah untuk dibaca dan
diperiksa (debug).
• Memecah program Anda yang panjang ke dalam beberapa metode dapat
memudahkan Anda untuk membaginya ke dalam beberapa bagian terpisah.
Dengan keadaan seperti ini, Anda dapat memeriksa bagianbagian tersebut
dalam lingkungan yang terisolir (bebas dari bagian lainnya). Untuk kemudian
merangkainya kembali dalam program "besar" yang Anda inginkan.
• Metode dapat memfasilitasi rekursi sekaligus iterasi.
• Metode yang dirancang dengan sangat baik biasanya dapat digunakan oleh
programprogram lainnya. Sekali saja Anda mampu menulis dan memeriksanya
dengan baik, otomatis Anda akan dapat menggunakannya kembali.
6.8 Enkapsulasi .... sekali lagi!
Untuk mendemonstrasikan enkapsulasi lagi, Saya akan mengambil kode dari bagian
sebelumnya lalu membungkusnya dalam sebuah metode:
public static void printMultTable () {
133
int i = 1;
while (i <= 6) {
printMultiples (i);
i = i + 1;
}
}
Proses yang sedang saya demonstrasikan adalah sebuah strategi pengembangan
(development plan) yang umum digunakan. Anda mengembangkan suatu kode secara
bertahap dengan cara menambah beberapa baris dalam bagian main atau bagian
lainnya, lalu ketika Anda tahu bahwa proses ini bekerja dengan baik, Anda lalu
mengambil dan memasukkannya ke dalam sebuah metode.
Alasan mengapa cara ini bermanfaat untuk Anda adalah karena terkadang Anda tidak
mengetahui secara pasti kapan Anda akan membagi program Anda secara terpisah ke
dalam beberapa metode sekaligus. Pendekatan dengan model seperti ini dapat
memudahkan Anda untuk melakukan proses perancangan yang beriring /berbarengan
dengan proses pembuatan program.
6.9 Variabel Lokal
Saat ini, mungkin Anda sedang kebingungan memikirkan tentang bagaimana sebuah
variabel yang sama, yakni i, dapat digunakan oleh dua metode yang berhubungan
sekaligus pada saat yang bersamaan (printMultiples dan
printMultTable ). Bukankah sebelumnya saya tidak pernah mengatakan kepada
Anda bahwa Anda hanya dapat mendeklarasikan sebuah variabel satu kali saja? Dan
tidakkah hal yang demikian dapat menimbulkan masalah ketika salah satu dari metode
tersebut mengganti nilai dari variabel miliknya yang kita permasalahkan tersebut?
134
Heeee ... bingung ya ....
Jawaban untuk kedua pertanyaan tersebut adalah "tidak," karena variabel i di metode
printMultiples dan i dalam printMultTable adalah dua variabel
yang tidak sama. Mereka memang mempunyai dua nama yang sama, tapi mereka tidak
mengacu kepada satu lokasi penyimpanan yang sama. Oleh karena itulah, mengubah
nilai salah satu dari mereka tidak akan memberi pengaruh apapun kepada nilai yang
lainnya.
Variabelvariabel yang dideklarasikan di dalam sebuah metode disebut variabel lokal
karena mereka terlokalisasi dalam metodenya masingmasing. Anda tidak dapat
mengakses sebuah variabel lokal dari luar metode yang menjadi "rumahnya". Namun
Anda dapat memiliki lebih dari satu variabel dengan nama yang sama asalkan mereka
tidak berada dalam satu metode yang sama.
Adalah sebuah ide yang brillian untuk tetap menggunakan namanama variabel yang
berbeda meskipun variabelvariabel itu tidak berada dalam satu metode yang sama. Ini
sematamata dilakukan untuk menghindari kebingungan. Namun dalam beberapa kasus,
terkadang akan lebih baik jika kita menggunakan nama variabel yang sama. Sebagai
contoh, adalah sebuah tradisi atau keumuman dalam dunia pemrograman untuk
menggunakan namanama variabel seperti i, j, dan k sebagai namanama variabel untuk
loop. Jika Anda bersikeras untuk menghindari penggunaan ketiga variabel ini dalam
sebuah metode hanya karena Anda telah menggunakannya di metode lainnya, mungkin
Anda justru akan membuat program tersebut semakin sulit untuk dibaca.
6.10 Generalisasi. Sekali lagi dengannya ....
Sebagai satu contoh generalisasi lagi, silahkan membayangkan sebuah program yang
dapat mencetak tabel perkalian dengan ukuran berapapun, tidak hanya berupa tabel 6 x
135
6. Anda dapat menambahkan sebuah parameter ke dalam metode
printMultTable :
public static void printMultTable (int high) {
int i = 1;
while (i <= high) {
printMultiples (i);
i = i + 1;
}
}
Saya mengganti nilai 6 dengan paramater yang saya namai; high . Jika saya
memanggil printMultTable dengan argumen 7, saya akan mendapatkan output
sebagai berikut:
1 2 3 4 5 6
2 4 6 8 10 12
3 6 9 12 15 18
4 8 12 16 20 24
5 10 15 20 25 30
6 12 18 24 30 36
7 14 21 28 35 42
Keren bukan? Kecuali bahwa mungkin saya menginginkan hasil di atas agar tampak
lebih kotak (jumlah baris dan kolom yang sama; 7 x 7 bukan 7 x 6). Untuk
mewujudkannya, saya harus menambahkan parameter lain dalam metode
printMultiples , untuk menspesifikasikan berapa jumlah kolom yang harus
dimiliki oleh tabel tersebut.
Agar Anda lebih waspada, saya juga akan menamai parameter ini dengan nama high.
136
Hal ini akan menunjukkan kepada Anda semua bahwa dua atau lebih metode yang
berbeda dapat memiliki parameterparameter dengan nama yang sama (persis seperti
variabelvariabel lokal):
public static void printMultiples (int n, int high)
{
int i = 1;
while (i <= high) {
System.out.print (n*i + " ");
i = i + 1;
}
newLine ();
}
public static void printMultTable (int high) {
int i = 1;
while (i <= high) {
printMultiples (i, high);
i = i + 1;
}
}
Perhatikan bahwa ketika saya menambahkan sebuah parameter baru, saya harus
mengubah baris pertama dari metode tersebut (antarmuka atau prototipenya). Selain itu,
saya juga harus mengubah tempat di mana metode itu dipanggil dalam metode
printMultTable . Seperti yang diharapkan, program ini mampu membuat sebuah
tabel 7 x 7 yang tampak lebih "kotak":
1 2 3 4 5 6 7
2 4 6 8 10 12 14
137
3 6 9 12 15 18 21
4 8 12 16 20 24 28
5 10 15 20 25 30 35
6 12 18 24 30 36 42
7 14 21 28 35 42 49
Ketika Anda telah menggeneralisasi sebuah metode dengan tepat, terkadang Anda akan
menjumpai program yang Anda hasilkan ternyata memiliki kapabilitas atau kemampuan
yang tidak persis seperti Anda harapkan. Sebagai contoh, Anda mungkin
memperhatikan bahwa tabel perkalian yang kita buat sebelumnya memiliki sifat
simetris. Hal ini terjadi karena sifat komutatif dalam perkalian yakni ab = ba . Jadi,
semua entri yang berada dalam tabel akan dicetak 2 kali. Anda dapat menghemat tinta
dengan cara mencetak tabel tersebut setengah dari ukuran normalnya. Untuk
melakukannya, Anda cukup mengganti sebuah baris dalam metode
printMultTable . Ubahlah perintah:
printMultiples(i, high);
menjadi
printMultiples(i, i);
dengan pengubahan ini Anda akan mendapatkan hasil sebagai berikut:
1
2 4
3 6 9
4 8 12 16
5 10 15 20 25
6 12 18 24 30 36
7 14 21 28 35 42 49
138
Saya akan tinggalkan bagian ini khusus untuk Anda agar Anda dapat menemukan
bagaimana program tersebut dapat menghasilkan output yang sedemikian rupa.
6.11 Daftar KataKata
Istilah Arti
Loop Sebuah pernyataan yang dieksekusi berulangulang selama atau
sampai beberapa syarat/keadaan (condition) dipenuhi.
Infinite loop Sebuah loop yang syarat/kondisinya selalu bernilai benar.
Body Pernyataan yang berada dalam loop.
Iterasi Proses satu kali eksekusi yang terjadi dalam badan (body) loop,
termasuk di dalamnya adalah pengecekan nilai syarat/kondisi.
Enkapsulasi Membagi sebuah program yang besar dan kompleks ke dalam
komponenkomponen kecil (seperti metode) untuk kemudian
mengisolasinya dari komponenkomponen lainnya (contoh,
dengan cara menggunakan variabel lokal).
Variabel lokal variabel yang dideklarasikan di dalam sebuah metode dan eksis
hanya di dalam metode tersebut. Variabel lokal tidak dapat
diakses dari luar metode yang menjadi "rumah" mereka.
Variabel ini juga tidak akan berinterferensi (bertabrakan)
dengan metodemetode lainnya.
Generalisasi Mengganti sesuatu yang spesifik tapi tidak penting (misalnya
nilai konstan) dengan sesuatu yang lebih cocok/tepat secara
umum (seperti variabel atau parameter). Generalisasi membuat
kode lebih fleksibel, memungkinkan untuk digunakan kembali,
dan terkadang lebih mudah dan nyaman untuk dibaca.
Development plan Suatu proses untuk mengembangkan sebuah program. Dalam
139
Istilah Arti
bab ini, saya mendemonstrasikan sebuah teknik pengembangan
yang berbasis pada pengembangan kode untuk melakukan
sesuatu yang sederhana, khusus, lalu mengenkapsulasi dan
menggeneralisasinya. Dalam bagian 5.2 saya
mendemonstrasikan sebuah teknik yang saya sebut
pengembangan bertingkat. Di babbab akhir saya akan
menunjukkan teknik pengembangan yang lain.
6.12 Latihan
Latihan 6.1
public static void main (String[] args) {
loop (10);
}
public static void loop (int n) {
int i = n;
while (i > 0) {
System.out.pr intln (i);
if (i%2 == 0) {
i = i/2;
} else {
i = i+1;
}
}
}
a) Buatlah sebuah tabel yang dapat menunjukkan nilai dari variabel i dan n selama loop
sedang dieksekusi. Tabel tersebut harus berisi satu kolom untuk setiap variabel dan
140
satu baris untuk setiap iterasi.
b) Apa output dari program ini?
Latihan 6.2
a) Enkapsulasilah fragmen kode di bawah ini, kemudian ubahlah bentuknya ke dalam
metode yang menggunakan sebuah variabel bertipe String sebagai argumennya dan
mengembalikan (tapi bukan mencetak) nilai terakhir dari count.
b) Dalam sebuah kalimat, deskripsikan secara abstrak apa yang telah dihasilkan oleh
metode buatan Anda tersebut.
c) Asumsikan bahwa Anda telah menggeneralisasi metode berikut ini sehingga ia dapat
digunakan untuk semua String. Menurut Anda, kirakira apa lagi yang dapat Anda
lakukan sehingga metode tersebut dapat lebih tergeneralisasi?
String s = "((3 + 7) * 2)";
int len = s.length ();
int i = 0;
int count = 0;
while (i < len) {
char c = s.charAt(i);
if (c == '(') {
count = count + 1;
} else if (c == ')') {
count = count 1;
}
i = i + 1;
}
System.out.pr intln (count);
141
Latihan 6.3
Misalkan Anda diberi sebuah bilangan, misalkan a, lalu Anda ingin menemukan nilai
akarnya. Salah satu cara yang dapat digunakan adalah dengan menebak mentahmentah
nilai tersebut dengan sebuah bilangan misalkan, xo. Lalu, menaikkan tingkat kebenaran
bilangan tersebut dengan menggunakan rumus berikut ini:
x1 = ( x0 + a/x 0) /2 (6.2)
Sebagai contoh, jika kita hendak menemukan akar dari 9. Kita bisa memulai tebakan
kita dengan xo = 6, kemudian x1 = (6 + 9/6) / 2 = 15 / 4 = 3.75. Lihat, mulai mendekati
bukan?
Kita dapat mengulangi prosedur ini, dengan menggunakan x1 untuk menghitung nilai x2,
x2 untuk x3, lalu seterusnya. Dalam contoh yang kita gunakan, berarti x2 = 3.075 dan x3
= 3.00091. Begitu seterusnya, sampai mendekati nilai pendekatan yang kita tetapkan.
Buatlah sebuah metode dengan nama squareRoot yang menggunakan sebuah variabel
bertipe double sebagai parameter dan mengembalikan sebuah nilai pendekatan dari
parameter tersebut, dengan menggunakan algoritma di atas tentunya. Anda dilarang
menggunakan metode Math.sqrt yang merupakan metode bawaan (builtin) Java.
Gunakan a/2 sebagai nilai awal tebakan Anda. Metode Anda harus melakukan
perulangan (iterasi) sampai metode ini menghasilkan dua nilai pendekatan yang
berurutan dan juga memiliki selisih kurang dari 0.0001. Anda dapat menggunakan
metode Math.abs untuk menghitung nilai mutlak.
Latihan 6.4
Dalam latihan 5.9 kita telah menulis sebuah versi rekursif untuk mencari bilangan
pangkat (power) yang menggunakan variabel x bertipe double dan n bertipe integer
142
serta mengembalikan nilai xn. Sekarang, tulislah sebuah metode iteratif untuk
melakukan hal yang sama.
Latihan 6.5
Bagian 5.10 memperlihatkan sebuah metode rekursif yang menghitung nilai dari fungsi
faktorial. Tulislah sebuah versi iteratif dari metode faktorial.
Latihan 6.6
Salah satu cara untuk menghitung ex adalah dengan menggunakan bentuk ekspansi tak
hingga berikut ini:
ex = 1 + x + x 2/2! + x 3/3! + x 4/4! + .... (6.3)
Jika variabel loop kita namai dengan i, maka urutan term yang kei akan sama dengan
xi/i!.
a) Buatlah sebuah metode dengan nama myexp yang mampu menjumlahkan n term
pertama dari bentuk di atas. Anda dapat menggunakan metode factorial dari bagian
5.10 atau versi iteratifnya. Terserah!
b) Anda dapat membuat metode ini lebih efisien jika saja Anda sadar bahwa dalam
setiap iterasi, nilai numerator dari term sama dengan nilai predecessornya
(sebelumnya) dikali dengan x, sementara denominatornya adalah sama dengan nilai
predecessornya dikali dengan i. Gunakan hasil analisa ini untuk meniadakan
penggunaan Math.pow dan factorial, lalu periksalah kembali dengan cermat bahwa
Anda masih tetap akan mendapatkan hasil yang sama.
c) Buatlah sebuah metode dengan nama check, yang menggunakan sebuah parameter,
yakni x. Metode Anda ini akan memiliki kemampuan untuk mencetak nilai dari x,
Math.exp(x) dan myexp(x) untuk berbagai variasi nilai dari x. Hasil yang akan
143
ditampilkan harus tampak seperti berikut:
1.0 2.708333333333333 2.718281828459045
Petunjuk: Anda dapat menggunakan String "\t" untuk mencetak sebuah karakter tab
diantara kolomkolom dalam tabel tersebut.
d) Ubahubahlah (variasikan) nilainilai term (argumen kedua yang dikirimkan oleh
metode check kepada myexp), lalu lihatlah pengaruhnya terhadap akurasi dari hasil
tersebut. Cocokkan nilai ini hingga nilai yang diperkirakan (estimasi) sesuai dengan
jawaban ketika x sama dengan 1.
e) Tulislah sebuah loop dalam main yang memanggil metode check dengan nilai 0.1,
1.0, 10.0, dan 100.0. Lihat bagaimana akurasi (ketepatan) dari hasil yang ditampilkan
ketika nilai x juga bervariasi. Bandingkan jumlah digit dari agreement ketimbang
membandingkan perbedaan antara nilai aktual dan nilai perkiraan.
f) Tambahkan sebuah loop dalam main yang berfungsi untuk memeriksa myexp
dengan nilai 0.1, 1,0, 10.0, dan 100.0. Berilah tanggapan untuk ketepatan yang
dihasilkan.
Latihan 6.7
Salah satu cara yang digunakan untuk mengevaluasi bentuk e^(x^2) adalah dengan
menggunakan bentuk ekspansi takhingga berikut:
e^(x^2) = 12 x + 3x2/2! 4x3/3! + 5x4/4! ......
(6.4)
Dengan kata lain, kita perlu menambahkan beberapa term dimana term yang kei akan
sama dengan (1)i (i+1) xi / i!. Buatlah sebuah metode dengan nama Gauss yang
menggunakan x dan n sebagai argumen. Metode ini akan mengembalikan nilai
penjumlahan dari nterm pertama. Anda tidak diperkenankan untuk menggunakan
metode factorial atau pow.
144
Bab 7
String
7.1 Memanggil metode dalam objek
Dalam Java dan bahasa pemrograman berorientasi objek lainnya, objek merupakan
koleksi dari data dan sekumpulan metode yang saling berkaitan. Metodemetode
digunakan untuk melakukan operasioperasi tertentu dalam objek, mengerjakan
perhitungan dan terkadang memodifikasi data milik objek itu sendiri.
Diantara semua tipe data yang telah kita bahas sampai saat ini, hanya String yang dapat
dikategorikan sebagai suatu objek. Berdasarkan pengertian objek yang telah kita ungkap
di atas, mungkin di antara Anda akan ada yang bertanya, “data apa yang dimiliki oleh
suatu String yang juga berstatus objek?” dan “metodemetode apa saja yang dapat kita
panggil untuk memfungsikan objekobjek yag berupa String?”
Data yang terkandung dalam objek String adalah hurufhuruf dari string itu sendiri.
Sebenarnya ada cukup banyak metode yang dapat digunakan untuk mengoperasikan
atau beroperasi dalam objekobjek String, tapi di buku ini Saya hanya akan
menggunakan beberapa di antaranya saja. Sisanya dapat Anda simak dalam halaman
berikut:
http://java.sun.com/javase/6/ docs/api/java/lang/String.html
Metode pertama yang akan kita bahas adalah charAt. Metode ini dapat Anda gunakan
untuk mengambil/mengekstrak hurufhuruf yag berada dalam sebuah String. Untuk
menyimpan hasil pengambilan tersebut, kita membutuhkan sebuah variabel yang dapat
menyimpan hurufhuruf tersebut sebagai sebuah satuan tersendiri (kebalikan dari String
itu sendiri yang merupakan sekumpulan itemitem yang berurutan). Satu satuan huruf
145
itu disebut sebagai karakter (character), dan tipe data yang digunakan untuk
menyimpannya disebut dengan char.
char bekerja dengan cara yang sama seperti tipe datatipe data yang sudah kita lihat
sebelumnya:
char fred = 'c';
if (fred == 'c') {
System.out.println (fred);
}
Nilai karakter tampil dalam kurungan tanda petik satu 'c'. Ini berbeda dengan nilai
variabel bertipe data String yang diapit dengan tanda petik ganda ( “ ” ). Nilai variabel
bertipe data char hanya boleh mengandung satu huruf atau satu simbol saja. Berikut ini
adalah cara menggunakan metode charAt:
String fruit = "banana";
char letter = fruit.charAt(1);
System.out.println (letter);
Sintaks fruit.charAt mengindikasikan bahwa Saya sedang memanggil metode charAt
dalam sebuah objek yang bernama fruit. Argumen yang Saya lewatkan (pass) untuk
metode ini adalah 1. Hal ini mengindikasikan kepada Anda bahwa saya tampaknya
ingin mengetahui huruf pertama dari string tersebut. Hasilnya adalah sebuah karakter
yang saya simpan dalam sebuah variabel bertipe data char, yakni letter. Ketika saya
mencetak nilai variabel letter itu, saya pun terkejut:
a
Hei .... bukankah a itu huruf kedua? Anda juga mungkin akan terkejut, kecuali kalau
Anda adalah seorang Computer Scientist. Untuk beberapa alasan tertentu, para ilmuwan
146
komputer cenderung untuk memulai perhitungan dari 0. huruf yang berada pada urutan
ke0 dalam string “banana” adalah b. sedangkan urutan yang ke1 adalah a, urutan ke2
adalah n, dst. Jadi, jika Anda menginginkan huruf yang berada pada urutan ke0 dari
sebuah string, Anda harus menggunakan 0 sebagai argumen yang Anda lewatkan.
Simaklah kode berikut ini:
char letter = fruit.charAt(0);
7.2 Length
Metode kedua untuk String yang akan kita bahas adalah length. Metode ini berfungsi
untuk mengembalikan jumlah karakter yang menyusun suatu string. sebagai contoh:
int length = fruit.length();
length tidak membutuhkan argumen apapun. Hal ini dapat dilihat dari tanda “ ( ) ” yang
memang tidak terisi dengan apapun. Metode ini akan mengembalikan nilai yang bertipe
integer, dalam kasus di atas, nilai yang dikembalikan adalah 6. Perhatian! Adalah
sesuatu yang legal dalam Java untuk menamai sebuah variabel dengan nama yang sama
seperti nama metode. Namun Anda harus tetap hatihati karena hal ini dapat
menimbulkan kebingungan untuk pengguna yang sedang membaca kode tersebut.
Untuk mencari nilai akhir dari sebuah string, terkadang Anda mungkin akan tergoda
untuk melakukan sesuatu seperti yang tampak dalam dua baris berikut:
int length = fruit.length();
char last = fruit.charAt(length); // wrong!!
Itu tidak akan berhasil. Mengapa? Karena memang tidak ada huruf ke6 dalam string
“banana”. Ingat! Kita melakukan perhitungan dari angka 0. Jadi, string yang memiliki 6
huruf akan diurut menggunakan nomor urutan 0 hingga 5, bukan 1 hingga 6. Untuk
mendapatkan karakter terakhir, Anda harus mengurangkan length dengan angka 1.
Perhatikan kode berikut:
147
int length = fruit.length();
char last = fruit.charAt(length 1);
7.3 Traversal
Pola/hal umum yang sering dijumpai dalam bentukbentuk string adalah kegiatan
berikut; mulailah dari awal, pilihlah setiap karakter secara bergantian, lakukan sesuatu
terhadap karakter itu, lalu lanjutkan hingga karakter terakhir. Pola pemrosesan ini
dikenal dengan sebutan traversal. Cara alami untuk mengenkode sebuah traversal
adalah dengan menggunakan pernyataan while:
int index = 0;
while (index < fruit.length()) {
char letter = fruit.charAt (index);
System.out.println (letter);
index = index + 1;
}
Loop di atas akan mentraverse string dan mencetak setiap huruf dalam satu baris.
Perhatikan bahwa syarat yang digunakan dalam kode di atas adalah index <
fruit.length(). Artinya, ketika index sama dengan panjang dari string, syarat tersebut
akan bernilai false. Akibatnya, badan dari loop itu tidak akan dieksekusi. Karakter
terakhir yang kita akses adalah karakter dengan urutan fruit.length() 1.
Nama dari variabel loop adalah index. Satu index merupakan suatu variabel atau nilai
yang digunakan untuk menentukan satu anggota dari sebuah himpunan berurutan (baca:
himpunan karakter yang berada dalam string). Index ini akan menentukan karakter
mana (baca: nama karakter tersebut) yang Anda inginkan. Himpunan ini haruslah
merupakan suatu himpunan berurutan agar setiap huruf dapat memilki index dan setiap
index dapat mengacu kepada satu karakter saja.
148
Sebagai latihan, tulislah sebuah metode yang menggunakan String sebagai sebuah
argumen dan yang mampu mencetak semua huruf dalam posisi yang berkebalikan
(backwards). Cetak semua huruf tersebut dalam satu baris tampilan saja.
7.4 RunTime errors
Kembali lagi ke bagian 1.3.2, di sana saya telah menyinggung sedikit mengenai run
time errors. Istilah ini digunakan untuk menunjukkan suatu error dalam program yang
tidak akan ditampilkan kecuali kalau program itu telah dijalankan (running). Dalam
Java, runtime error disebut juga dengan exceptions.
Sejauh ini, Anda mungkin belum terlalu sering melihat runtime errors. Maklum saja,
ini karena kita memang belum banyak mengerjakan sesuatu yang dapat menyebabkan
terjadinya runtime errors itu sendiri. Nah, sekaranglah saatnya. Jika Anda menggunakan
perintah charAt lalu pada saat yang bersamaan Anda juga menggunakan sebuah index
yang bernilai negatif atau lebih besar dari length1, maka Anda akan menjumpai sebuah
exceptions. Dalam kasus ini; StringIndexOutOf BoundsException. Cobalah dan lihat
bagaimana exceptions ini bekerja.
Jika program Anda menyebabkan terjadinya suatu exceptions, maka program tersebut
akan mencetak sebuah pesan error yang mengindikasikan tipe exceptions sekaligus
lokasi terjadinya error tersebut dalam program Anda.
7.5 Membaca Dokumentasi
Jika Anda mengunjungi situs
http://java.sun.com/javase/6/docs/api/java/lang/String.html, lalu Anda mengklik link
charAt, Anda akan mendapatkan dokumentasi yang tampak (mungkin mirip) seperti di
bawah ini:
public char charAt(int index)
149
Returns the character at the specified index.
An index ranges from 0 to length() 1.
Parameters: index the index of the character.
Returns: the character at the specified index of this
string.
The first character is at index 0.
Throws: StringIndexOut OfBoun dsExce ption if the index is
out of range.
Baris pertama adalah prototipe milik metode charAt yang berisi nama dari metode ini,
tipe data dari parameter dan nilai pengembalian yang digunakan.
Baris berikutnya menginformasikan manfaat/fungsi dari metode. Berikutnya,
merupakan informasi mengenai parameterparameter dan nilai pengembalian. Dalam
kasus ini, penjelasan yang diberikan mungkin terkesan berulangulang, tapi
dokumentasi seperti inilah yang merupakan bentuk standar dokumentasi dalam Java.
Baris terakhir akan menjelaskan kepada kita mengenai exceptions, kalau memang
benarbenar ada, yang dapat disebabkan karena penggunaan metode ini.
7.6 Metode indexOf
Dalam beberapa hal, indexOf dapat disebut sebagai lawan (anti) dari metode charAt.
Alasannya? Ini karena charAt bekerja dengan cara mengambil nilai sebuah index lalu
mengembalikan karakter yang diacu (diindeks) pada indeks tersebut. Sementara
indexOf bekerja dengan cara mengambil sebuah karakter lalu mengembalikan nilai
indeks tempat dimana karakter tersebut berada.
charAt akan gagal manakala indeks yang dimintanya berada di luar jangkauan. Hal ini
akan menyebabkan terjadinya exceptions pada program. Lain dengan metode indexOf
yang akan mengalami kegagalan jika karakter yang diminta tidak berada dalam string
yang dimaksud. Pada saat kasus seperti ini terjadi, metode ini akan mengembalikan nilai
150
1.
String fruit = "banana";
int index = fruit.indexOf('a');
Kode di atas digunakan untuk menemukan indeks (lokasi) huruf ' a ' dalam string
“banana”. Menariknya, huruf yang kita inginkan ternyata muncul sebanyak 3 kali. Jadi,
metode ini (seharusnya Anda juga) mungkin akan mengalami “kebingungan” karena
tidak tahu harus mengembalikan nilai indeks yang mana untuk huruf ' a '. Dalam kasus
kasus seperti inilah terkadang sebuah dokumentasi menjadi sangat bermanfaat. Bila kita
mengacu pada informasi dalam dokumentasi, kita akan tahu bahwa metode indexOf
akan mengembalikan nilai indeks dari kemunculan karakter ' a ' yang pertama kali (a
diindeks pada urutan yang pertama. Ingat, Java memulai indeks dari urutan 0 bukan 1).
Untuk menemukan indeks dari kemunculan berikutnya, ada versi alternatif dari metode
indexOf (penjelasan lebih lanjut mengenai kasus overloading semacam ini, silahkan
membuka kembali bagian 5.4). Kita membutuhkan argumen kedua yang akan berfungsi
untuk menginformasikan lokasi pada string dimana kita akan memulai pencarian. Jika
kita memanggil
int index = fruit.indexOf('a', 2);
kode ini akan mulai melakukan pencarian pada huruf yang berada di urutan kedua
(sekali lagi ingat, dalam Java ini berarti huruf ' n ' bukan ' a '), dan akan langsung
menemukan karakter a yang kedua pada indeks ketiga. Jika huruf yang dicari tepat
berada pada indeks dimana kita memulai pencarian, maka indeks ini sendirilah yang
menjadi jawaban (nilai yang dikembalikan oleh indexOf). Dus,
int index = fruit.indexOf('a', 5);
akan mengembalikan nilai 5. Kalau kita membaca kembali dokumentasi resmi
mengenai metode ini, mungkin tidak ada salahnya jika kita sedikit bereksperimen
dengan cara memberikan nilai indeksmulai pencarian yang lebih besar ketimbang
151
jangkauan string yang kita maksudkan:
Metode indexOf mengembalikan nilai indeks dari
karakter pertama yang berada dalam string yang
direpresentasikan melalui objek yang lebih besar
atau sama dengan indeksmulai, atau 1 jika
karakter yang dimaksud tidak berada dalam string
tersebut.
indexOf returns the index of the rst occurrence of
the character in the character sequence represented
by this object that is greater than or equal to
fromIndex, or 1 if the character does not occur.
Salah satu cara untuk mengetahui maksud 'aneh' dokumentasi di atas adalah dengan
mengujicobanya. Berikut hasil uji coba saya:
● Jika indeksmulai lebih besar atau sama dengan length(), hasil yang akan
dikembalikan adalah 1. Hasil ini mengindikasikan bahwa huruf yang
dicari/diinginkan tidak berada dalam indeks yang lebih besar daripada indeks
mulai.
● Jika indeksmulai bernilai negatif, hasilnya adalah 1. hasil ini mengindikasikan
bahwa kemunculan pertama huruf yang dicari berada dalam indeks yang lebih
besar daripada nilai indeksmulai.
Jika Anda membaca ulang dokumentasi tersebut, Anda akan memahami bahwa dua
keadaan di atas memang sesuai dengan definisi yang ada dalam dokumentasi. Meskipun
mungkin definisi tersebut awalnya tidaklah terlalu jelas. Nah, karena kita semua sudah
mengetahui cara kerja indexOf dengan benar, sekarang barulah kita bisa
152
menggunakannya dalam programprogram yang akan kita buat.
7.7 Looping dan Counting
Program berikut ini akan menghitung frekwensi kemunculan huruf ' a ' dalam string:
String fruit = "banana";
int length = fruit.length();
int count = 0;
int index = 0;
while (index < length) {
if (fruit.charAt(index) == 'a') {
count = count + 1;
}
index = index + 1;
}
System.out.println (count);
Program ini menunjukkan sebuah istilah populer dalam dunia pemrograman, yakni
counter. Variabel count awalnya diinisialisasi dengan nilai 0 lalu kemudian secara
bertahap akan dinaikkan nilainya tiap kali program menemukan ' a '. Ketika kita keluar
dari loop, count akan berisi nilai yang berupa jumlah total ' a ' yang ditemukan dalam
string.
Sebagai latihan awal, enkapsulasilah kode ini ke dalam sebuah metode dengan nama
countLetters. Lalu generalisasilah kode itu sehingga dapat menerima string dan huruf
sebagai argumenargumen.
Untuk latihan yang kedua, tulisulanglah metode itu dengan memasukkan indexOf
153
untuk mencari letak huruf yang diinginkan ketimbang melakukan pemeriksaaan
karakterkarakter tersebut satupersatu.
7.8 Operator Increment dan Decrement
Sebelum kita pergi lebih jauh, ada baiknya jika kita pahami terlebih dahulu apa yang
dimaksud dengan increment dan decrement.
● Increment berarti menaikkan/menambahkan nilai suatu variabel dengan nilai
satu (1) secara berulangulang sampai syarat yang ditetapkan bernilai false.
● Decrement berarti menurunkan/mengurangi nilai suatu variabel dengan nilai
satu (1) secara berulangulang sampai syarat yang ditetapkan bernilai false.
Baik increment maupun decrement sebenarnya hanya operator biasa. Namun, dalam
Java, keduanya memang mendapatkan operator khusus, yakni ' ++ ' untuk increment
dan ' ' untuk decrement. Operator ++ akan menambahkan satu (1) untuk nilai yang
sedang disimpan oleh suatu variabel yang bertipe int atau char. Sementara operator ' '
akan mengurangi satu. Keduanya tidak dapat digunakan untuk variabel bertipe double,
boolean, atau String.
Secara teknis, adalah legal dalam Java untuk mengincrement suatu variabel, untuk
kemudian langsung menggunakannya dalam sebuah ekspresi. Sebagai contoh, Anda
boleh membuat ekspresi seperti berikut:
System.out.println (i++);
Melihat ekspresi seperti ini, hasil yang akan ditampilkan tentunya belum jelas, apakah
increment yang kita lakukan akan bekerja sebelum nilai tersebut dicetak ataukah
sesudahnya. Mengingat ekspresiekspresi seperti ini dapat menimbulkan kebingungan,
saya tidak menganjurkan kepada Anda untuk memakainya. Dan untuk membuat Anda
lebih tidak ingin memakainya, saya tidak akan memberi tahu Anda hasil ekspresi di
atas. Namun jika Anda sungguhsungguh hendak mengetahuinya, coba saja sendiri!
154
Menggunakan operator increment, kita dapat menulis ulang lettercounter:
int index = 0;
while (index < length) {
if (fruit.charAt(index) == 'a') {
count++;
}
index++;
}
Anda akan menjumpai pesan error jika menulis ekspresi seperti di bawah ini:
index = index++; // WRONG!!
Sayangnya, ekspresi tersebut dianggap legal secara sintaks. Jadi, kompiler Java
mungkin tidak akan memberikan peringatan untuk Anda sebelumnya. Dampak yang
timbul dari pernyataan ini adalah tidak berubahnya nilai variabel index yang seharusnya
hendak kita ubah. Kesalahan seperti ini terkadang menjadi bug yang sulit untuk dilacak
keberadaannya.
Ingatlah, Anda dapat menulis ekspresi
index = index + 1;
atau
index++;
tapi sebaiknya jangan sampai mencampur/menggabungnya.
7.9 Strings are Immutable
Ketika Anda melihatlihat kembali dokumentasi untuk metode String, Anda mungkin
akan memperhatikan metode toUpperCase dan toLowerCase. Metode ini sering menjadi
biang masalah. Kok bisa? Karena keduanya tampak seolaholah telah mengubah string
yang sudah ada ke bentuk lainnya. Padahal, sebenarnya tidak ada satupun metode yang
dapat mengubah suatu string, karena string memang bersifat immune alias tidak bisa
155
diubahubah.
Simak yang berikut ini! Ketika Anda memanggil toUpperCase dalam sebuah String,
Anda akan mendapatkan sebuah String baru (untuk sementara anggap saja demikian)
sebagai hasil dari nilai yang dikembalikan oleh metode. Sebagai contoh:
String name = "Alan Turing";
String upperName = name.toUpperCase ();
Setelah baris kedua dieksekusi, upperName akan berisi nilai “ALAN TURING”, tapi
tidak juga, karena sebenarnya variabel name masih berisi “Alan Turing”.
7.10 Strings are Incomparable
Terkadang ada saatsaat dimana membandingkan dua string merupakan hal yang
penting. Perbandingan ini digunakan misalnya; untuk melihat apakah dua string yang
dibandingkan tersebut sama ataukah tidak, atau untuk melihat string manakah yang
berada di depan string lainnya bila dilihat secara alfabetis (in alphabetical order).
Senang rasanya jika kita dapat menggunakan operatoroperator perbandingan yang
sudah ada, seperti == dan >, tapi sayang, Java tidak mengizinkannya.
Untuk membandingkan String, kita harus menggunakan metode equals dan compareTo.
Contoh:
String name1 = "Alan Turing";
String name2 = "Ada Lovelace";
if (name1.equals (name2)) {
System.out.println ("The names are the same.");
}
int flag = name1.compareTo (name2);
if (flag == 0) {
156
System.out.println ("The names are the same.");
} else if (flag < 0) {
System.out.println ("name1 comes before
name2.");
} else if (flag > 0) {
System.out.println ("name2 comes before
name1.");
}
Sintaks yang Anda lihat di atas mungkin kelihatan aneh (yaa, gitu deh). Untuk
membandingkan dua string, Anda harus memanggil sebuah metode melalui salah satu
string itu lalu melewatkan yang lainnya sebagai argumennya.
Nilai kembalian dari metode equals terkesan lugas dan jelas; true jika kedua string
berisi karakterkarakter yang sama, dan false jika tidak.
Namun nilai yang dikembalikan oleh compareTo terkesan agak ganjil alias aneh.
Parameter yang dibandingkan adalah karakter pertama dalam string saja. Jika kedua
string itu sama, maka nilai kembalian adalah 0. Jika string pertama (string tempat
metode compareTo dipanggil) mendahului string kedua dalam urutan alfabetis, nilai
kembalian akan negatif. Jika tidak, maka nilainya positif. Dalam contoh ini, nilai
pengembaliannya adalah 8. Ini disebabkan karena huruf kedua dalam string “Ada
Lovelace” mendahului huruf kedua string “Alan Turing” sebanyak 8 huruf (huruf D dan
L terpisah sebanyak 8 huruf dalam alfabet; cara menghitung; E F G H I J K L).
Menggunakan metode compareTo terkadang malah merepotkan. Meskipun begitu, sisi
positif dari metode ini adalah bahwa interface yang digunakannya bisa dibilang cukup
lumayan sebagai standar untuk membandingkan berbagai tipe objek. Jadi, ketika Anda
sudah memahami cara menggunakannya, Anda akan lebih siap untuk yang lainnya.
157
Sebagai pelengkap, saya harus mengakui bahwa adalah legal dalam Java, meskipun
jarang sekali benar, menggunakan operator ' = = ' untuk tipe data String. Anda mengerti
maksud saya? Tidak! Itu wajar, karena pembahasannya akan berada di bagianbagian
berikutnya. Sabar ya .....
7.11 Daftar KataKata
Istilah Arti
Object Sebuah koleksi yang mengandung data yang saling berkaitan
dan sekumpulan metode yang dapat beroperasi di dalamnya.
Objek yang telah kita gunakan sejauh ini adalah objek
Graphic yang disediakan oleh system dan String.
Index Sebuah variabel atau nilai yang digunakan untuk memilih satu
anggota dari suatu himpunan terurut. Misalnya, satu karakter
dalam string.
Traverse Melakukan suatu operasi yang sama secara berulangulang
untuk seluruh elemen yang berada dalam sebuah himpunan.
Counter Sebuah variabel yang digunakan untuk menghitung sesuatu.
Variabel ini biasanya diinisialisasi dengan nilai 0 lalu
dinaikkan satusatu secara bertahap (increment).
Increment menaikkan/menambahkan nilai suatu variabel dengan nilai
satu (1) secara berulangulang sampai syarat yang ditetapkan
bernilai false. Operatornya adalah ++
Decrement menurunkan/mengurangi nilai suatu variabel dengan nilai
satu (1) secara berulangulang sampai syarat yang ditetapkan
bernilai false. Operatornya adalah
Exceptions Sebuah run time error. Exceptions menyebabkan terhentinya
158
proses eksekusi sebuah program.
7.12 Latihan
Latihan 7.1
Inti dari latihan ini adalah untuk mencoba beberapa operasi yang berkaitan dengan
String dan memasukkan beberapa bagian yang belum dibahas dalam bagian ini.
a) Buatlah sebuah program dengan nama Test.java, lalu buatlah sebuah metode main
yang berisi ekspresiekspresi yang mengkombinasikan beberapa tipe data
menggunakan operator ' + '. Sebagai contoh, apa yang akan terjadi jika Anda
“menambahkan” sebuah String dan sebuah char? Apakah ekspresi ini akan
menghasilkan penambahan (addition) atau penggabungan (concatenation).
b) Buatlah tabel yang lebih besar ketimbang tabel di bawah ini! Lalu isilah setiap irisan
(interseksi) yang terjadi antara tipe datatipe data ini dengan informasi yang
memberitahu legaltidaknya penggunaan operasi ' + ' . Jika legal, maka termasuk
kategori yang manakah operasi tersebut. Apakah penambahan atau penggabungan.
c) Bayangkan dan pikirkanlah beberapa pilihan yang telah dibuat oleh developer Java
ketika mereka hendak mengisi tabel ini. Berapa entri di dalamnya yang tampak
masuk akal, jika kemungkinan untuk masuk akal itu sendiri mungkin tidak ada?
Berapa banyak diantaranya yang tampak seperti hasil dari pemilihan acak atau suka
suka (arbitrary) dari beberapa kemungkinan yang masuk akal? Berapa banyak
diantaranya yang kelihatan sebagai hasil pilihan bodoh?
d) Waktunya bermain tekateki: normalnya, pernyataan x++ equivalen dengan x = x +
1. Kecuali jika x adalah char. Dalam kasus ini, x++ adalah sesuatu yang legal.
Namun tidak dengan x = x + 1.
159
e) Ujilah! Lalu lihatlah pesan error yang keluar. Jangan lupa, berusahalah juga untuk
memahami apa yang sebenarnya terjadi dengan error itu.
Latihan 7.2
Apakah output yang akan dihasilkan program ini? Ungkapkanlah dalam sebuah kalimat,
secara abstrak, sebenarnya apa sih bing itu (tapi bukan bagaimana cara ia bekerja)?
public class Mystery {
public static String bing (String s) {
int i = s.length() 1;
String total = "";
while (i >= 0 ) {
char ch = s.charAt (i);
System.out.println (i + " " + ch);
total = total + ch;
i;
}
return total;
}
public static void main (String[] args) {
System.out.println (bing ("Allen"));
}
}
Latihan 7.3
160
Salah seorang temanmu menunjukkan metode berikut ini kepadamu. Ia juga
menjelaskan bahwa jika variabel number adalah semua bilangan yang terdiri dari dua
digit, maka program akan mengeluarkan bilangan tersebut secara terbalik (backward).
Dia mengklaim bahwa kalau number sama dengan 17, output akan sama dengan 71.
Benarkah itu? Jika tidak, jelaskan apa yang sebenarnya dikerjakan oleh program. Lalu
modifikasilah program ini sehingga bisa berfungsi dengan baik dan tepat.
int number = 17;
int lastDigit = number%10;
int firstDigit = number/10;
System.out.println (lastDigit + firstDigit);
Latihan 7.3
Apakah output dari program ini?
public class Rarefy {
public static void rarefy (int x) {
if (x == 0) {
return;
} else {
rarefy (x/2);
}
System.out.print (x%2);
}
public static void main (String[] args) {
rarefy (5);
161
System.out.println ("");
}
}
Jelaskan dalam 4 s.d. 5 kata tentang apa yang sebenarnya dikerjakan oleh metode ini.
Latihan 7.5
a) Buatlah sebuah program dengan nama Palindrome.java.
b) Buatlah sebuah metode dengan nama first dan last yang menggunakan sebuah String
sebagai parameter. Metode first hanya akan mengembalikan karakter awal dari
string sementara last hanya akan mengembalikan karakter terakhir dari string itu.
c) Buatlah sebuah metode dengan nama middle yang menggunakan String sebagai
parameter. Metode ini akan mengembalikan sebuah substring yang mengandung
semua anggota string tersebut kecuali karakter awal dan akhirnya.
Petunjuk: bacalah dokumentasi mengenai metode substring dalam kelas String.
Eksekusilah beberapa uji coba yang dapat membuat Anda mengerti bagaimana cara
substring bekerja sebelum Anda menulis middle.
Apa yang akan terjadi jika Anda memanggil middle dalam sebuah string yang hanya
berisi dua huruf atau karakter? Satu karakter? Tanpa karakter? Hmmm ...
d) Pengertian umum untuk palindrome adalah sebuah kata yang apabila dibaca baik dari
depan maupun dari belakang akan menghasilkan ejaan baca yang sama. Contohnya;
“otto”, “palindromeemordnilap”. Cara alternatif untuk mendefinisikan kasuskasus
seperti ini adalah dengan menentukan sebuah metode khusus dan spesifik yang dapat
digunakan untuk menguji kasus itu sendiri. Misalnya, mungkin kita semua sepakat
berkata, “satu huruf yang tunggal termasuk palindrome, dan sebuah kata yang terdiri
dari dua huruf termasuk palindrome jika kedua huruf adalah sama, dan semua kata
yang lain adalah palindrome jika huruf pertama dan terakhir adalah sama sementara
162
kata yang berada diantaranya termasuk palindrome.”
Buatlah sebuah metode dengan teknik rekursif, isPalindrome, yang menggunakan
sebuah String sebagai parameter dan mampu mengembalikan sebuah nilai boolean
yang dapat dijadikan penanda apakah kata yang Anda masukkan termasuk
palindrome ataukah bukan.
e) Jika Anda sudah mempunyai metode pemeriksa palindrome yang dapat berfungsi
dengan baik, carilah cara untuk menyederhanakannya dengan cara mengurangi
jumlah persyaratan (conditions) yang harus diperiksa.
Petunjuk: mungkin akan berguna bagi Anda untuk menetapkan string kosong sebagai
palindrome.
f) Di atas selembar kertas, rancanglah sebuah strategi iteratif untuk memeriksa bentuk
palindrome. Ada beberapa pendekatan yang mungkin bisa dipakai untuk
menyelesaikan ini, jadi buatlah rencana yang matang terlebih dahulu sebelum
menulis kodenya.
g) Implementasikan strategi yang sudah Anda putuskan ke dalam sebuah metode
dengan nama isPalindromeIter.
Latihan 7.6
Sebuah kata dapat dikategorikan sebagai “abecedarian” jika hurufhuruf dalam kata
tersebut muncul dalam urutan yang alfabetis (dari A ke Z). Sebagai contoh, 19 kata dari
bahasa inggris berikut ini merupakan “abecedarian” yang semuanya terdiri dari 6 huruf.
abdest, acknow, acorsy, adempt, adipsy, agnosy, befist, behint, beknow,
bijoux, biopsy, cestuy, chintz, deflux, dehors, dehort, deinos, diluvy, dimpsy
a) Deskripsikan sebuah algoritma untuk memeriksa apakah kata (String) yang dijadikan
argumen dapat digolongkan sebagai “abecedarian”. Asumsikan bahwa semua kata
hanya boleh berisi huruf kecil (lowercase). Algoritma Anda bisa bersifat rekursif
163
ataupun iteratif.
b) Implementasikan algoritma Anda dalam metode yang disebut dengan isAbecedarian.
Latihan 7.7
dupledrome adalah sebuah kata yang hanya boleh mengandung huruf ganda, seperti
“llaammaa” atau “ssaabb”. Saya menduga bahwa tidak ada satu pun dupledrome dalam
katakata bahasa inggris yang sering digunakan. Untuk menguji dugaan itu, saya
menginginkan sebuah program yang dapat membaca setiap satu kata dalam kamus lalu
memeriksanya, kemudian memberi tahu apakah kata itu termasuk dalam dupledromity
juga.
Tulislah sebuah metode isDupledrome yang mengambil sebuah String sebagai
parameter. Metode ini akan mengembalikan sebuah nilai bertipe boolean yang
mengindikasikan apakah suatu kata memiliki sifat dupledromitas.
Latihan 7.8
Ketika namanama telah disimpan dalam komputer, terkadang namanama itu ditulis
secara langsung, yakni dengan menempatkan nama depan di depan nama belakang,
seperti “Allen Downey”, atau sebaliknya, dengan cara menempatkan nama belakang di
depan nama depan namun di antara keduanya diberi tanda koma, seperti “Downey,
Allen”. Keadaan ini dapat menyulitkan kita untuk membandingkan namanama tersebut
dan menempatkannya dalam urutan yang alfabetis.
Masalah yang masih ada kaitannya dengan hal semacam ini adalah bahwa beberapa
individu memiliki nama dengan penempatan huruf kapital yang lucu, seperti teman
saya, Beth DeSombre, atau bahkan tanpa satu huruf kapital pun, seperti poet e.e.
cummings. Ketika komputerkomputer digunakan untuk membandingkan karakter
karakter, mereka cenderung menempatkan semua huruf kapital dalam susunan yang
salah.
164
Jika Anda mau, lihatlah buku telepon umum, seperti buku direktori kampus, lalu
cobalah pahami bagaimana teknik yang dipakai oleh buku itu untuk menuliskan nama
nama yang ada di dalamnya. Perhatikan namanama yang mengandung beberapa kata
sekaligus seperti Van Houten juga namanama dengan huruf kapital yang non
standar, seperti desJardins. Coba lihat, apakah Anda sanggup untuk merancang
aturan/ramburambu pengurutannya. Jika Anda memiliki akses ke buku telepon negara
negara di eropa, coba juga itu, dan telitilah kalaukalau aturan yang digunakan juga
berbeda dengan standar Amerika.
Hasil dari semua bentuk nonstandar ini adalah bahwa terkadang tidaklah tepat untuk
mengurutkan namanama tersebut menggunakan aturan String yang biasa. Satu solusi
yang umum adalah dengan cara menyimpan dua versi sekaligus untuk tiaptiap nama:
pertama, untuk dicetak; kedua, format internal yang digunakan untuk melakukan
pengurutan.
Dalam latihan kali ini, Anda diminta menulis sebuah metode yang akan
membandingkan dua nama dengan cara mengkonversikan keduanya ke dalam format
standar. Kita akan bekerja dari bawah ke atas (bottomup), menulis beberapa metode
penolong untuk kemudian baru menulis metode compareName.
a) Buatlah sebuah program dengan nama Name.java. Dalam dokumentasi untuk String,
bacalah tentang metode find, toLower, dan compareTo. Buatlah beberapa kode
sederhana untuk menguji masingmasing metode. Pastikan Anda dapat memahami
bagaimana cara kerja ketiganya.
b) Buatlah metode hasComa yang menggunakan sebuah nama sebagai argumen serta
mampu mengembalikan nilai boolean yang mengindikasikan apakah nama itu
mengandung koma ataukah tidak. Jika ya, maka Anda sudah dapat menganggapnya
sebagai format nama dengan nama belakang mendahului nama depan.
c) Buatlah metode convertName yang menggunakan sebuah nama sebagai argumen.
165
Metode ini harus memeriksa apakah nama itu mengandung koma. Jika ya, maka
metode ini hanya perlu mengembalikan string nama itu saja. Jika tidak, maka
anggaplah nama tersebut termasuk dalam format namadepan duluan. Selain itu,
metode ini juga harus mengembalikan sebuah string baru yang berisi nama yang
telah dikonversikan ke format namabelakang duluan.
d) Tulislah sebuah metode dengan nama compareName yang menggunakan dua buah
nama sebagai argumen dan yang mengembalikan nilai 1 jika nama pertama
mendahului nama kedua, dalam urutan abjad alfabet, 0 jika kedua nama bernilai
sama secara alfabetis, dan 1 untuk keadaan lainnya. Metode yang Anda buat haruslah
bersifat caseinsensitive. Artinya, metode ini tidak akan memedulikan apakah huruf
huruf penyusun namanama yang Anda berikan nantinya merupakan huruf kapital
(upper case) atau huruf nonkapital (lower case).
Latihan 7.9
a) Cincin Decoder Kapten Crunch bekerja dengan cara mengambil setiap huruf dalam
sebuah string, lalu menambahkan ke masingmasing huruf tersebut nilai 13. Sebagai
contoh, ' a ' menjadi ' n ' dan ' b ' menjadi ' o '. Hurufhuruf tersebut kemudian akan
“berputar” (wrap around) di bagian akhir, sehingga ' z ' akan menjadi ' m ' (z + 13 =
m) .
Buatlah sebuah metode yang mengambil sebuah String sebagai parameter dan yang
mengembalikan sebuah String baru yang mengandung versi enkode dari String
tersebut. Anda harus mengasumsikan bahwa String yang akan dilewatkan sebagai
parameter dapat berisi huruf kecil, huruf kapital, dan spasi. Tetapi tidak untuk tanda
baca yang lain. Huruf kecil harus diubah ke dalam huruf kecil lagi, begitu pula
dengan huruf kapital. Bagaimana dengan spasi? Anda tidak perlu mengenkodenya.
b) Generalisasilah metode ini sehingga dapat menambahkan nilai berapa pun yang Anda
166
kehendaki ketimbang hanya mampu menambahkan 13 ke dalam setiap huruf.
Sekarang Anda pastinya bisa mengenkode sesuatu dengan menjumlahkan 13 lalu
mendekodenya dengan menambahkan 13. Ayo coba!
167
Bab 8
Interesting Objects
8.1 Apanya yang menarik?
Meskipun String adalah objek, tapi mereka bukanlah objek yang benarbenar menarik.
Maklum, String itu:
● Tidak bisa “diubahubah” alias immutable.
● Mereka tidak mempunyai variabel instan.
● Tidak mengharuskan kita untuk menggunakan perintah new ketika membuatnya.
Dalam bagian ini, kita akan menggunakan dua tipe objek baru yang merupakan bagian
dari Java, yakni Point dan Rectangle. Mulai saat ini juga, saya ingin membuat Anda
tahu bahwa points dan rectangle bukanlah objekobjek gambar yang akan tampil di
layar. Keduanya merupakan variabel yang mengandung data, sama halnya dengan int
dan double. Layaknya variabelvariabel lain, keduanya digunakan secara internal untuk
mengerjakan perhitunganperhitungan.
Definisidefinisi mengenai kelas Point dan kelas Rectangle berada dalam paket java.awt,
jadi kita harus mengimport keduanya.
8.2 Paketpaket
Kelaskelas bawaan Java dibagi ke dalam beberapa paket, termasuk paket java.lang
yang mengandung hampir semua kelas yang telah kita gunakan sejauh ini. Begitu juga
dengan javax.swing yang berisi kelaskelas untuk bekerja dengan jendela (window),
tombol (button), grafis (graphic), dsb.
Untuk menggunakan sebuah paket, Anda harus mengimportnya, inilah yang menjadi
168
alasan kenapa program yang ada di bagian D.1 dimulai dengan sintaks import
javax.swing.*. Tanda ' * ' mengindikasikan bahwa kita ingin mengimport semua kelas
yang berada dalam paket SWING. Jika Anda mau, Anda dapat menuliskan kelaskelas
yang Anda inginkan secara eksplisit, tapi sayangnya tidak terlalu menguntungkan.
Kelaskelas dalam java.lang sudah diimport secara otomatis. Hal inilah yang
menyebabkan kenapa programprogram yang kita tulis sejauh ini belum memerlukan
sebuah perintah import.
Semua perintah import tampil di bagian awal dari sebuah program, di luar definisi milik
kelas.
8.3 Objek Titik (Point)
Dalam pengertian yang paling sederhana, sebuah titik (point) adalah dua angka
(koordinat) yang kita anggap/sepakati sebagai sebuah objek tunggal. Dalam notasi
matematika, titiktitik ini (points) sering ditulis dalam tanda kurung (parentheses),
dengan satu koma yang memisahkan kedua koordinat. Contohnya, (0, 0) yang
mengindikasikan titik asal/awal, dan (x, y) yang mengindikasikan titik di lokasi x satuan
ke kanan dan y satuan ke atas dengan acuan titik asal (0, 0).
Dalam Java, sebuah titik direpresentasikan dengan sebuah objek yang disebut Point.
Untuk membuat sebuah titik yang baru, Ada harus menggunakan perintah new:
Point blank;
blank = new Point (3, 4);
Baris pertama adalah contoh deklarasi variabel yang sudah umum alias konvensional:
blank memiliki tipe data Point. Sementara baris kedua adalah sesuatu yang terlihat lucu;
baris ini memanggil (invoke) perintah new, lalu menentukan tipe untuk objek yang baru
itu, kemudian memberinya argumenargumen. Mungkin ini bukanlah sesuatu yang
mengejutkan Anda bahwa argumenargumen yang berada dalam tanda kurung, (3, 4),
169
adalah koordinat dari titik yang baru kita buat.
Hasil dari perintah new yang baru kita buat adalah sebuah acuan (reference) bagi titik
yang baru. Saya akan menjelaskan masalah acuan ini secara lebih mendalam dalam
bagian yang lain. Untuk sekarang, yang lebih penting adalah bahwa variabel blank
berisi acuan kepada objek yang baru saja dibuat. Ada cara standar untuk menggambar
diagram assignment seperti ini. Lihat gambar berikut:
Seperti biasa, nama variabel blank muncul di luar kotak sementara nilainilainya berada
di dalam. Dalam kasus ini, nilai adalah suatu acuan, seperti yang ditunjukkan oleh
sebuah dot dan anak panah dalam gambar. Anak panah itu menunjuk ke objek yang
sedang kita acu.
Kotak besar itu memperlihatkan objek yang baru saja kita buat beserta 2 nilai yang
berada di dalamnya. x dan y adalah nama dari variabelvariabel instan.
Jika disatukan/digabungkan, maka semua komponen, variabel, nilai, dan objek dalam
program tersebut akan disebut dengan state. Diagramdiagram seperti ini yang
menunjukkan state dari suatu program disebut juga dengan diagramdiagram state (state
diagram). Ketika program berjalan (running), statenya juga akan berubah. Jadi, Anda
harus menganggap diagram state sebagai sebuah snapshot (tangkapan) dari suatu titik
tertentu ketika eksekusi sedang terjadi.
8.4 Variabel Instan
Kumpulan data yang menyusun sebuah objek sering juga disebut sebagai komponen,
rekaman (record), atau field. Dalam Java, mereka disebut sebagai variabel instan karena
170
setiap objek, yang merupakan sebuah instan dari tipenya sendiri, memiliki salinan instan
variabelnya sendiri.
Hal ini persis seperti laci kecil yang ada di dalam sebuah mobil. Setiap mobil adalah
instan dari tipe “mobil”, dan setiap mobil memiliki laci kecilnya masingmasing. Jika
Anda menyuruh saya untuk mengambil sesuatu dari laci kecil dalam mobil Anda, maka
Anda harus memberi tahu saya dahulu mobil mana yang menjadi kepunyaan Anda.
Mirip dengan analogi di atas, jika Anda ingin membaca sebuah nilai dari sebuah
variabel instan, maka Anda harus menentukan terlebih dahulu objek mana yang akan
mengambil nilai tersebut. Dalam Java, aksi ini akan dikerjakan dengan menggunakan
perintah “notasi dot”.
Int x = blank.x;
Ekspresi blank.x mengandug arti “pergilah ke tempat yang diacu oleh objek blank, lalu
ambillah nilai x”. Dalam kasus ini, kita mengassign nilai tersebut ke dalam sebuah
variabel lokal yakni x. Perhatikan bahwa tidak ada konflik yang terjadi antara variabel
lokal x dengan variabel instan x. Fungsi notasi dot dalam kasus ini adalah untuk
mengidentifikasi dengan pasti variabel mana yang sedang atau akan Anda acu.
Anda dapat menggunakan notasi dot untuk ekspresi bahasa Java apapun, jadi bentuk
berikut ini dianggap legal.
System.out.println (blank.x + ", " + blank.y);
int distance = blank.x * blank.x + blank.y * blank.y;
Baris pertama mencetak 3, 4; sementara baris kedua menghasilkan nilai 25.
8.5 Objek Sebagai Parameter
Anda dapat melewatkan (pass) suatu objek sebagai parameter dengan cara yang sudah
lazim digunakan. Contoh,
public static void printPoint (Point p) {
171
System.out.println ("(" + p.x + ", " + p.y + ")");
}
adalah sebuah metode yang menggunakan sebuah point sebagai argumen lalu
mencetaknya dalam format standar. Jika Anda memanggil metode printPoint (blank),
Anda akan mencetak (3, 4). Sebenarnya, Java mempunyai metode bawaan untuk
mencetak Points. Jika Anda memanggil System.out.println(blank) Anda akan
mendapatkan
java.awt.Point[x=3,y=4]
Ini merupakan format standar Java untuk mencetak mencetak suatu objek. Metode ini
akan mencetak nama tipe, kemudian diikuti dengan isi dari objek tersebut, termasuk
namanama dan nilainilai dari variabel instan.
Untuk contoh tambahan, kita dapat menulis ulang metode distance dari bagian 5.2 agar
ia menggunakan dua Point sebagai parameter ketimbang empat double.
public static double distance (Point p1, Point p2) {
double dx = (double)(p2.x p1.x);
double dy = (double)(p2.y p1.y);
return Math.sqrt (dx*dx + dy*dy);
}
Typecast di atas sebenarnya tidaklah terlalu bermanfaat; saya hanya menambahkannya
sebagai pengingat bahwa variabel instan dalam sebuah Point adalah integer.
8.6 Rectangle
Rectangle memiliki sifat yang hampir mirip dengan Point, kecuali variabel instannya
yang berjumlah empat. Keempat variabel itu antara lain; x, y, width, dan height. Selain
itu, semuanya sama saja.
Rectangle box = new Rectangle (0, 0, 100, 200);
menghasilkan sebuah objek Rectangle baru sekaligus membuat variabel box akan
172
mengacu kepadanya. Gambar berikut ini akan menunjukkan efek dari assignment ini.
Jika Anda mencetak box, Anda akan mendapatkan
java.awt.Rectangle[x=0,y=0,width=100,height=200]
Lagilagi, ini merupakan hasil dari metode bawaan Java yang sudah tahu bagaimana
caranya mencetak objek Rectangle.
8.7 Objek sebagai tipe kembalian (Return Type)
Anda dapat menulis metode yang menghasilkan nilai kembalian bertipe objek. Sebagai
contoh, findCenter menggunakan Rectangle sebagai argumen lalu mengembalikan
sebuah Point, yang merupakan objek, yang berisi koordinatkoordinat pusat dari
Rectangle:
public static Point findCenter (Rectangle box) {
int x = box.x + box.width/2;
int y = box.y + box.height/2;
return new Point (x, y);
}
Perhatikan bahwa Anda dapat menggunakan new untuk membuat sebuah objek baru,
untuk kemudian langsung menggunakan hasilnya sebagai nilai kembalian.
8.8 Objek dapat diubahubah (Mutable)
Anda dapat mengubahubah isi dari sebuah objek dengan cara membuat sebuah
assignment untuk salah satu variabel instan yang dimilikinya. Sebagai contoh, untuk
“memindahkan” sebuah lingkaran tanpa mengubah ukurannya, Anda dapat
memodifikasi nilai x dan y:
box.x = box.x + 50;
box.y = box.y + 100;
Hasilnya dapat Anda lihat dalam gambar di bawah ini:
173
Kita dapat mengambil kode ini lalu mengenkapsulasinya dalam sebuah metode, lalu
menggeneralisasinya agar dapat digunakan untuk memindahkan sebuah lingkaran
dengan nilai berapa pun yang kita mau.
public static void moveRect (Rectangle box, int dx, int dy) {
box.x = box.x + dx;
box.y = box.y + dy;
}
Variabel dx dan dy akan memberi tahu sejauh mana perpindahan yang telah terjadi pada
lingkaran dalam setiap arah. Jika Anda memanggil metode ini, berarti Anda juga telah
mengubah objek Rectangle yang dilewatkan sebagai sebuah argumen.
Rectangle box = new Rectangle (0, 0, 100, 200);
moveRect (box, 50, 100);
System.out.println (box);
akan mencetak
java.awt.Rectangle[x=50,y=100,width=100,height=200].
Memodifikasi objek dengan cara melewatkan mereka sebagai argumen untuk sebuah
metode memang dapat memberikan manfaat. Namun hal ini juga dapat menimbulkan
kesulitan ketika kita hendak melakukan debugging. Kok bisa? Karena terkadang
tidaklah terlalu jelas bagi kita untuk menentukan dengan cepat pemanggilan metode
mana yang turut mengubah atau tidak mengubah masingmasing argumen yang mereka
bawa. Nanti, saya akan membahas beberapa pro dan kontra yang berkaitan dengan
teknik pemrograman seperti ini.
174
Untuk saat ini, kita sudah dapat menikmati “kemewahan” yang tersedia dalam metode
metode bawaan Java. Termasuk di dalamnya translate, yang kemampuannya sepadan
dengan metode moveRect, meskipun sintaks yang digunakan untuk memanggilnya
mungkin agak sedikit berbeda. Ketimbang melewatkan Rectangle sebagai sebuah
argumen, kita memanggil translate dalam objek Rectangle kemudian melewatkan
variabel dx dan dy saja sebagai argumen.
box.translate (50, 100);
Pengaruh yang dihasilkan akan sama dengan moveRect.
8.9 Aliasing
Penting untuk diingat, ketika Anda mengassign suatu variabel dengan objek, ini berarti
Anda sedang mengassign sebuah acuan (reference) untuk sebuah objek. Java
mengizinkan Anda untuk membuat lebih dari satu variabel yang mengacu kepada satu
objek yang sama. Untuk lebih jelasnya, perhatikan kode berikut ini:
Rectangle box1 = new Rectangle (0, 0, 100, 200);
Rectangle box2 = box1;
kode di atas akan menghasilkan diagram state seperti yang tampak di bawah ini:
Baik box1 maupun box2 mengacu kepada objek yang sama. Dengan kata lain, objek ini
memiliki dua nama, yakni box1 dan box2. Ketika seorang individu memiliki dua buah
nama, kita menyebutnya dengan istilah aliasing. Nah, begitu juga dengan objek.
Ketika dua variabel berada dalam status aliasing, semua perubahan yang dapat
memengaruhi satu variabel akan turut memengaruhi variabel lainnya. Contohnya:
175
Baris pertama akan mencetak 100, yang merupakan lebar dari Rectangle yang diacu
oleh box2. Sementara baris kedua digunakan untuk memanggil metode grow melalui
box1. Pernyataan ini akan melebarkan/memperluas Rectangle sebanyak 50 pixel ke
segala arah (silahkan membaca dokumentasinya agar lebih jelas). Perubahan yang
terjadi ditunjukkan oleh gambar di bawah ini:
Melalui gambar ini, seharusnya Anda sudah dapat memahami, bahwa perubahan apapun
yang terjadi pada box1 juga turut memengaruhi box2. Oleh karena itulah, nilai yang
dicetak oleh pernyataan pada baris ketiga adalah 200, sebagai lebar dari hasil perluasan
lingkaran. (Sebagai pertimbangan, Java juga mengizinkan nilai negatif sebagai
koordinat Rectangle)
Dari kode sederhana di atas, Anda mungkin akan langsung menyimpulkan bahwa kode
yang menerapkan prinsip aliasing justru dapat merepotkan kita sendiri. Apalagi kalau
sudah masuk ke dalam urusan debugging. Untuk itulah, sebaiknya Anda menghindari
penggunaan teknik ini, atau gunakan ia dengan sehatihati mungkin.
8.10 Null
Ketika Anda membuat sebuah variabel objek, Anda harus ingat bahwa Anda sedang
mengacu kepada suatu objek. Jika Anda belum membuat variabel tersebut mengacu
kepada suatu objek, maka nilai dari variabel itu akan sama dengan null. null adalah nilai
yang khusus (juga sebuah keyword) dalam Java yang berarti “tidak ada objek”.
Pendeklarasian bentuk
Point blank;
176
akan sama dengan inisialisasi beriktu ini:
Point blank = null;
Diagram state dari deklarasi di atas dapat dilihat melalui gambar di bawah ini:Nilai null
direpresentasikan dengan sebuah dot tanpa anak panah.
Jika Anda mecoba untuk menggunakan objek yang berstatus null, entah dengan
mengaksesnya melalui variabel instan atau memanggilnya melalui sebuah metode, Anda
akan mendapatkan sebuah pesan berbunyi, “NullPointerException”. Sistem akan
menampilkan sebuah pesan error lalu menghentikan program itu.
Point blank = null;
int x = blank.x; // NullPointerException
blank.translate (50, 50); // NullPointerException
Di sisi lain, adalah legal dalam Java untuk melewatkan sebuah objek berstatus null
sebagai sebuah argumen atau menerima salah satunya sebagai sebuah nilai kembalian.
Faktanya, memang demikianlah adanya. Para pengguna Java terbiasa mengaplikasikan
konsep ini untuk merepresentasikan sebuah himpunan kosong atau untuk
menginformasikan sebuah keadaan error.
8.11 Garbage Collection
Dalam bagian 8.9 kita telah berbicara mengenai apa yang akan terjadi jika lebih dari
satu variabel mengacu kepada satu objek yang sama. Lalu sekarang, apa yang akan
terjadi jika tidak ada satu pun variabel yang mengacu kepada suatu objek? Sebagai
contoh:
Point blank = new Point (3, 4);
blank = null;
177
Baris pertama menghasilkan sebuah objek Point lalu membuat variabel blank mengacu
kepadanya. Baris kedua mengubah blank sehingga ketimbang mengacu kepada suatu
objek, ia justru lebih memilih untuk tidak mengacu kepada objek mana pun (objek null).
Jika tidak ada variabel yang mengacu kepada sebuah objek, maka tidak akan ada yang
dapat membaca (read) atau menulisi (write) objek ini dengan nilai apa pun, atau
memanggil sebuah metode melalui objek ini. Walaupun begitu, objek seperti ini tetap
eksis dalam memori. Sebenarnya, kita bisa saja menyimpan objek ini dalam memori,
tapi hal ini hanya akan membuangbuang ruang (space). Oleh karena itulah, selagi
program Anda berjalan, sistem Java akan mencari objekobjek yang tidak terpakai ini
secara berkala (periodically), untuk kemudian mengaturnya. Proses inilah yang disebut
dengan garbage collection. Walhasil, ruang memori yang tadinya dipakai oleh objek ini
akan dapat digunakan kembali sebagai bagian dari objek yang baru.
Anda tidak perlu melakukan apapun untuk membuat proses garbage collection bekerja.
Lagipula, umumnya Anda memang tidak akan memerhatikannya.
8.12 Objek dan Primitif
Ada dua jenis tipe data dalam Java, tipe data primitif dan tipe data objek. Tipe primitif,
seperti int dan boolean diawali dengan huruf kecil; sementara tipe objek diawali dengan
huruf kapital. Perbedaan ini sangat bermanfaat karena dapat mengingatkan kita kepada
beberapa perbedaan antara keduanya.
● Ketika Anda mendeklarasikan sebuah tipe data primitif, Anda akan
178
mendapatkan ruang penyimpanan untuk nilai primitif. Ketika Anda
mendeklarasikan variabel bertipe data objek, Anda akan mendapatkan ruang
memori sebagai acuan (reference) kepada sebuah objek. Agar objek yang Anda
buat memiliki ruang, Anda harus menggunakan perintah new.
● Jika Anda tidak menginisialisasi sebuah tipe primitif, Java telah memberi nilai
bawaan (default) untuk setiap tipe data tersebut. Contohnya, 0 untuk tipe data int
dan true untuk tipe boolean. Nilai bawaan untuk tipetipe objek adalah null yang
berarti tidak ada objek.
● Variabelvariabel primitif diisolasi dengan baik oleh Java. Maksud pernyataan
ini adalah bahwa Anda tidak dapat melakukan apapun melalui sebuah metode
untuk mengubah variabel lain yang berada dalam metode lain. Variabelvariabel
objek menjadi agak sulit dan membingungkan untuk digunakan karena mereka
tidak diisolasi sebaik variabel bertipe primitif. Jika Anda melewatkan suatu
referensi kepada sebuah objek sebagai sebuah argumen, maka metode yang
Anda panggil dapat mengubah objek tersebut. Keadaan yang sama juga berlaku
ketika Anda memanggil sebuah metode melalui suatu objek. Tetapi tentu saja,
hal ini sebenarnya juga dapat bermanfaat untuk Anda, asalkan Anda hatihati
ketika menggunakannya.
Ada satu perbedaan lagi antara tipe data primitif dengan tipe data objek. Anda tidak
dapat menambahkan tipe data primitif baru ke dalam bahasa Java (kecuali kalau Anda
termasuk dalam anggota komite standardisasi Java milik Sun Microsystem), tapi Anda
dapat membuat tipe objek yang baru! Caranya? Kita akan melihatnya di bagian
berikutnya.
179
8.13 Daftar KataKata
Istilah Arti
paket (package)
Sebuah kumpulan dari kelaskelas (classes). Kelaskelas
yang dibawa (builtin) oleh Java dikelola dalam bentuk
paketpaket.
AWTAbstract Window Toolkit, salah satu paket terbesar
dalam Java.
instan (instance)
Sebuah contoh dari suatu kategori/golongan. Kucing
saya adalah suatu instan dari (baca: yang termasuk ke
dalam golongan) “keluarga kucing”. Semua objek
adalah instan dari beberapa kelas.
variabel instan
Salah satu itemitem data yang membentuk sebuah
objek. Setiap objek (instan) memiliki salinan variabel
variabel instan untuk kelasnya masingmasing.
acuan (reference)
Sebuah nilai yang mengindikasi sebuah objek. Dalam
sebuah diagram state, suatu acuan diwakili dengan
gambar anak panah.
aliasingSuatu kondisi dimana dua atau lebih variabel mengacu
ke objek yang sama.
garbage collection
Suatu proses untuk mencari objekobjek yang tidak
punya acuan untuk kemudian mengelola kembali ruang
memori yang telah dipakainya.
state
Sebuah deskripsi lengkap dari semua variabel dan objek
sekaligus nilainilai yang dikandungnya pada suatu
waktu tertentu selama eksekusi program sedang
berlangsung.
180
diagram state
Sebuah pencitraan (snapshot) atas kondisi/keadaan
yang terjadi pada sebuah program, ditunjukkan dengan
gambar.
8.14 Latihan
Latihan 8.1
a) Untuk program di bawah ini, gambarlah sebuah diagram stack yang menunjukkan
variabelvariabel lokal dan parameterparameter dari main dan fred, lalu tunjukkan
suatu objek yang diacu oleh variabelvariabel tersebut.
b) Apakah output yang dihasilkan oleh program berikut ini?
public static void main (String[] args)
{
int x = 5;
Point blank = new Point (1, 2);
System.out.println (fred (x, blank));
System.out.println (x);
System.out.println (blank.x);
System.out.println (blank.y);
}
public static int fred (int x, Point p)
{
x = x + 7;
return x + p.x + p.y;
}
181
Tujuan utama dari latihan ini adalah untuk memastikan bahwa Anda benarbenar
memahami mekanisme untuk melewatkan objek sebagai parameter.
Latihan 8.2
a) Untuk program berikut ini, gambarlah sebuah diagram stack yang menunjukkan
keadaan dari program beberapa saat sebelum metode distance dikembalikan.
Masukkan ke dalam gambar Anda itu semua variabel, parameter, dan objek yang
diacu oleh variabelvariabel tersebut.
b) Output apakah yang dihasilkan oleh program berikut ini?
public static double distance (Point p1, Point p2)
{
int dx = p1.x p2.x;
int dy = p1.y p2.y;
return Math.sqrt (dx*dx + dy*dy);
}
public static Point findCenter (Rectangle box) {
int x = box.x + box.width/2;
int y = box.y + box.height/2;
return new Point (x, y);
}
public static void main (String[] args) {
Point blank = new Point (5, 8);
Rectangle rect = new Rectangle (0, 2, 4, 4);
Point center = findCenter (rect);
double dist = distance (center, blank);
System.out.println (dist);
}
182
Latihan 8.3
Metode grow adalah bagian dari kelas Rectangle. Berikut ini merupakan dikumentasi
mengenai grow (diambil dari situs Sun):
public void grow(int h, int v)
Grows the rectangle both horizontally and
vertically.
This method modifies the rectangle so that it is h
units larger on both the left and right side, and v
units larger at both the top and bottom.
The new rectangle has (x h, y v) as its top
left corner, a width of width + 2h, and a height of
height + 2v.
Jika nilai negatif
a) Output apakah yang dihasilkan oleh program berikut ini?
b) Gambarkan sebuah diagram state yang menunjukkan keadaan dari program sesaat
sebelum main berakhir. Masukkan ke dalam gambar Anda tersebut semua variabel
dan objekobjek yang diacunya.
c) Pada bagian akhir dari main, apakah p1 dan p2 termasuk aliasing? Mengapa atau
mengapa tidak?
public static void printPoint (Point p) {
System.out.println ("(" + p.x + ", " + p.y +
")");
183
}
public static Point findCenter (Rectangle box) {
int x = box.x + box.width/2;
int y = box.y + box.height/2;
return new Point (x, y);
}
public static void main (String[] args) {
Rectangle box1 = new Rectangle (2, 4, 7, 9);
Point p1 = findCenter (box1);
printPoint (p1);
box1.grow (1, 1);
Point p2 = findCenter (box1);
printPoint (p2);
}
Latihan 8.4
Mungkin sekarang Anda sudah benarbenar bosan dengan metode faktorial, tapi
sayangnya kita akan membuat satu versi lagi dari metode ini.
a) Buatlah sebuah program baru dengan nama Big.java lalu mulailah dengan menulis
sebuah versi iteratif dari factorial.
b) Cetaklah sebuah tabel integer dari 0 s.d. 30 bersama faktorial mereka. Ketika sampai
ke nilai 15an, Anda mungkin akan mendapati bahwa jawaban Anda sepertinya
sudah tidak tepat lagi. Coba periksa! Mengapa begitu?
c) BigInteger adalah metode bawaan Java yang dapat merepresentasikan bilangan
184
bilangan integer. Tidak ada batasatas kecuali ukuran memori dan kecepatan
pemrosesan itu sendiri. Cetaklah dokumentasi untuk kelas BigInteger yang terdapat
dalam paket java.math, lalu bacalah!
d) Ada beberapa cara untuk membuat metode BigInteger, tapi yang saya
rekomendasikan adalah dengan menggunakan valueOf. Kode berikut ini akan
mengkonversikan sebuah integer ke bentuk metode BigInteger:
int x = 17;
BigInteger big = BigInteger.valueOf (x);
Tulislah kode ini! Lalu cobalah beberapa kasus sederhana, seperti membuat sebuah
BigInteger kemudian mencetaknya. Perhatikan bahwa metode println sudah
mengetahui cara untuk mencetak BigInteger! Jangan lupa untuk menambahkan
import java.math.BigInteger di awal program Anda.
e) Sayangnya, karena BigInteger bukanlah tipe primitif, kita jadi tidak dapat
menggunakan operator matematika biasa untuknya. Sebaliknya, kita akan
menggunakan metode objek seperti add. Untuk menambah dua BigInteger, Anda
harus memanggil add melalui salah satu objek dan melewatkan objek lainnya sebagai
argumen. Sebagai contoh:
BigInteger small = BigInteger.valueOf (17);
BigInteger big = BigInteger.valueOf (1700000000);
BigInteger total = small.add (big);
Cobalah beberapa metode lainnya, seperti multiply dan pow.
f) Konversikanlah metode factorial sehingga ia dapat melakukan penghitungan
menggunakan BigInteger, lalu mengembalikan BigInteger sebagai hasilnya. Anda
tidak perlu menghiraukan parameternya – karena tipe data yang akan digunakan
tetaplah berupa integer.
185
g) Cobalah mencetak tabel lagi dengan metode factorial yang telah anda modifikasi.
Benarkah hasilnya untuk angka di atas 30? Sampai seberapa besar (baca: angkanya)
Anda dapat mempertahankan ketepatan ini? Saya telah menghitung faktorial dari
semua angka mulai dari 0 s.d. 999. Namun karena mesin saya terlalu lambat, saya
jadi perlu waktu yang agak lama. Angka terakhir, 999!, mempunyai 2565 digit.
Bagaimana dengan Anda?
Latihan 8.5
Banyak algoritmaalgoritma enkripsi sangat bergantung kepada kemampuan untuk
menaikkan nilai integer yang besar ke bentuk integer berpangkat. Berikut ini adalah
sebuah metode yang mengimplementasikan sebuah algoritma cepat untuk pemangkatan
(exponentiation) integer.
public static int pow (int x, int n) {
if (n==0) return 1;
// find x to the n/2 recursively
int t = pow (x, n/2);
// if n is even, the result is t squared
// if n is odd, the result is t squared times x
if (n%2 == 0) {
return t*t;
} else {
return t*t*x;
}
}
Masalah yang muncul dalam metode ini adalah bahwa ia hanya bekerja dengan benar
186
jika hasil yang didapatkan kurang dari 2 miliar. Tulisulanglah metode ini sehingga
hasilnya merupakan bentuk BigInteger. Meskipun begitu, parameterparamater yang
digunakan haruslah tetap berupa integer.
Anda diizinkan menggunakan metode add dan multiply milik BigInteger, tapi tidak
dengan metode pow, karena metode ini bisa jadi justru akan merusak 'keceriaan' Anda.
187
Bab 9
Membuat Objek Anda Sendiri
9.1 Definisidefinisi kelas dan tipetipe objek
Setiap kali Anda menulis sebuah definisi untuk kelas, Anda sebenarnya telah membuat
sebuah tipe objek baru dengan nama yang sama dengan nama kelas Anda. Kembali ke
bagian 1.5, ketika kita mendefinisikan sebuah kelas bernama Hello, pada saat yang
sama (tanpa perlu Anda sadari) kita juga membuat sebuah tipe objek dengan nama
Hello. Kita tidak membuat variabel apapun untuk tipe Hello, tidak juga menggunakan
perintah new untuk membuat objekobjek Hello, tapi kita sudah berhasil melakukannya!
Contoh di atas agak tidak masuk akal, karena kita pada dasarnya memang tidak
bermaksud untuk membuat objek Hello. Lagipula, masih tidak jelas apa manfaatnya
untuk kita kalau memang kita benarbenar telah membuatnya. Dalam bab ini, kita
akan melihat beberapa contoh definisidefinisi kelas yang mampu menunjukkan kepada
kita tipe objek baru yang bermanfaat.
Berikut ini adalah halhal penting yang harus diperhatikan dalam bab ini:
● Mendefinisikan sebuah kelas baru akan menyebabkan 'lahirnya' sebuah tipe
objek baru dengan nama yang sama.
● Suatu definisi untuk kelas dapat diasumsikan sebagai sebuah cetakan (template)
untuk objek: definisi ini akan menentukan variabel instan mana yang dimiliki
objek sekaligus metodemetode yang bisa beroperasi di atasnya.
● Setiap objek merupakan milik beberapa objek sekaligus; oleh karena itulah,
setiap objek juga merupakan instan dari beberapa kelas.
● Ketika Anda menggunakan perintah new untuk membuat sebuah objek, Java
188
memanggil sebuah metode khusus yang disebut constructor untuk
menginisialisasi variabelvariabel instan.
● Semua metode yang beroperasi dalam sebuah tipe akan masuk dalam definisi
kelas untuk tipe tersebut.
Berikut ini merupakan isuisu sintaks mengenai definisidefinisi kelas:
● Nama kelas (atau tipe objek) selalu dimulai dengan huruf kapital yang akan
membedakannya dengan tipetipe primitif dan namanama variabel.
● Anda biasanya meletakkan satu definisi kelas dalam setiap file, dan nama file
tersebut haruslah sama dengan nama kelas yang Anda buat dengan ekstensi
.java. Sebagai contoh, kelas Time didefinisikan dalam sebuah file bernama
Timed.Java.
● Dalam program manapun, sebuah kelas dirancang untuk menjadi kelas startup
(startup class). Kelas ini harus mengandung sebuah metode dengan nama main,
yang akan menjadi tempat dimana eksekusi program dimulai. Kelas lain
diizinkan untuk memiliki metode main, tapi ia tidak akan dieksekusi.
Dengan topiktopik di atas, sekarang kita akan melihat sebuah contoh kelas yang
merupakan hasil buatan sendiri (userdefined type), yakni Time.
9.2 Time
Tujuan utama yang mendasari pembuatan sebuah tipe objek baru adalah untuk
mengambil beberapa data yang saling berhubungan lalu mengenkaspsulasi mereka ke
dalam sebuah objek yang dapat dimanipulasi (dilewatkan sebagai sebuah argumen,
beroperasi dengan suatu metode) sebagai sebuah unit tunggal. Sebelumnya kita semua
sudah melihat dua tipe bawaan Java seperti ini, yaitu Point dan Rectangle.
Contoh lainnya, yang akan kita impelementasikan sendiri adalah Time. Tipe objek ini
akan kita gunakan untuk merekam waktu dalam satu hari. Beberapa informasi yang
189
menyusun 'sebuah' waktu antara lain; jam, menit dan detik. Karena setiap objek waktu
akan berisi data ini, kita memerlukan variabel instan untuk menyimpan data tersebut.
Langkah pertama adalah menentukan tipe data yang cocok untuk variabel yang akan
kita gunakan. Untuk variabel hour dan minute, kita sebaiknya menggunakan integer.
Untuk membuat proses ini lebih menyenangkan, mari kita simpan data second dalam
variabel bertipe double agar nantinya dapat merekam detikdetik yang 'kecil'.
Variabel instan akan dideklarasikan di awal definisi kelas, di luar semua definisi metode
yang ada, seperti ini bentuknya:
class Time {
int hour, minute;
double second;
}
Lampiran kode di atas termasuk definisi kelas yang legal dalam Java. Diagram state
untuk objek Time akan tampak sebagai berikut:
Setalah mendeklarasikan variabel instan, langkah selanjutnya adalah
mendefinisikan/menentukan sebuah konstruktor untuk kelas yang baru.
Fungsi utama dari konstruktor adalah untuk menginisialisasi variabel instan. Sintaks
untuk membuat konstruktor sama dengan yang digunakan untuk membuat metode
metode biasa, tapi dengan tiga pengecualian:
● Nama konstruktor harus sama dengan nama kelas.
● Konstruktor tidak mempunyai tipe dan atau nilai pengembalian.
● Keyword static tidak boleh digunakan.
Berikut ini adalah contoh kelas Time:
public Time () {
190
this.hour = 0;
this.minute = 0;
this.second = 0.0;
}
Perhatikan bahwa tidak ada apaapa di antara keyword public dan Time (yang biasanya
terdapat tipe data pengembalian diantaranya) . Inilah salah satu cara yang digunakan
oleh kita (juga compiler) untuk menyatakan bahwa lampiran kode di atas merupakan
sebuah konstruktor.
Konstruktor di atas tidak menggunakan satu argumen pun, sebagaimana yang
diindikasikan oleh tanda kurung yang kosong; ( ). Setiap baris yang terkandung dalam
konstruktor akan menginisialisasi sebuah variabel instan dengan nilai default yang kita
buat sendiri (dalam kasus ini, kita menginisialisasi Time dengan waktu tepat di saat
tengah malam, yaitu 0, 0, dan 0.0). keyword this merupakan kata tercadang khusus yang
merupakan nama dari objek yang sedang kita buat. Anda dapat menggunakan this
seperti anda menggunakan namanama objek lainnya. Sebagai contoh, Anda dapat
membaca dan menulis variabel instan dengan this, lalu Anda juga dapat melewatkan this
sebagai argumen kepada metode lainnya.
Tetapi Anda tidak perlu mendeklarasikan this atau menggunakan perintah new untuk
membuatnya. Kenyataannya, Anda bahkan tidak diizinkan untuk mengassignnya
dengan sesuatu apapun. this merupakan produk sistem Java secara otomatis; yang harus
Anda lakukan hanyalah menyimpan nilainilai ke dalam variabel instan miliknya. Itu
saja!
Suatu bentuk error yang sering dilakukan oleh para pemrogram ketika menulis sebuah
konstruktor yaitu ketika mereka meletakkan perintah return di akhir kode untuk
konstruktor mereka. Hindari kesalahan seperti ini!
191
9.4 Konstruktor Lagi
Konstruktorkonstruktor dalam Java juga dapat berstatus overloading seperti metode
pada lazimnya. Akibatnya, Anda dapat menyediakan beberapa konstruktor sekaligus
dalam sebuah tipe objek dengan parameterparameter yang berbeda. Java mengetahui
konstruktor mana yang dipanggil dengan cara mencocokkan argumen yang digunakan
oleh perintah new dengan parameterparameter yang digunakan oleh konstruktor
konstruktor.
Adalah sesuatu yang biasa dalam Java jika terdapat sebuah definisi kelas yang selain
mengandung konstruktor yang tidak mempunyai satu argumen pun (seperti ditunjukkan
di atas), ia juga mengandung sebuah konstruktor yang menggunakan beberapa
parameter sekaligus yang identik dengan variabelvariabel instan miliknya. Contohnya:
public Time (int hour, int minute, double second) {
this.hour = hour;
this.minute = minute;
this.second = second;
}
Nama dan tipe paramater yang digunakan akan sama persis dengan nama dan tipe dari
variabel instan. Semua konstruktor bertugas untuk menyalin informasi dari parameter
parameter itu ke dalam variabelvariabel instan.
Jika Anda kembali dan melihat dokumentasi untuk Point dan Rectangle, Anda akan
melihat bahwa kedua kelas tersebut menyediakan konstruktor seperti ini. Status
overloading dalam konstruktor dapat memberikan fleksibilitas bagi Anda untuk
membuat sebuah objek terlebih dahulu lalu mengisi kekurangankekurangannya, atau
Anda dapat mengumpulkan semua informasi sebelum membuat suatu objek yang benar
benar sesuai dengan keinginan Anda.
192
Sejauh ini, apa yang sedang kita lakukan mungkin tidaklah terlalu menarik, dan
memang demikianlah adanya. Menulis konstruktor adalah pekerjaan yang
membosankan, maklum saja, proses ini memang dapat dianalogikan seperti proses
mekanis (mereparasi mesin). Sekali saja Anda telah mampu menulis dua konstruktor
sekaligus, Anda akan sadar bahwa tanpa berkedip pun Anda sebenarnya tetap bisa
membuatnya. Cukup dengan memperhatikan daftar variabelvariabel instannya saja.
9.5 Membuat Sebuah Objek yang Baru
Meskipun konstruktorkonstruktor ini tampak seperti sebuah metode, Anda tetap tidak
akan pernah bisa memanggilnya secara langsung. Sebaliknya, ketika Anda
menggunakan perintah new, sistem akan mengalokasikan ruang memori untuk objek
yang baru lalu memanggil konstruktor Anda untuk menginisialisasi variabelvariabel
instannya.
Program berikut ini memperlihatkan kepada kita dua cara untuk membuat dan
menginisialisasi objek Time:
class Time {
int hour, minute;
double second;
public Time () {
this.hour = 0;
this.minute = 0;
this.second = 0.0;
}
public Time (int hour, int minute, double
second) {
193
this.hour = hour;
this.minute = minute;
this.second = second;
}
public static void main (String[] args) {
// one way to create and initialize a Time
object
Time t1 = new Time ();
t1.hour = 11;
t1.minute = 8;
t1.second = 3.14159;
System.out.println (t1);
// another way to do the same thing
Time t2 = new Time (11, 8, 3.14159);
System.out.println (t2);
}
}
Sebagai latihan untuk Anda, cobalah pahami lalu tentukan bagaimana aliran eksekusi
program ini.
Dalam metode main, ketika kali pertama kita memanggil perintah new, kita tidak
memberikan satu argumen pun, akibatnya, Java akan memanggil konstruktor pertama
dari kelas Time di atas. Beberapa baris berikutnya mengassign nilainilai ke setiap
variabelvariabel instan.
194
Pada kesempatan kedua ketika kita memanggil perintah new, kita memberikan
argumenargumen yang cocok dengan parameterparameter dari konstruktor kedua.
Cara penginisialisasian variabel instan seperti ini mungkin lebih jelas (dan efisien), tapi
juga dapat lebih menyulitkan untuk dibaca. Hal ini bisa jadi disebabkan karena tidak
jelasnya nilainilai apa yang akan diassign ke variabel instan yang mana.
9.6 Mencetak Sebuah Objek
Output dari program di atas adalah:
Time@80cc7c0
Time@80cc807
Ketika Java mencetak nilai dari sebuah tipe objek buatan pengguna (userdefined object
type), yang akan dicetaknya adalah nama dari tipe objek tersebut beserta suatu kode
heksadesimal (basis 16) yang unik untuk setiap objek. Kode ini memang tidak artinya;
kenyataannya, hasil ini bisa bervariasi diantara satu mesin dengan mesin lainnya atau
bahkan diantara waktu pemrosesan yang satu dengan waktu pemrosesan yang lainnya.
Meskipun begitu, kita masih tetap dapat memanfaatkannya untuk debugging, misalnya
untuk kasus dimana kita tetap ingin mengawasi keberadaan suatu objek secara
individual.
Untuk mencetak objek yang menghasilkan pesan yang lebih manusiawi (baca: dapat
dimengerti) bagi pengguna, Anda biasanya ingin menulis sebuah metode seperti
printTime:
public static void printTime (Time t) {
System.out.println (t.hour + ":" + t.minute
+
1. ":" + t.second);
195
}
Bandingkanlah metode ini dengan metode printTime yang ada di bagian 3.10.
Output metode ini, jika kita melewatkan t1 atau t2 sebagai argumen adalah
11:8:3.14159. Meskipun bentuk seperti ini kita anggap sebagai sebuah penunjuk waktu,
tapi ia belumlah dapat dikategorikan ke dalam bentuk standar yang sudah kita kenal.
Sebagai contoh, jika angka menit atau detik kurang dari 10, maka kita mengharapkan
adanya awalan berupa angka 0 yang berfungsi sebagai penjagatempat (placekeeper).
Selain itu, kita mungkin ingin membuang angkaangka yang termasuk dalam angka
desimal/pecahan dari detik. Dengan kata lain, kita ingin sesuatu yang tampak seperti
11:08:03.
Dalam lingkungan bahasabahasa pemrograman lainnya, biasanya sudah terdapat cara
sederhana untuk mengendalikan format output yang berupa angkaangka. Namun dalam
Java, tidak ada cara seperti itu.
Java menyediakan tools yang sangat bagus untuk mencetak sesuatu yang sudah
memiliki bentuk standar (formatted things) seperti waktu atau tanggal, juga untuk
menginterpretasikan inputinput terformat (formatted input). Sayangnya, tools ini
tidaklah terlalu mudah untuk digunakan, jadi saya tidak akan membahasnya di dalam
buku ini. Tetapi jika Anda benarbenar ingin menggunakannya, silahkan melihatlihat
dokumentasi mengenai kelas Date dalam paket java.util.
9.7 Operasi Pada Objek
Meskipun kita tidak dapat mencetak waktu dalam format yang optimal, kita masih dapat
menulis sebuah metode yang bisa memanipulasi objek Time. Dalam bagianbagian
berikutnya, saya akan menunjukkan beberapa antarmuka (interface) yang mungkin
dapat digunakan untuk metodemetode yang beroperasi melalui objekobjek. Untuk
196
beberapa operasi, Anda mempunyai beberapa pilihan antarmuka yang bisa digunakan,
jadi Anda harus mempertimbangkan sendiri pro dan kontra untuk setiap pilihan:
● pure function: menggunakan objekobjek/primitif sebagai argumen tetapi tidak
memodifikasi objek. Nilai kembalian berupa primitif atau sebuah objek baru
yang dibuat di dalam metode tersebut.
● Modifier: menggunakan objek sebagai argumen dan memodifikasi beberapa
atau semua bagian di dalamnya. Seringkali mengembalikan void.
● Fillinmethod: salah satu argumennya adalah sebuah objek 'kosong' yang diisi
oleh metode. Secara teknis, sebenarnya ini merupakan salah satu bentuk dari
modifier.
9.8 Fungsifungsi murni
Sebuah metode dianggap sebagai fungsi murni jika hasilnya hanya bergantung kepada
argumenargumen, dan pada saat yang bersamaan dia tidak memberikan efek samping;
seperti memodifikasi sebuah argumen atau mencetak sesuatu. Satusatunya hasil dari
melakukan pemanggilan terhadap sebuah fungsi murni adalah nilai kembalian itu
sendiri.
Salah satu contohnya adalah after, yang membandingkan dua buah waktu, lalu
mengembalikan nilai bertipe boolean yang mengindikasikan apakah operan pertama
berada di belakang operan kedua:
public static boolean after (Time time1, Time
time2) {
if (time1.hour > time2.hour) return true;
if (time1.hour < time2.hour) return false;
if (time1.minute > time2.minute) return true;
197
if (time1.minute < time2.minute) return false;
if (time1.second > time2.second) return true;
return false;
}
Apa yang akan dihasilkan oleh metode ini jika kedua waktu adalah sama? Apakah hasil
tersebut merupakan hasil yang tepat dan sesuai dengan apa yang kita harapkan dari
metode ini? Jika Anda menulis dokumentasi untuk metode ini, maukah Anda
menyebutkan kasus tersebut secara spesifik?
Contoh kedua adalah addTime, yang mengkalkulasi jumlah dari dua buah waktu.
Sebagai contoh, jika sekarang waktu menunjukkan 9:14:30, dan pemanggang roti Anda
membutuhkan 3 jam lebih 35 menit, Anda dapat menggunakan addTime untuk
menentukan kapan roti Anda akan selesai dipanggang:
public static Time addTime (Time t1, Time t2) {
Time sum = new Time ();
sum.hour = t1.hour + t2.hour;
sum.minute = t1.minute + t2.minute;
sum.second = t1.second + t2.second;
return sum;
}
Meskipun metode ini mengembalikan nilai yang berupa objek waktu, ia bukanlah
sebuah konstruktor. Anda harus kembali lagi untuk membandingkan sintaks dari metode
seperti ini dengan sintaks sebuah konstruktor. Mengapa? Karena kita seringkali menjadi
terlalu mudah untuk kebingungan.
Berikut ini adalah sebuah contoh mengenai bagaimana cara menggunakan metode ini.
198
Jika currentTime berisikan waktu saat ini, sementara breadTime mengandung total
waktu yang dibutuhkan oleh pembuatroti Anda untuk membuat roti yang Anda
inginkan, maka Anda harus menggunakan addTime untuk menentukan kapan roti
tersebut akan selesai dibuat.
Time currentTime = new Time (9, 14, 30.0);
Time breadTime = new Time (3, 35, 0.0);
Time doneTime = addTime (currentTime, breadTime);
printTime (doneTime);
Output dari program ini adalah 12:49:30.0, yang berarti sesuai dengan apa yang kita
inginkan. Namun di sisi lain, ada kasuskasus tertentu di mana hasil yang dikeluarkan
ternyata tidak sesuai. Bisakah Anda menunjukkan salah satunya?
Masalahnya adalah bahwa metode ini ternyata tetap menjumlahkan dan menampilkan
nilai detik dan menit meskipun nilai keduanya telah melampaui angka 60. Untuk
mengatasinya, kita harus berupaya agar jumlah detik setelah angka 60 bisa ditambahkan
kepada nilai menit (+1 menit). Begitu juga dengan jumlah menit, setiap menembus
angka 60, nilai menit tersebut akan ditambahkan ke dalam jam (+1 jam).
Kode berikut ini merupakan perbaikan dari kode di atas:
public static Time addTime (Time t1, Time t2) {
Time sum = new Time ();
sum.hour = t1.hour + t2.hour;
sum.minute = t1.minute + t2.minute;
sum.second = t1.second + t2.second;
if (sum.second >= 60.0) {
sum.second = 60.0;
sum.minute += 1;
199
}
if (sum.minute >= 60) {
sum.minute = 60;
sum.hour += 1;
}
return sum;
}
Meskipun kode di atas sudah benar, tapi ia akan menjadi semakin kompleks. Nanti, saya
akan menunjukkan sebuah pendekatan alternatif yang lebih singkat untuk
menyelesaikan masalahmasalah seperti ini.
Kode di atas menampilkan dua operator yang belum kita lihat sebelumnya, yakni ' += '
dan ' = '. Kedua operator ini memberikan cara yang jelas untuk menaikkan (increment)
atau menurunkan (decrement) nilai variabelvariabel. Keduanya hampir sama dengan '
++ ' dan ' ' kecuali bahwa:
1. Keduanya dapat digunakan untuk operasi variabel bertipe double, selain untuk
integer.
2. Derajat kenaikan atau penurunan tidak harus 1. Pernyataan
sum.second = 60.0; sama dengan sum.second = sum.second – 60;
9.9 Modifier
Untuk mencontohkan suatu modifier, perhatikanlah metode increment berikut! Metode
ini menambahkan nilai detik ke dalam objek Time. Lagilagi, bentuk mentah dari
metode ini akan tampak sebagai berikut:
public static void increment (Time time, double
secs) {
200
time.second += secs;
if (time.second >= 60.0) {
time.second = 60.0;
time.minute += 1;
}
if (time.minute >= 60) {
time.minute = 60;
time.hour += 1;
}
}
Baris pertama mengerjakan operasi dasar; remainder (nilai pengingat) bekerja dengan
cara yang sama seperti pada kasus yang sudah kita lihat sebelumnya.
Sudah benarkah metode ini? Apa yang terjadi jika argumen secs lebih besar daripada
60? Dalam kasus ini, tidak cukup mengurangi nilai 60 sebanyak satu kali saja. Kita
harus terus mengurangi 60 sampai second bernilai kurang dari 60. kita dapat
melakukannya dengan hanya mengganti perintah if dengan while:
public static void increment (Time time, double
secs) {
time.second += secs;
while (time.second >= 60.0) {
time.second = 60.0;
time.minute += 1;
}
201
while (time.minute >= 60) {
time.minute = 60;
time.hour += 1;
}
}
Solusi ini sudah tepat, tapi tampaknya tidak terlalu efisien. Bisakah Anda memberikan
sebuah solusi yang tidak membutuhkan bentukbentuk perulangan (iteration)?
9.10 Fillin Methods
Umumnya Anda akan jarang sekali menemui metode seperti addTime ditulis dengan
antar muka yang berbeda (dari segi argumen dan nilai kembaliannya). Ketimbang
membuat sebuah objek baru setiap kali addTime dipanggil, kita bisa menyuruh si
pemanggil (the caller) untuk menyediakan objek 'kosong' sebagai tempat bagi addTime
untuk menyimpan hasilnya. Bandingkan kode berikut ini dengan kode sebelumnya:
public static void addTimeFill (Time t1, Time t2,
Time sum) {
sum.hour = t1.hour + t2.hour;
sum.minute = t1.minute + t2.minute;
sum.second = t1.second + t2.second;
if (sum.second >= 60.0) {
sum.second = 60.0;
sum.minute += 1;
}
if (sum.minute >= 60) {
sum.minute = 60;
202
sum.hour += 1;
}
}
Satu keuntungan dari pendekatan seperti ini adalah bahwa si pemanggil memiliki
kesempatan/opsi untuk menggunakan objek yang sama secara berulangulang untuk
melakukan operasi penjumlahan yang beruntun. Pendekatan ini mungkin sedikit lebih
efisien, meskipun dapat membingungkan juga ketika malah menyebabkan error. Di
dalam dunia pemrograman, banyak yang lebih memilih untuk menghabiskan waktunya
demi sebuah program dengan run time yang pendek (baca: cepat) ketimbang
menghabiskan waktunya untuk mendebug kode program yang panjang.
9.11 Mana yang terbaik?
Semua yang dapat dilakukan dengan menggunakan modifier dan fillinmethod dapat
juga dikerjakan menggunakan fungsi murni. Dalam kenyataannya, ada sebuah bahasa
pemrograman, yang disebut sebagai bahasa pemrograman fungsional, yang hanya
mengizinkan penggunaan fungsifungsi murni saja. Beberapa pemrogram percaya
bahwa program yang dibangun dengan menggunakan fungsi murni akan lebih cepat
untuk dikembangkan dan lebih kecil risiko errornya ketimbang program yang dibangun
dengan menggunakan modifier. Meskipun begitu, tetap ada saatsaat dimana modifier
lebih sesuai untuk diterapkan, dan saatsaat dimana program fungsional menjadi tidak
efisien.
Secara umum, saya merekomendasikan Anda untuk menulis fungsi murni saja di
manapun Anda berkesempatan untuk menulisnya, lalu berganti ke modifier ketika Anda
melihat keuntungan besar di dalamnya. Pendekatan seperti inilah yang mungkin disebut
dengan teknik pemrograman fungsional.
203
9.12 Pengembangan secara bertahap vs perencanaan
Dalam bab ini saya telah menunjukkan sebuah pendekatan untuk mengembangkan
sebuah program yang saya sendiri menyebutnya sebagai rapid prototyping with
iterative improvement. Dalam tiap kasusnya, saya selalu menuliskan satu bentuk
mentah (atau prototipe) yang berfungsi untuk mengerjakan kalkulasi dasar, lalu
mengujinya dalam beberapa kasus, untuk kemudian langsung memperbaiki
kesalahan/kelemahan yang terdapat di dalamnya.
Walaupun efektif, pendekatan ini juga dapat menyebabkan lahirnya kode yang tidak
perlu sekaligus membingungkan – karena digunakan untuk menyelesaikan banyak kasus
khusus juga tidak dapat dipercaya (unreliable) – karena Anda sendiri masih belum
bisa yakin apakah Anda telah menemukan semua error yang ada.
Alternatif yang ada diantaranya adalah highlevel programming (pemrograman tingkat
tinggi). Dalam teknik ini, Anda harus benarbenar mengandalkan deepthinking Anda
terhadap suatu kasus. Untuk kasus Time di atas, deepthinking ini berarti Anda
menganggap bahwa Time merupakan angka tigadigit dalam basis 60! Artinya, second
adalah “kolom satuan”, minute adalah “kolom 60an”, lalu hour adalah “kolom 3600
an”.
Ketika kita menulis addTime dan increment, sebenarnya kita juga sedang mengerjakan
penjumlahan dalam basis 60. Inilah yang menjadi penyebab sehingga kita memerlukan
“carry” dari satu kolom ke kolom berikutnya.
Dus, pendekatan alternatif yang akan kita gunakan untuk menyelesaikan semua masalah
ini adalah dengan cara mengkonversikan Time ke bentuk double. Hal ini kita lakukan
karena sebuah fakta bahwa komputer memang sudah “mengerti” cara mengoperasikan
aritmetika untuk bentuk double. Berikut ini adalah sebuah metode yang
mengkonversikan objek Time ke bentuk double:
204
public static double convertToSeconds (Time t) {
int minutes = t.hour * 60 + t.minute;
double seconds = minutes * 60 + t.second;
return seconds;
}
Sekarang, apa yang perlu kita kerjakan adalah menemukan cara untuk mengubah bentuk
double ke bentuk Time. Sebenarnya kita bisa saja menulis sebuah metode untuk
melakukannya, tapi akan lebih masuk akal jika kita menuliskannya sebagai konstruktor
ketiga:
public Time (double secs) {
this.hour = (int) (secs / 3600.0);
secs = this.hour * 3600.0;
this.minute = (int) (secs / 60.0);
secs = this.minute * 60;
this.second = secs;
}
Konstruktor ini memang tampak sedikit berbeda dengan yang lainnya, mungkin karena
ia melibatkan sejumlah kalkulasi dan assignment terhadap variabelvariabel instan.
Anda mungkin harus berpikir lebih dalam lagi untuk meyakinkan diri Anda sendiri
bahwa teknik yang saya gunakan untuk mengubah satu basis ke dalam basis lainnya
adalah benar. Jika Anda sudah yakin, maka kita dapat menggunakan metode berikut
untuk menulis ulang metode addTime:
public static Time addTime (Time t1, Time t2) {
double seconds = convertToSeconds (t1) +
convertToSeconds (t2);
return new Time (seconds);
205
}
Kode di atas lebih singkat daripada versi aslinya. Selain itu, saya juga lebih mudah
untuk menunjukkan kepada Anda bahwa metode ini memang benar (anggaplah, seperti
biasa, bahwa metodemetode yang dipanggilnya juga benar). Untuk latihan, cobalah
menulis ulang increment dengan cara yang sama.
9.13 Generalisasi
Dalam beberapa kasus, mengubah satu bentuk dari basis 60 ke basis 10 atau sebaliknya
adalah lebih sulit daripada berpacu dengan waktu itu sendiri. Konversi suatu basis itu
lebih abstrak; bahkan intuisi kita terhadap “berpacu dengan waktu” itu sendiri lebih
baik.
Namun jika kita mempunyai pemahaman mendalam untuk memperlakukan waktu
sebagai angka berbasis 60, lalu membuat “investasi” dengan cara menulis metode
pengubah/konverter (convertToSecond dan konstruktor ketiga), maka kita akan
mendapatkan program yang lebih pendek, lebih enak untuk dibaca dan didebug, juga
lebih andal.
Kita juga akan lebih mudah untuk menambahkan beberapa fitur di kesempatan lainnya.
Misalnya, Anda membayangkan untuk mengurangi dua Time lalu mencari durasi/tempo
antara keduanya. Pendekatan yang naif adalah dengan mengimplementasikan suatu
pengurangan yang lengkap dengan teknik “peminjaman”. Menggunakan metode
pengubah akan lebih mudah.
Ironisnya, terkadang tidak menganggap ringan suatu masalah justru akan membuat
masalah itu menjadi 'ringan' dengan sendirinya. (semakin kecil kasuskasus khusus,
maka akan semakin kecil pula kesempatan untuk membuat error)
206
9.14 Algoritma
Ketika Anda menulis sebuah solusi umum untuk suatu masalah, sebagai lawan dari
solusi spesifik untuk suatu masalah khusus, sebenarnya Anda sudah menulis apa yang
disebut dengan algoritma. Saya menyebut kata ini dalam bab 1, tapi tidak
mendefinisikannya secara mendalam. Tidaklah mudah untuk mendefinisikannya, jadi
saya akan mencoba beberapa pendekatan.
Pertama, pikirkanlah beberapa hal yang tidak termasuk algoritma. Sebagai contoh,
ketika Anda telah belajar untuk mengalikan beberapa angka berdigit tunggal, Anda
mungkin ingat akan tabel perkalian. Sebagai dampaknya, Anda mengingat 100 solusi
khusus, jadi pengetahuan Anda tersebut belumlah termasuk ke dalam apa yang disebut
dengan algoritma.
Tapi jika Anda adalah seorang “pemalas”, Anda mungkin akan mencuranginya dengan
mengambil beberapa trik di dalamnya. Sebagai contoh, untuk menemukan hasil
perkalian n dengan 9, Anda dapat menulis n – 1 sebagai digit pertama dan 10 – n
sebagai digit kedua. Trik ini merupakan solusi umum untuk mengalikan sebua angka
berdigit tunggal dengan 9. Nah, ini baru algoritma!
Dengan cara yang sama, teknikteknik yang sudah Anda pelajari ketika melakukan
penjumlahan dengan “penyimpanan”, pengurangan dengan peminjaman, dan pembagian
yang panjang juga termasuk algoritma. Salah satu sifat algoritma adalah bahwa ia sama
sekali tidak membutuhkan kecerdasan di dalamnya. Ia merupakan proses mekanik
dimana setiap proses, dari awal hingga akhir, dibuat berdasarkan sekumpulan aturan
tertentu.
Menurut pendapat saya, adalah memalukan bagi seorang manusia untuk menghabiskan
begitu banyak waktu di sekolah hanya untuk mengeksekusi algoritma yang, secara
harfiah, tidak melibatkan kecerdasan sama sekali.
207
Justru, proses ketika merancang algoritma itu sendirilah yang menarik, menantang
intelektual, sekaligus sebagai pusat dari apa yang kita sebut dengan programming.
Beberapa hal yang dilakukan oleh manusia secara alamiah, tanpa kesulitan atau pun
kesadaran pikiran, adalah hal yang paling sulit untuk diterjemahkan dengan algoritma.
Memahami bahasa natural adalah salah satu contoh yang bagus. Kita semua memang
melakukannya, tapi sampai sejauh ini, belum ada satu orang pun yang mampu
menjelaskan bagaimana cara kita melakukannya, setidaknya dalam bentuk algoritma.
Nantinya, Anda akan berkesempatan untuk meracang suatu algoritma sederhana untuk
beberapa masalah.
9.15 Daftar KataKata
Istilah Arti
Class (kelas)
Sebelumya, saya telah mendefinisikan kelas sebagai sebuah
koleksi/kumpulan dari metodemetode yang saling berkaitan.
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
(konstruktor)
Metode khusus yang menginisialisasi variabelvariabel instan
dari objek yang baru akan dibuat.
Project (proyek)Sebuah koleksi dari satu atau lebih definisi kelas (satu kelas
per satu file) yang membentuk sebuah program.
Startup class Kelas yang mengandung metode main, tempat di mana
208
Istilah Arti
eksekusi program dimulai.
Function (fungsi)
Sebuah metode yang hasilnya bergantung kepada paramater
parameternya sendiri, dan tidak memberikan dampak apa pun
selain nilai yang dikembalikannya.
modifierSuatu metode yang mengubah satu atau lebih objek yang
diterimanya sebagai parameter, biasanya mengembalikan void.
Fillinmethod
Suatu jenis metode yang mengambil sebuah objek “kosong”
sebagai sebuah parameter lalu mengisi variabelvariabel
instannya ketimbang membuat suatu nilai kembalian. Metode
jenis ini biasanya bukanlah pilihan terbaik untuk digunakan.
algoritmaSuatu himpunan aturan yang digunakan untuk menyelesaikan
suatu masalah dengan proses mekanis.
9.16 Latihan
Latihan 9.1
Dalam permainan Scrabble1, setiap petak mengandung sebuah huruf, yang bisa
digunakan untuk mengeja sebuah kata, lalu sebuah nilai (score), yang digunakan untuk
menentukan nilai dari setiap kata.
a) Buatlah sebuah definisi kelas dengan nama Tile yang merepresetasikan petakpetak
Scrabble. Variabelvariabel insatannya haruslah sebuah karakter dengan nama letter
dan sebuah integer dengan nama value.
b) Tulislah sebuah konstruktor yang menggunakan parameterparameter dengan nama
letter dan value lalu inisialisasilah variabelvariabel instannya.
c) Tulislah sebuah metode dengan nama printTile yang menggunakan satu objek Tile
209
sebagai parameter lalu mencetak variabelvariabel instannya dalam format yang enak
dan mudah untuk dibaca.
d) Tulislah sebuah metode dengan nama testFile yang dapat menghasilkan satu objek
Tile dengan huruf Z dan nilai 10, lalu gunakan printTile untuk mencetak keadaan
(the state) objek tersebut.
Inti dari latihan ini adalah untuk melatih Anda memahami sisisisi mekanis pembuatan
definisi kelas yang baru sekaligus membuat kode yang digunakan untuk mengujinya.
Latihan 9.2
Tulislah sebuah definisi kelas untuk Date, sebuah tipe objek yang berisi tiga integer,
yakni year, month, dan day. Kelas ini harus menyediakan dua konstruktor. Konstruktor
Pertama tidak boleh menggunakan parameter apapun. Sedangkan konstruktor kedua
harus menggunakan year, month, dan day sebagai parameter, kemudian menggunakan
ketiganya untuk menginisialisasi variabelvariabel instan.
Tambahkan kode ke dalam main yang membuat sebuah objek Date baru dengan nama
birthday. Objek yang baru tersebut harus berisi tanggal lahir Anda. Anda dapat
menggunakan kedua konstruktor tersebut.
Latihan 9.3
Sebuah bilangan rasional (rational number) adalah suatu bilangan yang dapat
ditampilkan sebagai suatu rasio (perbadingan) dari dua integer. Contohnya; 2/3 adalah
bilangan rasional, dan Anda pun dapat menganggap 7 sebagai bilangan rasional dengan
implisit 1 dalam denominatornya. Untuk tugas kali ini, Ada akan menulis sebuah
definisi kelas untuk bilangan rasional.
a) Perhatikan kode berikut ini dan pastikan Anda dapat memahami apa yang
dikerjakannya:
public class Complex
{
210
double real, imag;
// simple constructor
public Complex () {
this.real = 0.0; this.imag = 0.0;
}
// constructor that takes arguments
public Complex (double real, double imag) {
this.real = real; this.imag = imag;
}
public static void printComplex (Complex c) {
System.out.println (c.real + " + " + c.imag +
"i");
}
// conjugate is a modifier
public static void conjugate (Complex c) {
c.imag = c.imag;
}
// abs is a function that returns a primitive
public static double abs (Complex c) {
return Math.sqrt (c.real * c.real + c.imag * c.imag);
}
// add is a function that returns a new Complex object
public static Complex add (Complex a, Complex b) {
return new Complex (a.real + b.real, a.imag + b.imag);
}
public static void main(String args[]) {
// use the first constructor
Complex x = new Complex ();
211
x.real = 1.0;
x.imag = 2.0;
// use the second constructor
Complex y = new Complex (3.0, 4.0);
System.out.println (Complex.abs (y));
Complex.conjugate (x);
Complex.printComplex (x);
Complex.printComplex (y);
Complex s = Complex.add (x, y);
Complex.printComplex (s);
}
}
b) Buatlah sebuah program dengan nama Rational.java yang mendefinisikan sebuah
kelas dengan nama Rational. Suatu objek Rational haruslah memiliki 2 variabel
instan berupa integer yang digunakan untuk menyimpan nilai numerator dan
denumerator dari bilangan rasional.
c) Tulislah sebuah konstruktor tanpa parameter yang menginisialisasi kedua variabel
instan tersebut dengan nilai 0.
d) Tulislah sebuah metode dengan nama printRational yang menggunakan objek
Rational sebagai sebuah argumen lalu mencetaknya ke dalam format yang dapat
diterima oleh Anda sendiri.
e) Tulislah sebuah metode main yang mampu menghasilkan sebuah objek baru bertipe
Rational, inisialisasilah variabelvariabel instannya dengan nilainilai tertentu, lalu
cetaklah objek tersebut.
f) Sampai sejauh ini, Anda telah membuat programprogram yang dapat diuji
(debuggable), meski dalam jumlah yang tidak terlalu banyak. Sekarang ujilah semua
212
program itu, lalu debuglah jika memang perlu.
g) Tulislah sebuah konstruktor kedua untuk kelas Anda. Kelas ini menggunakan dua
argumen yang sekaligus berfungsi untuk menginisialisasi variabelvariabel instan.
h) Tulislah sebuah metode dengan nama negate yang mengubah tanda dari suatu
bilangan rasional. Metode ini haruslah termasuk ke dalam tipe modifier yang akan
mengembalikan void. Tambahkan barisbaris ke dalam metode main untuk menguji
metode baru itu.
i) Tulislah sebuah metode dengan nama invert yang membalik angka dengan cara
menukar (swapping) numerator dengan denumerator. Ingatingatlah pola swapping
yang telah kita lihat sebelumnya. Ujilah metode ini dengan menambahkan beberapa
baris kode dalam metode main.
j) Tulislah sebuah metode dengan nama toDouble yang dapat mengubah bilangan
rasional ke bentuk double (bilangan floatingpoint) sekaligus mengembalikan
hasilnya. Metode ini termasuk ke dalam kelompok fungsi murni; jadi ia tidak akan
mengubah atau memodifikasi objek yang dioperasikannya. Lalu seperti biasanya,
ujilah metode yang baru Anda buat itu.
k) Tulislah sebuah modifier dengan nama reduce yang dapat mengurangi sebuah
bilangan rasional sampai ke bentuknya (term) yang terkecil dengan cara menemukan
GCD dari numerator dan denumerator untuk kemudian membagi atas dan bawah
dengan GCD. Metode ini seharusnya berupa fungsi murni yang tidak mengubah
variabelvariabel instan milik objek tempat dimana dia dipanggil.
l) Tulislah sebuah metode dengan nama add yang menggunakan dua bilangan rational
sebagai argumen dan mengembalikan sebuah objek Rational yang baru. Objek yang
dikembalikan, jelas, harus mengandung jumlah dari kedua argumen tersebut.
Tujuan dari latihan ini adalah untuk menulis sebuah definisi kelas yang melibatkan
berbagai jenis metode sekaligus yakni; konstruktor, modifier, dan fungsifungsi murni.
213
Bab 10
Array
Array adalah suatu kumpulan nilai yang diidentifikasi dengan sebuah indeks. Anda
dapat membuat sebuah array berisi nilainilai yang bertipe int, double, atau tipe lainnya.
Tapi ingat, semua nilai tersebut harus sama tipenya.
Dilihat dari sintaksnya, tipe array tampak seperti tipe Java lainnya kecuali bahwa tipe
ini diikuti dengan simbol ' [ ] '. sebagai contoh, int [ ] adalah tipe array untuk integer dan
double [ ] adalah tipe array untuk double.
Anda dapat mendeklarasikan variabelvariabel bertipe ini dengan cara yang sudah lazim
digunakan:
int[] count;
double[] values;
Sampai Anda menginisialisasi variabelvariabel ini, semua akan diinisialisasi dengan
null. Untuk membuat array, gunakan perintah new.
count = new int[4];
values = new double[size];
Assignment pertama membuat variabel count akan mengacu pada sebuah array yang
berisi empat nilai bertipe integer. Sementara Assignment kedua akan menyebabkan
values mengacu pada suatu array bertipe double. Jumlah elemen dalam variabel values
bergantung kepada size. Anda dapat menggunakan ekspresi integer apapun untuk
membuat ukuran array.
Gambar berikut ini menunjukkan representasi array dalam diagram keadaan.
215
Nomor yang dicetak tebal di dalam kotak merupakan elemen dari array. Sedangkan
nomornomor yang berada di luar kotak merupakan indeks yang digunakan untuk
mengidentifikasi setiap kotak. Ketika Anda mengalokasikan sebuah array baru, elemen
elemennya akan diinisialisasi ke nol (0).
10.1 Mengakses Elemen
Untuk menyimpan nilainilai di dalam array, gunakanlah operator []. Sebagai contoh,
count [0] akan mengacu kepada elemen ke0 dari array tersebut. Sedangkan count [1]
akan mengacu kepada elemen ke1 array tersebut.
Anda dapat menggunakan operator [] dimanapun di dalam sebuah ekspresi:
count[0] = 7;
count[1] = count[0] * 2;
count[2]++;
count[3] = 60;
Semua pernyataan di atas merupakan bentuk assignment yang legal dalam Java. Berikut
ini adalah pengaruh yang ditimbulkan oleh lampiran kode di atas:
Mulai sekarang Anda harus memperhatikan bahwa keempat elemen dalam array ini
diindeks mulai dari 0 sampai dengan 3. Ini berarti bahwa tidak ada elemen dengan
nomor indeks yang sama dengan 4. Anda semestinya sudah terbiasa dengan bentuk
bentuk seperti ini karena sebelumnya kita sudah melihat hal yang sama terjadi pada
penomoran indeks untuk variabel bertipe data String. Meskipun begitu, terkadang kita
seringkali melakukan kesalahan dengan menggunakan nomor indeks yang di luar batas
array itu sendiri. Kesalahan semacam ini akan menghasilkan sebuah eksepsi, yakni
ArrayOutOfBoundsExceptions. Laiknya yang terjadi pada kemuculan semua eksepsi,
216
Anda juga akan mendapatkan sebuah pesan error yang akan menyebabkan terhentinya
eksekusi program Anda.
Anda dapat menggunakan ekspresi apapun sebagai suatu indeks, selama ia bertipe data
int. Salah satu cara yang paling umum untuk mengindeks sebuah array adalah dengan
menggunakan variabel perulangan (loop variable). Simaklah contoh berikut ini:
int i = 0;
while (i < 4) {
System.out.println (count[i]);
i++;
}
Bentuk ini merupakan bentuk perulangan while yang lazim digunakan untuk
menghitung indeks mulai dari indeks 0 hingga 4. ketika variabel perulangan, i, berisi
nilai 4, syarat perulangan tersebut akan menghasilkan kesalahan. Kesalahan ini
menyebabkan terhentinya eksekusi program. Oleh karena itu, badan dari perulangan
tersebut hanya dieksekusi ketika i sama dengan 0, 1, 2 dan 3.
Setiap kali kita melewati perulangan di atas, kita menggunakan variabel i sebagai indeks
untuk array. Bentuk penelusuran array seperti ini merupakan bentuk yang paling sering
digunakan. Array dan perulangan memadu bersama laksana memakan sate madura yang
gurih dengan wedang jahe manis nan hangat.
10.2 Menyalin Array
Ketika Anda menyalin sebuah variabel bertipe array, ingatlah bahwa Anda sebenarnya
sedang menyalin sebuah acuan kepada array tersebut. Sebagai contoh:
double[] a = new double [3];
double[] b = a;
217
Kode ini akan membuat sebuah array yang diisi dengan tiga elemen bertipe double, lalu
membuat dua variabel yang berbeda mengacu kepadanya. Situasi ini merupakan salah
satu bentuk aliasing.
Perubahan apapun yang Anda lakukan pada satu array akan memberikan pengaruh
kepada array lainnya. Mungkin ini bukanlah perilaku yang Anda inginkan; sebaliknya,
Anda harus membuat sebuah salinan dari array, dengan mengalokasikan sebuah array
baru dan menyalin setiap elemen dari astu array ke array lainnya.
double[] b = new double [3];
int i = 0;
while (i < 4) {
b[i] = a[i];
i++;
}
10.3 Bentuk Perulangan for
Perulanganperulangan yang sudah kita tulis sejauh ini merupakan perulangan yang
sudah terlalu sering kita lihat. Semuanya dimulai dengan menginisialisasi sebuah
variabel; menggunakan suatu test, atau sebuah persyaratan, yang bergantung kepada
variabel itu sendiri; lalu di dalam perulangan itu mereka mengerjakan sesuatu untuk
variabel tersebut, seperti misalnya melakukan increment untuknya.
Ada sebuah bentuk perulangan alternatif bagi bentuk sangat umum seperti di atas, yakni
perulangan for. Bentuk ini mampu mengekspresikan perulangan secara tepat dan lugas.
Bentuk umum sintaksnya tampak sebagai berikut:
for (INITIALIZER; CONDITION; INCREMENTOR) {
BODY
218
}
Pernyataan ini eqivalen dengan
INITIALIZER;
while (CONDITION) {
BODY
INCREMENTOR
}
Selain lebih lugas, bentuk ini juga lebih mudah untuk dibaca. Ini disebabkan karena
semua pernyataan yag terkait dengan perulangan itu sendiri ditempatkan di dalam satu
baris. Sebagai contoh:
for (int i = 0; i < 4; i++) {
System.out.println (count[i]);
}
Eqivalen dengan
int i = 0;
while (i < 4) {
System.out.println (count[i]);
i++;
}
Untuk latihan Anda, tulislah sebuah perulangan for yang mampu menyalin elemen
elemen yang terdapat dalam sebuah array.
219
10.4 Array dan Objek
Dalam beberapa kesempatan, array bertingkahlaku laiknya objek:
● ketika Anda mendeklarasikan sebuah variabel array, Anda akan mendapatkan
sebuah acuan/referensi ke sebuah array.
● Anda harus menggunakan perintah new untuk membuat sebuah array.
● Ketika Anda melewatkan sebuah array sebagai argumen, Anda berarti
melewatkan sebuah acuan, yang berarti bahwa metode yang Anda panggil dapat
mengubah elemen yang dikandung array.
Beberapa objek yang sudah kita lihat, seperti Rectangles, dapat disamakan dengan
array, dalam artian bahwa keduanya merupakan kumpulan dari nilainilai yang diberi
nama. Hal ini mengundang sebuah pertanyaan, “Apa beda array dengan 4 elemen
bertipe integer dengan sebuah objek bertipe Rectangle?”;
Jika Anda melihat kembali definisi “array” di awal bab, Anda akan menemukan sebuah
perbedaan, yakni bahwa elemen dari sebuah array diidentifikasi menggunakan indeks,
sementara elemenelemen (variabel instan) dari suatu objek menggunakan nama, seperti
x, width, dst.
Perbedaan lain antara array dan objek adalah bahwa semua elemen dari sebuah array
haruslah bertipe sama. Meskipun hal yang sama juga terjadi pada objek Rectangle,
namun pada kesempatan lain kita juga mengetahui bahwa ada objek lain yang
mempunyai variabelvariabel instan dengan tipe data yang berbedabeda. Coba lihat
kembali objek Time.
10.5 Panjang Array
Sebenarnya, array sudah memiliki satu variabel instan, yakni length. Mudah diterka,
variabel ini berisi informasi (nilai) mengenai panjang suatu array (jumlah elemen
220
elemen). Adalah ide brilian untuk menggunakan nilai variabel instan ini sebagai batas
atas untuk sebuah perulangan ketimbang menggunakan nilai yang konstan. Dengan
variabel ini, jika ukuran array suatu array mengalami perubahan, maka Anda tidak perlu
lagi bersusah payah mengubah semua pernyataan yang terkait dengan perulangan; ini
akan bekerja dan berfungsi untuk semu ukuran array.
for (int i = 0; i < a.length; i++) {
b[i] = a[i];
}
Eksekusi terakhir yang terjadi dalam badan perulangan di atas adalah ketika i sama
dengan a.length – 1. ekspresi ini merupakan indeks untuk elemen terakhir array
tersebut. Ketika i sama dengan a.length, syarat perulangan akan bernilai salah sehingga
badan perulangan tidak akan dieksekusi lagi. Hal ini menguntungkan untuk kita karena
jika ekspresi tersebut sampai dieksekusi maka eksepsi tidak akan dapat dihindari.
Lampiran kode ini mengasumsikan bahwa array b mempunyai jumlah elemen yang
sama dengan array a.
Sebagai latihan, tulislah sebuah metode cloneArray yang menggunakan sebuah array
bertipe integer sebagai parameter. Metode ini nantinya akan mampu menghasilkan
sebuah array baru yang memiliki ukuran yang sama, menyalin elemenelemen dari
array pertama ke array yang baru, lalu mengembalikan nilai kembalian yang berupa
acuan ke array baru tersebut.
10.6 AngkaAngka Acak (Random)
Programprogram komputer pada umumnya selalu mengerjakan hal yang sama setiap
kali mereka dieksekusi. Sifat seperti ini membuat programprogram komputer pantas
disebut sebagai sesuatu yang deterministik. Biasanya, sesuatu yang determinis adalah
221
hal yang baik karena kita cenderung untuk mengharapkan perhitungan yang sama untuk
menghasilkan hasil yang sama pula. Namun untuk beberapa aplikasi, ternyata kita lebih
menginginkan sebuah komputer yang memiliki sifat tidak bisa diterka (unpredictable).
Permainanpermainan (games) yang dimainkan dengan sebuah komputer adalah contoh
yang jelas, tapi masih banyak lagi contoh yang lain tentunya.
Membuat sebuah program yang benarbenar bersifat nondeterministik adalah perkara
yang sangat tidak mudah. Namun tetap ada cara untuk membuat sebuah program agar
tampak bersifat nondeterministik. Salah satu cara adalah dengan menggunakan angka
angka acak lalu memanfaatkannya untuk menentukan keluaran/hasil sebuah program.
Java menyediakan metode bawaan yang dapat membuat angkaangka pseudorandom
(imitasi terhadap definisi angka random sesungguhnya). Angkaangka ini sebenarnya
tidaklah sungguhsungguh random secara matematis, tapi untuk tujuan yang hendak kita
capai, mereka tampaknya bisa mengerjakan apa yang kita maksudkan.
Periksalah dokumentasi untuk metode random dalam kelas Math. Nilai kembalian
merupakan angka bertipe double antara 0.0 dan 1.0. untuk lebih jelasnya; angkaangka
ini lebih besar atau sama dengan 0.0 dan kurang dari 1.0. Setiap kali Anda memanggil
metode random, Anda akan medapatkan angka berikutnya dalam urutan yang bersifat
pseudorandom (alias tidak terurut). Untuk melihat contohnya, eksekusilah perulangan
ini:
for (int i = 0; i < 10; i++) {
double x = Math.random ();
System.out.println (x);
}
Untuk menghasilkan bilangan acak bertipe double antara 0.0 dengan batas atas yang
berupa variabel seperti high, Anda dapat mengalikan x dengan high.
222
Bagaimana cara Anda membuat sebuah bilangan acak antara variabel low dengan
high? Bagaimana cara Anda membuat bilangan acak bertipe integer?
Latihan 10.1
Tulislah sebuah metode randomDouble yang menggunakan dua variabel bertipe double,
yakni low dan high, yang mampu mengembalikan nilai acak bertipe double, yakni x
dimana low ≤ x < high.
Latihan 10.2
Tulislah sebuah metode randomInt yang menggunakan dua argumen, yakni low dan
high, yang mampu mengembalikan nilai acak bertipe integer diantara low dan high
dimana low ≤ x ≤ high.
10.7 Array dari Angkaangka Random (Acak)
Jika implementasi metode randomInt buatan Anda berhasil, maka setiap nilai dalam
interval (range) antara low dan high harus memiliki probabilitas kemunculan yang
sama. Jika Anda membuat sebuah deretan angka yang panjang, maka setiap nilai harus
muncul, setidaknya mendekati, angka yang sama setiap kali kemunculannya.
Salah satu cara untuk menguji metode Anda adalah dengan membuat angkaangka acak
dalam jumlah yang banyak, lalu menyimpannya ke dalam array, untuk kemudian
menghitung jumlah kemunculan setiap nilai yang telah ditampilkan.
Metode berikut ini menggunakan satu argumen, yakni ukuran array. Metode ini
mengalokasikan sebuah array baru yang berisi elemenelemen bertipe integer.
Kemudian mengisinya dengan bilanganbilangan acak, lalu mengembalikan sebuah
acuan/referensi kepada array yang baru.
public static int[] randomArray (int n) {
223
int[] a = new int[n];
for (int i = 0; i<a.length; i++) {
a[i] = randomInt (0, 100);
}
return a;
}
Tipe kembaliannya adalah int [], yang berarti bahwa metode ini akan mengembalikan
nilainilai bertipe integer. Untuk menguji metode ini, Anda sebaiknya menulis metode
lain yang dapat mencetak isi array tersebut.
public static void printArray (int[] a) {
for (int i = 0; i<a.length; i++) {
System.out.println (a[i]);
}
}
Kode berikut ini akan membuat sebuah array lalu mencetak hasilnya:
int numValues = 8;
int[] array = randomArray (numValues);
printArray (array);
Di mesin saya, hasilnya adalah:
27
6
54
62
54
224
2
44
81
Hasil ini kelihatan seperti sungguhsungguh acak. Hasil yang Anda peroleh mungkin
akan berbeda.
Jika ini merupakan hasil ujian, maka ini adalah hasil ujian yang buruk, sang guru
mungkin akan menampilkan hasilnya kepada kelas dalam bentuk histogram. Histogram
merupakan suatu kumpulan penghitungpenghitung (counter) yang menyimpan jejak
mengenai jumlah kemunculan suatu nilai dalam setiap kesempatan.
Untuk nilainilai ujian, kita mungkin menyediakan 10 penghitung untuk menyimpan
jejak mengenai berapa banyak murid yang mendapatkan nilai dalam interval 90an,
80an, dst. Subbab berikutnya akan memperlihatkan Anda mengenai cara untuk
membuat suatu histogram.
10.8 Pencacahan
Sebuah pendekatan yang bagus untuk masalahmasalah seperti ini adalah dengan cara
memikirkan sebuah metode sederhana yang mudah untuk ditulis, sekaligus memiliki
kemungkinan untuk dimanfaatkan. Kemudian Anda dapat mengkombinasikannya untuk
menghasilkan sebuah solusi. Tentu, adalah tidak mudah untuk mengetahui metode mana
yang bisa dimanfaatkan dalam waktu yang singkat, tapi seiring meningkatnya
pengalaman Anda, maka akan semakin banyak pula ideide Anda.
Selain itu, terkadang kita juga tidak bisa selalu menyadari dengan jelas dan pasti suatu
hal yang menurut kita mudah untuk ditulis. Namun pendekatan yang mungkin berhasil
adalah dengan cara mencari penyelesaian atas submasalah yang sesuai dengan polapola
masalah yang sebelumnya sudah pernah Anda lihat.
225
Kembali ke subbab 7.7, kita telah melihat sebuah perulangan yang melakukan
penelusuran sebuah string lalu menghitung jumlah kemunculan suatu huruf. Anda dapat
menganggap pola yang dianut oleh program ini sebagai sebuah pola dengan nama
“penelusuran dan penghitungan”. Elemenelemen dalam pola ini antara lain:
● Sebuah kumpulan kontainer yang dapat ditelusuri, seperti sebuah array atau
string.
● Sebuah pengujian yang dapat Anda lakukan kepada setiap elemen yang berada
di dalam kontainer tersebut.
● Sebuah pencacah (counter) yang menyimpan jejak tentang berapa banyak
elemen yang telah melewati ujian.
Dalam kasus ini, kontainer yang dimaksudkan adalah suatu array yang bertipe integer.
Pengujian yang dilakukan adalah dengan cara memasukkan nilainilai yang ada ke
dalam intervalinterval nilai yang ada.
Berikut ini adalah metode inRange yang menghitung jumlah suatu elemen dalam array
yang masuk ke dalam interval nilai yang ada. Parameterparameter yang digunakan
adalah array dan dua integer yang akan menentukan batas atas dan bawah suatu interval.
public static int inRange (int[] a, int low, int
high) {
int count = 0;
for (int i=0; i<a.length; i++) {
if (a[i] >= low && a[i] < high) count++;
}
return count;
}
Ketika saya mendeskripsikan metode ini, saya tidak terlalu peduli apakah suatu nilai
yang sama dengan low atau high akan turut masuk ke dalam interval ataukah tidak, tapi
226
dari kode tersebut Anda dapat melihat bahwa low akan ikut masuk sementara high
tidak. Dari sini kita langsung dapat mencegah terjadinya penghitungan ganda untuk
satu elemen.
Sekarang kita dapat menghitung banyak nilai (cacah bukan jumlah) yang berada dalam
interval yang kita inginkan:
int[] scores = randomArray (30);
int a = inRange (scores, 90, 100);
int b = inRange (scores, 80, 90);
int c = inRange (scores, 70, 80);
int d = inRange (scores, 60, 70);
int f = inRange (scores, 0, 60);
10.9 Histogram
Kode yang kita tulis dan lihat sejauh ini tampaknya seperti sesuatu yang selalu
berulangulang. Tetapi selama interval nilai yang kita miliki tidaklah terlalu besar, hal
ini bukanlah masalah serius. Tapi coba bayangkan jika Anda memiliki 100 interval.
Apakah Anda mau menulis kode berikut ini?
int count0 = inRange (scores, 0, 1);
int count1 = inRange (scores, 1, 2);
int count2 = inRange (scores, 2, 3);
...
int count3 = inRange (scores, 99, 100);
Saya pikir tidak! Apa yang kita inginkan sesungguhnya adalah suatu cara untuk
menyimpan 100 integer, dan lebih bagus lagi kalau kita bisa memakai sebuah indeks
untuk mengaksesnya. Biar saya tebak, Anda pasti langsung terpikir dengan “array!”
227
Pola penghitungan yang akan kita gunakan adalah sama, tak peduli apakah kita
menggunakan satu pencacah (single counter) atau satu array berisi pencacah. Dalam
kasus ini, kita menginisialisasi array di luar perulangan; lalu, di dalam perulangan itu,
kita panggil metode inRange untuk menyimpan hasilnya:
int[] counts = new int [100];
for (int i = 0; i<100; i++) {
counts[i] = inRange (scores, i, i+1);
}
Satusatunya 'kecurangan' di sini adalah bahwa kita menggunakan variabel perulangan
untuk dua kepentingan sekaligus: sebagai indeks untuk array juga sebagai parameter
untuk inRange.
10.10 Solusi SinglePass
Meskipun kode ini bekerja, tapi ia belum bisa dianggap efisien. Karena setiap kali
memanggil inRange, kode ini akan menelusuri semua array. Ketika jumlah interval
bertambah, maka bertambah pula waktu untuk penelusuran.
Akan lebih baik bagi kita untuk membuat satu singlepass yang dapat menelusuri
seluruh array, dan untuk setiap nilai yang ditelusuri, cukup dengan menghitung interval
tempat nilai tersebut berada. Kemudian barulah kita dapat menaikkan nilai pencacah
yang kita gunakan. Dalam contoh ini, aspek komputasi menjadi tidak begitu penting.
Hal ini terjadi karena kita dapat menggunakan nilai itu sendiri sebagai indeks untuk
array pencacah (array of counters).
Berikut ini adalah kode yang mampu menelusuri sebuah array yang berisi nilai nilai
(skor) dengan hanya satu kali penelusuran. Hasilnya adalah sebuah histogram.
228
int[] counts = new int [100];
for (int i = 0; i < scores.length; i++) {
int index = scores[i];
counts[index]++;
}
Latihan 10.3
Enkapsulasilah kode ini dalam satu metode dengan nama scoreHist yang menggunakan
array berisi nilainilai (skor) lalu mengembalikan sebuah histogram yang berisi nilai
nilai dalam array tersebut.
Modifikasilah metode tersebut sehingga histogram hanya memiliki 10 pencacah, lalu
menghitung banyak nilai yang muncul dalam setiap intervalnya; maksudnya banyak
nilai dalam interval 90an, 80an, dst.
10.11 Daftar KataKata
Istilah Arti
Array
Kumpulan nilainilai yang diberi nama, dimana semua nilai
tersebut memiliki tipe yang sama dan setiap nilai
diidentifikasi dengan indeks.
koleksiSuatu bentuk struktur data yang berisi kumpulan item atau
elemen
elemenSalah satu nilai di dalam array. Operator [] digunakan untuk
memilih elemen dalam suatu array.
indexVariabel integer atau nilai yang digunakan untuk
mengindikasi (menunjukkan) satu elemen dalam array.
229
deterministikSebuah program yang mengerjakan halhal yang sama setiap
kali digunakan/dipanggil.
pseudorandom
Suatu deretan angkaangka yang tampak seperti angkaangka
yang diacak, tetapi sebenarnya tidak. Angkaangka tersebut
hanyalah hasil dari komputasi yang determinis.
histogram
Array bertipe integer dimana setiap nilai integer akan
mencacah banyak nilai yang masuk ke dalam interval
interval yang sudah ditentukan.
10.12 Latihan
Latihan 10.4
Tulislah metode areFactors yang menggunakan sebuah integer, n, dan sebuah array
bertipe integer, lalu mengembalikan true jika angkaangka dalam array merupakan
faktor dari n (dengan kata lain; n bisa dibagi dengan semua angka tersebut). Petunjuk :
Lihat Latihan 5.1
Latihan 10.5
Tulislah metode yang menggunakan sebuah array integer dan sebuah integer dengan
nama target sebagai argumen. Metode ini akan mengembalikan nilai yang berupa indeks
tempat dimana target pertama kali muncul dalam array tersebut. Jika nilai yang dicari
tidak ditemukan maka metode akan mengembalikan nilai 1.
Latihan 10.6
Tulislah metode arrayHist dengan sebuah array integer sebagai argumen, yang dapat
menghasilkan sebuah array histogram yang baru. Histogram ini harus berisi 11 elemen
dengan tampilan sebagai berikut:
elemen t 0 numbe r of elem ent s in the array that are <= 0
230
1 numbe r of elem ent s in the array that are == 1
2 numbe r of elem ent s in the array that are == 2
...
9 numbe r of elem ent s in the array that are == 9
10 numb er of elemen ts in the array that are >= 10
Latihan 10.7
Beberapa programmer tidak setuju dengan aturan umum yang menyebutkan bahwa
variabel dan metode haruslah diberi nama yang mengandung makna. Sebaliknya,
mereka justru beranggapan bahwa variabel atau metode seharusnya dinamai setelah
nama buahbuahan.
Untuk setiap metode di bawah ini, tulislah satu kalimat yang mendeskripsikan secara
abstrak tugas/fungsi yang dikerjakannya. Lalu untuk setiap variabel, identifikasilah
peran yang diembannya.
public static int banana (int[] a) {
int grape = 0;
int i = 0;
while (i < a.length) {
grape = grape + a[i];
i++;
}
return grape;
}
public static int apple (int[] a, int p) {
int i = 0;
int pear = 0;
while (i < a.length) {
231
if (a[i] == p) pear++;
i++;
}
return pear;
}
public static int grapefruit (int[] a, int p) {
for (int i = 0; i<a.length; i++) {
if (a[i] == p) return i;
}
return 1;
}
Tujuan latihan ini adalah untuk melatih kemampuan Anda dalam membaca kode
sekaligus mengenali polapola penyelesaian yang telah kita lihat sebelumnya.
Latihan 10.8
a) Apa output yang dihasilkan dari program berikut ini?
b) Gambarlah diagram stack yang menunjukkan keadaan dari program sesaat sebelum
mus dikembalikan.
c) Deskripsikan dalam beberapa kata mengenai apa yang dilakukan oleh mus.
public static int[] make (int n) {
int[] a = new int[n];
for (int i=0; i<n; i++) {
a[i] = i+1;
}
return a;
}
232
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<zoo.length; i++) {
fus = fus + zoo[i];
}
return fus;
}
public static void main (String[] args) {
int[] bob = make (5);
dub (bob);
System.out.println (mus (bob));
}
Latihan 10.9
Umumnya, polapola yang digunakan untuk penelusuran array yang telah kita lihat
sebelumnya dapat ditulis secara rekursif. Sebenarnya hal ini tidak lazim dilakukan, tapi
ini bisa menjadi latihan yang bagus buat kita.
a) Tulislah metode maxInrange yang menggunakan sebuah array integer dan sebuah
interval indeks (lowIndex dan highIndex). Metode ini akan mencari nilai maksimum
233
dalam array, elemenelemen yang dicari berada di antara lowIndex dan highIndex,
termasuk di kedua ujungnya.
Metode ini haruslah bersifat rekursif. Jika panjang interval adalah 1, yaitu ketika
lowIndex == highIndex, maka kita akan segera mengetahui bahwa satusatunya
elemen yang berada dalam interval tersebut adalah nilai maksimumnya. Dengan
demikian, inilah base case yang akan kita gunakan.
Jika terdapat lebih dari satu elemen dalam suatu interval, kita dapat memecah
arraynya menjadi dua bagian; tentukan nilai maksimum dalam setiap bagian
arraynya, lalu temukan nilai maksimum diantara nilai maksimum yang sudah
ditentukan sebelumya.
b) Metodemetode seperti maxInRange seringkali mengundang 'kejanggalan' ketika
digunakan. Untuk menemukan elemen terbesar dalam sebuah array, kita harus
menyediakan sebuah interval yang menyertakan semua elemen dalam array.
double max = maxInRange (array, 0, a.length1);
Tulislah metode max yang menggunakan sebuah array sebagai parameter dan
maxInRange untuk menemukan dan mengembalikan nilai terbesar. Metodemetode
seperti max terkadang disebut juga metode pembungkus (wrapper methods).
Disebut demikian karena metodemetode tersebut meyediakan sebuah lapisan
abstraksi (layer of abstraction) di sekeliling suatu metode yang janggal. Selain itu,
metode ini juga menyediakan sebuah antarmuka untuk lingkungan di luarnya
sehingga bisa lebih mudah untuk digunakan. Metode yang mengerjakan
komputasinya sendiri dikeal dengan istilah helper method. Kita akan melihat pola
ini lagi di subbab 14.9.
c) Tulislah sebuah versi rekursif dari metode find menggunakan pola wrapperhelper.
Metode find harus menggunakan sebuah array integer sekaligus sebuah integer
tujuan/target. Metode ini harus mengembalikan indeks dari lokasi awal dimana
234
integer target muncul di dalam array. Jika tidak, metode akan menghasilkan 1.
Latihan 10.10
Salah satu cara yang tidak efisien untuk mengurutkan elemenelemen dalam suau array
adalah dengan cara menemukan elemen terbesar lalu menukarnya dengan elemen
pertama, kemudian menemukan elemen terbesar kedua lalau menukarnya dengan
elemen kedua, dst.
a) Tulislah metode indexOfMaxInRange yang menggunakan sebuah array integer
sebagai argumen, yang mampu menemukan elemen terbesar dalam interval yang
telah ditentukan, lalu mengembalikan hasil yang berupa nilai indeksnya. Anda dapat
memodifikasi versi rekursif dari maxInRange atau Anda dapat menulis versi
perulangannnya dari awal.
b) Tulislah metode swapElemet yang menggunakan sebuah array integer dan dua
indeks, yang mampu menukar elemenelemen yang berada pada indeks yang telah
ditentukan.
c) Tulislah metode sortArray yang menggunakan sebuah array integer beserta metode
indexOfMaxInRange dan swapElement untuk mengurutkan array dari yang terbesar
hingga yang terkecil.
Latihan 10.11
Tulislah metode letterHist yang menggunakan sebuah String sebagai parameter yang
mampu menghasilkan histogram dari hurufhuruf yang berada di dalam String. Elemen
ke0 dari histogram harus berisi jumlah huruf a yang berada dalam string, baik huruf
kecil maupun kapital, sementara elemen ke26 harus berisi jumlah huruf z yang berada
dalam string. Solusi Anda ini harus menerapkan pola satu kali penelusuran. Jangan
lebih!
Latihan 10.12
235
Sebuah kata disebut “doubloon” jika setiap huruf yang muncul dalam kata tersebut
muncul tepat dua kali. Sebagai contoh, katakata berikut ini merupakan doubloon yang
saya temukan dalam kamus milikku.
Abba, Anna, appall, appearer, appeases,
arraigning, beriberi, bilabial, boob,
Caucasus, coco, Dada, deed, Emmett, Hannah,
horseshoer, Otto, intestines, Isis, mama,
Mimi, murmur, noon, papa, peep, reappear,
redder, sees,Toto, Shanghaiings
Tulislah metode isDoubloon yang mengembalikan nilai true jika kata yang dimasukkan
merupakan doubloon dan false jika sebaliknya.
Latihan 10.13
Dalam permainan scrabble, setiap pemain memiliki kotakkotak yang berisi huruf di
dalamnya. Tujuan permainan ini adalah untuk membuat hurufhuruf yang pada awalnya
tidak beraturan menjadi teratur sehingga dapat digunakan untuk mengeja katakata.
Sistem penilaian yang digunakan sangat membingungkan, tetapi sebagai panduan dasar,
kata yang lebih panjang lebih banyak nilainya ketimbang kata yang lebih pendek.
Bayangkan saat ini ada seseorang yang memberi Anda sebuah kumpulan kotak yang
berupa String, seperti “qijibo” lalu Anda juga diberi String lain untuk menguji coba,
seperti “jib”. Tulislah metode testWord yang menggunakan dua String ini. Metode ini
mengembalikan nilai true jika kumpulan kotak yang diberikan dapat digunakan untuk
mengeja kata. Anda diperkenankan untuk memiliki lebih dari satu kotak dengan huruf
yang sama, tapi Anda hanya boleh menggunakan masingmasing kotak sebanyak satu
kali saja. Jangan lebih!
Latihan 10.14
236
Dalam permainan Scrabble yang sebenarnya, ada beberapa kotak kosong yang bisa
digunakan sebagai wild cards; yakni sebuah kotak kosong yang dapat digunakan untuk
merepresentasikan huruf apa pun.
Pikirkanlah sebuah algoritma untuk testWord yang berkenaan dengan konsep wild
cards. Tapi ingat! Jangan sampai terlalu jauh berurusan dengan detil implementasi,
misalnya memikirkan cara untuk merepresentasikan wild cards. Bukan itu! Cobalah
untuk mendeskripsikan algoritmanya saja, menggunakan bahasa yang Anda kuasai,
dengan pseudocode, atau Java.
237
Bab 11
Array berisi Objek
11.1 Komposisi
Sejauh ini kita telah melihat beberapa contoh komposisi (kemampuan untuk
mengombinasikan fiturfitur bahasa dalam beberapa variasi susunan). Salah satunya
adalah dengan menggunakan pemanggilan metode sebagai bagian dari ekspresi. Contoh
lainnya adalah struktur pernyataan tersarang; Anda dapat meletakkan pernyataan if di
dalam perulangan while, atau di dalam pernyataan if lainnya, dst.
Setelah melihat pola ini, dan setelah belajar mengenai array dan objek, Anda semestinya
tidak perlu terkejut ketika mengetahui bahwa ternyata Anda boleh memiliki array berisi
objek. Kenyataannya, Anda juga dapat memiliki sebuah objek yang berisi arrayarray
(sebagai variabel instan); Anda boleh memiliki array yang mengandung array; Anda
juga boleh memiliki objek yang mengandung objek, dsb.
Di dua bab berikutnya, kita akan melihat beberapa contoh komposisi. Diantaranya
adalah objek Card.
11.2 Objek Card
Jika Anda tidak akrab dengan permainan kartu yang biasa dimainkan oleh banyak orang
di seluruh dunia, maka sekarang adalah waktu yang tepat untuk memulainya. Karena
kalau tidak, bab ini akan menjadi siasia. Ada 52 kartu dalam sebuah tumpukan kartu
(deck). Masingmasing kartu tersebut akan masuk ke dalam satu di antara empat
kelompok (suit) yang ada. Empat kelompok tersebut antara lain; Sekop (Spade), Hati
(Heart), Permata (Diamond), dan Wajik (Club). Selain masuk ke dalam empat
238
kelompok, setiap kartu juga akan memiliki nomor urutan/peringkat (rank) yang
membedakannya dengan kartukartu lain yang masih ada dalam kelompok yang sama.
Nomor urutan/peringkat ini antara lain; As (ace), 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen,
King. Dalam permainan kartu, kartu As bisa bernilai lebih tinggi ketimbang kartu King
atau malah lebih rendah ketimbang kartu 2. Hal ini tergantung dengan jenis permainan
kartu yang dimainkan.
Jika kita hendak mendefinisikan sebuah objek untuk merepresentasikan permainan
kartu, maka kita bisa dengan mudah menyebutkan variabel instan yang diperlukan: rank
dan suit. Tepat sekali! Tapi sayangnya, menentukan tipe data apa yang akan digunakan
untuk menyimpan kedua variabel ini justru yang agak sulit. Kemungkinan pertama
adalah String yang bisa menyimpan sesuatu seperti “Spade” untuk suit dan “Queen”
untuk rank. Satu masalah yang muncul dengan implementasi seperti ini adalah kita
akan menemukan kesulitan ketika harus membandingkan kartu untuk memeriksa kartu
mana yang memiliki suit atau rank yang lebih tinggi.
Alternatif penyelesaian masalah adalah dengan menggunakan tipe integer untuk
menyandi (encode) rank dan suit. Dengan melakukan “penyandian”, bukan berarti saya
ingin mengerjakan apa yang mungkin langsung terpikir di kepala banyak orang, yakni
enkripsi, atau menyandi kedua variabel ini dengan kode rahasia. Apa yang dimaksud
dengan penyandian oleh seorang ilmuwan komputer adalah sesuatu yang seperti
“buatlah sebuah bentuk pemetaan antara deretan angkaangka dengan sesuatu yang
ingin saya tampilkan”. Sebagai contoh,
Spades 3→
Hearts 2→
Diamonds 1→
Clubs 0→
239
Manfaat yang jelas bisa kita lihat dari pemetaan ini adalah bahwa variabel suit
dipetakan ke tipe integer dalam keadaan terurut. Hal ini akan memberikan kemudahan
untuk kita ketika hendak membandingkan suit dengan cara membandingkan integer.
Pemetaan untuk variabel rank tampak sangat jelas; setiap nomor urut kartu akan
dipetakan sesuai dengan integer yang sama, begitu juga dengan kartukartu yang
menggunakan simbolsimbol:
Jack 11→
Queen 12→
King 13→
Alasan saya menggunakan notasi matematika untuk pemetaan ini adalah karena mereka
semua bukanlah bagian dari program Java yang akan kita buat. Mereka hanyalah
merupakan bagian dari rancangan program (program design) yang tidak akan kita ikut
seratakan secara eksplisit di dalam kode. Definisi kelas untuk tipe Card akan tampak
sebagai berikut:
class Card
{
int suit, rank;
public Card () {
this.suit = 0; this.rank = 0;
}
public Card (int suit, int rank) {
this.suit = suit; this.rank = rank;
}
}
Seperti biasa, saya menyediakan dua konstruktor di sini, satu yang menggunakan
240
sebuah parameter untuk setiap variabel instan, dan satu lagi yang tidak menggunakan
satu parameter pun.
Untuk membuat sebuah objek yang dapat merepresentasikan rank (urutan) 3 milik
wajik, kita bisa menggunakan perintah new sebagai berikut:
Card threeOfClubs = new Card (0, 3);
Argumen pertama, 0, merepresentasikan kelompok wajik.
11.3 Metode printCard
Ketika Anda membuat sebuah kelas baru, langkah pertama yang biasanya Anda
kerjakan adalah mendeklarasikan variabel instan dan menulis konstruktor
konstruktornya. Kemudian diikuti dengan menulis metode standar yang harus dimiliki
oleh semua objek, termasuk satu metode yang akan mencetak objek tersebut, lalu satu
atau dua metode untuk membandingkan objekobjek. Saya akan memulainya dengan
metode printCard.
Untuk mencetak objek Card yang dapat dibaca dengan mudah oleh manusia, kita akan
memetakan kodekode integer ke bentuk yang berupa katakata. Cara alami untuk
melakukannya adalah dengan menggunakan sebuah array berisi String. Anda bisa
membuat sebuah array berisi String dengan cara yang sama seperti ketika Anda
membuat sebuah array yang berisi tipe data primitif:
String[] suits = new String [4];
Kemudian kita isi elemenelemen array ini dengan nilainilai sebagai berikut:
suits[0] = "Clubs";
suits[1] = "Diamonds";
suits[2] = "Hearts";
suits[3] = "Spades";
Membuat sebuah array dan langsung menginisialisasinya merupakan operasi yang
241
sangat sering dilakukan. Saking seringnya, sampaisampai Java malah sudah
menyediakan sintaks khusus untuk melakukannya:
String[] suits = {"Clubs", "Diamonds" , "Hearts",
"Spades"};
Pengaruh yang dihasilkan oleh pernyataan di atas akan sama dengan yang ditimbulkan
oleh deklarasi, alokasi dan assignment secara terpisah. Diagram keadaan dari array ini
akan tampak seperti gambar berikut:
Elemenelemen array merupakan sesuatu yang mengacu (references) ke String,
ketimbang menjadi String itu sendiri. Hal ini adalah fakta umum yang berlaku untuk
semua array yang berisi objekobjek, sebagaimana yang akan saya bahas lebih detail di
bagian berikutnya. Sekarang, yang kita butuhkan adalah array berisi String lain yang
digunakan untuk mengkode balik (decode) peringkatperingkat dalam kartu:
String[] ranks = { "narf", "Ace", "2", "3", "4", "5",
"6",
"7", "8", "9", "10", "Jack", "Queen", "King" };
Elemen “narf” digunakan sebagai penjagatempat (placekeeper) untuk elemen ke0
dari array yang tidak akan pernah dipakai dalam permainan kartu. Indeks array yang
bisa dipakai hanyalah 1 s.d. 13. Elemen yang tidak digunakan ini memang tidak penting
tentunya. Sebenarnya kita bisa saja memulainya dari urutan ke0, seperti biasa, tapi
akan lebih baik bagi kita untuk mengkode 2 dengan 2, 3 dengan 3, 4 dengan 4, dst.
Dengan arrayarray ini, kita bisa memilih String yang tepat dengan menggunakan
variabel suit dan rank sebagai indeks. Dalam metode printCard,
publi c st atic voi d pr intC ard ( Car d c) {
242
Strin g[] suits = { "Clubs ", "Di amon ds", "Hear ts",
"Spad es" } ;
Strin g[] rank s = { "narf" , "Ace", "2" , "3", "4", "5",
"6", " 7", "8" , "9 ", " 10", "Ja ck", "Qu een" , "K ing" };
Syste m.ou t.pr intl n (ran ks[c .ran k] + " of " +
suits [c.s uit] );
}
Ekspresi suits[c.suit] artinya adalah “gunakan variabel instan suit dari objek c sebagai
sebuah indeks ke dalam array dengan nama suits, lalu pilih string yang ada di situ.”
hasil dari kode berikut
Card card = new Card (1, 11);
printCard (card);
adalah Jack of Diamonds.
11.4 Metode sameCard
Kata “sama” termasuk salah satu kata dari beberapa kata dalam bahasa alami (natural
language) yang tampaknya baru akan jelas maksudnya ketika Anda memberinya
dengan maksudmaksud tertentu.
Sebagai contoh, jika saya berkata “Chris dan saya mempunyai mobil yang sama,” maka
di sini saya bermaksud menginformasikan bahwa mobil miliknya dan milik saya
mempunyai model dan tahun pembuatan yang sama. Jika saya berkata bahwa bahwa
“Chris dan saya mempunyai ibu yang sama,” di sini saya bermaksud mengatakan bahwa
ibunya dan ibu saya adalah satu ibu yang sama. Jadi, ide mengenai “kesamaan” bisa
dimaknai berbedabeda tergantung dengan konteks kalimatnya.
Ketika Anda berbicara mengenai objek, Anda juga akan menemui ambiguitas yang
sama. Sebagai contoh, jika dua kartu adalah kartu yang sama, apakah itu berarti
243
keduanya memiliki data yang sama (rank dan suit), ataukah keduanya hanya
merupakan dua objek Card yang sama?
Untuk melihat apakah dua objek berbeda mengacu ke objek yang sama, kita bisa
menggunakan operator “ == ”. Sebagai contoh:
Card card1 = new Card (1, 11);
Card card2 = card1;
if (card1 == card2) {
System.ou t.prin tln ("card1 and card2 are the same
object.");
}
Bentuk kesamaan seperti ini disebut shallow equality (tidak dalam) karena hanya
membandingkan objek yang di acu, bukan isi dari objek tersebut.
Untuk membandingkan isi objek tersebut – deep quality – kita bisa menulis sebuah
metode dengan nama sameCard.
public static boolean sameCard (Card c1, Card c2) {
return (c1.suit == c2.suit && c1.rank ==
c2.rank);
}
Sekarang jika kita membuat dua objek yang berbeda yang berisi data yang sama, kita
bisa menggunakan metode sameCard untuk melihat apakah keduanya
merepresentasikan kartu yang sama:
Card card1 = new Card (1, 11);
244
Card card2 = new Card (1, 11);
if (sameCard (card1, card2)) {
System.out.println ("card1 and card2 are the
same card.");
}
Dalam kasus ini, card1 dan card2 adalah dua objek berbeda yang berisi data yang
sama.
Jadi, persyaratan (condition) di dalam kode di atas bernilai True. Bagaimana bentuk
diagram keadaan ketika card1 == card2 bernilai True?
Di bagian 7.10 saya mengatakan bahwa Anda sebaiknya jangan pernah menggunakan
operator == dalam String karena operator ini sebenarnya tidak mengerjakan apa yang
Anda harapkan. Ketimbang membandingkan isi String (deep equality), kita lebih baik
memeriksa apakah kedua String merupakan objek yang sama (shallow equality) ataukah
tidak.
11.5 Metode compareCard
Untuk tipe datatipe data primitif, terdapat operatoroperator kondisional yang dapat
membandingkan nilainilai sekaligus menentukan nilai mana yang lebih besar daripada
nilai lainnya. Operatoroperator ini (<, >, dll) tidak bisa digunakan untuk tipetipe
245
objek. Khusus untuk String, ada sebuah metode builtin yang dinamai compareTo.
Untuk Card, kita harus menulisnya sendiri. Metode ini akan kita sebut sebagai metode
compareCard. Berikutnya, kita akan menggunakan metode ini untuk mengurutkan
tumpukan kartu.
Beberapa himpunan/kelompok (set) data berada dalam keadaan yang memang sudah
terurut. Ini artinya, Anda bisa membandingkan dua elemen apapun dari kelompok data
tersebut untuk kemudian langsung mengatakan elemen mana yang lebih besar atau
sebaliknya. Angkaangka yang termasuk ke dalam kelompok tipe data integer dan
floatingpoint bisa Anda jadikan contoh dalam hal ini. Namun ada juga kelompok data
yang tidak terurut secara alami (baca: tidak untuk diurutkan). Maksudnya, tidak ada
cara untuk menyebutkan bahwa satu elemen lebih besar daripada elemen lainnya.
Sebagai contoh, buahbuahan merupakan kelompok data yang tidak bisa atau memang
tidak untuk diurutkan. Hal inilah yang menyebabkan kita tidak akan pernah bisa
membandingkan buah jeruk dengan buah apel menggunakan sudut pandang
perbandingan antara dua angka seperti pada contoh sebelumnya. Dalam Java, tipe
boolean juga tidak termasuk ke dalam kelompok tipe data terurut. Oleh karena itulah,
kita tidak bisa menyebut True lebih besar daripada False.
Kelompok kartu yang biasa dimainkan bisa dianggap sebagai kelompok data yang
terurut secara parsial. Maksudnya, terkadang kita memang bisa membandingkan dua
kartu tetapi terkadang malah sebaliknya. Contohnya, saya mengetahui bahwa kartu
wajik dengan nomor 3 lebih tinggi daripada kartu wajik dengan nomor 2, dan kartu
permata bernomor 3 lebih tinggi daripada kartu wajik bernomor sama. Tapi kartu mana
yang lebih baik, kartu wajik dengan nomor 3 ataukah kartu permata yang hanya
bernomor 2? Kartu pertama memiliki peringkat yang lebih tinggi, tapi kartu lainnya
memiliki kelompok yang lebih tinggi.
246
Agar kartukartu ini bisa dibandingkan, kita harus memutuskan variabel mana yang
lebih penting, variabel rank (peringkat) ataukah suit (kelompok). Jujur saja, dua pilihan
di atas akan sangat tergantung dengan selera masingmasing individu. Namun untuk
kasus ini, saya berpendapat bahwa variabel suit lebih penting, karena ketika Anda
membeli satu set kartu, kartukartu itu tersusun dalam susunan di mana kartukartu
wajik ditempatkan di atas permata dan kelompok lainnya. Bukan disusun berdasarkan
nomor kartu.
Dengan keputusan di atas, kita bisa menulis metode compareCard. Di sini kita
membutuhkan dua buah Card sebagai parameter, angka 1 sebagai nilai kembalian jika
kartu yang ditulis pertama menang dan 1 jika sebaliknya, serta 0 jika keduanya sama
(dari segi isi/kandungan objek Card tersebut). Terkadang akan membingungkan juga
jika kita selalu menggunakan ketiga nilai di atas sebagai nilai kembalian, tetapi fakta
berkata lain, ketiga nilai tersebut sebenarnya telah menjadi standar untuk metode
metode yang berhubungan dengan perbandingan.
Awalnya, kita akan membandingkan kartukartu tersebut berdasarkan kelompok:
if (c1.suit > c2.suit) return 1;
if (c1.suit < c2.suit) return 1;
Jika kedua pernyataan di atas tidak ada yang bernilai True, maka kelompokkelompok
kartu tersebut tentunya bernilai sama. Jika ini yang terjadi, kita harus membandingkan
peringkatnya:
if (c1.rank > c2.rank) return 1;
if (c1.rank < c2.rank) return 1;
Jika kedua pernyataan di atas juga tidak bernilai True, maka peringkatnya tentu sama,
jadi kita akan mengembalikan nilai 0. Dalam model pemeringkatan seperti ini, kartu As
247
akan dianggap lebih rendah daripada kartu dengan nomor 2.
Untuk latihan, perbaikilah metode di atas agar kartu As bisa bernilai lebih tinggi
ketimbang kartu King, lalu enkapsulasilah kode Anda ini ke dalam sebuah metode.
11.6 Array berisi Card
Alasan saya memilih Card sebagai objek untuk bab ini adalah karena terdapat sebuah
manfaat jelas yang bisa didapat dari array berisi kartu, yakni sebuah tumpukan. Berikut
ini adalah lampiran kode untuk membuat sebuah tumpukan yang berisi 52 kartu.
Card[] deck = new Card [52];
Berikut ini adalah diagram keadaan untuk objek ini:
Hal penting yang harus dilihat di sini adalah bahwa array yang kita buat hanya berisi
referensi ke objek; tapi tidak menampung objekobjek Card itu sendiri. Nilainilai dari
elemen array diinisialisasi dengan null. Anda bisa mengakses elemenelemen array
dengan cara yang sudah lazim dipakai:
if (deck[3] == null) {
System.out.println ("No cards yet!");
}
Tapi jika Anda mencoba mengakses variabelvariabel instan dari Card yang memang
tidak ada atau belum dideklarasikan, maka Anda akan mendapatkan
NullPointerException.
deck[2].rank; // NullPointerException
Tidak perlu diperdebatkan lagi, sintaks di atas merupakan sintaks yang tepat untuk
mengakses kartu dengan nomor 2 yang berada dalam tumpukan (sebenarnya yang
248
ketiga – kita memulainya dari urutan ke0 bukan?). Ini adalah contoh lain dari bentuk
composition, kombinasi antara sintaks untuk mengakses satu elemen dalam sebuah
array dan sebuah variabel instan dari suatu objek.
Cara termudah untuk mengisi suatu tumpukan dengan objekobjek Card adalah dengan
menggunakan perulangan tersarang (nested loop):
int index = 0;
for (int suit = 0; suit <= 3; suit++) {
for (int rank = 1; rank <= 13; rank++) {
deck[index] = new Card (suit, rank);
index++;
}
}
Perulangan bagian luar akan menghasilkan kelompokkelompok kartu, mulai dari nol
sampai dengan tiga (0 s.d. 3). Untuk setiap kelompok kartu, perulangan bagian dalam
akan menghasilkan peringkatnya, yakni mulai dari 1 s.d. 13. Karena perulangan (loop)
bagian luar dieksekusi 4 kali dan perulangan bagian dalam 13 kali, total eksekusi yang
terjadi di dalam badan perulangan adalah 52 kali (sama dengan 13 x 4).
Saya menggunakan variabel index untuk melacak keberadaan kartu berikutnya di dalam
tumpukan. Diagram keadaan berikut ini menunjukkan keadaan tumpukan setelah dua
kartu dimasukkan:
249
Latihan 11.1
Enkapsulasilah kode pembuat tumpukan ini ke dalam sebuah metode dengan nama
buildDeck yang tidak menggunakan parameter dan menghasilkan sebuah array berisi
kumpulan Card.
11.7 Metode printDeck
Ketika Anda bekerja dengan array, adalah sesuatu yang wajar untuk menulis sebuah
metode yang akan mencetak isi array tersebut. Sebelumnya kita telah melihat pola
penelusuran array beberapa kali. Jadi, Anda seharusnya sudah cukup akrab dengan
metode berikut ini:
public static void printDeck (Card[] deck) {
for (int i=0; i<deck.length; i++) {
printCard (deck[i]);
}
}
Karena parameter deck bertipe Card[], elemen dalam deck akan bertipe Card. Oleh
karena itu, deck[i] merupakan argumen legal untuk metode printCard.
250
11.8 Searching
Metode berikutnya yang ingin saya tulis adalah findCard. Metode ini akan melakukan
pencarian di dalam array berisi Card untuk melihat apakah array tersebut memiliki kartu
yang sedang dicari. Mungkin metode ini tidak jelas akan dimanfaatkan untuk apa, tapi
hal ini justru memberikan kesempatan bagi saya untuk mendemonstrasikan dua cara
untuk melakukan pencarian, yaitu linear search dan bisection search.
Teknik linear search lebih sering digunakan; teknik ini menggabungkan penelusuran
tumpukan dan pembandingan setiap kartu dengan kartu yang kita cari. Ketika kita
menemukannya, kita akan mengembalikan indeks lokasi kartu tersebut. Tapi jika kita
tidak menemukannya di dalam tumpukan, kita mengembalikan nilai 1.
public static int findCard (Card[] deck, Card card)
{
for (int i = 0; i< deck.length; i++) {
if (sameCard (deck[i], card)) return i;
}
return 1;
}
Argumenargumen findCard kita namai dengan nama card dan deck. Mungkin akan
terlihat ganjil jika kita menggunakan sebuah variabel dengan nama yang sama dengan
tipenya (variabel card bertipe Card). Hal seperti ini adalah sesuatu yang legal dan
lazim dipakai, meskipun terkadang hal ini justru akan membuat kode yang kita tulis
menjadi lebih sulit untuk dibaca. Namun dalam kasus ini, saya kira hal ini bisa
berfungsi dengan baik.
Metode ini akan langsung mengembalikan nilai kembalian ketika kartu yang dicari
sudah ditemukan. Ini berarti, kita tidak perlu menelusuri semua indeks dalam tumpukan
jika kartu yang dicari sudah ditemukan. Jika perulangan yang dilakukan sudah
251
dihentikan tanpa menemukan kartu yang dicari, kita akan segera tahu bahwa kartu
tersebut tidak berada di dalam tumpukan. Oleh karena itu, metode ini akan
mengembalikan nilai 1.
Jika kartukartu yang berada dalam tumpukan tidak berada dalam posisi yang terurut,
maka tidak ada cara yang lebih cepat untuk melakukan pencarian ketimbang cara ini.
Kita harus memeriksa setiap kartu karena tanpa ini, kita tidak akan bisa memastikan
bahwa kartu yang kita cari memang benarbenar tidak ada di dalam tumpukan itu.
Namun ketika Anda mencari sebuah kata di dalam kamus, Anda tidak mencari kata
tersebut secara linear atau kata demi kata. Hal ini terjadi karena katakata tersebut
berada dalam posisi yang sudah terurut secara alfabetis. Akibatnya, Anda mungkin
menggunakan sebuah algoritma yang sama dengan bisection search:
1. Mulailah mencari dari bagian tengah.
2. Pilihlah sebuah kata dalam halaman lalu bandingkanlah setiap kata tersebut
dengan kata yang Anda cari.
3. Jika Anda menemukan kata yang Anda cari, berhenti.
4. Jika kata yang sedang Anda cari, secara alfabetis lebih besar daripada kata yang
berada dalam halaman, pindahlah ke halaman berikutnya lalu kerjakan kembali
langkah 2.
5. Jika kata yang sedang Anda cari, secara alfabetis lebih kecil daripada kata yang
berada dalam halaman, pindahlah ke halaman sebelumnya lalu kerjakan kembali
langkah 2.
Jika Anda sudah menemukan dua kata yang adjacent (saling mengiringi, contohnya;
kata fakta dengan faktor) di suatu halaman dalam kamus tetapi Anda tidak mendapati
kata yang Anda cari (misalnya; fakto) yang seharusnya terletak diantara dua kata itu,
Anda bisa menyimpulkan bahwa kata yang Anda cari tersebut tidak tercantum dalam
kamus itu. Satusatunya alternatif yang mungkin terjadi adalah kata yang sedang Anda
252
cari tidak berada dalam berkas kamus tersebut, tapi ini justru kontradiktif dengan
asumsi yang ada; bahwa katakata sudah tersusun dalam keadaan terurut alfabetis.
Dalam kasus tumpukan kartu ini, jika kita tahu bahwa kartukartu tersebut berada dalam
posisi yang terurut, kita bisa menulis metode findCard yang lebih cepat. Cara terbaik
untuk menulis bisection search adalah dengan menggunakan teknik rekursif. Hal ini
terjadi karena bisection sebenarnya merupakan sesuatu yang secara alamiah bersifat
rekursif.
Trik yang digunakan adalah dengan cara menulis sebuah metode findBisect yang
menggunakan dua indeks sebagai parameter, yakni low dan high, yang akan
mengindikasikan segmen/zona dari array yang akan dikenai pencarian (termasuk dua
parameter itu sendiri, low dan high).
1. Untuk mencari di dalam array, pilihlah sebuah indeks di antara low dan high
(berilah nama mid) lalu bandingkan dengan kartu yang sedang Anda cari.
2. Jika Anda telah menemukannya, hentikan pencarian.
3. Jika kartu yang berada dalam mid lebih tinggi daripada kartu yang Anda cari,
carilah kartu tersebut di daerah dalam interval antara low hingga mid1.
4. Jika kartu yang berada dalam mid lebih rendah daripada kartu yang Anda cari,
carilah kartu tersebut di daerah dalam interval antara mid1 hingga high.
Langkah 3 dan 4 kelihatan seperti sesuatu yang bersifat rekursif. Berikut ini adalah
implementasinya dalam kode Java:
public static int findBisect (Card[] deck, Card
card, int low, int high) {
int mid = (high + low) / 2;
int comp = compareCard (deck[mid], card);
253
if (comp == 0) {
return mid;
} else if (comp > 0) {
return findBisect (deck, card, low, mid1);
} else {
return findBisect (deck, card, mid+1,
high);
}
}
Ketimbang memanggil metode compareCard tiga kali, saya lebih memilih untuk
memanggilnya sekali lalu menyimpan hasilnya.
Meskipun kode ini merupakan inti (kernel) dari bisection search, tetapi kode ini masih
menyisakan sesuatu yang janggal. Dalam kode di atas, terlihat bahwa jika kartu yang
kita cari tidak berada di dalam tumpukan, metode ini akan berekursi selamanya. Kita
membutuhkan sebuah cara untuk mendeteksi kondisi ini dan mengatasinya dengan
cepat (misalnya dengan cara mengembalikan nilai 1).
Cara termudah untuk menyatakan bahwa kartu Anda tidak berada dalam tumpukan
adalah jika memang benarbenar tidak ada kartu lagi dalam tumpukan. Kondisi ini
terjadi ketika high lebih rendah daripada low. Sebenarnya, masih ada kartu di dalam
tumpukan, itu pasti, namun yang saya maksudkan di sini adalah bahwa tidak ada kartu
di dalam segmen tumpukan yang telah diindikasikan dengan low dan high.
Dengan tambahan baris seperti di bawah ini, metode ini akan bekerja dengan baik:
public static int findBisect (Card[] deck, Card
card, int low, int high) {
System.out.println (low + ", " + high);
if (high < low) return 1;
254
int mid = (high + low) / 2;
int comp = deck[mid].compareCard (card);
if (comp == 0) {
return mid;
} else if (comp > 0) {
return findBisect (deck, card, low, mid1);
} else {
return findBisect (deck, card, mid+1,
high);
}
}
Saya menambahkan sebuah pernyataan print di awal metode agar saya bisa memantau
urutan pemanggilan rekursif yang terjadi sekaligus meyakinkan diri saya sendiri bahwa
metode ini akan sampai ke base case.
Saya mencoba kode berikut ini:
Card card1 = new Card (1, 11);
System.out.println (findBisect (deck, card1, 0,
51));
Lalu saya mendapatkan hasil sebagai berikut:
0, 51
0, 24
13, 24
19, 24
22, 24
23
255
Kemudian saya buat sebuah kartu yang tidak berada di dalam tumpukan, yakni kartu
permata bernomor 15. Saya mencoba untuk mencarinya, lalu saya mendapatkan hasil
seperti ini:
0, 51
0, 24
13, 24
13, 17
13, 14
13, 12
1
Uji coba ini belum membuktikan benartidaknya program ini. Bahkan tidak diketahui
berapa jumlah uji coba yang harus dilakukan untuk memastikan kebenaran program
ini. Di lain pihak, dengan melihat beberapa contoh kasus sekaligus mempelajari kode
tersebut, mungkin Anda bisa meyakinkan diri Anda sendiri.
Jumlah pemangilan rekursif yang terjadi bisa dibilang relatif kecil, antara 6 atau 7. Ini
berarti bahwa kita hanya perlu memanggil compareCard sebanyak 6 atau 7 kali.
Bandingkan dengan 52 kali kemungkinan pemanggilan jika kita menggunakan
pencarian linear. Secara umum, bisection search lebih cepat daripada linear search.
Bahkan untuk array dengan ukuran yang besar.
Dua kesalahan yang sering terjadi dalam programprogram rekursif adalah kealpaan
dalam memasukkan base case sehingga program tidak akan pernah menemui pemicu
untuk berhenti melakukan pemanggilan. Kesalahan seperti ini akan menyebabkan
terjadinya pemanggilan rekursi yang tidak berhingga (infinite recursion). Dalam
keadaan seperti ini, Java (secara otomatis) akan mengeluarkan
StackOverflowException.
256
11.9 Tumpukan dan subtumpukan
Melihat kembali interface yang digunakan oleh metode findBisect
public static int findBisect (Card[] deck, Card card,
int low, int high)
mungkin masih masuk akal untuk menganggap tiga parameter di atas, yakni; deck, low,
dan high, sebagai parameter tunggal yang menentukan sebuah subdeck. Cara berpikir
seperti ini sebenarnya sudah lazim digunakan, dan terkadang saya menyebutnya sebagai
parameter abstrak (abstract parameter). Maksud saya dengan kata 'abstrak' adalah
sesuatu yang secara literal tidak termasuk ke dalam kode program, tetapi digunakan
untuk mendeskripsikan fungsi dari program dalam level yang lebih tinggi.
Sebagai contoh, ketika Anda memanggil sebuah metode, lalu melewatkan sebuah array
dengan batasan berupa variabel low dan high ke dalamnya, maka tidak akan ada yang
dapat mencegah metode terpanggil untuk mengakses bagianbagian dari array yang
berada di luar batas. Jadi, Anda sebenarnya tidak mengirim sebuah subbagian dari
tumpukan; sebaliknya, Anda benarbenar mengirimkan semua tumpukan. Namun
selama si penerima bermain sesuai dengan aturan, saya kira masih masuk akal untuk
menganggap tumpukan di atas sebagai sebuah subtumpukan, tentunya secara abstrak.
Masih ada satu contoh lagi tentang abstraksi seperti ini yang mungkin bisa Anda temui
dalam subbab 9.7, yakni ketika saya menyebutnyebut mengenai struktur data yang
“kosong”. Alasan saya menggunakan tanda petik untuk kata kosong adalah untuk
menyiratkan kepada Anda bahwa kata kosong di situ sebenarnya tidaklah terlalu tepat.
Semua variabel sebenarnya memiliki nilai sepanjang program tersebut berfungsi. Ketika
Anda menciptakan variabelvariabel tersebut, semua sudah diberi dengan nilainilai
bawaan (default). Jadi, sejatinya tidak ada satu objek yang layak disebut sebagai objek
yang kosong.
257
Tapi jika sebuah program bisa menjamin bahwa nilai terbaru dari sebuah variabel tidak
pernah dibaca sebelum ditulis, maka nilai terbaru tersebut termasuk irrelevan. Oleh
karena itu, secara abstrak, dalam kondisi seperti ini, kita bisa saja menganggap variabel
tersebut sebagai variabel yang “kosong”.
Cara berpikir seperti ini, di mana sebuah program dipersepsikan melebihi (beyond) apa
yang bisa ditulis merupakan salah satu bagian penting dalam kerangka berpikir para
ilmuwan komputer (computer scientist). Terkadang, kata “abstrak” terlalu sering
digunakan. Saking seringnya, sampaisampai ia sering kehilangan maknanya dalam
berbagai konteks kalimat. Walaupun begitu, abstraksi merupakan ide utama dalam
dunia ilmu komputer (sebagaimana juga terjadi di bidangbidang lainnya).
Definisi abstraksi yang lebih umum adalah sebagai berikut; “suatu proses yang
bertujuan untuk memodelkan sebuah sistem kompleks dengan menggunakan deskripsi
sederhana agar dapat menekan detaildetail yang tidak penting pada saat menangkap
perilakuperilaku yang relevan.”
11.10 Daftar KataKata
Istilah Arti
encode 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
parameter
Satu set parameter yang beraksi bersamasama sebagai sebuah
parameter.
abstraction Proses menginterpretasi/memaknai sebuah program (atau sesuatu
258
yang lain) dengan pemaknaan yang lebih tinggi daripada apa
yang direpresentasikan secara literal di dalam kode.
11.11 Latihan
Latihan 11.2
Bayangkanlah sebuah permainan kartu yang tujuannya adalah untuk mengumpulkan
kartukartu dengan jumlah total sama dengan 21. Skor total untuk setiap pemain adalah
skor total dari semua kartu yang ada dalam genggaman tangan pemain. Skor untuk
masingmasing kartu adalah sebagai berikut: kartu As bernilai 1, semua kartu yang
bergambar (Jack, Queen, King) bernilai 10, sementara kartukartu yang lain diberi nilai
sesuai dengan peringkatnya. Contoh: pemain yang memegang kartu As, 10, Jack, dan 3
memiliki skor total sebesar 1 + 10 + 10 + 3 = 24.
Tulislah metode handScore yang menggunakan sebuah array berisi kartukartu sebagai
argumen. Nilai yang akan dikembalikan adalah skor total dari kartukartu tersebut.
Peringkatperingkat kartu yang digunakan harus berdasarkan pada pemetaan yang ada
dalam subbab 11.2, dengan kartu As sebagai kartu yang bernilai 1.
Latihan 11.3
Metode printCard yang ada dalam subbab 11.3 menggunakan sebuah objek Card dan
mengembalikan sebuah string yang merepresentasikan kartu yang diminta.
Buatlah sebuah metode kelas untuk kelas Card dengan nama parseCard. Metode ini
menggunakan sebuah String sebagai argumen dan mampu mengembalikan kartu yang
terkait dengan String tersebut. Anda bisa mengasumsikan bahwa String tersebut berisi
nama dari sebuah kartu dalam format yang valid seperti yang sudah dihasilkan oleh
metode printCard.
259
Dengan kata lain, string itu akan berisi satu spasi di antara variabel rank dan kata “of”,
dan di antara kata “of” dengan variabel suit. Jika string itu tidak berisi nama kartu yang
legal, maka metode akan mengembalikan objek berisi null.
Tujuan dari latihan ini adalah untuk mengulang konsep tentang parsing dan
mengimplementasikan sebuah metode yang dapat memparse suatu string yang
ditentukan.
Latihan 11.4
Tulislah sebuah metode, suitHist, yang menggunakan sebuah array berisi kartukartu
sebagai parameter dan mengembalikan sebuah histogram dari kelompokkelompok
kartu yang berada di genggaman tangan seorang pemain. Solusi yang Anda gunakan
hanya boleh menelusuri array sebanyak satu kali saja, tidak lebih!
Latihan 11.5
Tulislah sebuah metode isFlush yang menggunakan sebuah array berisi kartukartu
sebagai sebuah parameter dan mampu mengembalikan nilai True jika kartukartu yang
sedang dipegang berstatus flush, dan False jika sebaliknya. Flush adalah sebuah
keadaan ketika seorang pemain mempunyai lima atau lebih kartu yang berasal dari
kelompok yang sama.
260
Bab 12
Objek berisi Array
12.1 Kelas Deck
Dalam bab sebelumnya, kita sudah bekerja dengan sebuah array yang berisi objek,
tetapi saya juga menyebutkan bahwa kita juga berkesempatan memiliki objek yang
berisi sebuah array sebagai sebuah variabel instan. Dalam bab ini kita akan membuat
sebuah objek baru, yakni Deck, yang berisi sebuah array yang berisi Card sebagai
variabel instannya.
Definisi kelasnya tampak sebagai berikut:
class Deck {
Card[] cards;
public Deck (int n) {
cards = new Card[n];
}
}
Nama variabel instannya adalah cards. Nama ini digunakan agar kita bisa membedakan
antara objek Deck dengan array berisi Card yang dikandungnya. Berikut ini adalah
diagram keadaan yang menunjukkan keadaan objek Deck ketika tidak ada satu pun
kartu dimasukkan ke dalamnya.
261
Seperti biasa, konstruktor akan menginisialisasi variabel instan, tapi dalam kasus ini,
konstruktor menggunakan perintah new untuk membuat sebuah array yang berisi kartu
kartu. Meskipun begitu, konstruktor ini tidak membuat kartu yang bisa masuk ke
dalamnya. Untuk melakukannya, kita akan menulis konstruktor lain yang akan
membuat sebuah tumpukan berisi 52 kartu lalu mengisinya dengan objekobjek Card:
public Deck () {
cards = new Card[52];
int index = 0;
for (int suit = 0; suit <= 3; suit++) {
for (int rank = 1; rank <= 13; rank++) {
cards[index] = new Card (suit, rank);
index++;
}
}
}
Perhatikan kemiripan metode di atas dengan metode buildDeck. Bedanya, di sini kita
harus sedikit mengubah sintaksnya agar bisa digunakan sebagai konstruktor. Untuk
memanggilnya, kita akan menggunakan perintah new:
Deck deck = new Deck ();
Sekarang, karena kita sudah mempunyai kelas Deck, kita bisa meletakkan semua
metode yang terkait dengan Deck di dalam definisi kelas Deck. Bila kita melihat
kembali semua metode yang sudah kita tulis sejauh ini, salah satu calon metode
262
(kandidat) yang bisa dimasukkan ke dalam definisi kelas adalah metode printDeck
(subbab 11.7).
Inilah metode tersebut setelah ditulis ulang. Hal ini dilakukan agar metode ini bisa
berfungsi ketika dipakai oleh objek Deck:
public static void printDeck (Deck deck) {
for (int i=0; i<deck.cards.length; i++) {
Card.printCard (deck.cards[i]);
}
}
Sesuatu yang paling jelas harus diganti adalah tipe parameternya, dari Card[] menjadi
Deck. Perubahan kedua adalah bahwa sekarang kita tidak bisa lagi menggunakan
perintah deck.length untuk mendapatkan panjang array. Hal ini terjadi karena deck
sekarang merupakan objek dari Deck, bukan sebuah array lagi. deck memang berisi
array, tapi dia sendiri bukanlah sebuah array. oleh karena itu, kita harus menulis
perintah deck.cards.length untuk mengekstrak array dari objek Deck untuk kemudian
mengambil ukuran array tersebut.
Untuk alasan yang sama, kita harus menggunakan perintah deck.cards[i] untuk
mengakses sebuah elemen milik array, bukan dengan perintah deck[i]. Perubahan
terakhir adalah bahwa pemanggilan printCard harus dilakukan secara eksplisit. Hal ini
sebagai bukti bahwa metode ini memang benarbenar didefinisikan di dalam kelas
Card.
Untuk metodemetode yang lainnya, masih tidak jelas apakah mereka harus dimasukkan
ke dalam kelas Card ataukah Deck. Sebagai contoh, findCard menggunakan argumen
yang terdiri dari sebuah Card dan Deck. Oleh karena itu, Anda sebenarnya bisa
263
meletakkan metode ini ke dalam salah satu kelas yang disebutkan.
12.2 Shuffling
Untuk permainanpermainan kartu yang sering dimainkan, Anda harus mampu
melakukan pengacakan (kocok) satu tumpukan kartu. Kegiatan ini disebut dengan
shuffling. Dalam subbab 10.6 kita telah melihat cara membuat bilangan acak, tapi Anda
mungkin masih bingung untuk mengimplementasikannya ke dalam permainan kartu.
Satu kemungkinan yang bisa diambil adalah dengan menggunakan cara kita (baca:
manusia) ketika mengacak kartu. Umumnya, pengacakan dilakukan dengan cara
membagi tumpukan kartu ke dalam dua bagian. Setelah itu, kita akan menyusun ulang
tumpukan itu dengan mengambil satu kartu secara acak dari dua tumpukan tersebut.
Karena manusia tidak bisa mengacak dengan sempurna, setelah 7 kali pengulangan,
biasanya urutan kartu dalam tumpukan menjadi tampak seperti benarbenar tidak
beraturan. Pun begitu dengan sebuah program komputer, setelah 8 kali melakukan
pengacakan, urutan kartu dalam tumpukan justru kembali ke dalam posisi semula.
Untuk mendiskusikan hal seperti ini, silahkan mengunjungi
http://www.wiskit.com/marilyn/craig.html atau Anda bisa mencarinya di mesin pencari
menggunakan kata kunci “perfect shuffle”.
Algoritma pengacakan yang lebih baik adalah dengan cara menelusuri tumpukan kartu
satu kali untuk kemudian memilih dua kartu lalu menukarnya dalam tiap perulangan.
Berikut ini adalah ringkasan cara kerja algoritma ini. Untuk mendeskripsikan program
ini, saya akan menggunakan kombinasi antara pernyataanpernyataan dalam Java
dengan katakata dalam bahasa alamiah (baca: bahasa yang kita gunakan seharihari).
Inilah yang disebut dengan pseudocode:
for (int i=0; i<deck.length; i++) {
// pilihlah sebuah bilangan acak di antara i dan
264
// deck.cards.len gth
// tukarkan kartu urutan ke i dengan kartu yang sudah
// terpilih secara acak.
}
Hal positif ketika menggunakan pseudocode seperti di atas adalah bahwa terkadang kita
dapat melihat metode yang akan kita butuhkan dengan lebih jelas. Dalam kasus ini, kita
membutuhkan metode seperti randomInt, yang dapat memilih bilangan bertipe integer
secara acak di dalam interval antara variabel low dan high. Selain itu, metode ini juga
mampu mengambil dua indeks lalu menukar kartu yang berada pada posisi indeks yang
terpilih.
Anda mungkin dapat menulis metode randomInt dengan melihat subbab 10.6, tapi
Anda juga harus hatihati terhadap indeks yang dihasilkan, karena mungkin saja indeks
tersebut keluar dari batasan interval.
Anda juga bisa membuat metode swapCard sendiri tentunya. Satusatunya trik yang
akan Anda gunakan adalah apakah Anda hanya akan menukar referensi yang menunjuk
ke posisi kartu atau Anda justru akan menukar juga isi dari kartukartu tersebut. Pilihan
Anda akan sangat menentukan kecepatan program yang Anda buat secara keseluruhan.
Saya akan meninggalkan implementasi masalah ini sebagai latihan.
12.3 Pengurutan (Sorting)
Karena sebelumnya kita telah mengacakacak tumpukan kartu, sekarang kita
membutuhkan suatu cara untuk menempatkan semuanya kembali ke posisi terurut.
Ironisnya, ada sebuah algoritma untuk melakukan pengurutan yang sangat mirip dengan
pengacakan. Algoritma ini sering disebut selection sort. Disebut demikian karena ia
265
bekerja dengan cara menelusuri array secara berulangulang lalu memilih kartu
terendah dalam setiap perulangan.
Pada perulangan pertama, kita menemukan kartu terendah (the lowest) lalu menukarnya
dengan kartu yang berada di posisi ke0. Pada perulangan berikutnya, yaitu yang kei,
kita akan mendapatkan kartu terendah di sebelah kanan i lalu menukarnya dengan kartu
di indeks yang kei.
Berikut ini merupakan pseudocode dari selection sort:
for (int i=0; i<deck.length; i++) {
// temukan kartu terendah yag berada dalam i
atau
// yang berada di daerah sebelah kanannya
// tukarlah kartu urutan kei dengan kartu
terendah
}
Lagilagi, pseudocode akan menolong kita dengan memakai rancangan helper
methods. Dalam kasus ini, kita bisa menggunakan swapCards kembali, jadi kita hanya
membutuhkan satu buah metode lagi, yakni findLowestCard. Metode ini
membutuhkan sebuah array berisi kartukartu dan sebuah indeks tempat di mana ia
pertama kali melakukan pencarian.
Sekali lagi, saya akan meninggalkan implementasinya khusus untuk pembaca.
12.4 Subtumpukan (Subdecks)
Apa yang harus kita lakukan untuk merepresentasikan kartukartu yang berada dalam
266
genggaman tangan seorang pemain (Hand) atau beberapa bentuk subset lain dari satu
tumpukan kartu yang penuh? Kemungkinan pertama adalah dengan cara menulis sebuah
kelas baru dengan nama Hand, yang mungkin akan mewarisi sifatsifat kelas Deck.
Kemungkinan lainnya, yang akan saya tunjukkan di sini, adalah merepresentasikan
Hand menggunakan sebuah objek Deck yang jumlahnya kurang dari 52 kartu.
Kita mungkin membutuhkan sebuah metode, yaitu subdeck, yang menggunakan sebuah
Deck dan sekumpulan indeks. Metode ini akan mengembalikan sebuah Deck baru yang
berisi subset dari kartukartu yang telah ditentukan:
public static Deck subdeck (Deck deck, int low, int
high) {
Deck sub = new Deck (highlow+1);
for (int i = 0; i<sub.cards.length; i++) {
sub.cards[i] = deck.cards[low+i];
}
return sub;
}
Ukuran subdeck adalah highlow+1. Hal ini terjadi karena kita juga akan
mengikutsertakan kartu terendah dan kartu tertinggi. Proses komputasi semacam ini
bisa menimbulkan kebingungan, dan juga bisa membawa kita ke dalam error yang
termasuk offbyone error. Membuat gambar atau visualisasi merupakan cara yang
ampuh untuk menghindarinya.
Karena kita menyediakan sebuah argumen dengan peringkat new, maka konstruktor
yang dipanggil akan menjadi yang pertama, yang hanya mengalokasikan array tapi tidak
mengalokasikan kartu apa pun. Di dalam perulangan for, subtumpukansubtumpukan
akan dibuat beserta salinan referensi yang ada dalam tumpukannya.
267
Berikut ini merupakan diagram keadaan sebuah subtumpukan yang dibuat
menggunakan parameter low = 3 dan high = 7. Hasilnya adalah sebuah hand berisi 5
kartu yang saling berbagi referensi dengan tumpukan utamanya; Anda dapat
menganggap hand tersebut sebagai nama alias dari tumpukan.
Saya tidak menyarankan Anda untuk menggunakan konsep aliasing karena perubahan
yang terjadi pada satu subtumpukan akan langsung memengaruhi subtumpukan lain.
Kecuali kalau objekobjek yang akan dikenai aliasing tersebut merupakan objek yang
tidak bisa diubah (immutable). Dalam kasus ini, kita tidak bisa mengubah peringkat atau
kelompok sebuah kartu. Sebaliknya, kita justru akan membuat setiap kartu satu kali saja
lalu memperlakukannya sebagai objek yang immutable. Dengan alasan ini, penggunaan
aliasing untuk Card merupakan keputusan yang masih relevan.
12.5 Pengacakan dan Pembagian Kartu (Shuffling and Dealing)
Dalam subbab 12.2 saya menulis pseudocode untuk algoritma pengacakan. Anggaplah
saat ini kita mempunyai sebuah metode dengan nama shuffleDeck. Metode ini
menggunakan sebuah tumpukan sebagai argumen lalu mengacaknya. Kita akan
mengacak tumpukan dengan cara seperti berikut:
Deck deck = new Deck ();
268
shuffleDeck (deck);
Kemudian, untuk membagi/mendistribusikan kartu ke beberapa hand lain, kita bisa
menggunakan subdeck:
Deck hand1 = subdeck (deck, 0, 4);
Deck hand2 = subdeck (deck, 5, 9);
Deck pack = subdeck (deck, 10, 51);
Kode ini akan menempatkan 5 kartu di hand pertama, 5 kartu berikutnya di hand yang
lain, dst.
Pada saat Anda berpikir mengenai proses bagibagi kartu (dealing), apakah Anda juga
berpikir mengenai pembagian kartu yang harus dilakukan secara satu per satu untuk
setiap pemain, dengan teknik roundrobin, laiknya permainan kartu dalam dunia nyata?
Saya sempat kepikiran juga tentang ini, tapi kemudian saya menganggapnya sebagai
sesuatu yang tidak terlalu penting ketika harus diimplementasikan ke dalam program
komputer. Teknik roundrobin digunakan untuk menghindari pengacakan yang tidak
sempurna sekaligus kecurangan yang mungkin dilakukan oleh sang pembagi kartu
(dealer). Tetapi Anda tidak usah khawatir, isu di atas bukanlah masalah penting dalam
dunia komputer.
Contoh ini dapat menjadi sebuah peringatan yang berguna terhadap salah satu metafora
berbahaya dalam dunia rekayasa (engineering): terkadang kita memaksa komputer
untuk mengerjakan halhal tidak perlu yang memang berada di luar kemampuannya,
atau mengharapkan kemampuankemampuan dari komputer yang sebenarnya memang
tidak dimilikinya. Ini semua terjadi karena kita telah melebihlebihkan sebuah metafora
jauh di atas ambang batasnya sendiri. Berhatihatilah terhadap penggunaan analogi yang
salah.
269
12.6 Mergesort
Dalam subbab 12.3, kita telah melihat sebuah algoritma pengurutan sederhana yang
akhirnya malah menimbulkan ketidakefisienan bagi program itu sendiri. Untuk
mengurutkan data sebanyak n item, algoritma ini harus menelusuri array sebanyak n
kali. Dan untuk setiap penelusuran, dibutuhkan waktu sejumlah n. Oleh karena itu, total
waktu yang dibutuhkan dalam satu pemrosesan lengkap adalah n 2.
Dalam bab ini saya akan memberikan sebuah algoritma yang lebih efisien, yakni
mergesort. Untuk mengurutkan n item, mergesort membutuhkan waktu sebanyak n log
n. Mungkin tidak terlalu bagus, tapi jika nilai n bertambah semakin besar, maka
perbedaan antara n2 dan n log n menjadi sangat signifikan. Cobalah dengan beberapa
nilai n lalu lihat hasilnya.
Ide dasar dari mergesort adalah sebagai berikut: jika Anda mempunyai dua
subtumpukan yang keduanya sudah dalam posisi terurut, maka Anda akan lebih mudah
(juga cepat) untuk menggabungkannya ke dalam satu tumpukan tunggal yang terurut.
Cobalah beberapa hal di bawah ini:
1. Buatlah dua subtumpukan yang masingmasing terdiri dari 10 kartu kemudian
urutkanlah keduanya sehingga ketika keduanya saling berhadaphadapan, kartu
terendah akan berada di posisi teratas. Letakkan kedua subtumpukan tersebut
tepat di depan muka Anda.
2. Bandingkan kartu teratas dari masingmasing subtumpukan lalu pilihlah yang
terendah di antara keduanya. Tukarkan posisi keduanya lalu tempatkan
keduanya ke dalam tumpukan baru yang akan menjadi tumpukan tunggal.
3. Ulangi langkah kedua sampai kartukartu di kedua subtumpukan itu habis.
Kartukartu yang sudah dikenai proses pada langkah kedua adalah kartukartu
yang telah digabung ke dalam satu tumpukan tunggal.
270
Hasilnya adalah sebuah tumpukan tunggal yang terurut tentunya. Berikut ini merupakan
pseudocode algoritma di atas:
public static Deck merge (Deck d1, Deck d2) {
/* buat sebuah tumpukan yang cukup besar untuk
menampung
semua kartu dari dua subtumpuk an */
Deck result = new Deck (d1.cards.len gth +
d2.cards.leng 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 n
// masukkan pemenangnya ke dalam tumpukan yang baru
}
return result;
271
}
Cara terbaik untuk menguji metode merge adalah dengan membuat sebuah tumpukan
lalu mengacaknya, gunakan subtumpukan untuk membuat dua hand dengan jumlah
kartu yang sedikit, lalu gunakan algoritma yang ada di bab sebelumnya untuk
mengurutkan setiap subtumpukan milik hand. Setelah selesai, Anda bisa melewatkan
kedua subtumpukan milik hand ke metode merge untuk melihat apakah metode ini
berhasil atau justru sebaliknya.
Jika Anda melihat metode merge ini berhasil dengan baik, cobalah implementasi
sederhana dari metode mergeSort berikut ini:
public static Deck mergeSort (Deck deck) {
// temukan titik tengah tumpukan tersebut
// bagilah tumpukan itu menjadi dua subtumpukan
// urutkan kedua subtumpukan menggunakan sortDeck
// gabungkan dua subtumpukan itu lalu kembalikan
// hasilnya
}
Nah, kalau Anda juga berhasil mengimplementasikan kode di atas dengan benar, halhal
yang menarik akan segera terjadi! Sesuatu yang ajaib dalam mergesort adalah fakta
bahwa teknik ini melakukan proses yang bersifat rekursif. Di saat Anda mengurutkan
subtumpukansubtumpukan itu, Anda tidak perlu lagi repotrepot memanggil metode
lama yang lebih lambat. Anda cukup menggunakan metode mergeSort milik Anda
sendiri yang lebih keren di dalamnya.
Bukan hanya soal penggunaan pemikiran/ide yang bagus, langkah di atas sebenarnya
kita lakukan agar kita dapat mencapai kinerja yang sebelumnya telah saya janjikan.
272
Agar dapat berfungsi dengan baik, Anda harus menambahkan base case ke dalam
program Anda agar program itu tidak merekursif selamanya. Base case yang sederhana
adalah subtumpukan yang berisi 0 atau 1 kartu. Jika mergeSort menerima subtumpukan
seperti ini, ia bisa mengembalikan subtumpukan itu tanpa mengubahnya sama sekali
karena subtumpukan seperti itu tentu berada dalam posisi yang sudah terurut.
Berikut ini adalah versi rekursif dari mergesort:
public static Deck mergeSort (Deck deck) {
// if the deck is 0 or 1 cards, return it
// find the midpoint of the deck
// divide the deck into two subdecks
// sort the subdecks using mergesort
// merge the two halves and return the result
}
Seperti biasa, ada dua paradigma pengembangan untuk program yang bersifat rekursif;
Anda bisa melihatnya dari sisi keseluruhan proses yang akan dieksekusi atau Anda juga
bisa melihatnya sebagai sesuatu yang bisa dipilahpilah menjadi bagianbagian kecil.
Saya telah mencontohkan contohcontoh di atas agar Anda bisa mempraktikkan
paradigma kedua.
Ketika Anda menggunakan sortDeck untuk mengurutkan subtumpukansubtumpukan,
Anda tentu tidak merasa harus mengikuti dan memperhatikan keseluruhan proses yang
dieksekusi bukan? Anda hanya perlu menganggap bahwa metode sortDeck akan
bekerja dengan baik karena Anda telah memeriksanya (debug) sebelum digunakan.
Well, sesuatu yang harus Anda lakukan jika ingin melihat mergeSort bersifat rekursif
adalah dengan mengganti satu algoritma pengurutan dengan algoritma lainnya. Saya
kira Anda tidak mempunyai alasan yang cukup bagus untuk melihat program ini dengan
273
cara yang lain.
Sebenarnya, ketika Anda menulis programprogram bersifat rekursif, Anda harus
memberikan perhatian lebih pada saat membuat base case. Base case tersebut haruslah
benar dan bisa berfungsi dengan tepat. Selain faktor ini, membuat versi rekursif dari
suatu program tentunya tidak akan menjadi masalah lagi untuk Anda. Semoga berhasil!
12.7 Daftar KataKata
Istilah Arti
pseudocode Suatu teknik untuk merancang program komputer dengan
cara membuat deskripsi singkat (rough drafts) program
tersebut menggunakan kombinasi antara bahasa natural
(bahasa yang kita gunakan seharihari) dengan bahasa Java
Helper method Metode kecil yang umumnya tidak melakukan sesuatu
secara signifikan untuk dirinya sendiri tetapi justru malah
menolong metode lain agar metode tersebut dapat berfungsi
lebih baik
12.8 Latihan
Latihan 12.1
Tulislah sebuah versi metode findBisect yang menggunakan sebuah subtumpukan
sebagai argumennya ketimbang menggunakan sebuah tumpukan dan interval indeks
(lihat subbab 11.8). Versi mana yang kirakira lebih rawan dengan error? Versi mana
yang menurut Anda lebih efisien?
Latihan 12.2
Dalam versi kelas Card sebelumnya, sebuah tumpukan diimplementasikan sebagai
274
sebuah array yang berisi Card. Contoh; ketika kita melewatkan sebuah “deck” sebagai
parameter, tipe parameter yang sebenarnya adalah Card[].
Dalam bab ini, kita telah mengembangkan sebuah representasi alternatif untuk sebuah
tumpukan, yaitu sebuah objek dengan nama Deck yang mengandung sebuah array berisi
kartukartu sebagai variabel instannya. Dalam latihan ini, Anda akan
mengimplementasikan sebuah representasi baru untuk sebuah tumpukan.
a) Buatlah file tambahan dengan nama Deck.java ke dalam program Anda. File ini
akan berisi semua definisi mengenai kelas Deck.
b) Buatlah sebuah konstruktor untuk kelas ini seperti yang telah ditunjukkan dalam
subbab 12.1.
c) Di antara metodemetode yang ada di dalam kelas Card, pilihlah metode yang sesuai
dan dapat dijadikan sebagai anggota kelas Deck yang baru. Pindahkan metode
metode yang Anda pilih tersebut ke dalam kelas baru itu lalu buatlah beberapa
perubahan signifikan agar program Anda bisa dicompile dan dieksekusi kembali
dengan benar.
d) Lihat kembali program tersebut kemudian identifikasilah setiap tempat ketika sebuah
array yang berisi Card digunakan untuk merepresentasikan sebuah tumpukan.
Modifikasilah keseluruhan program sehingga program akan menggunakan objek
Deck daripada yang lainnya. Anda bisa melihat metode printDeck dalam subbab
12.1 sebagai contoh.
Perubahan ini sebaiknya dilakukan dengan sistem per metode. Jadi, di sini Anda
diminta untuk langsung memeriksa perubahan yang terjadi setiap kali Anda telah
selesai memodifikasi sebuah metode. Di sisi lain, jika Anda memang benarbenar
yakin dengan apa yang Anda lakukan, Anda juga bisa menggunakan perintah
searchandreplace.
275
Latihan 12.3
Tujuan latihan ini adalah untuk mengimplementasikan algoritma pengacakan dan
pengurutan yang telah dipelajari dalam bab ini.
a) Tulislah metode swapCards yang menggunakan sebuah tumpukan (array yang berisi
kartukartu) dan dua indeks. Metode ini berfungsi untuk menukar kartukartu yang
berada dalam dua lokasi tersebut.
PETUNJUK: Metode ini harus menukar referensi yang mengacu ke dua kartu itu,
bukan isinya. Cara ini tidak hanya memiliki kinerja lebih cepat, tapi juga dapat
memudahkan kita ketika harus berhadapan dengan kasus di mana kartukartu
tersebut sedang disamarkan (aliasing).
b) Tulislah metode shuffleDeck yang menggunakan algoritma di subbab 12.2. Anda
mungkin ingin memanfaatkan randomInt yang ada di latihan 10.2.
c) Tulislah metode findLowestCard yang menggunakan compareCard untuk
menemukan kartu terendah di dalam interval kartukartu yang diberikan (antara
lowIndex hingga highIndex).
d) Tulislah metode sortDeck yang menyusun satu tumpukan kartu dari kartu terendah
hingga yang tertinggi.
Latihan 12.4
Untuk membuat hidup para pengacak dan pembagi kartu (cardcounter) semakin sulit,
sekarang banyak kasino yang menggunakan mesin pengacak yang mengacak kartu
secara secara naiksatu per satu (incremental shuffling). Ini berarti, setiap selesai
membagi kartu untuk satu pemain, kartu yang telah dibagi tersebut akan dikembalikan
lagi ke dalam tumpukan. Kartu ini dikembalikan lagi ke dalam tumpukan tanpa
melakukan pengacakan ulang. Jadi, mesin hanya memasukkan kartu itu ke posisi lain
secara acak.
276
Tulislah metode incrementalShuffle yang menggunakan sebuah Deck dan sebuah
Card sebagai argumen. Metode ini berfungsi untuk memasukkan atau menyisipkan
kartu ke dalam tumpukan di posisi yang acak. Berikut ini merupakan contoh sebuah
incremental algorithm.
Latihan 12.5
Tujuan latihan ini adalah untuk menulis sebuah program yang dapat membuat
permainan poker untuk beberapa hand secara acak lalu mengelompokkannya. Dengan
cara ini, kita dapat memperkirakan probabilitas dari masingmasing hand. Jangan
khawatir jika Anda belum pernah memainkannya; saya akan memberi tahu semua yang
perlu Anda ketahui.
a) Untuk pemanasan, tulislah sebuah program yang menggunakan shuffleDeck dan
subdeck untuk membuat dan mencetak masingmasing lima kartu yang berada di
empat hand pemain poker yang sudah diacak. Bagaimana, sudah mengerti? Berikut
ini adalah beberapa status hand dalam permainan poker, dalam posisi yang terurut
naik:
pair: dua kartu dengan peringkat (rank) yang sama
two pair: dua pasang kartu dengan peringkat yang sama
three of a kind: tiga kartu dengan peringkat yang sama
straight: lima kartu dengan peringkat yang berurutan
flush: lima kartu dengan kelompok (suit) yang sama
full house: tiga kartu dengan satu peringkat yang sama, dua kartu lainnya tidak sama
four of a kind: empat kartu dengan peringkat yang sama
straight flush: lima kartu yang peringkatnya berurutan dan tergabung dalam satu
kelompok yang sama
277
b) Tulislah metode isFlush yang menggunakan sebuah Deck sebagai parameter dan
mengembalikan nilai bertipe boolean sebagai indikator apakah hand yang diperiksa
berstatus flush ataukah bukan.
c) Tulislah sebuah metode isThreeKind yang menggunakan sebuah hand dan
mengembalikan sebuah nilai bertipe boolean yang mengindikasikan apakah hand
tersebut termasuk Three Of A Kind ataukah bukan.
d) Tulislah sebuah perulangan yang menghasilkan beberapa ribu hand dan memeriksa
apakah handhand tersebut termasuk Flush ataukah Three Of A Kind.
e) Tulislah beberapa metode yang bisa menguji statusstatus hand poker lainnya.
Beberapa diantaranya mungkin lebih mudah ketimbang lainnya. Mungkin akan lebih
baik jika Anda menulis beberapa helpermethod yang bisa digunakan secara umum
untuk beberapa metode penguji sekaligus.
f) Dalam beberapa permainan poker, masingmasing pemain akan mendapatkan tujuh
kartu. Dari ketujuh kartu tersebut, mereka diizinkan untuk membuat kombinasi dari
lima kartu saja. Modifikasilah program Anda agar bisa menghasilkan hand yang
terdiri dari tujuh kartu sekaligus menghitung ulang probabilitas yang mungkin
terjadi.
Latihan 12.6
Sebagai sebuah tantangan khusus untuk Anda, pikirkanlah sebuah algoritma yang
mampu memeriksa wild card yang terdapat dalam handhand yang sedang bermain.
Sebagai contoh, jika “deuces are wild”, ini berarti bahwa jika Anda mempunyai sebuah
kartu dengan peringkat 2, Anda bisa menggunakan kartu ini untuk merepresentasikan
kartu apa pun yang berada dalam tumpukan.
Latihan 12.7
Tujuan latihan ini adalah agar Anda bisa mengimplementasikan mergesort.
278
a) Dengan pseudocode yang ada di subbab 12.6, tulislah metode dengan nama merge.
Ujilah metode ini sebelum Anda memasukkannya ke dalam metode mergeSort.
b) Tulislah versi sederhana dari mergeSort, yakni yang mampu membagi tumpukan
menjadi dua. Gunakan sortDeck untuk mengurutkan kedua bagian tersebut, lalu
gunakan metode merge untuk membuat sebuah tumpukan baru yang sudah terurut.
c) Tulislah versi mergeSort yang benarbenar bersifat rekursif. Ingatlah bahwa
sortDeck merupakan sebuah modifier sementara mergeSort adalah sebuah fungsi.
Ini berarti bahwa keduanya dipanggil dengan cara yang berbeda:
sortDeck (deck); // modifies existing deck
deck = mergeSort (deck); // replaces old deck with new
279
Bab 13
Pemrograman Berorientasi Objek
13.1 Bahasa Pemrograman dan Tekniktekniknya
Ada begitu banyak bahasa pemrograman di dunia ini, begitu juga dengan teknikteknik
pemrograman (sering disebut sebagai paradigma). Tiga teknik yang sudah muncul
dalam buku ini antara lain; prosedural, fungsional, dan objectoriented. Meskipun Java
sering disebutsebut sebagai bahasa pemrograman berorientasi objek, tapi kita masih
diperkenankan untuk menulis program dengan teknik mana pun. Teknik yang saya
peragakan di sini termasuk teknik prosedural. Programprogram Java yang sudah ada
beserta paketpaket bawaan Java ditulis menggunakan gabungan dari ketiga teknik
tersebut. Bedanya, programprogram dan paketpaket tersebut lebih cenderung ditulis
menggunakan paradigma pemrograman berorientasi objek.
Bukan perkara mudah untuk mendefinisikan arti dari pemrograman berorientasi objek,
namun beberapa poin berikut ini bisa Anda jadikan pegangan untuk melihat
karakteristiknya:
● Definisi objek (yang berupa kelaskelas) biasanya berkaitan dengan objekobjek
yang berada di dunia nyata. Contoh; di subbab 12.1, pembuatan kelas Deck
merupakan langkah menuju penggunaan konsep pemrograman berorientasi
objek.
● Metodemetode yang dibuat umumnya berupa metode objek (yang Anda panggil
melalui objek) ketimbang metode kelas (yang bisa langsung Anda panggil).
Sejauh ini, semua metode yang kita tulis merupakan metodemetode kelas.
Dalam bab ini, kita akan mencoba menulis beberapa metode objek.
280
● Fitur bahasa yang sangat terkait dengan sifat pemrograman berorientasi objek
adalah inheritance. Saya akan membahasnya dalam bab ini.
Akhirakhir ini, pemrograman berorientasi objek menjadi sangat populer, dan bahkan
ada orang yang menganggapnya sebagai teknik pemrograman yang lebih hebat
(superior) ketimbang teknikteknik lainnya. Saya berharap bahwa dengan menyuguhkan
kepada Anda cara menggunakan beberapa teknik yang lain berarti saya telah memberi
Anda alat untuk memahami sekaligus mengevaluasi keabsahan klaim di atas.
12.3 Metode Objek dan Metode Kelas
Ada dua tipe metode dalam Java; metode kelas (class method) dan metode objek (object
method). Sejauh ini, semua metode yang kita tulis termasuk ke dalam tipe metode kelas.
Metode ini bisa dikenali dari keberadaan kata kunci static yang berada di baris pertama
pada tiap metode. Sementara itu, semua metode yang tidak mempunyai static bisa
dianggap sebagai metode objek.
Meskipun kita belum menulis satu pun metode objek, sebenarnya kita telah memanggil
beberapa diantaranya. Kapan pun Anda memanggil sebuah metode melalui sebuah
objek, maka itulah yang disebut dengan metode objek. Misalnya metode charAt dan
semua metode yang kita panggil melalui objek String.
Semua yang bisa ditulis sebagai metode kelas juga bisa ditulis sebagai metode objek.
Begitu pula sebaliknya. Terkadang, akan tampak lebih alamiah untuk menggunakan
salah satunya saja. Sebentar lagi kita akan melihat bahwa metodemetode objek
seringkali terlihat lebih singkat dan padat ketimbang metodemetode kelas.
13.3 Objek Terkini
Ketika Anda memanggil sebuah metode melalui sebuah objek, objek tersebut akan
281
menjadi objek terkini (the current object). Di dalam metode ini, Anda bisa mengacu ke
variabel instan milik objek terkini menggunakan namanya, tanpa harus menulis nama
objek tersebut secara spesifik.
Selain itu, Anda juga bisa mengacu ke objek terkini menggunakan kata kunci this. Kita
sebelumnya telah melihat this digunakan di dalam konstruktor. Faktanya, Anda bisa
saja menganggap konstruktorkonstruktor sebagai metode objek yang bersifat khusus.
13.4 Bilangan Kompleks
Untuk contoh kasus, sepanjang bab ini kita akan mencoba membuat definisi kelas untuk
bilangan kompleks. Bilangan kompleks banyak digunakan di lingkungan matematika
dan dunia perekayasaan (engineering). Di samping itu, banyak juga perhitungan
perhitungan yang menggunakan aritmetika kompleks.
Sebuah bilangan kompleks merupakan hasil penjumlahan antara bilangan real dan
bilangan imajiner. Bilangan ini biasanya ditulis dalam bentuk x + yi, di mana x adalah
bilangan realnya, sementara y merupakan bilangan imajinernya. Notasi i sendiri adalah
representasi dari akar 1. Oleh karena itu, i * i = 1.
Kode berikut ini merupakan definisi kelas untuk tipe objek baru, Complex:
class Complex
{
// instance variables
double real, imag;
// constructor
public Complex () {
this.real = 0.0; this.imag = 0.0;
}
282
// constructor
public Complex (double real, double imag) {
this.real = real;
this.imag = imag;
}
}
Harusnya tidak ada yang mengejutkan Anda dari lampiran kode ini. Variabelvariabel
instannya bertipe double yang mewakili bilangan real dan imajiner. Kedua konstruktor
di atas termasuk jenis konstruktor yang sudah lazim dipakai: satu konstruktor tidak
membutuhkan satu parameter pun dan mengassign nilainilai default untuk variabel
variabel instan, sementara konstruktor lainnya menggunakan dua parameter yang
identik dengan variabel instannya. Seperti yang sudah kita lihat sebelumnya, kata kunci
this digunakan untuk mengacu ke objek yang sedang diinisialisasi.
Di dalam main, atau di mana pun kita ingin membuat objek Complex, kita mempunyai
pilihan; untuk membuat objeknya dahulu lalu mengatur variabelvariabel instannya atau
melakukan keduanya sekaligus pada saat yang bersamaan:
Complex x = new Complex ();
x.real = 1.0;
x.imag = 2.0;
Complex y = new Complex (3.0, 4.0);
13.5 Fungsifungsi dalam Bilangan Kompleks
Mari kita lihat beberapa operasi yang mungkin akan kita kerjakan untuk mengolah
bilanganbilangan kompleks. Nilai absolut dari sebuah bilangan kompleks didapat dari
283
hasil x2 y 2 . Metode abs adalah fungsi murni yang menghitung nilai mutlak. Bila
ditulis dalam bentuk metode kelas, seperti inilah kodenya:
// class method
public static double abs (Complex c) {
return Math.sqrt (c.real * c.real + c.imag *
c.imag);
}
Versi abs ini menghitung nilai absolut milik c, objek Complex yang diterimanya
sebagai parameter. Versi lain dari abs adalah sebuah metode objek; metode ini
menghitung nilai absolut milik objek terkini (objek yang memanggil metode). Oleh
karena itu, metode ini tidak akan membutuhkan parameter apa pun.
// object method
public double abs () {
return Math.sqrt (real*real + imag*imag);
}
Saya menghilangkan kata kunci static untuk mengindikasikan bahwa metode ini
merupakan metode objek. Selain itu, saya akan menghilangkan parameter yang tidak
diperlukan. Di dalam metode, saya bisa mengacu ke variabel instan real dan imag
dengan namanya tanpa harus menyertakan suatu objek secara spesifik. Java mengetahui
secara implisit bahwa saya sedang mengacu ke variabelvariabel instan milik objek
terkini. Jika saya ingin membuatnya bersifat eksplisit, saya akan menambahkan kata
kunci this:
// ob ject met hod
publi c do uble abs () {
ret urn Math .sqr t (this. real * this. real + this. imag *
284
this. imag );
}
Tapi itu hanya akan menambah panjang kode ini, ketimbang membuatnya tampak lebih
ringkas. Untuk memanggil metode ini, kita bisa memanggilnya melalui sebuah objek,
sebagai contoh:
Compl ex y = n ew C ompl ex ( 3.0, 4.0 );
doubl e re sult = y .abs ();
13.6 Fungsifungsi lainnya dalam Bilangan Kompleks
Operasi lain yang mungkin akan kita perlukan adalah fungsi penjumlahan. Anda bisa
menjumlahkan bilanganbilangan kompleks dengan menjumlahkan masingmasing
bilangan dengan pasangannya masingmasing; real dengan real dan imajiner dengan
imajiner. Dalam bentuk metode kelas, bentuknya akan seperti berikut:
publi c st atic Com plex add (Co mple x a, Com plex b) {
r etu rn n ew C ompl ex ( a.re al + b.r eal, a.i mag + b. imag );
}
Untuk memanggil metode ini, kita akan melewatkan kedua operan sebagai argumen:
Complex sum = add (x, y);
Dalam bentuk metode objek, metode ini hanya perlu satu argumen yang akan
ditambahkan ke objek yang sedang digunakan (current object).
publi c Co mple x ad d (C ompl ex b ) {
r etur n ne w Co mple x (r eal + b. real , im ag + b.i mag) ;
}
Lagilagi, di sini kita bisa mengacu ke variabel instan milik objek terkini secara
implisit, tapi untuk mengacu ke variabel instan milik objek b kita harus menamai b
285
secara eksplisit menggunakan notasi titik (dot). Untuk memanggil metode ini, Anda bisa
memanggilnya melalui salah satu operan lalu melewatkan operan lainnya sebagai
argumen.
Complex sum = x.add (y);
Dari contohcontoh ini, Anda bisa melihat bahwa objek terkini (this) bisa mengambil
alih dari salah satu parameter. Karena alasan inilah objek terkini sering juga disebut
sebagai parameter implicit.
13.7 Modifier
Seperti contohcontoh sebelumnya, kita akan melihat metode conjugate. Metode ini
merupakan metode pengubah yang mentransformasikan sebuah bilangan Complex ke
bentuk konjugasi kompleksnya. Konjugasi kompleks dari x + yi adalah x yi.
Sebagai metode kelas, bentuknya akan menjadi seperti ini:
public static void conjugate (Complex c) {
c.imag = c.imag;
}
Dalam bentuk metode objek, kodenya akan menjadi seperti ini:
public void conjugate () {
imag = imag;
}
Mulai sekarang, Anda harusnya sudah paham bahwa mengubah suatu metode dari satu
bentuk ke bentuk lainnya merupakan proses mekanis. Dengan sedikit latihan, Anda
akan mampu melakukannya tanpa harus berpikir panjang terlebih dahulu. Hal ini akan
bagus sekali karena Anda memang sudah seharusnya tidak perlu terhambat untuk
286
menulis salah satu diantaranya. Anda harus akrab dengan keduanya agar Anda bisa
langsung memilih mana yang lebih cocok dengan operasi yang sedang Anda tulis.
Sebagai contoh, saya berpikir bahwa add harusnya ditulis sebagai metode kelas karena
metode ini merupakan operasi simetris dari dua operan. Anda juga bisa menampilkan
kedua operan sebagai parameter. Bagi saya, agak ganjil rasanya jika kita memanggil
satu metode melalui sebuah operan lalu melewatkan operan lainnya sebagai
argumennya.
Di sisi lain, beberapa operasi sederhana yang digunakan untuk satu objek bisa ditulis
dengan baik menggunakan metodemetode objek (pun jika metodemetode itu
membutuhkan beberapa argumen tambahan).
13.8 Metode toString
Setiap tipe objek mempunyai sebuah metode, yakni toString yang mampu
menghasilkan representasi string dari objek tersebut. Ketika Anda mencetak sebuah
objek menggunakan print atau println, Java akan memanggil metode toString milik
objek tersebut. Versi default metode ini akan mengembalikan sebuah string yang berisi
tipe objek beserta sebuah identifier unik (lihat subbab 9.6). Ketika Anda mendefinisikan
sebuah tipe objek yang baru, Anda bisa mengoverride perilaku (behaviour) default dari
suatu metode dengan membuat sebuah metode baru yang perilakunya sesuai dengan
keinginan Anda.
Berikut ini tampilan toString di dalam kelas Complex:
public String toString () {
return real + " + " + imag + "i";
}
287
Tentu saja, tipe kembalian milik toString adalah String. Metode ini tidak
membutuhkan parameter apa pun. Anda bisa memanggil toString dengan cara yang
sudah biasa kita pakai:
Complex x = new Complex (1.0, 2.0);
String s = x.toString ();
atau Anda juga bisa memanggilnya langsung melalui println:
System.out.println (x);
Dalam kasus ini, outputnya adalah 1.0 + 2.0i.
Versi toString ini masih belum cukup baik jika bilangan imajinernya bernilai negatif.
Sebagai latihan, buatlah sebuah versi yang lebih baik:
13.9 Metode equals
Ketika Anda menggunakan operator == untuk membandingkan dua objek, yang
sebenarnya Anda maksudkan adalah, “apakah dua objek ini merupakan objek yag
sama?” Itu dia, apakah kedua objek mengacu ke lokasi yang sama dalam memori.
Untuk kebanyakan tipe, definisi di atas bukanlah definisi yang tepat untuk menjelaskan
prinsip kesamaan (equality). Sebagai contoh, dua bilangan kompleks dianggap sama
jika bilangan real keduanya sama dan begitu pula dengan bilangan imajiner mereka.
Mereka tidak harus berupa objek yang sama.
Ketika Anda mendefinisikan sebuah tipe objek yang baru, Anda bisa membuat definisi
persamaan sendiri dengan menulis sebuah metode objek, yaitu equals. Untuk kelas
Complex, bentuknya akan tampak sebagai berikut:
public boolean equals (Complex b) {
return (real == b.real && imag == b.imag);
}
288
Dari hasil konvensi, metode ini akan selalu menjadi metode objek yang mengembalikan
nilai bertipe boolean.
Dokumentasi untuk equals di dalam kelas Object memberikan beberapa panduan yang
harus Anda terapkan ketika Anda ingin membuat definisi persamaan Anda sendiri:
Metode equals mengimplementasikan sebuah relasi ekuivalensi:
● Refleksif: untuk setiap nilai yang diacu x, x.equals(x) harus bernilai true.
● Simetris: untuk setiap nilai yang diacu x dan y, x.equals(y) harus bernilai true
jika dan hanya jika y.equals(x) juga bernilai true.
● Transitif: untuk setiap nilai yang diacu x, y dan y, jika x.equals(y) bernilai true
dan y.equals(z) juga bernilai true maka x.equals(z) juga harus bernilai true.
● Konsisten: untuk setiap nilai yang diacu x dan y, pemanggilan berulangulang
x.equals(y) harus bernilai true secara konsisten, atau bernilai false secara
konsisten.
● Untuk setiap nilai yang diacu x, x.equals(null) harus bernilai false.
Definisi equals yang saya berikan sudah memenuhi semua persyaratan di atas kecuali
satu. Yang mana? Sebagai latihan, coba perbaiki!
13.10 Memanggil Satu Metode Objek dari Metode Objek Lain
Seperti yang Anda harapkan, Java mengizinkan Anda untuk memanggil satu metode
objek dari metode objek lainnya. Sebagai contoh, untuk menormalisasi bilangan
kompleks, Anda akan membagi kedua bagian berdasarkan nilai mutlaknya. Sebenarnya
masih tidak cukup jelas kenapa ini bisa bermanfaat, inilah kenyataannya.
Mari kita tulis metode normalize sebagai sebuah metode objek, lalu membuatnya
sebagai modifier.
289
public void normalize () {
double d = this.abs();
real = real/d;
imag = imag/d;
}
Baris pertama akan menemukan nilai mutlak milik objek terkini dengan cara
memanggil abs melalui objek terkini. Dalam kasus ini, saya menyebut objek terkini
secara eksplisit, meski pun sebenarnya saya juga bisa melewatkannya. Jika Anda
memanggil satu metode objek melalui metode objek lainnya, Java akan mengasumsikan
bahwa Anda sedang memanggil metode tersebut melalui objek terkini.
Latihan 13.1
Tulisulang lah normalize agar menjadi fungsi murni. Lalu tulisulang lah metode ini
agar bisa menjadi metode kelas.
13.11 Keganjilan dan Error
Jika Anda mempunyai metode objek dan metode kelas dalam satu definisi kelas yang
sama, Anda justru akan lebih sering menjadi bingung. Cara yang lazim dipakai untuk
mengelola suatu definisi kelas adalah dengan meletakkan semua konstruktor di awal
awal kode, diikuti dengan semua metode objek lalu baru menuliskan semua metode
kelasnya.
Anda dapat memiliki sebuah metode objek dan metode kelas dengan nama yang sama
selama keduanya tidak sama dalam jumlah dan tipe parameter yang digunakan. Seperti
lazimnya sebuah overloading, Java akan menentukan versi mana yang dipanggil oleh
pengguna dengan melihat argumen yang akan dilewatkan.
Sekarang, karena kita sudah tahu apa maksud dan manfaat dari kata kunci static, Anda
290
mungkin berpendapat bahwa main termasuk metode kelas., yang juga berarti bahwa
tidak ada “objek terkini” ketika metode ini sedang dipanggil.
Karena objek terkini tidak boleh ada dalam sebuah metode kelas, Anda akan
menghasilkan error jika bersikeras menggunakan kata kunci this. Jika Anda
mencobanya, Anda akan mendapatkan pesan error yang berbunyi: “Undefine variable:
this”. Selain itu, Anda juga tidak akan bisa mengacu ke variabel instan tanpa notasi dot
dan nama objeknya. Jika masih berani mencobanya, Anda akan mendapatkan pesan:
“Can't make a static reference to non static variable ....”. Pesan ini sebenarnya bukanlah
pesan error yang baik karena masih menggunakan bahasa yang di luar standar. Tapi
jika Anda sudah paham apa maksudnya, otomatis Anda juga akan mengerti apa
maksudnya.
13.12 Inheritance
Kemampuan suatu bahasa yang paling sering dikaitkan dengan pemrograman
berorientasi objek adalah inheritance. Inheritance adalah suatu kemampuan untuk
mendefinisikan sebuah kelas baru yang merupakan hasil modifikasi dari kelas yang
sebelumnya telah didefinisikan (termasuk kelaskelas builtin).
Manfaat utama dari fitur ini adalah agar Anda dapat menambahkan metodemetode atau
variabelvariabel instan baru ke dalam kelas yang sudah ada tanpa harus
memodifikasinya. Hal ini sangat berguna jika Anda hendak melakukan sesuatu terhadap
kelaskelas builtin yang memang tidak bisa diubahubah meski pun Anda sangat ingin
melakukannya.
Disebut inheritance karena kelas yang baru akan mewarisi (inherit) semua variabel
instan dan metode yang ada dalam kelas yang sudah ada terlebih dahulu itu. Dengan
menggunakan analogi di atas, kelaskelas yang sudah ada/eksis terlebih dahulu itu
291
sering disebut dengan kelaskelas induk (parent class).
13.13 Lingkaran yang bisa digambar
Berikut ini merupakan satu contoh inheritance, di sini kita akan mengambil kelas
Rectangle yang sudah kita buat sebelumnya lalu kita beri tambahan agar “bisa
digambar”. Kita akan membuat sebuah kelas baru yang bernama DrawableRectangle
yang akan mewarisi semua variabel instan dan metode milik kelas Rectangle. Ditambah
sebuah metode draw yang akan mengambil sebuah objek Graphics sebagai parameter
lalu menggambar lingkaran yang kita inginkan.
import java.awt.*;
class DrawableRectangle extends Rectangle {
public void draw (Graphics g) {
g.drawRect (x, y, width, height);
}
}
Ya, itulah keseluruhan definisi kelas yang kita maksud. Baris pertama mengimpor paket
java.awt yang menjadi tempat di mana Rectangle dan Graphics didefinisikan.
Baris berikutnya mengindikasikan bahwa DrawableRectangle diwariskan dari
Rectangle. Kata kunci extends digunakan untuk mengidentifikasi kelas yang kita
warisi, yang dalam hal ini kita sebut sebagai kelas induk (parent class).
Sisanya adalah definisi dari metode draw, yang mengacu ke variabel instan x, y, width
dan height. Mungkin akan terkesan ganjil jika kita mengacu ke variabel instan yang
tidak tampak dalam definisi kelas, tapi ingatlah bahwa mereka sebenarnya sudah
diwariskan oleh kelas induk.
292
Untuk membuat dan menggambar sebuah DrawableRectangle, Anda bisa
menggunakan kode berikut ini:
public static void draw (Graphics g, int x, int y,
int width, int height) {
DrawableRectangle dr = new DrawableRectangle
();
dr.x = 10; dr.y = 10;
dr.width = 200; dr.height = 200;
dr.draw (g);
}
Parameterparameter dalam draw adalah sebuah objek Graphics dan kotak untuk
menampilkan gambar lingkaran (bukan koordinatkoordinat lingkaran tersebut).
Lagilagi, mungkin akan terkesan ganjil bila menggunakan perintah new untuk sebuah
kelas yang tidak mempunyai konstruktor. DrawableRectangle mewarisi konstruktor
bawaan (default) milik kelas induknya, jadi tidak ada masalah di sana.
Kita bisa menset variabel instan milik dr lalu memanggil metodemetode melalui
dirinya sendiri dengan cara yang sudah biasa kita lakukan sebelumnya. Ketika kita
memanggil grow atau metodemetode lain milik Rectangle melalui dr, Java akan
menggunakan metode yang sudah didefinisikan dalam kelas induk.
13.14 Hierarki Kelas
Dalam Java, semua kelas cenderung memperluas beberapa kelas yang sudah ada. Kelas
yang paling dasar adalah Object. Kelas ini tidak mengandung variabel instan tapi
menyediakan metode equals dan toString untuk kelas lainnya.
Banyak sekali kelas yang memperluas Object, termasuk hampir semua kelas yang
293
sudah kita tulis dan kelaskelas bawaan seperti Rectangle. Semua kelas yang tidak
menuliskan nama kelas induknya secara eksplisit akan mewarisi kelas Object secara
default.
Bagaimana pun juga, beberapa rantai pewarisan ada yang lebih panjang dari apa yang
kita perkirakan. Sebagai contoh, di Appendix D.6, di situ kita melihat bahwa kelas Slate
merupakan perluasan dari kelas Frame, yang merupakan perluasan dari kelas Window,
lalu berturutturut Container, Component, sampai ke kelas Object. Tidak peduli
seberapa panjang rantai pewarisan ini, Object merupakan induk dari semua induk kelas
yang ada.
Semua kelas dalam Java bisa dikelola ke dalam sebuah “silsilah keluarga” (family tree)
yang disebut hierarki kelas (class hierarchy). Object biasanya muncul di bagian atas,
dengan semua “anak” yang berada di bawahnya. Jika Anda melihat dokumentasi
mengenai Frame, sebagai contoh, Anda akan melihat hierarki yang menyusun silsilah
kelas Frame.
13.15 Rancangan Berorientasi Objek
Inheritance merupakan fitur yang sangat bagus (powerfull). Beberapa program yang
seringkali kompleks tanpanya, bisa ditulis secara lugas dan sederhana dengannya.
Selain itu, inheritance bisa memfasilitasi pemakaian ulang suatu kode (code reuse). Ini
karena Anda memiliki kesempatan untuk mengkustomisasi perilaku kelaskelas builtin
tanpa harus mengubahnya.
Di sisi lain, inheritance juga dapat menyebabkan suatu program lebih sulit dibaca. Hal
ini terjadi jika kodenya ditulis tanpa adanya kejelasan; kapan sebuah metode bisa
dipanggil, di mana mencari definisinya. Sebagai contoh, salah satu metode yang bisa
Anda panggil melalui Slate adalah getBounds. Bisakah Anda menemukan dokumentasi
294
mengenai metode ini? Saking sulitnya mencari dokumentasi untuk metode ini,
terkadang kita malah beranggapan bahwa janganjangan dokumentasinya terdapat di
induk dari induk dari induk dari induk Slate.
Dan jangan lupa, banyak halhal yang bisa dikerjakan dengan konsep inheritance juga
bisa dikerjakan sama baiknya (bahkan lebih) jika dikerjakan tanpanya.
13.16 Daftar KataKata
Istilah Arti
object method
Metode yang dipanggil melalui sebuah objek, yang
beroperasi dengan objek itu, yang diacu menggunakan kata
kunci this dalam Java atau “objek terkini” dalam bahasa
Indonesia.
class method
Metode dengan kata kunci static. Metode kelas tidak
dipanggil melalui objekobjek dan mereka juga tidak
memiliki kaitan dengan objek terkini.
current objectObjek tempat di mana sebuah metode objek dipanggil. Di
dalam metode, objek ini diacu dengan kata kunci this.
this Kata kunci yang mengacu ke objek terkini
implicit
Semua yang tidak dituliskan atau diungkapkan secara
langsung. Di dalam sebuah metode objek, Anda bisa
mengacu ke variabel instan secara implisit (tanpa
menyebutkan nama objek yang Anda maksud).
explicit
Semua yang dituliskan atau dinyatakan secara lengkap. Di
dalam metode kelas, semua referensi yang mengacu ke
variabel instan harus dinyatakan secara eksplisit.
295
13.17 Latihan
Latihan 13.2
Ubahlah metode kelas berikut ini ke dalam bentuk metode objek.
public static double abs (Complex c) {
return Math.sqrt (c.real * c.real + c.imag *
c.imag);
}
Latihan 13.3
Ubahlah metode objek berikut ini ke dalam bentuk metode kelas.
public boolean equals (Complex b) {
return (real == b.real && imag == b.imag);
}
Latihan 13.4
Latihan ini merupakan lanjutan dari latihan 9.3. Tujuannya adalah untuk melatih Anda
menulis sintaks metode objek dan membuat Anda akrab dengan beberapa pesan error
yang relevan dengan masalah ini.
a) Ubahlah metodemetode kelas yang ada dalam kelas Rational ke bentuk metode
objek. Lalu buatlah perubahan seperlunya dalam metode main.
b) Buatlah beberapa kesalahan! Cobalah memanggil metode kelas dengan cara yang
sama seperti ketika Anda memanggil metode objek. Lalu lakukan hal yang
sebaliknya. Cobalah mencari tahu halhal mana yang legal dan mana yang tidak.
Selain itu, pelajari juga pesanpesan error yang Anda dapat ketika Anda sengaja
mengacaukan keadaan.
c) Pikirkan mengenai pro dan kontra yang menyelimuti penggunaan dua tipe metode
296
ini; kelas dan objek. Tipe mana yang lebih jelas (secara umum)? Cara mana yang
lebih alami untuk mengekspresikan komputasi (atau, agar lebih adil dan jelas, tipe
komputasi seperti apa yang bisa diekspresikan paling baik menggunakan masing
masing bentuk)?
297
Bab 14
Linked List
14.1 Acuanacuan dalam Objek
Di dalam bab sebelumnya kita sudah melihat bahwa variabelvariabel instan sebuah
objek dapat berupa array dan atau juga objek.
Satu kemungkinan yang paling menarik adalah bahwa sebuah objek bisa berisi sebuah
acuan yang mengacu ke objek lain yang sama tipenya. Ada sebuah struktur data yang
akan mengambil manfaat besar dari fitur ini; list.
List terbuat dari dari kumpulan node yang dalam setiap nodenya berisi suatu acuan ke
node berikutnya dalam list. Selain itu, setiap node biasanya berisi satu unit data yang
disebut cargo. Dalam contoh pertama kita, kargo ini berupa integer tunggal. Namun di
kesempatan berikutnya kita akan menulis sebuah list generic yang dapat menyimpan
objek dari tipe apapun.
14.2 Kelas Node
Seperti biasa, ketika kita menulis sebuah kelas baru, kita akan memulainya dengan
variabel instan. Lalu dengan satu atau dua konstruktor baru kemudian diteruskan
dengan pembuatan metode toString agar kita bisa menguji mekanisme dasar dalam hal
pembuatan dan penyajian tipe baru tersebut.
public class Node {
int cargo;
Node next;
298
public Node () {
cargo = 0;
next = null;
}
public Node (int cargo, Node next) {
this.cargo = cargo;
this.next = next;
}
public String toString () {
return cargo + "";
}
}
Deklarasi untuk variabelvariabel instan sudah mengikuti spesifikasi yang ada secara
alami, sementara sisanya tinggal menyesuaikan dengan variabel instan. Ekspresi
cargo + "" sebenarnya agakagak aneh, tapi justru inilah cara terbaik untuk
mengkonversi sebuah integer menjadi String.
Untuk menguji implementasi ini, kita akan meletakkan kode seperti ini dalam main:
Node node = new Node (1, null);
System.out.println (node);
Hasilnya adalah
1
agar tambah menarik, kita membutuhkan sebuah list yang terdiri dari dua atau lebih
node!
Node node1 = new Node (1, null);
299
Node node2 = new Node (2, null);
Node node3 = new Node (3, null);
Kode ini menghasilkan tiga node tapi kita masih belum memiliki list karena nodenode
tersebut belum kita hubungkan (linked). Diagram keadaannya akan tampak sebagai
berikut:
Untuk menghubungkan nodenode itu, kita harus merancang kode sedemikian rupa
sehingga node pertama akan mengacu ke node kedua, sementara node kedua mengacu
ke node ketiga.
node1.next = node2;
node2.next = node3;
node3.next = null;
Acuan milik node ketiga adalah null. hal ini mengindikasikan bahwa node ini
merupakan akhir dari list. Sekarang diagram keadaannya akan tampak sebagai berikut:
Sekarang kita semua sudah tahu cara membuat nodenode sekaligus bagaimana cara
menghubungkannya agar menjadi list. Apa yang mungkin kurang jelas sampai saat ini
300
adalah kenapa.
14.3 List sebagai Collection
Sesuatu yang menyebabkan list bermanfaat adalah karena list merupakan suatu teknik
untuk merangkai objekobjek yang berbeda menjadi satu entitas tunggal, biasanya
diistilahkan dengan collection. Dalam contoh ini, node pertama dari list akan bertindak
sebagai acuan untuk keseluruhan list.
Jika kita ingin melewatkan list sebagai parameter maka yang harus kita lewatkan
hanyalah acuan yang mengacu ke node pertama. Sebagai contoh, metode printList
menggunakan satu node sebagai argumennya. Dimulai dari bagian kepala (head) list,
metode ini mencetak setiap node sampai selesai (ditandai dengan acuan yang berupa
null).
public static void printList (Node list) {
Node node = list;
while (node != null) {
System.out.print (node);
node = node.next;
}
System.out.println ();
}
Untuk memanggil metode ini, kita hanya perlu melewatkan sebuah acuan ke node
pertama:
printList (node1);
Di dalam printList, kita mempunyai sebuah acuan yang mengarah ke node pertama
301
dalam list, tapi tidak ada variabel yang mengacu ke nodenode lainnya. Kita harus
menggunakan nilai next dari setiap node untuk mendapatkan node berikutnya.
Diagram berikut ini menunjukkan nilai list dan nilai yang diambil oleh node:
Pergerakan untuk menelusuri sebuah list seperti ini sering disebut dengan istilah
traversal. Sama seperti pola penelusuran yang telah kita lakukan terhadap elemen
elemen suatu array. Adalah hal yang biasa/lazim untuk menggunakan variabel loop
seperti node untuk mengacu ke setiap node dalam list secara beruntun.
Hasil metode ini adalah
123
Jika menggunakan konvensi yang sudah ada, list sebaiknnya dicetak dalam tanda
kurung (parentheses) dengan koma di antara masingmasing elemen, misalnya (1, 2, 3).
Untuk latihan Anda, ubahlah printList sedemikian sehingga ia bisa menghasilkan
output seperti format di atas.
Untuk tambahan, tulisulanglah printList menggunakan perulangan for ketimbang
memakai while.
14.4 List dan Rekursi
Rekursi dan list berjalan seiringseirama laksana fava beans dan chianti yang nikmat.
Sebagai contoh, berikut ini merupakan algoritma untuk mencetak sebuah list dari bagian
302
belakang:
1. Pisahlah list ke dalam dua bagian: node pertama (sebagai kepala (head)) dan
sisanya (sebagai ekor (tail)).
2. Cetak ekornya dari belakang.
3. Cetak kepalanya.
Tentu saja, langkah kedua, pemanggilan rekursinya, mengasumsikan bahwa kita sudah
mempunyai suatu cara untuk mencetak list dari belakang. Tapi jika kita menganggap
bahwa pemanggilan rekursi ini bekerja dengan baik – leap of faith – barulah kita boleh
yakin kepada diri kita sendiri kalau algoritma ini telah bekerja dengan baik dan benar.
Yang kita butuhkan adalah base case, lalu sebuah cara untuk membuktikan bahwa
untuk setiap list yang kita gunakan, kita akan bisa kembali lagi ke base case. Pilihan
paling wajar untuk base case adalah sebuah list dengan satu elemen tunggal, tapi akan
lebih baik kalau kita mau menggunakan list kosong, yang direpresentasikan dengan
null.
public static void printBackward (Node list) {
if (list == null) return;
Node head = list;
Node tail = list.next;
printBackward (tail);
System.out.print (head);
}
Baris pertama merupakan base case yang tidak mengembalikan nilai apapun. Dua baris
berikutnya memecah list menjadi dua; head dan tail. Sementara dua baris terakhir
digunakan untuk mencetak list itu sendiri.
303
Kita memanggil metode ini dengan cara yang sama seperti ketika kita memanggil
printList:
printBackward (node1);
Hasilnya adalah sebuah backward list.
Bisakah kita membuktikan bahwa metode ini pasti akan selalu bisa dihentikan? Dengan
kata lain, apakah ia akan selalu sampai menuju base case? Sayangnya, jawabannya
adalah tidak. Ada beberapa list yang justru akan membuat lumpuh metode ini.
14.5 Infinite List
Tidak akan ada yang dapat mencegah sebuah node untuk mengacu kembali ke node
sebelumnya dalam list, tidak terkecuali node itu sendiri. Sebagai contoh, gambar berikut
ini menunjukkan sebuah list dengan dua node, satu di antaranya mengacu ke dirinya
sendiri.
Jika kita memanggil printBackward, metode ini akan terus melakukan rekursi tanpa
henti. Perilaku semacam ini akan membuat kita sulit bekerja dengannya.
Tapi meskipun begitu, mereka sebenarnya seringkali berguna. Contohnya, kita bisa
merepresentasikan suatu bilangan dalam bentuk list yang berisi digitdigit, lalu
menggunakan infinite list untuk merepresentasikan bagian atau angkaangka (fraction)
yang berulang (muncul lebih dari 1 kali).
Terlepas dari semua itu, sangat disayangkan karena kita tidak dapat membuktikan
304
bahwa printList dan printBackward bisa dihentikan. Hal terbaik yag bisa kita lakukan
adalah mengeluarkan pernyataan bersifat hipotesis (hypotetical statement), “jika suatu
list tidak berisi perulangan, maka metode ini akan berhenti”. Klaim seperti ini disebut
precondition. Klaim ini menekankan pada satu hambatan yang terdapat dalam salah
satu parameter lalu mendeskripsikan perilaku metode tersebut jika hambatan yang
dimaksud muncul. Kita akan melihat contoh lain sesaat lagi.
14.6 Teorema Ambiguitas Fundamental
Ada bagian dalam printBackward yang mungkin akan membuat Anda mengangkat
kedua alis:
Node head = list;
Node tail = list.next;
Setelah argumen pertama, head dan list mempunyai tipe dan nilai yang sama. Lalu
kenapa saya membuat satu variabel baru?
Alasannya adalah karena kedua variabel tersebut memiliki fungsi yang berbeda. Kita
menganggap head sebagai sebuah acuan yang mengacu ke node tunggal, sementara tail,
kita menganggapnya sebagai acuan yang mengacu ke node pertama dalam list. “Aturan
aturan” ini memang bukan bagian dalam program; tapi keduanya ada dalam benak sang
programmer.
Assignment kedua membuat sebuah acuan baru ke node kedua dalam list, tapi dalam
kasus ini kita menganggapnya sebagai sebuah list. Jadi, meskipun head dan tail
memiliki tipe yang sama, keduanya tetap memainkan peran yang berbeda.
Ambiguitas seperti ini sangat bermanfaat, tapi dapat menyebabkan programprogram
dengan list justru sulit dibaca. Saya sering menggunakan namanama variabel seperti
305
node dan list untuk mendokumentasikan bagaimana saya menggunakan sebuah
variabel, dan terkadang saya juga membuat variabel tambahan agar tidak terjadi
ambiguitas lagi.
Saya bisa menulis printBackward tanpa head dan tail, tapi saya pikir hal ini justru
akan membuatnya semakin sulit untuk dibaca:
public static void printBackward (Node list) {
if (list == null) return;
printBackward (list.next);
System.out.print (list);
}
Ketika kita melihat pemanggilan dua fungsi, kita harus ingat bahwa printBackward
memperlakukan argumennya sebagai sebuah list sementara print memperlakukan
argumennya sebagai sebuah objek tunggal.
Ingatlah mengenai teorema ambiguitas fundamental:
Sebuah variabel yang mengacu ke sebuah node mungkin akan memperlakukan node
tersebut sebagai sebuah objek tunggal atau sebagai node pertama dalam list.
14.7 Metode Objek Untuk NodeNode
Anda mungkin akan heran kenapa printList dan printBackward masuk ke dalam
golongan metode kelas. Saya sudah membuat klaim sebelumnya bahwa semua yang
bisa ditulis sebagai metode kelas juga bisa ditulis sebagai metode objek; masalahnya
adalah format mana yang Anda anggap lebih jelas.
Dalam kasus ini terdapat sebuah alasan bagus kenapa kita sampai menggunakan metode
objek. Adalah suatu yang legal mengirimkan null sebagai argumen ke metode kelas,
306
tapi adalah hal sebaliknya untuk memanggil sebuah metode objek melalui objek null.
Node node = null;
printList (node); // legal
node.printList (); // NullPointerException
Pembatasan ini justru membuat kita akan tampak aneh ketika hendak menulis kode
pemanipulasi list yang bersih dalam paradigma berorientasi objek. Tapi nanti, kita akan
melihat cara untuk menyelesaikan masalah ini.
14.8 Memodifikasi List
Satu cara yang jelasjelas bisa dipakai untuk memodifikasi sebuah list adalah dengan
mengubah kargo salah satu node yang ada. Namun, operasi yang lebih menarik adalah
dengan cara menambahkan, menghilangkan, atau mengubah urutan nodenode tersebut.
Sebagai contoh, kita akan menulis sebuah metode yang dapat menghilangkan node
kedua dalam list lalu memberikan hasil yang berupa acuan ke node yang dihilangkan.
public static Node removeSecond (Node list) {
Node first = list;
Node second = list.next;
// make the first node refer to the third
first.next = second.next;
// separate the second node from the rest of
the // list
second.next = null;
return second;
}
307
Lagilagi, di sini saya menggunakan variabel temporer agar kode di atas lebih mudah
dibaca. Berikut ini adalah cara menggunakan metode ini.
printList (node1);
Node removed = removeSecond (node1);
printList (removed);
printList (node1);
Hasilnya adalah
(1, 2, 3) the original list
(2) the removed node
(1, 3) the modified list
Berikut ini adalah diagram keadaan yang menunjukkan efek yang ditimbulkan oleh
operasi ini.
Apa yang akan terjadi jika memanggil metode ini lalu melewatkan sebuah list yang
hanya berisi satu elemen (satu singleton)? Apa yang terjadi jika melewatkan list kosong
sebagai sebuah argumen? Adakah precondition untuk metode ini?
14.9 Wrappers dan Helpers
Untuk beberapa operasi list, akan sangat berguna jika kita pisahkan metode ini menjadi
308
dua. Sebagai contoh, untuk mencetak satu list dari belakang dalam bentuk yang
konvensional, yakni (3, 2, 1), kita bisa menggunakan printBackward untuk mencetak
3, 2, tapi kita membutuhkan satu metode terpisah untuk mencetak tanda kurung dan
node pertamanya. Kita akan menyebutnya printBackwardNicely.
public static void printBackwardNicely (Node list)
{
System.out.print ("(");
if (list != null) {
Node head = list;
Node tail = list.next;
printBackward (tail);
System.out.print (head);
}
System.out.println (")");
}
Kembali, adalah ide yang bagus untuk memeriksa metodemetode seperti ini untuk
melihat apakah mereka benarbenar berfungsi dengan kondisikondisi khusus seperti list
kosong atau sebuah singleton.
Di bagian manapun dalam program, ketika kita menggunakan metode ini, kita akan
memanggil printBackwardNicely lalu ia akan memanggil printBackward untuk kita.
Dalam kondisi seperti ini, printBackwardNicely bertindak sebagai wrapper.
Sementara printBackward, yang berada dalam printBackwardNicely, disebut sebagai
helper.
309
14.10 Kelas intList
Ada beberapa masalah serius dan penting terkait dengan cara kita dalam
mengimplementasikan list. Dalam hukum sebab akibat yang selalu berubahubah, saya
akan memperlihatkan sebuah cara alternatif untuk mengimplementasikan listnya dahulu
baru kemudian menjelaskan kepada Anda semua masalah apa yang dapat
diselesaikannya.
Pertamatama, kita akan membuat sebuah kelas baru, IntList. Variabel instan kelas ini
adalah sebuah integer yang berisi panjang list dan sebuah acuan ke node pertama dalam
list. Objekobjek IntList bertindak sebagai manipulator list milik objekobjek Node.
public class IntList {
int length;
Node head;
public IntList () {
length = 0;
head = null;
}
}
Satu hal bagus mengenai kelas IntList adalah karena ia dapat memberi kita tempat,
secara alami, untuk meletakkan fungsi pembungkus (wrapper) seperti
printBackwardNicely di mana bisa membuat sebuah metode objek dalam kelas IntList.
public void printBackward () {
System.out.print ("(");
if (head != null) {
310
Node tail = head.next;
Node.printBackward (tail);
System.out.print (head);
}
System.out.println (")");
}
Untuk membuatnya menjadi semakin membingungkan, saya menamai ulang
printBackwardNicely. Nah, karena sekarang ada dua metode dengan nama
printBackward: satu ada dalam kelas Node (helper) sementara satunya lagi berada di
dalam kelas IntList (wrapper). Agar wrapper bisa memanggil helper, ia harus
mengidentifikasi kelasnya secara eksplisit (Node.printBackward).
Jadi, satu keuntungan dari kelas IntList adalah karena ia dapat menyediakan tempat
yang bagus untuk meletakkan fungsifungsi pembungkus (wrapper). Keuntungan
lainnya adalah karena ia dapat memudahkan kita untuk menambah atau menghilangkan
elemen pertama dalam list. Sebagai contoh, addFirst merupakan metode objek milik
IntList; metode ini menggunakan sebuah int sebagai argumennya lalu meletakkannya
ke posisi pertama dalam list.
public void addFirst (int i) {
Node node = new Node (i, head);
head = node;
length++;
}
Seperti biasa, untuk memeriksa kode seperti ini, adalah hal yang bagus untuk
memikirkan beberapa kasus khusus. Seperti, apa yang terjadi jika list pada awalnya
311
sudah diinisialisasi dalam keadaan kosong?
14.11 Invariants
Beberapa list memang sudah berada dalam keadaan yang “wellformed”, sementara
yang lain tidak. Sebagai contoh, jika sebuah list berisi sebuah perulangan, ini akan
menyebabkan rusaknya beberapa metode yang sudah kita buat. Jadi, mungkin kita
menginginkan agar list kita tidak berisi perulangan sama sekali. Syarat lainnya adalah
bahwa nilai length dalam objek IntList harus sama dengan jumlah node yang berada
dalam list.
Persyaratan seperti ini disebut invariants karena, idealnya, mereka harusnya selalu
bernilai true untuk semua objek sepanjang waktu. Menentukan invarian untuk objek
objek merupakan praktik pemrograman yang bermanfaat karena dapat memudahkan
kita ketika hendak meningkatkan tingkat kebenaran suatu kode, memeriksa integritas
struktur data yang digunakan, dan mendeteksi error.
Satu hal yang sering membingungkan mengenai invarian adalah bahwa mereka
terkadang justru dilanggar dalam kode program. Contohnya, di tengah addFirst, setelah
kita menambahkan nodenya, tapi sebelum kita menaikkan nilai length, invariannya
justru dilanggar. Pelanggaran seperti ini masih bisa diterima; kenyataannya, terkadang
mustahil bagi kita untuk memodifikasi sebuah objek tapa melanggar sebuah invarian
meskipun itu untuk sesaat saja. Normalnya, persyaratan yang harus disertakan adalah
bahwa semua metode yang melanggar suatu invarian harus bisa
mengembalikan/memperbaikinya (restore) lagi.
Jika ada sesuatu yang signifikan dalam kode sehingga suatu invarian ternyata dilanggar,
adalah penting bagi komentar untuk menjelaskannya, sehingga tidak akan ada lagi
operasi yang bergantung kepada invarian tersebut.
312
14.12 Daftar KataKata
Istilah Arti
list
Struktur data yang mengimplementasikan sebuah koleksi
menggunakan sekumpulan nodenode yang saling
terhubung.
node
Suatu elemen dalam list, biasanya diimplementasikan
sebagai sebuah objek yang berisi acuan ke objek lain yang
sama tipenya.
cargo Item data yang dikandung sebuah node.
link Sebuah acuan objek yang dimasukkan ke dalam objek.
generic data structure Sejenis struktur data yang berisi data dari tipe apapun.
preconditionSebuah fakta yang harus bernilai true agar sebuah metode
bisa bekerja dengan baik.
invariant
Sebuah fakta yang seharusnya bernilai true untuk sebuah
objek sepanjang waktu (kecuali mungkin ketika objek
tersebut sedang dimodifikasi).
wrapper method
Sebuah metode yang bertindak sebagai penengah antara
sebuah metode pemanggil (caller) dan metode penolong
(helper method), seringkali berupa interface yang lebih
bersih ketimbang metode penolongnya.
14.13 Latihan
Latihan 14.2
Mulailah dengan mengunduh file IntList.java dari http://thinkapjava/code/IntList. Link
ini berisi definisidefinisi mengenai IntList dan Node dari bab ini, bersama kode yang
313
mendemonstrasikan dan menguji beberapa metode. Compile lalu eksekusi program itu.
Hasilnya akan tampak sebagai berikut:
(1, 2, 3)
(3, 2, 1)
Latihan berikut ini meminta Anda untuk menulis metode objek tambahan bagi kelas
IntList. Tetapi mungkin Anda ingin menulis beberapa metode penolong dalam kelas
Node seperti biasa.
Setelah Anda menulis masingmasing metode, tambahkan kode itu ke dalam main lalu
ujilah. Yakinlah untuk menguji beberapa kasus khusus seperti listlist kosong atau
singleton.
Untuk setiap metode, identifikasilah semua precondition yang diperlukan agar semua
metode bisa berfungsi dengan benar lalu tambahkan komentar yang akan bisa menjadi
dokumentasi untuknya. Komentar Anda juga harus mengindikasikan apakah masing
masing metode merupakan konstruktor, fungsi, ataukah modifier.
a) Tulislah metode removeFirst yang mampu menghapus node pertama dari sebuah
list. Metode ini mampu mengembalikan kargo yang dimiliki oleh list tersebut.
b) Tulislah metode set yang menggunakan sebuah index, i, dan sebuah item dari kargo.
Metode ini mampu menukar kargo node urutan kei dengan kargo yang diberikan.
c) Tulislah metode add yang menggunakan sebuah index, i, dan sebuah item dari kargo.
Metode ini akan menambahkan sebuah node baru yang berisi kargo yang diberikan
ke posisi i.
d) Tulislah metode addLast yang menggunakan sebuah item dari kargo lalu
menambahkannya ke posisi buncit dalam list.
e) Tulislah metode reverse yang bisa memodifikasi IntList, yang dapat memutarbalik
urutan nodenode yang ada di dalamnya.
314
f) Tulislah metode append yang menggunakan sebuah IntList sebagai parameter.
Metode ini bisa menambahkan salinan dari nodenode yang ada dalam parameter list
(list yang menjadi parameter) ke posisi buncit dari list yang sedang aktif dipakai saat
itu (current list).
g) Tulislah metode checkLength yang mengembalikan nilai true jika panjang field
yang ada sama dengan jumlah node yang ada dalam list, dan false jika sebaliknya.
Metode ini tidak boleh menyebabkan terjadinya exception di bawah kondisi apapun,
dan ia harus bisa berhenti bahkan jika list tersebut berisi perulangan.
Latihan 14.2
Satu cara untuk merepresentasikan angka yang sangat besar adalah dengan
menggunakan sebuah daftar (list) berisi digitdigit, yang biasanya disimpan dalam
posisi yang sudah terurut. Sebagai contoh, angka 123 akan direpresentasikan dengan list
(3, 2, 1).
Tulislah sebuah metode yang membandingkan dua bilangan yang direpresentasikan
sebagai IntList dan yang bisa mengembalikan 1 jika bilangan pertama yang lebih besar,
1 jika yang kedua lah yang lebih besar, dan 0 jika keduanya sama.
315
Bab 15
Stacks
15.1 Tipe Data Abstrak
Semua tipe data yang telah kita lihat sejauh ini merupakan tipe data konkret. Artinya,
kita harus menentukan, dengan lengkap, bagaimana tipe datatipe data tersebut
diimplementasikan. Sebagai contoh, kelas Card merepresentasikan sebuah kartu
menggunakan dua integer. Sebagaimana yang telah saya kemukakan sebelumnya,
teknik di atas bukanlah satusatunya cara untuk merepresentasikan sebuah kartu;
sebenarnya masih banyak cara lain untuk mengimplementasikannya.
Abstract Data Type (Tipe Data Abstrak), atau ADT, merupakan suatu kumpulan
operasi (atau metode) dan bentuk semantik dari operasioperasi tersebut (apa yang
dilakukan oleh operasi) tapi dia tidak menentukan implementasi operasioperasi itu
sendiri. Hal inilah yang membuat mereka abstrak.
Kenapa ADT berguna?
● ADT menyederhanakan tugas Anda ketika hendak menentukan sebuah
algoritma jika Anda bisa mengidentifikasi operasioperasi yang Anda butuhkan
tanpa harus berpikir pada saat yang bersamaan mengenai bagaimana operasi
operasi tersebut akan dikerjakan.
● Karena ada banyak alternatif cara untuk mengimplementasikan ADT, mungkin
akan berguna bagi kita untuk menulis sebuah algoritma yang bisa digunakan
untuk beberapa implementasi sekaligus.
● Varianvarian ADT yang sudah populer, seperti Stack ADT dalam bab ini,
sering diimplementasikan di dalam pustaka standar (standard library) sehingga
316
mereka bisa ditulis sekali dan langsung digunakan oleh banyak programmer.
● Operasioperasi ADT menyediakan sebuah level bahasa tingkattinggi yang
umum digunakan untuk memilih, menentukan, dan membahas suatu algoritma.
Ketika kita berbicara mengenai ADT, kita sering membedakan antara kode yang
menggunakan ADT, disebut client code, dari kode yang mengimplementasikan ADT,
disebut provider code karena kode ini menyediakan sebuah kumpulan standar yang
terdiri dari layananlayanan.
15.2 The Stack ADT
Dalam bab ini, kita akan melihat salah satu ADT yang sudah banyak dikenal, yakni
stack. Stack merupakan suatu koleksi (collection). Ini artinya, stack adalah struktur data
yang berisi lebih dari satu elemen. Koleksi lain yang sudah kita lihat sebelumnya antara
lain; array dan list.
Seperti yang sudah saya sebutkan, sebuah ADT didefinisikan dengan suatu kumpulan
yang terdiri dari operasioperasi. Stack bisa melakukan operasioperasi berikut ini:
Constructor : membuat sebuah stack yang baru sekaligus kosong.
Push: menambahkan item baru ke dalam stack.
Pop: menghapus dan mengembalikan sebuah item dari stack. Item yang dikembalikan
harus berupa item terakhir yang ditambahkan.
IsEmpty: memeriksa apakah stack dalam keadaan kosong.
Sebuah stack sering juga disebut dengan “last in, first out”, atau struktur data LIFO,
karena item terakhir yang ditambahkan akan menjadi item pertama yang akan
dihilangkan.
317
15.3 Objek Stack dalam Java
Java menyediakan sebuah tipe objek bawaan yang disebut Stack yang
mengimplementasikan tipe data abstrak stack. Anda harus berusaha untuk membedakan
kedua hal ini – versi ADT dan versi Java – dengan jelas. Sebelum menggunakan kelas
Stack, kita harus mengimpornya terlebih dahulu dari java.util.
Berikut ini merupakan sintaks untuk membuat sebuah Stack yang baru:
Stack stack = new Stack ();
pada mulanya, stack yang kita buat tidak berisi satu elemen pun alias kosong, kita bisa
mengkonfirmasi keadaan ini dengan metode isEmpty yang akan mengembalikan hasil
berupa nilai boolean:
System.out.println (stack.isEmpty ());
Stack merupakan struktur data yang bersifat generik. Ini berarti bahwa kita bisa
menambahkan item bertipe apapun ke dalamnya. Meskipun begitu, dalam implementasi
Java, kita hanya bisa menambahkan tipetipe objek. Untuk contoh pertama, kita akan
menggunakan objek Node yang digunakan dalam bab sebelumnya. Mari kita mulai
dengan membuat lalu mencetak sebuah daftar yang pendek (short list):
IntList list = new IntList ();
list.addFirst (3);
list.addFirst (2);
list.addFirst (1);
list.print ();
hasilnya adalah (1, 2, 3). Untuk meletakkan sebuah objek Node ke dalam stack,
gunakan metode push:
stack.push (list.head);
Perulangan berikut ini akan menelusuri daftar tersebut dan memasukkan (push) semua
318
node ke dalam stack.
for (No de nod e = list .hea d; nod e != nul l; nod e = node .nex t)
{
stack .pus h (n ode) ;
}
Kita bisa mengambil/menghapus sebuah elemen dari stack menggunakan metode pop.
Object obj = stack.pop ();
Tipe kembalian dari pop berupa Object! Ini terjadi karena implementasi stack tidak
benarbenar mengetahui tipetipe objek yang dikandungnya. Ketika kita memasukkan
objekobjek Node, mereka dikonversikan secara otomatis menjadi Object. Ketika kita
mengambil objekobjek tersebut dari stack, kita harus mengcast mereka menjadi Node
kembali.
Node node = (Node) obj;
System.out.println (node);
Sayangnya, programmer justru akan mengalami kesulitan besar untuk melacak jejak
objekobjek dalam stack dan juga ketika hendak mengcast mereka kembali ke tipe
yang benar sebelum mereka diambil. Jika Anda mencoba untuk mengcast sebuah objek
ke dalam tipe yang salah, Anda akan mendapatkan sebuah pesan error;
ClassCastException.
Perulangan berikut ini merupakan idiom yang sering dipakai untuk mengambil semua
elemen dari dalam stack, lalu berhenti ketika stack tersebut sudah kosong:
while (!stack.isEmpty ()) {
Node node = (Node) stack.pop ();
System.out.print (node + " ");
}
319
Hasilnya adalah 3 2 1. Dengan kata lain, kita baru saja menggunakan sebuah stack
untuk mencetak elemenelemen yang ada dalam daftar secara terbalik! Perhatikan, cara
ini bukanlah format standar yang biasa digunakan untuk mencetak sebuah daftar, tapi
dengan stack, hal ini justru sangat mudah dilakukan.
Anda harus membandingkan kode ini dengan implementasi yang sudah kita gunakan
untuk metode printBackward yang berada dalam bab sebelumnya. Terdapat semacam
kaitan yang alamiah sekaligus parallel antara versi rekursif dari printBackward dengan
algoritma stack di sini. Perbedaannya adalah karena printBackward menggunakan
runtime stack untuk melacak keberadaan nodenode ketika ia menelusuri suatu daftar,
lalu mencetak hasilnya secara terbalik dengan rekursi. Algoritma stack sebenarnya
melakukan hal yang sama, hanya saja algoritma ini menggunakan objek Stack
ketimbang menggunakan runtime stack.
15.4 Kelas Pembungkus
Untuk setiap primitif dalam Java, terdapat sebuah objek bawaan yang disebut dengan
kelas pembungkus (wrapper classes). Sebagai contoh, kelas pembungkus untuk int
adalah Integer; untuk double disebut dengan Double.
Kelas pembungkus menjadi bermanfaat karena beberapa faktor:
● Anda bisa menginstansiasi kelas pembungkus lalu membuat objekobjek yang
berisi nilainilai primitif. Dengan kata lain, Anda bisa membungkus sebuah nilai
primitif dengan sebuah objek. Hal ini akan bermanfaat jika Anda ingin
memanggil sebuah metode yang membutuhkan sebuah tipe objek.
● Setiap kelas pembungkus berisi nilainilai khusus (seperti nilai minimum da
maximum untuk sebuah tipe), dan metodemetode yang berfungsi untuk
mengkonversi dari satu tipe ke tipe lainnya.
320
15.5 Membuat Kelas Pembungkus
Cara yang paling cepat untuk membuat sebuah objek pembungkus adalah dengan
menggunakan konstruktornya sendiri:
Integer i = new Integer (17);
Double d = new Double (3.14159);
Character c = new Character (’b’);
Secara teknis, sebenarnya String bukanlah sebuah kelas pembungkus karena tipe ini
tidak berhubungan dengan tipe primitif manapun, tapi sintaks untuk membuat objek
String adalah sama seperti cara berikut:
String s = new String ("fred");
Di sisi lain, belum pernah ada seorang pun yang menggunakan konstruktor untuk objek
objek String karena Anda bisa mendapatkan hasil yang sama dengan nilai String yang
lebih sederhana:
String s = "fred";
15.6 Membuat Kelas Pembungkus Lagi
Beberapa kelas pembungkus memiliki konstruktor kedua yang menggunakan sebuah
String sebagai argumennya lalu untuk mengubahnya ke bentuk yang diinginkan.
Sebagai contoh:
Integer i = new Integer ("17");
Double d = new Double ("3.14159");
Proses pengubahan/konversi tipe ini sayangnya tidak kokoh (robust). Sebagai contoh,
jika String yang digunakan tidak berada dalam format yang benar maka String tersebut
321
akan menyebabkan munculnya NumberFormatException. Semua karakter non
numerik dalam String, termasuk spasi, akan mengakibatkan gagalnya proses konversi.
Integer i = new Integer ("17.1"); // WRONG!!
Double d = new Double ("3.1459 "); // WRONG!!
Adalah ide yang bagus untuk memeriksa format String yang digunakan sebelum Anda
mencoba untuk mengubahnya.
15.7 Mengeluarkan NilaiNilai
Java mengetahui cara mencetak objekobjek pembungkus, cara termudah adalah dengan
mengambil suatu nilai adalah cukup dengan mencetak objek tersebut:
Integer i = new Integer (17);
Double d = new Double (3.14159);
System.out.println (i);
System.out.println (d);
Alternatif lain yang bisa Anda gunakan adalah dengan memanfaatkan metode toString.
Metode ini bisa Anda gunakan untuk mengubah isi objek pembungkus menjadi String;
String istring = i.toString();
String dstring = d.toString();
Akhirnya, jika Anda hanya ingin mengambil nilai primitif dari suatu objek, ada sebuah
metode objek dalam tiap kelas pembungkus yang mengerjakan tugas ini:
int iprim = i.intValue ();
double dprim = d.doubleValue ();
Ada juga metodemetode yang bisa digunakan untuk mengubah objekobjek
322
pembungkus ke tipe primitif yang berbedabeda. Anda harus memeriksa dokumentasi
untuk masingmasing kelas pembungkus untuk melihat apa saja yang bisa diubah dan
apa yang tidak.
15.8 MetodeMetode Bermanfaat dalam Kelas Pembungkus
Seperti yang telah saya sebutkan, kelaskelas pembungkus berisi metodemetode
bermanfaat yang berkaitan dengan masingmasing tipe. Sebagai contoh, kelas
Character berisi metodemetode, dalam jumlah yang melimpah, yang bisa digunakan
untuk mengubah karakter ke bentuk upper case dan lower case. Juga untuk memeriksa
apakah sebuah karakter merupakan angka, huruf, ataukah simbol.
Kelas String juga berisi metodemetode untuk melakukan pengubahan ke bentuk upper
dan lower case. Meskipun demikian, Anda harus tetap ingat bahwa mereka adalah
fungsi, bukan modifier (lihat subbab 7.9).
Untuk contoh tambahan, kelas Integer juga berisi metodemetode untuk
menginterpretasikan dan mencetak integerinteger ke bentuk yang berbedabeda. Jika
Anda mempunyai sebuah String yang berisi angka dalam basis 6, Anda bisa
mengubahnya ke basis 10 menggunakan parseInt.
String base6 = "12345";
int base10 = Integer.parseInt (base6, 6);
System.out.println (base10);
Karena parseInt merupakan sebuah metode kelas, Anda memanggilnya dengan cara
menyebutkan nama kelas dan metodenya memakai notasi dot.
Basis 6 mungkin tidak terlalu berguna, tetapi heksadesimal (basis 16) dan oktaf (basis
8) merupakan sesuatu yang sangat sering dipakai dalam dunia ilmu komputer.
323
15.9 Ekspresi Postfix
Dalam banyak bahasa pemrograman, ekspresiekspresi matematika ditulis dengan
bantuan operator yang diletakkan di antara dua operan, seperti dalam 1 + 2. format
seperti ini disebut infix. Format alternatif yang digunakan oleh beberapa kalkulator
adalah postfix. Dalam postfix, operatornya mengikuti operanoperannya, misalnya 1 2
+.
Alasan yang membuat postfix terkadang berguna adalah bahwa terdapat semacam
kecenderungan alamiah untuk mengevaluasi suatu postfix menggunakan sebuah stack.
● Dimulai dari depan/awal sebuah ekspresi, ambil satu term (operator atau
operan) dalam satu waktu.
● Jika termnya berupa operan, masukkan (push) dia ke dalam stack.
● Jika termnya berupa operator, ambil (pop) operanoperan tersebut dari
dalam stack, kerjakan operasi untuk mereka, lalu masukkan hasilnya ke
dalam stack lagi.
● Ketika kita sudah sampai ke ujung/akhir sebuah ekspresi, harusnya sudah
terdapat sebuah operan yang tertinggal dalam stack. Operan itulah yang
menjadi hasilnya.
Sebagai latihan, gunakan algoritma ini untuk mengerjakan ekspresi 1 2 + 3 *.
Latihan ini menunjukkan salah satu kelebihan postfix: kita tidak perlu lagi
menggunakan tanda kurung untuk mengatur urutan suatu operasi. Untuk mendapatkan
hasil yang sama dalam infix. Kita mungkin harus menulis (1 + 2) * 3. Sebagai latihan,
tulislah ekspresi postfix yang ekuivalen dengan 1 + 2 * 3?
324
15.10 Parsing
Untuk mengimplementasikan algoritma yang ada di bab sebelumnya, kita harus bisa
menelusuri suatu string lalu membaginya ke dalam dua kelompok yaitu; operan dan
operator. Proses ini merupakan sebuah contoh parsing (penguraian), lalu hasilnya –
yang berupa string individual – disebut tokens.
Java menyediakan sebuah kelas bawaan yang disebut StringTokenizer yang bisa
menguraikan string lalu memecahnya menjadi tokentoken. Untuk menggunakan kelas
ini, Anda harus mengimpornya dari java.util.
Dalam bentuknya yang paling sederhana, StringTokenizer menggunakan spasispasi
untuk menandai batas antara tiap token. Sebuah karakter yang menandai suatu pembatas
disebut delimiter.
Kita bisa membuat StringTokenizer dengan cara yang sudah sering kita gunakan,
dengan melewatkan sebuah argumen berupa string yang akan kita uraikan.
StringTokenizer st = new StringTokenizer ("Here are
four tokens.");
Perulangan berikut ini merupakan idiom standar untuk mengambil tokentoken dari
StringTokenizer.
while (st.hasMoreTokens ()) {
System.out.println (st.nextToken());
}
Hasilnya adalah:
Here
are
325
four
tokens.
Untuk menguraikan ekspresiekspresi, kita memiliki opsi untuk menentukan karakter
tambahan yang akan kita gunakan sebagai delimiter.
StringTokenizer st = new StringTokenizer ("11
22+33*", " +*/");
Argumen kedua adalah sebuah String yang berisi semua karakter yang akan digunakan
sebagai delimiter. Hasilnya adalah:
11
22
33
Cara ini memang bekerja dengan baik untuk mengambil semua operannya tapi
sayangnya kita juga akan kehilangan operatoroperatornya. Untungnya, masih ada satu
opsi lagi dari StringTokenizer.
StringTokenizer st = new StringTokenizer ("11
22+33*", " +*/", true);
Argumen ketiga berkata, “Ya, kami ingin memperlakukan delimiterdelimiter sebagai
tokentoken.” sekarang hasilnya adalah
11
22
+
33
326
*
Ini hanyalah tokentoken yang akan kita gunakan untuk mengevaluasi ekspresiekspresi
ini.
15.11 Mengimplementasikan ADT
Salah satu tujuan dasar dari ADT adalah untuk memisahkan kebutuhan sang penyedia
(provider), si penulis kode yang mengimplementasikan ADT, dan sang klien (client),
orang yang menggunakan ADT. Sang penyedia hanya perlu mengkhawatirkan masalah
benartidaknya pengimplementasian – apakah sudah sesuai dengan ketentuan dari ADT
ataukah belum – bukan pada bagaimana kodenya akan digunakan.
Sebaliknya, sang klien mengasumsikan bahwa implementasi yang diterapkan untuk
suatu ADT adalah sudah benar sehingga tidak perlu lagi mengkhawatirkan detailnya.
Ketika Anda sedang menggunakan salah satu kelas bawaan Java, Anda memiliki
kemewahan untuk berpikir secara eksklusif sebagai seorang klien.
Ketika Anda mengimplentasikan sebuah ADT, di sisi yang lain, Anda juga harus
menulis kode dari klien untuk mengujinya. Untuk kasus ini, Anda sebaiknya berhati
hati terhadap aturan yang akan Anda terapkan untuk instan yang akan Anda gunakan.
Di bagian selanjutnya, kita akan menukar gears dan melihat salah satu cara untuk
mengimplementasikan stack ADT yakni dengan bantuan array. Sekarang mulailah
berpikir laksana seorang provider.
15.12 Implementasi Array untuk Stack ADT
Variabelvariabel instan untuk mengimplementasikan kasus ini adalah sebuah array
yang berisi sekumpulan Object. Array ini sendiri nantinya akan berisi itemitem yang
berada dalam stack dan sebuah indeks, berupa integer, yang akan memberi tahu kita
327
tentang sisa spasi yang ada dalam array. Pada mulanya, array ini kosong. Sementara
indeksnya diset dengan nilai 0.
Untuk menambahkan sebuah elemen ke dalam stack (push), kita akan menyalin acuan
yang mengacu kepadanya ke dalam stack lalu menaikkan indeksnya satu per satu
(increment). Untuk mengambil sebuah elemen (pop), kita harus mendecrement indeks
terlebih dahulu baru mengeluarkannya.
Berikut ini definisi kelasnya:
public class Stack {
Object[] array;
int index;
public Stack () {
this.array = new Object[128];
this.index = 0;
}
}
Seperti biasa, jika kita sudah menuliskan variabelvariabel instannya, maka otomatis
kita juga akan menuliskan konstruktor di dalamnya. Untuk sekarang, jumlah elemen
yang akan kita gunakan adalah 128 item. Nanti, kita akan mempelajari alternatif lain
yang lebih baik untuk menangani kasus semacam ini.
Memeriksa kosongtidaknya stack bisa dilakukan dengan cara yang sederhana.
public boolean isEmpty () {
return index == 0;
}
Meskipun sederhana, penting untuk diingat bahwa jumlah elemen di dalam stack tidak
sama dengan ukuran array. Pada awalnya, ukuran array adalah 128 tetapi jumlah
328
elemennya sama dengan 0.
Implementasi untuk push dan pop bisa dilakukan dengan cara seperti berikut.
public void push (Object item) {
array[index] = item;
index++;
}
public Object pop () {
index;
return array[index];
}
Untuk menguji kedua metode ini, kita bisa memanfaatkan kode klien yang sebelumnya
kita gunakan untuk menguji Stack bawaan. Kemudian, ketimbang menggunakan
impelementasi stack milik java.util, program kita justru akan memakai implementasi
yang telah kita tulis sebelumnya.
Jika segala sesuatunya berjalan sesuai dengan rencana, program harusnya langsung bisa
berfungsi tanpa perlu penambahan di sanasini. Dan ingat, salah satu nilai lebih ketika
menggunakan ADT adalah karena Anda bisa mengubah suatu implementasi tanpa harus
mengubah kode kliennya.
15.13 Mengubah Ukuran Array
Sebuah kelemahan dari penggunaan implementasi seperti ini adalah karena ia telah
membuat alokasi array secara kaku untuk setiap Stack yang akan dibuat. Jika pengguna
memasukkan lebih dari 128 item ke dalam stack, maka program akan mengeluarkan
eksepsi yang berbunyi ArrayIndexOutOfBounds.
329
Alternatif untuk kasus ini adalah dengan mengizinkan kode klien untuk menentukan
ukuran array tersebut. Cara ini memang menyelesaikan masalahnya, tapi hal ini hanya
bisa dilakukan dengan syarat bahwa sang klie harus tahu terlebih dahulu jumlah item
yang dibutuhkan. Dan sayangnya, hal ini sepertinya tidak bisa terjadi setiap saat.
Berikut ini versi push yang telah diperbaiki:
public void push (Object item) {
if (full ()) resize ();
// di sini kita bisa membuktikan bahwa
// indeks < array.length
array[index] = item;
index++;
}
Sebelum meletakkan item baru ke dalam array, kita akan memeriksa apakah array sudah
penuh? Jika ya, kita panggil metode resize. Setelah pernyataan if, kita akan tahu
apakah;
1. memang sudah terdapat ruang yang tersisa dalam array, atau
2. array sudah diubah ukurannya sehingga terdapat ruang yang tersisa di dalamnya.
Jika full dan resize sudah benar, maka selanjutnya kita bisa membuktikan bahwa index
< array.length, dan oleh karena itu pernyataan berikutnya tidak akan menyebabkan
eksepsi lagi.
Sekarang, yang harus kita lakuka adalah mengimplementasikan full dan resize.
private boolean full () {
return index == array.length;
}
330
private void resize () {
Object[] newArray = new Object[array.length *
2];
// kita asumsikan bahwa array yang lama sudah
penuh
for (int i=0; i<array.length; i++) {
newArray[i] = array[i];
}
array = newArray;
}
Kedua metode dideklarasikan secara private, ini berarti keduanya tidak bisa dipanggil
dari kelas yang lain. Bisanya hanya dari kelas ini. Hal semacam ini bisa diterima dengan
alasan karena memang tidak alasan bagi kode klien untuk menggunakan fungsi ini, atau
bahkan hanya sekedar ingin menggunakannya, karena metode ini memang sengaja
dibuat untuk menjadi pembatas antara kode provider dengan kode klien.
Implementasi untuk metode full sangat sederhana; metode ini hanya memeriksa apakah
indeks telah melampaui ukuran indeks yang telah ditetapkan sebelumnya.
Pun begitu dengan implementasi untuk metode resize, hanya dengan asumsi bahwa
array yang lama sudah penuh. Dengan kata lain, asumsi tersebut merupakan prasayarat
untuk metode ini. Sangat mudah bagi kita untuk melihat bahwa prasyarat ini sudah
memenuhi syarat karena satusatunya cara untuk memanggil resize adalah jika full
bernilai true. Nilai ini hanya bisa terjadi jika idex == array.length.
Di akhir metode resize, kita mengganti array yang lama dengan array yang baru
331
(menyebabkan array lama berstatus “garbage collected”). array.length yang baru
berukuran dua kali lebih besar ketimbang array yang lama, sementara index belum
berubah sehingga pernyataan index < array.length pastilah bernilai benar. Faktafakta
di atas merupakan postcondition untuk resize; sesuatu yang harus bernilai true ketika
metode telah selesai dieksekusi (selama prasyaratnya juga sudah dipenuhi).
Prasyarat, postcondition, dan invariant merupakan tools yang sangat berguna untuk
menganalisa program sekaligus menunjukkan tingkat ketepatan (correctness) yang
terkandung di dalamnya. Dalam contoh ini saya telah menunjukkan sebuah teknik
pemrograman yang memfasilitasi analisis program sekaligus sebuah teknik
pendokumentasian yang bisa membantu peningkatan mutu keakuratannya.
15.14 Daftar KataKata
Istilah Arti
ADT
Sebuah tipe data (umumnya merupakan kumpulan dari
objek) yang didefinisikan dengan sekumpulan operasi, tapi
yang bisa diimplementasikan dengan berbagai cara.
clientSebuah program yang menggunakan ADT (atau sebutan
untuk orang yang menulis programnya).
providerKode yang mengimplmentasikan suatu ADT (atau sebutan
untuk orang yang menulisnya).
wrapper classes
Salah satu kelas dalam Java, seperti Double dan Integer
yang memungkinkan objekobjek mengandung tipetipe
primitif, dan metodemetode yang beroperasi di atas
primitif.
private Kata kunci dalam Java yang mengindikasikan bahwa sebuah
metode atau variabel instan tidak bisa diakses dari luar
332
definisi kelasnya sendiri.
infix
Suatu cara dalam menulis ekspresiekspresi matematika
dengan posisi operator yang berada di antara operan
operannya.
postfix
Suatu cara dalam menulis ekspresiekspresi matematika
dengan posisi operator yang berada di belakang operan
operannya.
parse
Membaca sebuah string yang terdiri dari karakterkarakter
atau tokentoken untuk menganalisa struktur tata bahasanya
(grammatical structure).
token
Sekumpulan karakterkarakter yang diperlakukan sebagai
sebuah unit untuk tujuan parsing, seperti kata laiknya dalam
bahasa alamiah.
delimiterSebuah karakter yang digunakan untuk memisahkan token
token, seperti tanda baca laiknya dalam bahasa alamiah.
predicateSebuah pernyataan matematis yang bisa bernilai benar atau
salah.
postcondition
Sebuah predikat yang harus bernilai benar di akhir sebuah
metode (dengan catatan bahwa prasyaratnya juga bernilai
benar di awal metode).
15.15 Latihan
Latihan 15.1
Tulislah metode reverse yang mengambil sebuah array berisi integer, menelusuri array
itu, memasukkan itemitem ke dalam stack, lalu mengambil itemitem itu dari stack,
333
meletakkan kembali ke dalam array dalam posisi yang terbalik dari keadaan
sebelumnya.
Tujuan latihan ini adalah untuk mempraktikkan mekanisme pembuatan objekobjek
pembungkus, memasukkan dan mengeluarkan objekobjek, dan mengtypecast objek
objek generik ke bentuk yang lebih spesifik.
Latihan 15.2
Latihan ini berdasarkan solusi dari latihan 14.1. mulailah dengan membuat salinan
impelemntasi Anda dari metode IntList yang disebut LinkedList.
a) Ubahlah implementasi linked list ke dalam sebuah list yang generik dengan membuat
kargo untuk sebuah Object ketimbang untuk sebuah integer. Modifikasilah kode
pengujinya sesuai dengan kondisi sekarang lalu jalankan programnya.
b) Tulislah sebuah metode LinkedList dengan nama split yang menggunakan sebuah
String, memecahnya menjadi katakata (menggunakan spasi debagai delimiter), lalu
mengembalikan sebuah list yang berisi String, dengan satu kata untuk setiap node
dalam list tersebut. Implementasi Anda haruslah efisien, ini berarti bahwa waktu
yang dihabiskan harus sesuai dengan jumlah kata yang ada dalam string.
c) Tulislah sebuah metode LinkedList dengan nama join yang mengembalikan sebuah
String yang berisi representasi String dari setiap objek yang berada dalam list, terurut
sesuai dengan urutan kemunculannya, dengan sebuah spasi yang menjadi pemisah.
d) Tulislah metode toString untuk LinkedList.
Latihan 15.3
Tulislah sebuah implementasi dari Stack ADT menggunakan implementasi LinkedList
milik Anda sendiri sebagai struktur data utamanya. Ada dua pendekatan umum untuk
kasus ini: stack itu kemungkinan akan berisi sebuah LinkedList sebagai sebuah
variabel instan, atau kelas Stack mungkin akan memperluas kelas LinkedList itu
334
sendiri. Pilihlah yang kedengarannya lebih baik untuk Anda, atau, jika Anda memang
benarbenar seorang yang berambisi, impelementasikan saja keduanya lalu buatlah
perbandingan antara keduanya.
Latihan 15.4
Tulislah program Balance.java yang bisa membaca sebuah file dan memeriksa bahwa
tanda kurung, (), dan kurung kotak, [], dan kurung kurawal, {}, telah ditulis dengan
seimbang dan diletakkan di tempat yang benar.
Petunjuk: Lihat bagian C untuk kode yang mampu membaca barisbaris dalam suatu
file.
Latihan 15.4
Tulislah metode evalPostfix yang menggunakan sebuah String berisi ekspresi postfix
dan mampu mengembalikan nilai bertipe double yang mengandung hasil
perhitungannya. Anda bisa menggunakan StringTokenizer untuk menguraikan String
dan sebuah Stack berisi Double untuk mengevaluasi ekspresi tersebut. Langkahlangkah
berikut ini adalah saran saya untuk pengembangan program milik Anda.
a) Tulislah sebuah program yang bisa meminta user untuk memasukkan string
sekaligus mencetaknya, berulangulang, sampai si pengguna mengetikkan kata
“quit”. Lihat bagian C untuk informasi mengenai cara memasukkan data melalui
keyboard. Anda bisa melihat kode berikut ini sebagai permulaan:
public static void inputLoop () throws IOException
{
BufferedReader stdin =
new BufferedReader (new InputStreamReader
(System.in));
while (true) {
System.out.print ("=>"); // Cetak hasil masukan
335
String s = stdin.readLine(); // ambil masukan
if (s == null) break;
// periksa apakah s sama dengan "quit"
// cetak s
}
}
b) Identifikasikan helper methods yang menurut Anda bisa dimanfaatkan, lalu tulis dan
debug mereka dalam lingkungan yang terisolir. Saran: isOperator, isOperand,
parseExpression, performOperation.
c) Kita tahu bahwa kita ingin menambahkan nilai int ke dalam stack lalu mengambilnya
kembali, dengan kata lain, kita akan membutuhkan sebuah kelas pembungkus
(wrapper classes). Pastikan Anda tahu bagaimana cara melakukannya lalu ujilah
operasioperasi tersebut dalam lingkungan yang terisolir. Mungkin membuat mereka
menjadi helper methods.
d) Tulislah versi evaluate yang hanya menangani satu jenis operator saja (seperti
penjumlahan). Ujilah dalam lingkungan yang terisolir.
e) Hubungkan evaluator Anda dengan perulangan input/output Anda sendiri.
f) Tambahkan operasioperasi lainnya.
g) Ketika Anda tahu bahwa kode ini bekerja, Anda mungkin ingin mengevaluasi
struktur rancangannya. Bagaimana cara Anda membagi kode ini ke dalam kelas
kelas? Variabel instan apa yang harus dimiliki oleh kelaskelas tersebut? Parameter
parameter apa yang harus dilewatkan?
h) Agar rancangan Anda tambah elegan, Anda juga harus bisa membuat kode Anda
bersifat bulletproof, maksudnya adalah agar metode ini tidak menyebabkan
terjadinya eksepsi di bawah kondisi apapun, bahkan ketika si pengguna mengetikkan
sesuatu yang aneh misalnya.
336
Bab 16
Antrian dan Antrian Prioritas
Bab ini akan menyajikan dua ADT sekaligus: antrian dan antrian prioritas. Dalam
kehidupan seharihari, sesuatu yang disebut dengan antrian adalah sederetan pelanggan
yang menunggu untuk suatu pelayanan tertentu. Dalam banyak kasus, pelanggan
pertama yang berada dalam barisan biasanya akan dilayani terlebih dahulu. Tetapi
tunggu dulu, karena ternyata tidak semua kasus serta merta seperti itu. Sebagai contoh,
di bandara, calon penumpang yang pesawatnya akan terbang sesaat lagi akan langsung
ditaruh di pertengahan antrian. Begitu juga di supermarket, seorang pelanggan yang
baik hati biasanya mendahulukan pelanggan lain yang belanjaannya lebih sedikit.
Aturan untuk menentukan siapa yang harus dilayani berikutnya disebut queueing
discipline (disiplin antrian). Aturan yang paling sederhana adalah FIFO, atau “First In
First Out”. Aturan yang paling umum digunakan disebut priority queueing, di sini
masingmasing pelanggan akan diberi tingkat prioritas, pelanggan yang memiliki
prioritas paling tinggi akan didahulukan, tanpa melihat kapan si pelanggan itu datang.
Alasan yang membuat saya menyebutnya sebagai aturan yang paling umum adalah
karena prioritas itu sendiri bisa berdasarkan pada apa pun yang memungkinkan: kapan
sebuah penerbangan akan dilaksanakan, berapa banyak barang yang dimiliki oleh
pelanggan, atau seberapa penting si pelanggan tersebut jika dibandingkan pelanggan
lainnya. Tapi tentu, tidak semua aturan antrian bersifat “adil”, toh bukankah keadilan itu
sendiri sejatinya berada dalam persepsi masingmasing individu.
ADT Antrian (The Queue ADT) dan ADT Antrian Prioritas (The Priority Queue ADT)
337
mempunyai sekumpulan operasi dan antarmuka yang sama. Perbedaannya terletak di
dalam semantik operasioperasinya: satu Antrian menggunakan kebijakan FIFO,
sementara Antrian Prioritas (seperti namanya) menggunakan kebijakan pemrioritasan
dalam antrian.
Laiknya sebagian besar ADT, ada beberapa cara untuk mengimplementasikan antrian.
Karena antrian sendiri merupakan kumpulan item, kita bisa menggunakan mekanisme
dasar manapun untuk menyimpan koleksikoleksi tersebut, termasuk array dan list. Di
sini kita akan memilih teknik yang unjuk kerjanya paling bagus – berapa lama waktu
yang dibutuhkannya untuk melaksanakan operasioperasi yang kita inginkan – dan tentu
saja dalam hal kemudahan penggunaannya.
16.1 ADT Antrian
ADT antrian akan didefinisikan oleh operasioperasi berikut ini:
constructor: membuat satu antrian baru yang kosong.
add: menambahkan satu item baru ke dalam antrian.
remove: menghapus dan mengembalikan satu item dari antrian. Item yang dikem
balikan pertama kali adalah item yang pertama kali ditambahkan.
isEmpty: memeriksa apakah suatu antrian dalam keadaan kosong ataukah tidak.
Berikut ini adalah implementasi dari antrian secara umum, berdasarkan kelas bawaan
java.util.LinkedList:
public class Queue {
private LinkedList list;
public Queue () {
list = new LinkedList ();
}
338
public boolean isEmpty () {
return list.isEmpty ();
}
public void add (Object obj) {
list.addLast (obj);
}
public Object remove () {
return list.removeFirst ();
}
}
Satu objek berisi satu variabel instan, yakni list yang mengimplementasikannya. Untuk
metodemetode lainnya, yang harus kita lakukan hanyalah memanggil salah satu metode
dari kelas LinkedList.
Kita bisa menulis implementasi yang sama sedikit lebih jelas dengan memanfaatkan
fitur inheritance:
public class Queue extends LinkedList {
public Object remove () {
return removeFirst ();
}
}
Oke, kelihatannya sudah lebih jelas keltimbang sebelumnya! Karena Queue merupakan
perluasan (anak) dari LinkedList, kita akan mewarisi konstruktornya, yakni isEmpty
339
dan add. Kita juga mewarisi remove, tetapi sayang versi remove yang kita dapat dari
kelas LinkedList tidak melakukan apa yang kita maksudkan; metode ini justru
menghapus elemen terakhir dalam list (daftar), bukan yang pertama. Kita akan
mengatasinya dengan membuat remove versi baru yang akan mengoverride versi yang
kita warisi.
Pilihan yang akan kita ambil bergantung pada beberapa faktor. Pewarisan memang
dapat menyebabkan suatu implementasi menjadi lebih jelas, selama masih ada metode
metode dalam kelas induk yang bisa dimanfaatkan. Tetapi, fitur ini juga bisa membuat
suatu implementasi justru lebih sulit untuk dibaca dan didebug. Hal ini terjadi karena
metodemetode untuk kelas yang baru tersebut tidak berada di tempat yang sama. Selain
itu, fitur ini juga bisa menyebabkan munculnya perilaku yang mungkin tidak Anda
harapkan. Ini terjadi karena kelas yang baru akan mewarisi semua metode dari kelas
induk, tidak hanya yang Anda butuhkan. Keadaan seperti ini akan mengakibatkan kelas
Queue versi kedua juga memiliki metodemetode seperti removeLast dan clear.
Meskipun keduanya bukan merupakan bagian dari ADT Queue. Implementasi pertama
lebih aman; dengan mendeklarasikkan satu variabel instan yang bersifat private, kita
bisa mencegah klien yang ingin memanggil metodemetode yang berada dalam
LinkedList.
16.2 Veneer
Dengan menggunakan sebuah LinkedList untuk mengimplementasikan satu Antrian,
kita bisa mengambil manfaat dari kode yang sudah ada; kode yang baru saja kita tulis
hanya menerjemahkan metodemetode LinkedList ke dalam metodemetode Queue.
Implementasi seperti ini disebut veneer (kayu pelapis nan tipis). Dalam kehidupan di
dunia nyata, veneer adalah sebuah lapisan tipis yang terbuat dari kayu berkualitas baik
340
yang digunakan dalam pembuatan mebel untuk melapis kayu berkualitas kurang baik di
bawahnya. Ilmuwan komputer menggunakan metafora ini untuk mendeskripsikan
sebuah kode kecil yang menyembunyikan detail suatu implementasi sekaligus
menyediakan antarmuka yang lebih sederhana dan terstandardisasi.
Contoh kasus tentang Antrian akan mendemonstrasikan salah satu hal menarik
mengenai veneer, yakni kemudahan dalam implementasi, dan salah satu bahaya ketika
menggunakan veneer adalah karena performace hazard!
Normalnya, ketika kita memanggil sebuah metode, sebenarnya pada saat itu kita tidak
terlalu peduli mengenai detail implementasinya. Tapi ada satu “detail” yang mungkin
ingin kita ketahui – karakter unjuk kerja dari suatu metode. Berapa lama waktu yang
diperlukan oleh metode, sebagai sebuah fungsi dari angka itemitem yang berada dalam
list (daftar)?
Untuk menjawab pertanyaan di atas, kita harus mengetahui lebih banyak hal mengenai
implementasi. Jika kita mengasumsikan bahwa LinkedList benarbenar di
implementasikan sebagai sebuah linked list (daftar yang saling terkait), maka im
plementasi dari metode removeFirst mungkin akan kelihatan seperti ini:
public Object removeFirst () {
Object result = head;
if (head != null) {
head = head.next;
}
return result.cargo;
}
Kita mengasumsikan bahwa head mengacu ke node pertama dalam list, dan bahwa
setiap nodenya berisi kargo serta sebuah acuan yang mengarah ke node berikutnya
341
dalam list.
Tidak ada pemanggilan perulangan atau fungsi di sini. Akibatnya, runtime metode ini
hampir selalu sama setiap waktu. Metode seperti ini disebut dengan constant time
operation. Dalam realita, metode ini mungkin akan terlihat lebih cepat ketika list sedang
kosong. Hal ini terjadi karena metode akan melompati bagian badan (body) dari bagian
persyaratan (conditional), tapi perbedaannya tidak terlalu signifikan.
Unjuk kerja addLast sangat berbeda. Berikut ini adalah sebuah implementasi yang
bersifat hipotesis:
public void addLast (Object obj) {
// special case: list kosong
if (head == null) {
head = new Node (obj, null);
return;
}
Node last;
for (last = head; last.next != null; last = las
t.next) {
// telusuri list untuK menemukan node
terakhir
}
last.next = new Node (obj, null);
}
Syarat pertama menangani kasus khusus, yakni penambahan sebuah node baru ke dalam
list kosong. Dalam kasus ini, lagilagi, run time tidak bergantung kepada panjang list.
Meskipun begitu, dalam kasus yang lebih umum, kita harus menelusuri list untuk
342
menemukan elemen terakhir agar kita bisa membuatnya mengacu ke node yang baru.
Penelusuran ini menghabiskan waktu yang sesuai dengan panjang list. Karena run time
merupakan fungsi linear dari panjang, kita bisa mengatakan bahwa metode ini termasuk
linear time. Dibandingkan dengan constant time, linear time bisa dikategorikan sebagai
yang buruk sekali.
16.3 Linked Queue
Kita menginginkan sebuah implementasi dari ADT Antrian yang bisa melakukan semua
operasi dalam waktu yang konstan. Salah satu cara untuk menyelesaikan masalah ini
adalah dengan mengimplementasikan linked queue. Cara ini bisa dibilang sama dengan
linked list karena ia terdiri dari objekobjek Node yang saling terhubung, mulai dari
yang berjumlah 0 (tanpa Node) atau lebih dari itu. Perbedaannya adalah bahwa suatu
antrian memelihara acuan yang berasal dari node pertama dan terakhir, seperti tampak
dalam gambar.
Inilah tampilan implementasi linked Queue:
public class Queue {
public Node first, last;
public Queue () {
first = null;
last = null;
343
}
public boolean isEmpty () {
return first == null;
}
}
Sampai sejauh ini, tampaknya implementasi ini masih biasabiasa saja. Di dalam antrian
yang kosong, baik first maupun last diisi dengan null. Untuk memeriksa apakah suatu
list sedang kosong ataukah tidak, kita hanya perlu memeriksa salah satu diantaranya.
Implementasi metode add tampaknya lebih rumit karena kita harus berurusan dengan
beberapa kasus khusus.
public void add (Object obj) {
Node node = new Node (obj, null);
if (last != null) {
last.next = node;
}
last = node;
if (first == null) {
first = last;
}
}
Syarat pertama memeriksa dan memastikan bahwa last sudah mengacu ke sebuah node;
jika ya, maka kita harus membuatnya mengacu ke node yang baru.
Syarat yang kedua berurusan dengan kasus khusus di mana list pada awalnya memang
berstatus kosong. Dalam kasus ini, baik first maupun last mengacu ke node yang baru.
Metode remove juga berurusan dengan beberapa kasus khusus.
344
public Object remove () {
Node result = first;
if (first != null) {
first = first.next;
}
if (first == null) {
last = null;
}
return result;
}
Syarat pertama memeriksa apakah ada node di dalam antrian. Jika ya, kita harus
menyalin node next ke first. Syarat kedua terkait dengan kasus khusus bahwa list pada
saat yang bersamaan sedang kosong, dalam hal ini kita harus membuat last menjadi
null.
Untuk latihan, buatlah diagramdiagram yang menunjukkan operasioperasi ini, baik
dalam kasus normal maupun kasus khusus, dan yakinkan diri Anda sendiri bahwa
diagramdiagram tersebut memang benar adanya.
Jelas sekali, implementasi ini tentu lebih rumit ketimbang implementasi veneer, dan
lebih sulit lagi untuk membuktikan bahwa implementasi ini memang benar.
Keuntungannya adalah karena kita telah berhasil mencapai tujuan yang kita inginkan:
add dan remove adalah metode yang bersifat constant time operation.
16.4 Circular Buffer
Implementasi umum lainnya dari antrian adalah circular buffer. “Buffer” merupakan
nama umum untuk menyebutkan sebuah lokasi penyimpanan yang sifatnya
345
sementara/temporer, meskipun ia juga sering digunakan untuk menyebutkan suatu
array, seperti yang terjadi dalam kasus ini. Sekarang Anda tentu akan lebih paham
dengan apa yang dimaksud dengan kata “circular” di sini.
Implementasi dari circular buffer sama dengan implementasi array untuk stack di
subbab 15.12. Itemitem antrian disimpan di dalam sebuah array, dan kita menggunakan
indeks untuk mengetahui posisi kita di dalam array. Dalam implementasi stack, ada
sebuah indeks tunggal yang menunjuk ke ruang (space) berikutnya yang masih kosong.
Dalam implementasi antrian, terdapat dua indeks: first menunjuk ke ruang dalam array
yang berisi pelanggan pertama dalam barisan sementara next menunjuk ke ruang
berikutnya yang masih kosong.
Gambar berikut ini menunjukkan sebuah queue dengan dua item (direpresentasikan
dengan titiktitik).
Ada dua cara untuk memikirkan variabelvariabel untuk first dan last. Secara literal,
keduanya bertipe integer, dan nilai mereka ditunjukkan dalam kotak di sebelah kanan.
Walaupun begitu, secara abstrak, mereka merupakan indeks dari sebuah array. Oleh
karena itulah, mereka sering digambarkan sebagai sebuah anak panah yang menunjuk
ke lokasi dalam array. Representasi anak panah sebenarnya sangat bagus, tapi Anda
harus ingat bahwa indeksindeks tersebut bukanlah acuan; mereka hanyalah integer.
Berikut ini merupakan implementasi array dari sebuah antrian yang belum lengkap:
public class Queue {
public Object[] array;
public int first, next;
public Queue () {
array = new Object[128];
first = 0;
346
next = 0;
}
public boolean isEmpty () {
return first == next;
}
Variabelvariabel instan dan konstruktornya tampak normal dan biasabiasa saja, tapi
kita lagilagi menjumpai masalah ketika dihadapkan dengan ukuran yang harus
digunakan untuk array.
Nantinya kita akan menyelesaikan masalah ini, sebagaimana yang telah kita lakkan
dengan stack, dengan cara mengubah ukuran arraynya ketika array tersebut sudah terisi
penuh.
Implementasi isEmpty terkesan agak mengejutkan. Anda mungkin harus berpikir
bahwa first == 0 akan mengindikasikan sebuah antrian kosong, tapi itu menge
yampingkan fakta bahwa kepala antrian tidaklah harus berada di depan array. Se
baliknya, kita tahu bahwa antrian dikatakan kosong jika head sama dengan next, di
mana dalam kasus ini berarti sudah tidak ada lagi itemitem yang tersisa. Sekali saja
kita melihat implementasi add dan remove, kondisi tersebut akan lebih masuk akal.
public void add (Object item) {
array[next] = item;
next++;
}
public Object remove () {
Object result = array[first];
first++;
return result;
}
347
Metode add terlihat seperti push di subbab 15.12; ia meletakkan item baru ke dalam
ruang kosong di bagian berikutnya lalu menaikkan nilai indeksnya.
Implementasi remove sama saja. Metode ini mengambil item pertama dari antrian lalu
menaikkan first sehingga ia mengacu ke kepala baru milik antrian. Gambar berikut ini
menunjukkan seperti apa tampilan antrian ketika kedua item sudah dihapus.
Adalah selalu benar bahwa next menunjuk ke ruang yang masih kosong. Jika first telah
melebihi next dan menunjuk ke ruang yang sama, maka first sedang mengacu ke lokasi
yang “kosong”. Saya memberi tanda kutip pada kata kosong karena ada kemungkinan
bahwa lokasi yang ditunjuk oleh first sebenarnya memiliki suatu nilai (kita tidak
melakukan apapun untuk memastikan apakah lokasi yang kosong berisi null); di sisi
lain, karena kita sudah mengetahui kalau antrian itu memang kosong; kita tidak akan
pernah membaca lokasi ini, jadi kita bisa menganggapnya, secara abstrak, sebagai
sesuatu yang kosong.
Latihan 16.1
Modifikasilah remove sehingga ia bisa mengembalikan null jika suatu antrian sedang
kosong.
Masalah berikutnya dari implementasi ini adalah karena ia akan menghabiskan banyak
ruang. Ketika kita menambahkan sebuah item, kita juga menaikkan nilai next dan ketika
348
kita kita menghapus sebuah item kita menaikkan first, tapi sayangnya kita tidak
menurunkan nilai keduanya. Apa yang akan terjadi jika kita sudah sampai di ujung
array?
Gambar berikut ini menunjukkan keadaan antrian setelah kita menambahkan empat
item lagi:
arraynya sekarang telah penuh. Tidak ada lagi “ruang kosong berikutnya”. Jadi
sekarang next tidak bisa menunjuk ke manamana lagi. Satu kemungkinan adalah kita
bisa mengubah ukuran array, seperti yang telah kita lakukan dengan impelementasi
untuk stack. Tapi di kasus itu, arraynya akan terus membesar tanpa peduli dengan
berapa banyak jumlah item yang sedang berada di dalam antrian. Solusi yang lebih baik
adalah dengan melakukan “wrap around” (baca: putaran) ke awal array lalu
menggunakan kembali ruangruang yang masih ada di sana. “wrap around” seperti ini
adalah alasan kenapa implementasi ini disebut dengan “circular buffer”.
Salah satu cara untuk memutar indeks adalah dengan menambahkan sebuah kasus
khusus dimanapun kita menaikkan nilai sebuah indeks:
next++;
if (next == array.length) next = 0;
Alternatif yang lebih cantik adalah dengan menggunakan operator modulus:
next = (next + 1) % array.length;
349
Sampai di sini kita masih memiliki satu masalah terakhir yang harus diselesaikan.
Bagaimana kita bisa tahu kalau antrian benarbenar sudah penuh, maksudnya; kita
sudah tidak bisa lagi menambahkan item lainnya? Gambar berikut ini menunjukkan
tampilan antrian ketika sedang berstatus “penuh”.
Masih tersisa satu ruang kosong di dalam array, tapi antrian itu sudah penuh karena jika
kita menambahkan item lainnya, maka kita harus menaikkan next sedemikian rupa
sehingga next == first, dan dalam kasus ini, antrian akan kelihatan sedang berada
dalam status kosong!
Untuk menghindari hal kondisi seperti di atas, kita akan mengorbankan satu ruag dalam
array. Jadi bagaimana kita bisa bilang kalau antrian itu sudah penuh?
if ((next + 1) % array.length == first)
Dan apa yang harus kita lakukan jika arraynya penuh? Dalam kasus ini, mengubah
ukuran array mungkin adalah satusatunya cara.
Latihan 16.2
Tulislah sebuah impelementasi antrian menggunakan circular buffer yang bisa
mengubah ukurannya sendiri ketika sedang perlu.
350
16.5 Priority Queue
ADT Antrian Prioritas mempunyai antarmuka yang sama seperti ADT Antrian, tapi
semantiknya berbeda. Antarmukanya adalah sebagai berikut:
constructor: membuat sebuah antrian yang baru sekaligus kosong.
Add: menambahkan sebuah item baru ke dalam antrian.
remove: menghapus dan mengembalikan sebuah item dari antrian. Item yang
dikembalikan adalah item yang mempunyai prioritas tertinggi.
isEmpty: memeriksa apakah antriannya kosong ataukah tidak.
Yang dimaksud dengan perbedaan semantik adalah bahwa item yang telah dihapus dari
antrian tidaklah harus berupa item yang pertama kali dimasukkan/ ditambahkan.
Sebaliknya, item tersebut harus berstatus sebagai item dengan prioritas tertinggi. Apa
yang dimaksud dengan prioritas, dan bagaimana mereka dibandingkan dengan satu
sama lainnya tidak ditentukan oleh implementasi Antrian Prioritas. Semuanya
tergantung dari itemitem apa yang berada di dalam antrian tersebut.
Sebagai contoh, jika itemitem di dalam antrean mempunyai nama, kita mungkin akan
memilih mereka secara sesuai urutan alfabet. Jika mereka merupakan skor permainan
bowling, kita mungkin memilih dari urutan tertinggi ke urutan terendah, tapi jika
mereka itu skor permainan golf, mungkin kita akan memprioritaskan angka/item
terendah ke yang tertinggi.
Jadi sekarang kita mempunyai masalah baru. Kita menginginkan sebuah implementasi
Antrean Prioritas yang sifatnya generik – harus bisa bekerja dengan objek apapun – tapi
pada saat yang bersamaan, kode yang mengimplementasikan Antrian Prioritas harus
memiliki kemampuan untuk membandingkan objekobjek yang dikandungnya.
351
Kita sudah melihat satu cara untuk mengimplementasikan struktur data generik
menggunakan Object, tapi itu tidak menyelesaikan masalah ini, karena tidak ada cara
untuk membandingkan Object kecuali kita sudah tahu objek itu bertipe apa.
Jawabannya terdapat dalam sebuah fitur Java yang disebut metaclass.
16.6 Metaclass
Metaclass adalah suatu kumpulan kelas yang menyediakan satu kumpulan metode.
Definisi metaclass mengharuskan sebuah kelas untuk memenuhi persyaratan yang sudah
ditentukan agar bisa menjadi anggota dari suatu kumpulan.
Terkadang metaclassmetaclass memiliki nama yang diakhiri dengan kata “able” untuk
mengindikasikan kemampuan dasar (fundamental) yang dibutuhkan oleh metaclass.
Sebagai contoh, semua kelas yang menyediakan metode dengan nama draw bisa
menjadi anggota dari metaclass Drawable. Semua kelas yang mengandung metode
start bisa menjadi anggota dari metaclass Runnable.
Java menyediakan sebuah metaclass bawaan yang bisa kita gunakan dalam sebuah
implementasi untuk Antrian Prioritas. Metaclass ini disebut Comparable, dan artinya
sesuai dengan namanya. Semua kelas yang menjadi anggota metaclass Comparable
harus menyediakan sebuah metode dengan nama compareTo yang mampu
membandingkan dua objek lalu mengembalikan sebuah nilai yang mengindikasikan
apakah satu argumen lebih besar atau lebih kecil ketimbang argumen lainnya, atau
apakah keduanya merupakan dua argumen yang sama.
Umumnya kelaskelas bawaan Java merupakan anggota dari metaclass Comparable,
termasuk kelaskelas pembungkus numerik seperti Integer dan Double.
Di subbab berikutnya saya akan menunjukkan bagaimana caranya menulis sebuah ADT
352
yang bisa memanipulasi sebuah metaclass. Lalu kita akan melihat bagaimana caranya
menulis sebuah kelas baru yang akan menjadi anggota dari metaclass yang sudah ada.
Di bab berikutnya kita akan melihat bagaimana caranya mendefinisikan sebuah
metaclass baru.
16.7 Implementasi Array untuk Antrian Prioritas
Dalam implementasi Antrian Prioritas, setiap kali kita menentukan tipe itemitem yang
berada dalam antrian, kita menentukan metaclass Comparable. Sebagai contoh,
variabelvariabel instan berikut merupakan sebuah array yang berisi Comparable dan
sebuah integer:
public class PriorityQueue {
private Comparable[] array;
private int index;
}
Seperti biasa, index merupakan indeks dari ruang berikutnya yang masih tersisa/kosong
dalam array. Variabelvariabel instan dideklarasikan dengan private agar kelaskelas
lainnya tidak bisa mengakses mereka secara langsung.
Konstruktor dan isEmpty masih sama dengan yang pernah kita lihat sebelumnya.
Ukuran awal dari arraynya silahkan Anda tentukan sendiri.
public PriorityQueue () {
array = new Comparable [16];
index = 0;
}
public boolean isEmpty () {
return index == 0;
353
}
add sama dengan push:
public void add (Comparable item) {
if (index == array.length) {
resize ();
}
array[index] = item;
index++;
}
Satusatunya metode penting dalam kelas adalah remove, yang harus menelusuri array
untuk menemukan lalu menghapus item terbesar:
public Comparable remove () {
if (index == 0) return null;
int maxIndex = 0;
// find the index of the item with the highest
priority
for (int i=1; i<index; i++) {
if (array[i].comp areTo (array[max Index]) > 0) {
maxIndex = i;
}
}
Comparable result = array[maxInd ex];
// move the last item into the empty slot
index ;
354
array[maxIndex ] = array[index];
return result;
}
Ketika kita menelusuri array, maxIndex menjaga jejak elemen terbesar yang sudah kita
lihat sejauh ini. Yang dimaksud dengan “terbesar” di sini akan ditentukan oleh metode
compareTo. Dalam kasus ini, metode compareTo disediakan oleh kelas Integer, dan ia
melakukan apa yang memang kita inginkan – angka terbesar (lebih positif) adalah
pemenangnya.
16.8 Klien Antrian Prioritas
Implementasi Antrian Prioritas seluruhnya ditulis menggunakan susut pandang objek
objek Comparable, tapi tidak ada objek yang bisa disebut sebagai sebuah objek dari
metaclass Comparable! Kalau tidak percaya, cobalah untuk membuat satu objek saja:
Comparable comp = new Comparable (); // ERROR
Anda akan melihat pesan compiletime yang berbunyi “java.lang.Comparable is an
interface. It can't be instantiated”. Di Java, metaclassmetaclass juga disebut dengan
interface (antarmuka). Sampai sejauh ini saya telah menghindari kata ini karena ia juga
memiliki beberapa arti sekaligus, tapi sekarang Anda semua harus tahu.
Kenapa metaclassmetaclass itu tidak bisa diinstansiasi? Karena sebuah metaclass hanya
menentukan apaapa yang sudah disyaratkan (Anda harus mempunyai metode
compareTo); ia sendiri tidak menyediakan suatu implementasi.
Untuk membuat sebuah objek Comparable, Anda harus membuat satu objek yang
dimiliki oleh satu paket Comparable, seperti Integer. Kemudian barulah Anda bisa
menggunakan objek tersebut dimanapun metaclass Comparable dipanggil.
PriorityQueue pq = new PriorityQueue ();
355
Integer item = new Integer (17);
pq.add (item);
Kode ini akan membuat satu Antrian Prioritas yang baru sekaligus kosong, juga sebuah
objek Integer. Kemudian kode ini akan menambahkan Integer ke dalam antrian.
Metode add mengharapkan sebuah Comparable sebagai sebuah parameter, jadi dia
dengan senang hati menerima sebuah Integer. Jika kita mencoba untuk melewatkan
sebuah Rectangle yang bukan merupakan anggota Comparable, kita akan
mendapatkan sebuah pesan compiletime yang berbunyi, “Incompatible type for
method. Explicit cast needed to convert java.awt.Rectangle to java.lang.Comparable”.
Itulah yang dikatakan oleh compiler kepada kita. Jadi, jika kita ingin membuat konversi
itu terjadi, maka kita harus melakukannya secara eksplisit. Kita mungkin akan
melakukan apa yang dikatakan oleh compiler tersebut:
Rectangle rect = new Rectangle ();
pq.add ((Comparable) rect);
Tetapi di dalam kasus seperti ini kita justru menerima pesan error yang terkait dengan
ClassCastException. Ketika Rectangle mencoba untuk melewatkan sebagai sebuah
Comparable, sistem runtime akan memeriksa apakah cara ini memenuhi semua
persyaratan, lalu menolaknya. Jadi, itulah yang akan kita dapatkan jika kita mengikuti
saran dari compiler.
Untuk mengeluarkan itemitem dari antrian, kita harus membalik prosesnya:
while (!pq.isEmpty ()) {
item = (Integer) pq.remove ();
System.out.println (item);
}
356
Perulangan ini menghapus semua item dari antrian lalu mencetaknya. Proses ini
mengasumsikan bahwa itemitem yang berada di dalam antrian merupakan Integer.
Karena jika mereka bukan Integer, kita akan mendapatkan sebuah
ClassCastException.
16.9 Kelas Golfer
Akhirnya, mari kita lihat bagaimana caranya membuat sebuah kelas baru yang akan
menjadi anggota Comparable. Sebagai sebuah contoh atas sesuatu dengan definisi
yang tidak biasa tentang prioritas “tertinggi” kita akan menggunakan para pemain golf
(golfers):
public class Golfer implements Comparable {
String name;
int score;
public Golfer (String name, int score) {
this.name = name;
this.score = score;
}
}
Definisi kelas dan konstruktor selalu hampir sama dengan yang sudah kita buat
sebelumnya; bedanya adalah karena untuk saat ini kita harus bisa membuat deklarasi
agar kelas Golfer bisa mengimplementasikan metaclass Comparable (Golfer
implement Comparable). Dalam kasus ini kata kunci implement berarti bahwa Golfer
mengimplementasikan antarmuka yang ditentukan oleh Comparable.
357
Jika sekarang kita mencoba untuk mengcompile Golfer.java, kita akan mendapatkan
pesan yang berbunyi “class Golfer must be declared abstract. It doesn't define int
compareTo (java.lang.Object) from interface java.lang. Comparable”. Dengan kata lain,
agar bisa menjadi Comparable, Golfer harus menyediakan sebuah metode dengan
nama compareTo. Oke, kalau begitu, mari kita tulis sekarang juga:
public int compareTo (Object obj) {
Golfer that = (Golfer) obj;
int a = this.score;
int b = that.score;
// dalam golf, yang kecil itu lebih baik
if (a<b) return 1;
if (a>b) return 1;
return 0;
}
Dua hal yang ada di sini membuat kita sedikit terkejut. Satu, parameternya merupakan
sebuah Object. Ini karena secara umum si pemanggil tidak mengetahui objek bertipe
apa yang sedang dibandingkan. Sebagai contoh, dalam PriorityQueue.java, ketika kita
memanggil compareTo, kita juga melewatkan sebuah Comparable sebagai parameter.
Kita tidak perlu tahu apakah ia termasuk Integer, Golfer, atau apapun itu.
Di dalam compareTo, kita harus mengubah parameter dari sebuah Object menjadi
Golfer. Seperti biasa, akan ada risiko ketika kita menggunakan tipe cast seperti ini: jika
kita mengcast ke tipe yang salah maka kita akan mendapatkan pesan eksepsi.
Akhirnya, kita bisa membuat beberapa pemain golf:
358
Golfer tiger = new Golfer ("Tiger Woods", 61);
Golfer phil = new Golfer ("Phil Mickelson", 72);
Golfer hal = new Golfer ("Hal Sutton", 69);
Lalu memasukkan mereka ke dalam antrian:
pq.add (tiger);
pq.add (phil);
pq.add (hal);
Ketika kita mengeluarkan mereka:
while (!pq.isEmpty ()) {
golfer = (Golfer) pq.remove ();
System.out.println (golfer);
}
Mereka akan ditampilkan dalam urutan terbalik (khusus untuk pemain golf):
Tiger Woods 61
Hal Sutton 69
Phil Mickelson 72
Ketika kita beralih dari Integer ke Golfer, kita tidak perlu membuat perubahan apapun
dalam PriorityQueue.java. Sama sekali tidak! Jadi sekarang kita telah berhasil
mengatasi jurang antara PriorityQueue dan kelaskelas yang menggunakannya.
Dengan kondisi ini, kita bisa menggunakan ulang kode tersebut tanpa harus
mengubahnya. Lebih jauh lagi, kita bisa memberikan kendali kepada kode klien melalui
definisi yang sudah kita buat untuk compareTo. Dengan cara seperti ini, implementasi
PriorityQueue akan menjadi lebih adaptif.
359
16.10 Daftar KataKata
Istilah Arti
queueSekumpulan objek yang sedang menantikan suatu layanan
atau sejenisnya.
queueing disciplineAturan yang menentukan anggota antrian mana yang akan
dihapus lebih dulu.
FIFO
“first in first out”, sebuah aturan antrian di mana anggota
yang pertama kali datang akan menjadi yang pertama kali
dihapus.
priority queue
Suatu aturan antrian di mana setiap anggotanya mempunyai
prioritas yang ditentukan oleh faktorfaktor eksternal.
Anggota yang mempunyai prioritas tertinggi adalah anggota
yang pertama kali kita hapus.
Priority Queue
Sebuah ADT yang mendefinisikan operasioperasi yang
mungkin akan dilakukan oleh seseorang terkait dengan
antrian berprioritas.
veneer
Suatu definisi kelas yang mengimplementasikan sebuah
ADT dengan definisidefinisi metode yang merupakan hasil
pemanggilan dari metodemetode lainnya, terkadang dengan
beberapa perubahan kecil. Veneer tidak mengerjakan fungsi
yang signifikan, tapi dia bisa meningkatkan atau
menstandardisasi antarmuka yang dilihat oleh klien.
performance hazard
Bahaya yang terkait dengan penggunaan veneer ketika
beberapa metode mungkin diimplementasikan secara tidak
efisien sehingga metode tersebut justru malah tidak tampak
oleh klien.
360
constant timeOperasi yang memiliki runtime yang tidak bergantung
kepada ukuran struktur datanya.
linear timeOperasi yang runtimenya berupa fungsi linear dari ukuran
struktur datanya.
linked queueImplementasi antrian menggunakan linked list dan acuan ke
node pertama dan terakhir.
circular buffer
Implementasi antrian menggunakan array dan indeks dari
elemen pertama dan ruang kosong yang ada di sebelahnya
(next available space).
metaclass
Kumpulan kelaskelas. Spesifikasi metaclass berisi
persyaratanpersyaratan yang harus dipenuhi oleh suatu
kelas agar bisa dimasukkan ke dalam kumpulan.
interfaceIstilah Java untuk menyebut metaclass. Jangan sampai
dibingungkan dengan arti kata interface yang lebih luas.
16.11 Latihan
Latihan 16.3
Pertanyaan ini berdasarkan Latihan 9.3.
Tulislah sebuah compareTo untuk kelas Rational yang memungkinkan Rational untuk
mengimplementasikan Comparable.
Petunjuk: jangan lupa bahwa parameter adalah sebuah Object.
Latihan 16.4
Tulislah definisi kelas untuk SortedList yang merupakan perluasan dari kelas
LinkedList. SortedList sama dengan LinkedList; bedanya, elemenelemen SortedList
361
harus menjadi anggota Comparable, dan listnya tersusun dalam urutan terbalik.
Tulislah sebuah metode objek add untuk SortedList yang menggunakan Comparable
sebagai parameter dan mampu menambahkan objek baru ke dalam list, pada lokasi yang
tepat sehingga listnya tetap berada dalam posisi terurut.
Jika Anda mau, Anda bisa menulis satu metode helper dalam kelas Node.
Latihan 16.5
Tulislah satu metode objek, maximum, untuk kelas LinkedList yang bisa dipanggil
melalui objek LinkedList. Metode ini bisa mengembalikan objek kargo terbesar dalam
list, atau null jika listnya kosong.
Anda bisa mengasumsikan bahwa semua elemen kargo menjadi milik kelas yang
menjadi anggota metaclass Comparable, dan setiap dua elemen bisa dibandingkan satu
sama lainnya.
Latihan 16.6
Tulislah implementasi untuk Antrian Prioritas menggunakan linked list. Ada tiga
tahapan yang harus Anda proses:
● Antrian Prioritas mungkin berisi sebuah objek LinkedList sebagai sebuah
variabel instan.
● Antrian Prioritas mungkin berisi sebuah acuan ke objek Node pertama dalam
linked list.
● Antrian Prioritas diwarisi dari kelas LinkedList yang sudah ada.
Pikirkan mengenai pro dan kontra dari ketiga tahapan di atas kemudian pilihlah salah
satu diantaranya. Selain itu, Anda juga bisa memilih; untuk menjaga list agar tetap
terurut (slow add, fast remove), atau tidak terurut (slow remove, fast add).
362
Latihan 16.7
Antrian Kegiatan adalah sebuah struktur data yang menjaga urutan kegiatan, di mana
setiap kegiatan mempunyai waktu pelaksanaan yang terkait dengannya. Berikut ini
adalah ADTnya:
constructor: membuat antrian kegiatan yang baru dan kosong
add: menambahkan sebuah kegiatan baru ke dalam antrian. Parameternya adalah
kegiatan yang merupakan sebuah Object, dan waktu pelaksanaan kegiatan tersebut
yang berupa objek Date. Objek kegiatannya tidak boleh bernilai null.
nextTime: mengembalikan Date untuk pelaksanaan kegiatan berikutnya, kegiatan
“berikut” di sini adalah kegiatan di dalam antrian yang dimulai lebih dulu. Jangan
menghapus kegiatan ini dari antrian. Kembalikan nilai null jika antriannya kosong.
nextEvent: mengembalikan kegiatan berikutnya (sebuah Object) dari antrian lalu
menghapusnya dari antrian. Kembalikan nilai null jika antriannya kosong.
Kelas Date didefinisikan dalam java.util. Kelas ini mengimplementasikan metaclass
Comparable. Menurut dokumentasinya, metode compareTonya mengembalikan “the
value 0 if the argument Date is equal to this Date; a value less than 0 if this Date is
before the Date argument; and a value greater than 0 if this Date is after the Date
argument”.
Tulislah sebuah implementasi untuk antrian kegiatan menggunakan ADT
PriorityQueue. Anda tidak boleh membuat asumsi mengenai bagaimana PriorityQueue
diimplementasikan.
Hint: buatlah sebuah kelas, Event, yang berisi Date dan sebuah Object kegiatan. Kelas
ini harus mengimplementasikan Comparable dengan tepat.
363
Bab 17
Pohon (Trees)
17.1 Simpul Pohon
Laiknya list, pohon terdiri dari simpulsimpul. Salah satu pohon yang paling banyak
dikenal adalah pohon biner (binary tree). Setiap simpul pohon ini berisi sebuah acuan
yang mengacu ke dua simpul lainnya (kemungkinan null). Definisi kelasnya akan
tampak sebagai berikut:
public class Tree {
Object cargo;
Tree left, right;
}
Seperti simpulsimpul dalam list, simpulsimpul pohon juga berisi kargo: dalam hal ini
berupa Object yang generik. Variabelvariabel instan lainnya dinamai left dan right,
yang sesuai dengan standar mata kita ketika direpresentasikan menggunakan gambar:
Puncak pohon (simpul yang diacu dengan nama tree) disebut akar (root). Agar sesuai
364
dengan istilahistilah dalam dunia perpohonan (baca: metafora pohon), simpulsimpul
lain akan disebut cabangcabang (branches). Sementara simpul yang berada di posisi
bagian bawah yang mengacu ke null disebut daundaun (leaves). Mungkin di sini kita
telah melakukan sesuatu yang ganjil dengan menggambar akar di atas sementara daun
daun malah di bawah. Tapi tunggu dulu, itu bukanlah hal teraneh.
Untuk membuatnya lebih aneh lagi, ilmuwan komputer mengombinasikannya dengan
metafora lain: pohon keluarga (the family tree). Simpul yang berada di atas biasanya
disebut parent (induk) sementara simpulsimpul yang diacunya disebut children
(anak). Simpulsimpul dengan induk yang sama disebut siblings, begitu juga
sebaliknya.
Akhirnya, kita juga akan menjumpai kosakata bidang geometri ketika berbicara
mengenai pohon. Saya sudah menyebutkan kanan dan kiri, dan sekarang ada lagi “up”
(menuju ke induk/akar) dan “down” (menuju ke anak/daun). Selain itu, semua simpul
yang mempunyai jarak yang sama dari akar merupakan level dari sebuah pohon.
Saya sendiri tidak tahu kenapa kita harus menggunakan tiga metafora sekaligus ketika
berbicara tentang pohon, tapi itulah fakta yang ada.
17.2 Membuat Pohon
Proses penyusunan simpulsimpul pohon mirip dengan proses ketika kita merangkai
list. Kita mempunyai sebuah konstruktor untuk simpulsimpul pohon yang
menginisialisasi variabelvariabel instan.
public Tree (Object cargo, Tree left, Tree right) {
this.cargo = cargo;
this.left = left;
this.right = right;
}
365
Awalnya kita akan mengalokasikan tempat untuk simpulsimpul anak terlebih dahulu:
Tree left = new Tree (new Integer(2), null, null);
Tree right = new Tree (new Integer(3), null, null);
Kita bisa membuat simpul induk lalu mengaitkannya ke simpul anak pada saat yang
bersamaan:
Tree tree = new Tree (new Integer(1), left, right);
Kode ini menghasilkan keadaan seperti yang ditunjukkan pada gambar sebelumnya.
17.3 Menelusuri Pohon
Cara paling alami untuk menelusuri sebuah pohon adalah dengan memakai metode yang
rekursif. Sebagai contoh, untuk menambahkan semua integer ke dalam satu pohon, kita
bisa menulis metode kelas sebagai berikut:
public static int total (Tree tree) {
if (tree == null) return 0;
Integer cargo = (Integer) tree.cargo;
return cargo.intValue() + total (tree.left) +
total (tree.right);
}
Metode di atas merupakan metode kelas karena kita akan menggunakan null untuk
merepresentasikan pohon yang kosong. Nantinya, pohon kosong inilah yang akan kita
jadikan base case metode rekursif kita. Jika suatu pohon diketahui kosong, metode kita
akan mengembalikan nilai 0. Kalau tidak, metode ini akan membuat dua panggilan
bersifat rekursif untuk menemukan nilai total dari dua anaknya. Akhirnya, metode ini
akan menambahkan nilai total ini ke dalam kargonya lalu mengembalikan nilai total.
366
Meskipun metode ini bekerja dengan baik, ada beberapa kesulitan yang akan
menghadang kita ketika kita hendak mengimplementasikannya dengan konsep
pemrograman berorientasi objek. Metode ini harusnya tidak boleh muncul dalam kelas
Tree karena ia membutuhkan kargo untuk menjadi objekobjek Integer. Jika kita
membuat asumsi ini dalam Tree.java maka kita akan kehilangan manfaatmanfaat yang
bisa kita ambil dari penggunaan struktur data generik.
Di sisi lain, kode ini mengakses variabelvariabel instan simpulsimpul pohon. Jadi dia
“mengetahui” lebih banyak ketimbang yang seharusnya mengenai implementasi suatu
pohon. Jika kita mengubah implementasi ini, kodenya nanti rusak (baca: tidak bisa
dipakai) .
Nanti di bagian lain dalam bab ini, kita akan mengembangkan sebuah cara untuk
menyelesaikan masalah ini, membolehkan kode klien untuk menelusuri pohon yang
berisi objek apapun tanpa merusak perbedaan abstraksi antara kode klien dan
implementasinya. Sebelum kita sampai ke sana, mari kita melihat sebuah aplikasi yang
memanfaatkan konsep pohon ini.
17.4 Pohon Ekspresi
Pohon merupakan cara alami untuk merepresentasikan struktur/susunan suatu ekspresi
matematika. Tidak seperti notasi dalam bidang lain, pohon mampu merepresentasikan
proses komputasi tanpa harus mendatangkan ambiguitas. Sebagai contoh, ekspresi infix
untuk 1 + 2 * 3 termasuk yang mendatangkan ambiguitas, kecuali kalau kita mengetahui
bahwa unsur perkalian diproses lebih dahulu ketimbang unsur penjumlahan.
Gambar berikut ini menunjukkan komputasi yang sama:
367
Simpulsimpul di atas bisa saja merupakan sebuah operan seperti 1 dan 2 atau berupa
operatoroperator seperti + dan *. Operanoperan merupakan simpulsimpul daun;
sementara simpulsimpul operator berisi acuan yang mengacu ke operanoperan mereka
(semua operator di sini disebut operator biner (binary), karena mereka mempunyai
tepat dua operan).
Berdasarkan gambar di atas, tidak usah ditanya lagi bagaimana urutan operasi ini
terjadi: unsur perkalian dikerjakan terlebih dahulu sebelum proses penjumlahan terjadi.
Pohon ekspresi seperti ini mempunyai banyak manfaat. Contoh yang akan kita lihat
adalah dalam proses pengubahan (baca: penerjemahan) dari satu format (postfix) ke
format lainnya (infix). Pohonpohon yang sama digunakan di dalam compiler untuk
melakukan proses penguraian (parse), optimalisasi dan menerjemahkan program.
17.5 Penelusuran
Saya sebelumnya sudah menunjukkan bahwa rekursi sebenarnya merupakan cara yang
alami untuk menelusuri satu pohon. Kita bisa mencetak isi suatu pohon ekspresi dengan
cara seperti ini:
368
public static void print (Tree tree) {
if (tree == null) return;
System.out.print (tree + " ");
print (tree.left);
print (tree.right);
}
Dengan kata lain, untuk mencetak satu pohon, langkah pertama adalah dengan
mencetak isi akarnya, lalu mencetak semua subpohon sebelah kiri, baru kemudian
mencetak seluruh subpohon yang ada di bagian kanan. Cara penelusuran seperti ini
disebut preorder, karena isi dari akar muncul sebelum isi anakanaknya muncul.
Untuk ekspresi contoh tadi, outputnya adalah + 1 * 2 3. Hasil ini berbeda dari hasil yang
didapat ketika menggunakan postfix atau infix; output ini adalah notasi baru yang
dikenal dengan istilah prefix. Dalam notasi ini, operatoroperator muncul sebelum
operanoperan mereka.
Anda mungkin sudah menduga bahwa jika kita menelusurisuatu pohon dengan urutan
yang berbeda maka kita akan mendapatkan ekspresi dalam notasi yang berbeda pula.
Sebagai contoh, jika kita mencetak subpohon dulu, lalu dilanjutkan dengan simpul akar:
public static void printPostorder (Tree tree) {
if (tree == null) return;
printPostorder (tree.left);
printPostorder (tree.right);
System.out.print (tree + " ");
}
Kita akan mendapatkan ekspresi postfix (1 2 3 * +)! Seperti namanya, urutan
369
penelusuran seperti ini disebut postorder. Akhirnya, untuk menelusuri sebuah pohon
secara inorder, kita cetak dulu bagian kiri pohon, kemudian akar, diakhiri dengan
bagian kanannya:
public static void printInorder (Tree tree) {
if (tree == null) return;
printInorder (tree.left);
System.out.print (tree + " ");
printInorder (tree.right);
}
Hasilnya adalah 1 + 2 * 3, yang merupakan ekspresi infix.
Jujur saja, saya harus mengatakan bahwa sebenarnya saya telah mengacuhkan
komplikasi penting. Terkadang, ketika kita menulis sebuah ekspresi infix, kita harus
menggunakan tanda kurung (parentheses) untuk menjaga urutan operasioperasinya.
Jadi, penelusuran secara inorder belumlah cukup untuk menghasilkan satu ekspresi
infix.
Meskipun begitu, dengan beberapa perbaikan, pohon ekspresi dan ketiga penelusuran
rekursif tersebut mampu memberikan teknik/cara yang umum untuk menerjemahkan
ekspresi dari satu format ke format lainnya.
17.6 Enkapsulasi
Seperti yang sudah saya katakan sebelumnya, masih ada masalah dengan teknik/cara
yang kita ambil dalam menelusuri pohonpohon: cara ini membuat kita harus
memisahkan kode klien (aplikasi yang menggunakan pohon) dengan kode provider
(implementasi Pohon). Idealnya, suatu pohon harusnya bersifat umum; ia tidak harus
370
tahu segala sesuatu tentang pohonpohon ekspresi. Sementara kode yang menghasilkan
sekaligus menelusuri pohonpohon ekspresi tidak perlu tahu segala sesuatu tentang
implementasi pohonpohon. Prinsip/standar perancangan seperti ini disebut object
encapsulation (enkapsulasi objek). Istilah ini digunakan agar Anda tidak kebingungan
dengan istilah yang sudah ada dalam subbab 6.6, yakni method encapsulation.
Untuk versi saat ini, kode Tree tahu terlalu banyak tentang kliennya. Sebaliknya, kelas
Tree harus menyediakan kemampuan/kapabilitas umum untuk melakukan penelusuran
satu pohon dengan beragam cara. Ketika penelusuran sedang dilakukan, ia harus bisa
memproses operasi di setiap simpul yang ditentukan oleh klien.
Untuk memfasilitasi perbedaan kepentingan ini, kita akan membuat sebuah metaclass
baru yang disebut Visitable. Itemitem yang disimpan dalam sebuah pohon disyaratkan
agar bisa dikunjungi (visitable). Ini berarti bahwa itemitem tersebut akan
mendefinisikan sebuah metode – visit – yang akan melakukan apapun yang diinginkan
oleh klien terhadap masingmasing simpul. Dengan cara ini, Pohon kita akan mampu
melakukan penelusuran sementara klien mampu mengerjakan operasioperasi simpul.
Berikut ini adalah langkahlangkah yang harus kita kerjakan untuk membuat metaclass
yang bisa digunakan oleh kedua kubu sekaligus, klien dan provider:
1. Definisikanlah sebuah metaclass yang mampu mengidentifikasi metodemetode
yang akan dibutuhkan oleh kode provider untuk melakukan pemanggilan
melalui komponenkomponennya.
2. Tulislah kode provider sebagai satu metaclass yang baru, sebagai lawan dari
Object yang bersifat generik.
3. Definisikanlah sebuah kelas yang menjadi bagian dari metaclass dan mampu
mengimplementasikan metodemetode yang memang dibutuhkan oleh klien.
4. Tulislah kode klien untuk menggunakan kelas yang baru tersebut.
371
Subbab berikutnya akan mendemonstrasikan langkahlangkah ini.
17.7 Mendefinisikan metaclass
Sebenarnya terdapat dua cara untuk mengimplementasikan sebuah metaclass dalam
Java, sebagai interface atau sebagai abstract class. Perbedaan antara keduanya tidaklah
terlalu penting untuk saat ini, jadi kita akan mulai dengan mendefinisikan sebuah
antarmuka (interface).
Definisi antarmuka tampak seperti definisi kelas, dengan dua perbedaan:
● kata kunci class diganti dengan interface, dan
● definisi metode tidak mempunyai badan (bodies).
Definisi antarmuka menentukan metodemetode yang harus diimplementasikan oleh
sebuah kelas agar bisa menjadi anggota metaclass. Syaratsyarat yang harus dipenuhi
antara lain; nama, tipetipe parameter, dan tipe pengembalian dari setiap metode.
Definisi Visitable adalah sebagai berikut
public interface Visitable {
public void visit ();
}
Itu saja, cukup! Definisi visit tampak seperti definisi metode pada umumnya, kecuali
bahwa sekarang kita tidak memerlukan badan. Definisi ini mengindikasikan bahwa
semua kelas yang mengimplementasikan Visitable harus mempunyai metode dengan
nama visit yang tidak membutuhkan parameter apapun dan mengembalikan void.
Seperti definisi kelas lainnya, definisi antarmuka memiliki nama file yang sama dengan
nama kelasnya (dalam hal ini Visitable.java).
372
17.8 Mengimplementasikan Metaclass
Jika kita menggunakan sebuah pohon ekspresi untuk menghasilkan infix, maka
“mengunjungi” satu simpul berarti mencetak isiisinya. Karena isi dari sebuah pohon
ekspresi berupa token, kita akan membuat kelas baru yakni Token yang
mengimplementasikan Visitable.
public class Token implements Visitable {
String str;
public Token (String str) {
this.str = str;
}
public void visit () {
System.out.print (str + " ");
}
}
Ketika kita mengcompile definisi kelas ini (yang berada dalam file Token.java),
compiler akan memeriksa apakah metodemetode itu memenuhi persyaratan yang
ditentukan oleh metaclass. Jika tidak, ia akan menyebabkan munculnya pesan error.
Sebagai contoh, jika kita salah menyebutkan nama metode yang dikira visit, kita
mungkin akan menerima sesuatu seperti, “class Token must be declared abstract. It does
not define void visit() from interface Visitable.” Ini adalah satu dari sekian banyak
pesan error yang solusinya tidak tepat. Ketika compiler tersebut mengatakan bahwa
suatu kelas “must be declared abstract,” apa yang dimaksud dengan pernyataan tersebut
adalah bahwa Anda harus memperbaiki kelas itu sehingga ia mengimplementasikan
antarmuka dengan tepat. Terkadang saya sampai berpikir untuk melawan orang yang
373
menulis pesan ini.
Langkah berikutnya adalah memodifikasi parser untuk meletakkan objekobjek Token
ke dalam pohon ketimbang String. Berikut ini contoh kecilnya:
String expr = "1 2 3 * +";
StringTokenizer st = new StringTokenizer (expr, "
+*/", true);
String token = st.nextToken();
Tree tree = new Tree (new Token (token), null,
null));
Kode ini menggunakan token pertama dalam string lalu membungkusnya ke dalam
objek Token, kemudian meletakkan Token tersebut ke dalam satu simpul pohon. Jika
Tree di sini membutuhkan kargo agar bisa masuk ke dalam metaclass Visitable, ia akan
mengubah Token sehingga bisa menjadi bagian dari objek Visitable. Ketika kita
menghapus Visitable dari pohon, kita harus mengcastnya kembali ke bentuk Token.
Latihan 17.1
Tulislah versi printPreorder dengan nama visitPreorder yang menelusuri pohon dan
memanggil visit di setiap simpul dalam preorder.
Alur eksekusi untuk metodemetode seperti visitPreorder tampak tidak biasa. Klien
memanggil sebuah metode yang disediakan oleh implementasi Tree, lalu implementasi
Tree ini akan memanggil satu metode yang disediakan oleh klien. Pola seperti ini
disebut callback; adalah langkah yang baik untuk membuat kode provider yang lebih
umum tanpa harus memecah abstraksi yang sudah dibuat.
17.9 Kelas Vector
Vector merupakan kelas bawaan Java yang berada dalam paket java.util. Kelas ini
374
merupakan implementasi dari sebuah array yang berisi Object dengan fitur tambahan,
yakni kemampuan untuk mengubah ukurannya secara otomatis, sehingga kita tidak
perlu capekcapek melakukannya.
Sebelum menggunakan kelas Vector ini, Anda harus memahami beberapa konsep
terlebih dahulu. Setiap Vector mempunyai kapasitas masingmasing, yakni ruang yang
sudah dialokasikan untuk menyimpan nilainilai dan ukuran yang merupakan banyak
nilai yang berada dalam vektor.
Gambar berikut ini merupakan diagram sederhana dari sebuah vektor yang berisi tiga
elemen, tapi kapasitasnya tujuh.
Ada dua kelompok metode untuk mengakses elemenelemen yang berada dalam vektor.
Metodemetode tersebut menyediakan semantik dan kemampuan errorchecking yang
berbedabeda. Hatihati, mungkin mereka akan sering membuat Anda kebingungan.
Metode paling sederhana adalah get dan set. Dua metode ini menyediakan semantik
yang mirip dengan apa yang dimiliki oleh operator indeks array, []. Metode get
menggunakan indeks integer dan mengembalikan elemen yang berada pada posisi yang
diinginkan. Sementara set menggunakan sebuah indeks dan satu elemen, lalu
menyimpan elemen yang baru itu pada posisi yang diinginkan, mengganti elemen
sebelumnya yang sudah berada di dalam posisi itu.
Metode get dan set tidak digunakan untuk mengubah ukuran vektor (jumlah elemen).
Hal ini merupakan tanggung jawab kode klien untuk memastikan agar suatu vektor
mempunyai ukuran yang cukup sebelum get dan set dipanggil. Metode size
mengembalikan jumlah elemen yang ada dalam vektor. Jika Anda mencoba mengakses
satu elemen yang tidak eksis/ada (yaitu elemen dengan indeks 3 sampai 6), Anda akan
375
menerima eksepsi ArrayIndexOutOfBounds.
Kumpulan metode lain mengikutsertakan beberapa versi dari add dan remove. Metode
metode ini mampu mengubah ukuran vektor dan kapasitasnya, jika diperlukan. Salah
satu versi dari add menggunakan satu elemen sebagai parameter lalu menambahkannya
ke ujung vektor (baca: bagian belakang). Metode ini lebih aman karena tidak
menyebabkan eksepsi.
Versi lain dari add menggunakan satu indeks dan satu elemen. Seperti set, ia
meletakkan elemen baru di posisi yang diinginkan. Bedanya, add tidak mengganti
elemen yang sudah ada sebelumnya. Metode ini akan menaikkan ukuran vektor, lalu
menggeser elemenelemen yang ada ke sebelah kanan untuk memberi ruang bagi
elemen yang baru. Oleh karena itu, pemanggilan metode seperti ini; v.add (0, elt)
berarti menambahkan elemen baru di awal vektor. Sayangnya, metode ini selain tidak
aman juga kurang efisien; ia bisa menyebabkan eksepsi berupa
ArrayIndexOutOfBounds dan, dalam banyak implementasi, metode ini termasuk ke
dalam linear time (sesuai dengan ukuran vektornya).
Secara umum, para klien tidak perlu khawatir mengenai kapasitas. Kapanpun ukuran
vektor berubah, kapasitas akan diperbaharui secara otomatis. Karena alasan unjuk kerja,
beberapa aplikasi memegang kendali atas fungsi ini, yang sekaligus menjadi penyebab
kenapa ada metodemetode tambahan untuk menaikkan dan menurunkan kapasitas.
Karena kode klien tidak mempunyai akses untuk menggunakan implementasi vektor,
adalah tidak jelas bagaimana cara kita menelusurinya. Tentu saja, satu kemungkinan
adalah dengan memakai variabel perulangan sebagai indeks untuk vektor:
for (int i=0; i<v.size(); i++) {
System.out.println (v.get(i));
}
376
Tidak ada yang salah dengan kode di atas, tapi ada cara lain yang juga bisa digunakan
untuk mendemonstrasikan kelas Iterator. Vektorvektor menyediakan satu metode,
yakni iterator, yang bisa mengembalikan objekobjek Iterator sehingga
memungkinkan metode ini untuk melakukan penelusuran vektor.
17.10 Kelas Iterator
Iterator adalah antarmuka yang berada dalam java.util.package. Antarmuka ini terdiri
dari tiga metode:
hasNext: apakah iterasi ini mempunyai elemen yang lebih?
next: mengembalikan elemen berikutnya, atau mengembalikan sebuah eksepsi jika
kosong
remove: menghapus elemen yang paling baru dari struktur data yang kita telusuri
Contoh berikut ini menggunakan sebuah iterator untuk menelusuri dan mencetak
elemenelemen yang terdapat dalam satu vektor.
Iterator it = vector.it ();
while (it.hasNext ()) {
System.out.println (it.next ());
}
Setelah Iterator dibuat, ia akan menjadi objek yang terpisah dari Vector aslinya.
Perubahan yang terjadi berikutnya tidak akan memengaruhi Iterator itu sendiri. Pada
kenyataannya, jika Anda memodifikasi Vector setelah membuat Iterator, Iterator itu
akan menjadi tidak valid. Jika Anda mengakses Iterator itu lagi, hal ini akan
menyebabkan eksepsi ConcurrentModification.
377
Dalam subbab sebelumnya kita menggunakan metaclass Visitable untuk mengizinkan
satu klien menelusuri struktur data tanpa harus mengetahui detail implementasinya.
Iteratoriterator menyediakan cara lain bagi kita untuk melakukan hal yang sama.
Dalam kasus yang pertama, provider melakukan perulangannya lalu memanggil kode
klien untuk “mengunjungi” masingmasing elemen. Dalam kasus berikutnya, provider
memberikan kliennya satu objek yang bisa digunakannya untuk memilih satu elemen
pada suatu waktu.
Latihan 17.2
Tulislah sebuah kelas, PreIterator, yang mengimplementasikan antarmuka Iterator
lalu buatlah sebuah metode, preorderIterator untuk kelas Tree yang mengembalikan
satu PreIterator yang mampu memilih elemenelemen Tree secara preorder.
PETUNJUK:
Cara termudah untuk membuat Iterator adalah dengan meletakkan elemenelemen ke
dalam sebuah vektor yang urutannya sesuai dengan apa yang Anda inginkan.
Kemudian Anda bisa memanggil metode iterator melalui vektor tersebut.
17.11 Daftar KataKata
Istilah Arti
binary treePohon yang tiap simpulnya mengacu ke 0, 1, atau 2 simpul
yang saling terkait satu sama lainnya.
rootSimpul tertinggi yang ada pada sebuah pohon, yang tidak
diacu oleh simpul manapun.
leafSimpul terbawah pada sebuah pohon yang tidak mengacu ke
simpul manapun..
parent Simpul yang mengacu ke salah satu atau banyak simpul
378
dalam pohon.
child Salah satu simpul yang diacu oleh simpul lainnya.
levelKumpulan simpul yang masingmasing memiliki jarak yang
sama ke root.
preorderSebuah teknik penelusuran pohon di mana setiap simpul
dikunjungi lebih dahulu sebelum simpul anakanaknya.
postorderSebuah teknik penelusuran pohon; kunjungi simpul anak
dari semua simpul sebelum simpul itu sendiri.
inorder
Sebuah teknik penelusuran pohon; kunjungi subpohon
bagian kiri, kemudian akarnya, lalu baru kunjungi bagian
kanannya.
linked queueImplementasi antrian menggunakan linked list dan acuan ke
node pertama dan terakhir.
class variableVariabel static yang dideklarasikan di luar semua metode.
Variabel ini bisa diakses dari metode manapun.
binary operator Operator yang menggunakan dua operan.
object encapsulation
Suatu rancangan yang tujuannya adalah untuk menjaga
implementasi dari satu objek agar selalu terpisah dari
implementasi objek lainnya. Begitu juga dengan kelas. Satu
kelas tidak harus mengetahui detail implementasi kelas
lainnya.
method encapsulation
Suatu rancangan yang tujuannya adalah untuk menjaga
antarmuka dari suatu metode terpisah dari detail
implementasinya.
callbackAlur eksekusi di mana kode provider memanggil sebuah
metode yang disediakan oleh klien.
379
17.12 Latihan
Latihan 17.3
a) Apa hasil ekspresi postfix 1 2 + 3 * ?
b) Ekspresi postfix apa yang ekuivalen dengan ekspresi infix 1 + 2 * 3?
c) Apa hasil ekspresi postfix 17 1 – 5 /, asumsikan bahwa simbol / melakukan
pembagian integer?
Latihan 17.4
Tinggi sebuah pohon adalah jalur terpanjang dari akar ke daun manapun. Tinggi bisa
didefinisikan secara rekursif se[erti berikut:
● Tinggi dari pohon yang null adalah 0.
● Tinggi dari pohon yang tidak null adalah 1 + max (leftHeight, rightHeight), di
mana leftHeight adalah tinggi dari simpul anak sebelah kiri dan rightHeight
adalah tinggi dari simpul anak sebelah kanan.
Tulislah metode height yang bisa menghitung tinggi dari parameter yang berupa Tree.
Latihan 17.5
Bayangkan kita sedang mendefinisikan sebuah Tree yang berisi objekobjek
Comparable sebagai kargo:
public class ComparableTree {
Comparable cargo;
Tree left, right;
}
Tulislah metode kelas Tree, findMax, yang mengembalikan kargo terbesar dalam
380
pohon. Kata “terbesar” di sini ditentukan oleh metode compareTo.
Latihan 17.6
Pencarian biner pohon merupakan pohon khusus di mana setiap simpul pohon
N:
semua kargo di sebelah kiri subpohon N < kargo dalam simpul N
dan
kargo yang ada di simpul N < semua kargo di sebelah kanan subpohon N
Dengan menggunakan definisi kelas seperti di atas, buatlah sebuah metode objek
contains yang menggunakan satu Object sebagai argumen lalu mengembalikan nilai
True jika objeknya muncul dalam pohon, atau False jika tidak. Anda bisa
mengasumsikan bahwa objek yang menjadi target dan semua objek yang berada dalam
pohon merupakan anggota dari metaclass Comparable.
public class SearchTree {
Comparable cargo;
SearchTree left, right;
}
Latihan 16.7
Dalam matematika, satu himpunan (set) merupakan suatu kumpulan elemen yang tidak
ada duplikasi di dalamnya. Antarmuka java.util.Set digunakan untuk memodelkan
himpunan matematika. Metode yang dibutuhkannya adalah add, contains, containsAll,
remove, size, dan iterator.
Tulislah kelas TreeSet yang mewarisi kelas SearchTree sekaligus mampu
mengimplementasikan Set. Agar semuanya bisa tetap berjalan dengan sederhana, Anda
bisa mengasumsikan bahwa null tidak akan muncul dalam pohon atau sebagai argumen
381
dari metode manapun.
Latihan 17.8
Tulislah metode union yang menggunakan dua Set sebagai argumen lalu
mengembalikan sebuah TreeSet baru yang berisi semua elemen yang muncul di kedua
Set itu.
Anda bisa memasukkan metode ini ke dalam implementasi TreeSet, atau membuat
kelas baru yang mewarisi java.util.TreeSet yang menyediakan union di dalamnya.
Latihan 17.9
Tulislah metode intersection yang menggunakan dua Set sebagai parameter lalu
mengembalikan sebuah TreeSet baru yang berisi semua elemen yang muncul dalam
kedua Set tersebut.
Metode union dan intersection bersifat generik karena parameterparameternya bisa
bertipe apa saja dalam metaclass Set. Bahkan kedua parameter itu tidak harus memiliki
tipe yang sama.
Latihan 17.10
Salah satu alasan kenapa antarmuka Comparable bermanfaat adalah karena ia
mengizinkan satu tipe objek untuk memutuskan bahwa semua teknik pengurutan
sebenarnya tepat. Untuk tipetipe seperti Integer dan Double, teknik pengurutan yang
tepat sudah jelas, tapi ada banyak contoh di mana suatu pengurutan bergantung pada
apa yang ingin direpresentasikan oleh si objek. Dalam golf, sebagai contoh saja, skor
yang lebih rendah lebih baik ketimbang yang lebih tinggi; jika kita membandingkan dua
objek Golfer, objek yang mempunyai skor lebih rendah akan menjadi pemenangnya.
a) Tulislah definisi kelas Golfer yang berisi nama dan skor bertipe integer sebagai
variabel instan. Kelas ini harus mengimplementasikan Comparable dan
382
menyediakan metode compareTo yang bisa memberikan prioritas lebih tinggi untuk
skor yang lebih rendah.
b) Tulislah sebuah program yang bisa membaca file yang berisi namanama dan skor
skor dari kumpulan (Set) pegolf. Program ini harus bisa membuat objek Golfer,
meletakkan objekobjek tersebut ke dalam Antrian Prioritas lalu mengeluarkan dan
mencetak mereka. Mereka harus ditampilkan dalam urutan terbalik (descending)
berdasarkan prioritas. Hal ini sama saja dengan kenaikan urutan berdasarkan skor.
Tiger Woods 61
Hal Sutton 69
Phil Mickelson 72
Allen Downey 158
PETUNJUK:
Lihat bagian C untuk kode yang bisa membaca barisbaris dalam suatu file.
Latihan 17.11
Tulislah implementasi Stack menggunakan Vector. Coba pikirkan, apakah
memasukkan (push) elemen baru ke posisi awal stack akan lebih baik ketimbang
memasukkannya ke posisi terakhir?
383
Bab 18
Heap
18.1 Implementasi Pohon menggunakan Array
Apa yang dimaksud dengan mengimplementasikan pohon? Sejauh ini kita hanya
melihat satu implementasi pohon yang menggunakan linked list. Tetapi sebenarnya ada
struktur lain yang bisa kita identifikasi sebagai pohon. Dan segala hal yang bisa
melakukan operasi pohon “dasar” bisa disebut sebagai pohon.
Jadi apa saja operasi operasi pohon itu? Dengan kata lain bagaimana mendefinisikan
pohon ADT?
constructor: Membangun sebuah pohon kosong.
getLeft: Mengembalikan anak kiri dari simpul.
getRight: Mengembalikan anak kanan dari simpul.
getParent: Mengembalikan orang tua dari simpul.
getCargo: Mengembalikan muatan dari simpul.
setCargo: Memasukkan sebuah objek muatan pada simpul ini (dan membuat simpul
jika dibutuhkan).
Pada implementasi dengan linked list. Pohon kosong diwakili dengan nilai spesial null.
getLeft dan getRight dilakukan dengan mengakses variabel instans dari simpul,
demikian hal nya dengan getCargo dan setCargo. Dalam hal ini kita belum
mengimplementasikan getParent( anda bisa berpikir bagaimana untuk melakukan hal
ini)
384
Ada implementasi pohon lain yang menggunakan array dan indeks sebagai ganti objek
dan referensi. Untuk melihat bagaimana ia bekerja, kita akan memulai dengan melihat
implementasi campuran yang menggunakan array dan objek.
Gambar di bawah ini memperlihatkan sebuah pohon seperti sebelumnya yang pernah
kita lihat. Pada sisi kanan ada sebuah array dari referensi yang mengacu pada muatan
dari sebuah simpul.
Setiap simpul pada pohon memiliki indeks yang unik. Indeks ditempatkan pada simpul
menurut pola yang ditentukan, dengan aturan seperti di bawah:
1. Anak kiri dari simpul dengan indeks i memiliki indeks 2i.
2. Anak kanan dari simpul dengan indeks i memiliki indeks 2i+1.
3. Orang tua dari simpul dengan indeks i memiliki indeks i/2 (pembulatan ke
bawah).
Menggunakan rumus ini, kita bisa mengimplementasikan getLeft, getRight, dan
getParent dengan hanya menggunakan aritmatika; kita tidak harus menggunakan
referensi untuk semua hal!
Karena kita tidak menggunakan referensi, berarti apa yang digunakan sekarang hanya
objek muatan dan tidak ada yang lain. Sehingga kita bisa mengimplementasikan pohon
sebagai sebuah array dari objek muatan.
385
Di bawah ini contoh implementasi pohon menggunakan array:
public class ArrayTree {
Object[] array;
int size;
public ArrayTree () {
array = new Object [128];
}
Sejauh ini tidak ada yang baru. Satu satunya variabel instans adalah array dari objek
yang berisi muatan pohon. Konstruktor menginisialisasi array dengan kapasitas tertentu;
Hasilnya adalah sebuah pohon kosong.
Berikut ini implementasi sederhana dari getCargo dan setCargo.
public Object getCargo (int i) {
return array[i];
}
public void setCargo (int i, Object obj) {
array[i] = obj;
}
Metode ini tidak melakukan pengecekan error apapun, jadi jika parameter salah, metode
dapat menghasilkan eksepsi ArrayIndexOutOfBounds.
Implementasi dari getLeft, getRight, dan getParent hanya menggunakan operasi
aritmatika:
public int getLeft (int i) { return 2*i; }
public int getRight (int i) { return 2*i + 1; }
public int parent (int i) { return i/2; }
386
Akhirnya kelas pohon menggunakan array telah siap digunakan. Pada kelas lain (klien),
kita bisa menulis:
ArrayTree tree = new ArrayTree ();
tree.setCargo (1, "cargo for root");
Konstruktor membangun sebuah pohon kosong. Memanggil setCargo meletakkan
kalimat “cargo for root” kedalam simpul akar.
Untuk menambah anak pada simpul akar:
tree.setCargo (tree.getLeft(1), "cargo for left");
tree.setCargo (tree.getRight(1), "cargo for right");
Pada kelas pohon kita dapat menyediakan sebuah metode yang mencetak isi dari pohon
dalam bentuk preorder.
public void print (int i) {
Object cargo = tree.getCargo (i);
if (cargo == null) return;
System.out.println (cargo);
print (getRight (i));
print (getLeft (i));
}
Untuk memanggil metode ini, kita harus melewatkan indeks akar sebagai
parameter.
tree.print (1);
Keluarannya adalah:
cargo for root
cargo for left
cargo for right
387
Implementasi ini menyediakan operasi dasar yang mendefinisikan sebuah pohon.
Seperti yang telah saya tunjukkan sebelumnya, implementasi pohon menggunakan
linked list menyediakan operasi yang sama tetapi dengan sintaks yang berbeda.
Dalam beberapa hal, implementasi menggunakan array sedikit aneh. Pada suatu saat kita
mengasumsikan bahwa muatan null menunjukkan simpul yang tidak ada, tetapi itu
berati kita tidak dapat meletakkan objek null pada pohon sebagai muatan.
Masalah lain adalah subpohon tidak direpresentasikan sebagai objek; Mereka
direpresentasikan dengan indeks pada array. Untuk melewatkan simpul pohon sebagai
parameter, kita harus melewatkan sebuah referensi ke objek pohon dan indeks ke array.
Akibatnya beberapa operasi yang mudah dilakukan pada implementasi dengan linked
list, seperti mengganti keseluruhan subtree, lebih sulit dilakukan pada implementasi
dengan array.
Pada sisi lain, implementasi dengan array hemat dalam hal ruang penyimpanan, karena
tidak ada nya hubungan antara simpul, dan ada beberapa operasi yang lebih mudah dan
cepat jika pohon diimplementasikan dengan array.
Heap adalah implementasi dari PriorityQueue ADT yang berdasarkan atas
implementasi pohon menggunakan array. Yang akan lebih efisien jika dibandingkan
dengan implementasi lain.
Untuk membuktikan klaim ini, akan kita lakukan dalam beberapa langkah. Pertama, kita
akan membangun cara untuk membandingkan kinerja dari beberapa implementasi.
Berikutnya, kita akan melihat implementasi yang dilakukan heap. Dan terakhir kita akan
388
membandingkan implementasi PriorityQueue menggunakan heap dengan yang lainnya
(array dan list) dan melihat mengapa implementasi heap dianggap lebih efisien.
8.2 Analisis Kinerja
Ketika kita membandingkan algoritma, kita harus memiliki cara untuk mengetahui
kapan suatu algoritma lebih cepat dibandingkan dengan yang lain, atau menggunakan
ruang yang lebih sedikit, atau menggunakan sumber daya lain yang lebih sedikit.
Pertanyaan ini berat untuk dijawab secara detil, karena waktu dan ruang penyimpanan
yang digunakan suatu algoritma bergantung pada implementasi dari algoritma tersebut,
masalah tertentu yang akan diselesaikan, dan perangkat keras dimana program berjalan.
Tujuan dari bab ini adalah untuk mencari cara untuk mengetahui kinerja yang tidak
tergantung pada semua hal itu, dan hanya bergantung pada algoritm itu sendiri. Untuk
memulai, kita akan fokus pada waktu eksekusi; selanjutnya kita akan berbicara tentang
sumber daya yang lain.
Adapun masalahmasalah yang kita hadapi adalah:
1. Pertama, kinerja algoritma bergantung pada perangkat keras dimana ia berjalan,
jadi kita tidak akan berbicara tentang waktu eksekusi dengan satuan mutlak
seperti detik. Sebagai gantinya, kita menghitung operasi abstrak yang dilakukan
suatu algoritma.
2. Kedua, kinerja sering bergantung pada masalah tertentu yang sedang kita coba
untuk menyelesaikannya – beberapa masalah lebih mudah dibandingkan dengan
yang lain. Untuk membandingkan algoritma, kita akan fokus pada skenario
terburuk atau skenario rata rata (umum).
3. Ketiga, Kinerja bergantung pada ukuran masalah (biasanya, tetapi tidak selalu,
banyak elemen dalam sebuah collection). Kita menyelesaikan masalah ini
389
dengan menganggap waktu eksekusi sebagai fungsi dari besar ukuran masalah.
4. Kinerja bergantung pada implementasi detail sebuah algoritma seperti
pengalokasian objek dan pengeksekusian metode. Kita tidak akan mengurusi hal
ini karena tidak terlau berpengaruh pada tingkat kompleksitas seperti banyak
operasi abstrak menambah ukuran masalah.
Untuk membuat masalah proses lebih nyata, perhatikan dua algoritma
pengurutan bilangan bulat yang telah kita lihat. Yang pertama adalah selection sort,
yang telah kita lihat pada bagian 12.3. Berikut ini pseudocode dari algoritma tersebut.
selectionsort (array) {
for (int i=0; i<array.length; i++) {
// find the lowest item at or to the right of i
// swap the ith item and the lowest item
}
}
Untuk melakukan operasi yang dispesifikasikan pada pseudocode, kita menuliskan
metode yang bernama findLowest dan swap. Berikut ini pseudocode metode findLowest
dan swap:
// find the index of the lowest item between
// i and the end of the array
findLowest (array, i) {
// lowest contains the index of the lowest item so far
lowest = i;
for (int j=i+1; j<array.length; j++) {
// compare the jth item to the lowest item so far
390
// if the jth item is lower, replace lowest with j
}
return lowest;
}
swap (i, j) {
// store a reference to the ith card in temp
// make the ith element of the array refer to the jth card
// make the jth element of the array refer to temp
}
Untuk menganalisa kinerja algoritma ini, langkah pertama adalah menentukan operasi
apa yang akan dihitung. Seyogyanya program melakukan banyak hal seperti: menambah
i, membandingkannya dengan panjang deck, mencari elemen terbesar pada array, dan
lain lain. Sehingga tidaklah terlalu jelas hal mana yang akan dihitung.
Pilihan yang baik adalah berapa kali kita membandingkan dua item. Banyak pilihan lain
yang akan menghasilkan hasil yang sama pada akhirnya, tetapi menggunakan
pembandingan item akan mudah kita lakukan dan kita akan bisa membandingkan
algoritma pengurutan apapun dengan mudah.
Langkah berikutnya adalah mendefinisikan “ukuran masalah”. Pada kasus ini kita
memilih ukuran array, yang akan kita sebut n. Langkah terakhir, kita akan menurunkan
sebuah fungsi yang memberitahu berapa banyak operasi abstrak (dalam kasus ini,
perbandingan) yang harus kita lakukan, sebagai fungsi dari n.
391
Swap mengkopi beberapa referensi, tetapi tidak melakukan pembandingan apapun, jadi
kita mengabaikan waktu untuk mengeksekusi swap. findLowest dimulai pada i dan
menjelajahi array, membandingkan setiap item dengan lowest. Banyak item yang akan
dibandingkan adalah sebesar ni, sehingga jumlah total perbandingan adalah ni1.
Selanjutnya kita akan melilhat berapa kali findLowest dipanggil serta nilai i nya.
Terakhir kali dipanggil, i bernilai n2 sehingga jumlah perbandingan adalah 1. Iterasi
sebelumnya melakukan dua perbandingan dan demikian seterusnya. Selama iterasi
pertama, i bernilai 0 dan jumlah perbandingan adalah n1.
Jadi jumlah perbandingan total adalah 1+2+...+n1. Jumlahan ini sama dengan n2 /2 −
n/2. Untuk menjelaskan algoritma ini, kita akan mengabaikan n/2 dan menganggap total
perbandingan adalah n2.
18.3 Analisis Merge Sort
Pada bagian 12.6 saya mengklaim bahwa mergesort membutuhkan waktu nlogn, tetapi
saya tidak menjelaskan bagaimana atau mengapa. Sekarang saya akan menjelaskannya.
Kembali, kita mulai dengan melihat pseudocode algoritma.
mergeSort (array) {
// find the midpoint of the array
// divide the array into two halves
// sort the halves recursively
// merge the two halves and return the result
}
392
Pada setiap level rekursi. Kita membagi array dalam dua bagian, memanggil dua metode
rekursif, dan kemudian menggabungkannya. Secara grafis, proses nya adalah sebagai
berikut:
Setiap baris pada diagram adalah tingkat rekursi. Pada baris teratas, array tunggal dibagi
menjadi dua bagian. Pada baris paling bawah, n array dengan satu elemen tiap bagian
digabung menjadi n/2 array dengan dua elemen setiap bagian.
Dua kolom pertama pada tabel memperlihatkan banyak array pada setiap tingkatan dan
banyak elemen dalam setiap array. Kolom ketiga memperlihatkan berapa kali
penggabungan dilakukan setiap tingkatan rekursi. Kolom berikutnya memperlihatkan
banyak perbandingan setiap penggabungan dilakukan.
Jika and melihat pada pseudocode (atau pada implementasi anda) tentang
penggabungan, anda pasti menyadari kasus terburuk nya adalah sebanyak m1. Dimana
m adalah banyak elemen yang akan digabungkan.
Langkah berikutnya adalah mengalikan banyak penggabungan tiap tingkatan dengan
banyak perbandingan setiap penggabungan. Hasilnya adalah kerja total tiap tingkatan.
Pada titik ini kita mengambil keuntungan dari trik kecil. Kita tahu pada akhirnya kita
hanya tertarik pada pangkat depan dari hasil yang didapatkan, sehingga kita bisa
393
mengabaikan 1 pada bagian perbandingan tiap penggabungan. Jika kita melakukannya,
total kerja tiap tingkatan hanya n.
Berikutnya kita perlu tahu jumah tingkatan sebagai fungsi dari n. Kita akan mulai
dengan array dengan n elemen dan membaginya dalam dua bagian sampai mendapatkan
1 elemen tiap bagian. Hal ini sama dengan mulai dari 1 dan mengalikan dengan 2
sampai mendapatkan n. Dengan kata lain, kita ingin mengetahui berapa kali kita harus
mengalikan 2 sebelum kita mendapatkan n. Jawabannya adalah banyak tingkatan, l
adalah logaritma basis dua dari n.
Terakhir, kita mengalikan total kerja tiap tingkatan, n, dengan banyak tingkatan, log2 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 log2
n, dan jumlah objek total yang dialokasikan adalah 2n. Selalu benar bahwa
implementasi buruk dari algoritma yang baik adalah lebih baik dibandingkan
implementasi baik dari algoritma yang buruk.
18.5 Implementasi PriorityQueue
Pada bab 16 kita telah melihat sebuah implementasi dari PriorityQueue yang dibuat
dengan array. Elemen pada array tidak terurut, sehingga mudah untuk menambahkan
elemen baru, tetapi akan lebih susah untuk menghapus elemen, karena kita harus
mencari item dengan prioritas tertinggi.
Sebagai alternatif implementasi dibuat berdasarkam sorted list. Pada kasus ini ketika
kita menambah item baru kita menjelajahi list dan meletakkan item baru pada tempat
yang benar. Implementasi ini menggunakan kemampuan dari list, dimana akan mudah
menambah simpul baru pada pertengahan list. Begitu juga menghapus item dengan
prioritas tertinggi, dimana ia akan selalu terdapat pada awal list.
Analisis kinerja dari operasi ini cukup mudah. Menambahkan item pada akhir array atau
menghapus simpul dari awal list menggunakan waktu yang sama tanpa memperhatikan
jumlah item yang ada pada list. Jadi kedua operasi ini menggunakan waktu yang
konstan.
Setiap kali kita menjelajahi array atau list, menggunakan operasi dengan waktu yang
395
konstan tiap elemen, waktu sebanding dengan jumlah item. Itulah mengapa, menghapus
sesuatu dari array dan menambahkan sesuatu pada list keduanya menggunakan waktu
yang konstan.
Jadi berapa lama waktu yang digunakan untuk menambah dan kemudian menghapus n
item dari PriorityQueue? Untuk implementasi array, n menggunakan waktu sebanding
dengan n tetapi penghapusan menggunakan waktu lebih lama. Penghapusan yang
pertama harus menjelajahi semua n elemen; kedua harus menjelajahi n1 dan demikian
seterusnya, sampai penghapusan terakhir, yang hanya harus mencari pada 1 elemen.
Sehingga total waktu nya adalah 1+2+...+n, yang menghasilkan n2/2+n/2. Jadi total
untuk penambahan dan penghapusan adalah jumlah fungsi linear dan fungsi kuadrat,
yang kita bisa tahu akan menghasilkan fungsi kuadrat.
Analisis pada implementasi list sama. Penambahan pertama tidak membutuhkan
penjelajahan, tetapi setelah itu kita harus menjelajahi list setiap kali kita menambah
sebuah item baru. Pada umumnya kita tidak mengetahui berapa banyak elemen pada list
yang harus kita jelajahi, karena ia bergantung pada data dan dengan cara bagaiman
mereka ditambahkan, tetapi kita bisa mengasumsikan rata rata kita harus menjelajahi
setengah dari list. Sayangnya bahkan menjelajahi setengah dari list adalah operasi yang
linear.
Jadi, sekali lagi, untuk menambah dan menghapus n item membutuhkan waktu n2.
Sehingga berdasarkan analisis kita tidak bisa mengatakan implementasi mana yang
lebih baik; implementasi array dan list keduanya merupakan operasi kuadratik.
Jika kita mengimplementasikan priority queue menggunakan heap, kita bisa melakukan
396
penambahan dan penghapusan dengan waktu logn. Sehingga total waktu untuk n item
adalah nlogn, yang lebih baik dari n2. Itulah mengapa pada awal bab ini, saya
menyebutkan bahwa heap adalah implementasi efisien dari priority queue.
18.6 Definisi Heap
Heap adalah salah satu jenis pohon. Ia memiliki dua sifat yang tidak selalu dimiliki oleh
pohon lain:
completeness : pohon komplet, yang berarti simpul ditambahkan dari atas ke bawah,
kiri ke kanan, tanpa meninggalkan satu ruang pun.
heapness: Item pada pohon dengan prioritas tertinggi ada pada puncak pohon, begitu
juga pada setiap subpohon.
Kedua sifat ini membutuhkan sedikit penjelasan. Gambar berikut memperlihatkan
jumlah pohon yang komplet dan tidak komplet.
Pohon kosong selalu dianggap komplit. Kita bisa mendefinisikan completeness lebih
dalam dengan membandingkan tinggi subpohon. Dari pelajaran sebelumnya kita ketahui
bahwa tinggi pohon adalah banyak tingkatan yang ada.
397
Dimulai dari akar, jika pohon komplit, lalu tinggi subpohon kiri dan tinggi subpohon
kanan harus sama, atau subpohon kiri bisa lebih tinggi satu. Pada kasus lain, pohon
tidak disebut komplit. Dengan demikian jika pohon komplit, maka hubungan antara
subpohon harus selalu benar untuk setiap simpul pada pohon.
Sifat heap adalah rekursif. Agar pohon disebut heap, nilai terbesar pada pohon harus
ada pada akar, dan hal yang sama harus benar pada setiap subpohon.
Latihan 18.1 Tulis metode yang mengambil pohon sebagai parameter dan mengecek
apakah komplit atau tidak.
Petunjuk: Anda dapat menggunakan metode height dari latihan 17.4
Latihan 18.2 Tulis metode yang mengambil pohon sebagai parameter dan mengecek
apakah ia memiliki sifat heap.
18.7 Penghapusan pada heap
Mungkin kelihatan ganjil kita akan menghapus sesuatu dari heap sebelum menambah
apapun, tetapi saya pikir penghapusan lebih mudah untuk dijelaskan. Sekilas, kita
mungkin berpikir mengapus item dari heap adalah operasi yang konstan, karena item
dengan prioritas tertinggi selalu berada pada akar. Masalahnya adalah setiap kita
menghapus simpul akar, kita ditinggalkan sesuatu yang bukan lagi heap. Sebelum kita
dapat mengembalikan hasilnya, kita harus mempebaiki sifat heap. Kita menyebut
operasi ini reheapify.
Situasi ini digambarkan pada gambar berikut:
398
Simpul akar memiliki prioritas r dan dua subpohon, A dan B. nilai pada akar subpohon
A adalah a dan nilai pada subpohon B adalah b.
Kita mengasumsikan sebelum kita menghapus r dari pohon, pohon adalah heap. Ini
mengakibatkan r adalah nilai terbesar dalam heap dan bahwa a dan b adalah nilai
terbesar dalam subpohon mereka. Ketika kita menghapus r, kita harus membuat pohon
yang dihasilkan kembali menjadi heap. Dengan kata lain kita butuh meyakinkan ia
memiliki sifat completeness dan heapness.
Cara terbaik untuk menjamin completeness adalah dengan menghapus elemen paling
bawah, yang paling kanan, yang kita sebut c dan meletakkan nilai nya pada akar. Pada
implementasi pohon, kita harus menjelajahi pohon untuk mencari simpul ini, tetapi pada
implementasi array, kita dapat mencarinya pada waktu konstan karena ia akan selalu
elemen non null terakhir pada array.
Tentu, nilai terakhir bukan yang tertinggi, jadi meletakkanyya pada akar akan merusak
sifat heapness. Untungnya mudah untuk diperbaiki. Kita tahu bahwa nilai terbesar
399
dalam heap setelah r dihapus adalah a atau b. Itulah mengapa kita bisa memilih yang
mana yang lebih besar dan menggantinya dengan nilai pada akar.
Mari kita anggap b adalah yang terbesar. Karena kita tahu ia adalah nilai tertinggi pada
kiri heap, kita bisa meletakkannya pada akar dan meletakkan c pada puncak subpohon
B. Sekarang situasinya akan seperti di bawah ini:
lagi, c adalah nilai yang kita kopi dari elemen terakhir dalam array dan b adalah nilai
tertinggi dalam heap. Karena kita tahu kita tidak pernah mengganti subpohon A, kita
mengetahui ia masih merupakan heap. Satu satunya masalah adalah kita tidak dapat
mengetahui apakah subpohon B adalah heap, karena kita hanya menempel sebuah nilai
(kemungkinan rendah) pada akarnya.
Akan lebih baik jika kita memiliki metode yang bisa reheapify subpohon B?
Tunggu.... kita punya
18.8 Penambahan Heap
Menambahkan sebuah item baru pada heap adalah operasi yang mirip dengan
penghapusan heap, perbedaanya bahwa kita tidak menghamburkan elemen dari atas
400
tetapi kita menghamburkan elemen ke atas dari bawah heap.
Untuk menjamin sifat completeness, kita menambah elemen baru pada elemen paling
bawah pada pohon, yang merupakan ruang kosong berikutnya pada array. Lalu untuk
mengembalikan sifat heap, kita membandingkan nilai baru dengan tetangganya. Situasi
nya akan terlihat seperti ini:
Nilai baru adalah c. Kita bisa mengembalikan sifat heap pada subpohon ini dengan
membandingkan c dengan a. Jika c lebih kecil, maka sifat heap telah dipenuhi. Jika c
lebih besar maka kita mengganti c dan a. Proses swap akan memenuhi sifat heap karena
kita tahu c harus lebih besar dari b, karena c>a dan a>b.
Sekarang subtree telah menjadi heap kembali, selanjutnya proses dilanjutkan ke simpul
atas (a atau c) dengan membandingkan dengan orang tua nya demikian seterusnya
sampai mencapai akar.
18.9 Kinerja Heap
Untuk penambahan dan penghapusan, kita menggunakan operasi dengan waktu
yang konstan untuk penambahan dan penghapusan yang sebenarnya, tetapi kemudian
kita harus melakukan reheapify pada pohon. Pada satu kasus kita mulai dari akar dan
401
dilanjutkan ke bawah, membandingkan elemen dan secara rekursif melakukan
reheapify pada subpohon. Pada kasus lain kita mulai pada daun dan bekerja ke atas,
kembali kita membandingkan elemen pada tiap level pada pohon.
Seperti biasa, ada beberapa operasi yang akan kita hitung, seperti pembandingan dan
pertukaran. Kedua pilihan akan bekerja; masalah sebenarnya adalah banyak level pada
pohon yang kita periksa dan berapa banyak usaha yang kita lakukan setiap level. Pada
kedua kasus kita tetap bekerja pada sebuah level pohon sampai kita mengembalikan
sifat heap, yang berarti kita mungkin hanya berkunjung sekali, atau pada kasus terburuk
kita harus mengunjungi semua elemen, sekarang mari kita lihat pada kasus terburuk.
Pada setiap level, kita melakukan hanya operasi dengan waktu yang konstan seperti
pembandingan dan penggantian. Jadi total kerja sebanding dengan banyak tingkatan
pada pohon, dengan kata lain tinggi. Jadi kita bisa berkata operasi operasi ini
berbanding lurus denga tinggi pohon, tetapi “ukuran masalah” yang menarik bukan
tinggi nya, tetapi banyak elemen pada heap.
Dalam fungsi n, tinggi pohon adalah log2n. Ini tidak selalu benar untuk semua pohon,
tetapi hanya benar untuk pohon yang lengkap. Untuk mengetahui kenapa, pikirkan
banyak simpul pada setiap level dari pohon. Level pertama berisi 1, level 2 berisi 2,
level ketiga berisi 4, demikian seterus nya. Level ke i akan berisi 2i simpul dan jumlah
elemen di semua level sampai i adalah 2i1. Dengan kata lain 2h = n, yang berarti
h=log2n.
Sehingga, keduanya penambahan dan penghapusan menggunakan waktu yang
logaritmik. Untuk menambah dan menghapus n elemen maka akan menggunakan waktu
402
nlog2n.
18.10 Heapsort
Hasil dari bagian yang lalu bisa menjadi inspirasi untuk algoritma pengurutan. Terdapat
n elemen, kita menambahkan mereka ke dalam Heap kemudian menghapusnya. Karena
sifat heap, mereka akan keluar dalam keadaan terurut. Kita telah memperlihatkan
algoritma ini, yang disebut heapsort, ia menggunakan waktu sebanding dengan nlog2n,
yang lebih baik dari selection sort dan sama dengan mergesort.
Semakin besar nilai, kita berharap heapsort akan lebih cepat dari selection sort, tetapi
analisis kinerja tidak menunjukkan cara untuk mengetahui apakah ia akan lebih cepat
dari mergesort. Kita akan berkata kedua algoritma memiliki order of growth yang sama
karena waktu kinerja mereka bertambah dengan bentuk fungsi yang sama. Cara lain
untuk mengatakan hal yang sama adalah mereka memiliki kompleksitas yang sama.
Kompleksitas kadang kadang ditulis dalam “notasi bigO”. misalnya O(n^2), diucapkan
dengan “oh of en squared” adalah kumpulan fungsi yang berkembang tidak lebih cepat
dari n^2 untuk nilai n yang besar. Untuk mengatakan sebuah algoritma adalah O(n^2)
sama dengan algoritma tersebut kuadratik. Untuk kasus kompleksitas lain yang telah
kita lihat, dengan urutan kinerja menurun, adalah
O(1) constant time
O(log n) logarithmic
O(n) linear
O(n log n) “en log en”
O(n2 ) quadratic
O(2n ) exponential
403
Sejauh ini tidak ada algoritma yang telah kita lihat memiliki kompleksitas eksponensial.
Untuk nilai n yang besar, algoritma ini cepat menjadi tidak praktis. Namun demikian
frase “pertumbuhan eksponensial” muncul lebih sering bahkan pada bahasa yang tidak
teknis. Ia sering digunakan tidak tepat jadi saya ingin memasukkan arti teknis nya.
Orang sering menggunakan eksponensial untuk menjelaskan kurva yang bertambah dan
berakselerasi (kurva yang memiliki kemiringan positif dan lekukan). Tentu saja ada
banyak kurva lian yang sesuai dengan penjekasan ini, termasuk fungsi kuadrat (dan
polinomilal tingkat yang lebih tinggi) dan bahkan fungsi seperti nlogn. Sebagian besar
kurva ini tidak memiliki (bahkan merusak) sifat eksplosif dari eksponensial.
18.11 Daftar Kata Kata
selection sort: Algoritma pengurutan sederhana pada bab 12.3.
mergesort: Algoritma pengurutan yang lebih baik dari bab 12.6.
heapsort: Algoritma pengurutan yang lain.
kompleksitas: kumpulan algoritma yang kinerja nya (biasanya waktu eksekusi)
memiliki order of growth yang sama.
order of growth: Kumpulan fungsi dengan pangkat awal yang sama, dan karena
itu sama dalam sifat kualitatif untuk nilai n yang besar.
overhead: waktu atau sumber daya tambahan yang dikonsumsi oleh sebuah
program dalam melakukan suatu operasi selain dari operasi abstrak yang
diperhitungkan pada analisis kinerja.
404
18.12 Latihan
latihan 18.3
a) Gambar Heap yang direpresentasikan pada array di bawah ini.
b) Perlihatkan susunan array ketika 68 ditambahkan ke dalam heap.
Latihan 18.4
Asumsikan terdapat n elemen dalam heap. Untuk mencari nilai tengah dari elemen, kita
dapat menghapus n/21 elemen dan mengembalikan nilai elemen ke n/2. Kemudian kita
harus meletakkan n/2 elemen kembali ke dalam heap. Berapa order of growth dari
algoritma ini?
Latihan 18.5
Berapa kali loop di bawah ini dieksekusi? Kemukakan jawaban mu sebagai fungsi dari
n:
while (n > 1) {
n = n / 2;
}
Latihan 18.6
Berapa banyak pemanggilan rekursif yang dilakukan zippo? Kemukakan jawabanmu
sebagai fungsi dari x atau n atau keduanya.
public static double zippo (double x, int n) {
if (n == 0) return 1.0;
return x * zippo (x, n1);
}
405
Latihan 18.7
Tulis implementasi dari Heap berdasarkan implementasi pohon dengan array. Waktu
eksekusi add dan remove harus sebanding dengan log n, diman n adalah banyak elemen
dalam heap.
406
BAB 19
Maps
19.1 Array, Vector, Map
Array adalah struktur data yang secara umum sangat berguna, tetapi array
memiliki dua keterbatasan utama yaitu:
● Ukuran array tidak bergantung terhadap jumlah elemen yang ada di dalamnya.
Jika array lebih besar dibanding jumlah elemen di dalamnya maka akan
menghabiskan tempat. Sedangkan jika terlalu kecil (Ukuran array lebih kecil)
maka kemungkinan dapat menyebabkan kesalahan. Atau kita harus menulis
kode untuk mengubah ukurannya.
● Meskipun array dapat berisi tipe apapun, indeks array harus bertipe integer.
Kita tidak dapat misalnya menggunakan String untuk digunakan sebagai indeks.
Pada bab 17.9 kita telah melihat bagaimana kelas Vector yang merupakan kelas yang
disediakan oleh Java bisa menyelesaikan masalah pertama. Ketika sebuah kode
menambah elemen, maka Vector akan membesar secara otomatis. Dan juga
memungkinkan untuk memperkecil Vector sehingga kapasitasnya sama dengan jumlah
elemen yang ada di dalamnya.
Tetapi Vector tidak dapat menolong pada masalah kedua. Indeks Vector masih harus
bertipe integer. Masalah ini bisa diselesaikan dengan struktur data Map ADT. Map
serupa dengan Vector, perbedaannya adalah Map dapat menggunakan tipe objek apapun
sebagai indeks. Indeks pada Map disebut sebagai kunci.
407
Sebagaimana anda menggunakan indeks untuk mengakses nilai pada Vector, anda juga
menggunakan kunci untuk mengakses nilai pada Map. Setiap kunci diasosiasikan
dengan sebuah nilai, itulah mengapa Map kadang kadang disebut array assosiatif.
Assosiasi antara sebuah kunci dengan sebuah nilai disebut entri.
Contoh umum dari struktur data Map adalah kamus, yang memetakan kata kata (kunci)
pada definisinya (nilai). Karena hal ini Map juga kadang disebut kamus.
19.2 Map ADT
Seperti ADT lain yang telah kita lihat, Maps didefinisikan dengan himpunan operasi
yaitu:
contructor: Membuat sebuah Map kosong
put: Menciptakan sebuah entri yang mengasosiasikan sebuah nilai dengan sebuah kunci
get: Untuk sebuah kunci yang diberikan, cari nilai yang bersesuaian
containsKey: Mengembalikan true jika terdapat entri pada Map dengan kunci yang
diberikan
keySet: Mengembalikan struktur data Set yang berisi semua kunci pada Map
19.3 HashMap builtin
Java menyediakan implementasi dari Map ADT pada paket java.util.HashMap.
Selanjutnya pada bab ini kita akan lihat mengapa disebut HashMap
408
Untuk mendemonstrasikan penggunaan HashMap kita akan menulis sebuah program
sederhana yang menjelajahi sebuah String dan menghitung jumlah kemunculan suatu
kata.
Kita akan membuat kelas baru yaitu WordCount yang akan membangun Map dan
mencetak isinya. Setiap WordCount berisi HashMap sebagai variabel instance.
public class WordCount{
HashMap Map;
public WordCount(){
Map = new HashMap();
}
}
Metode publik pada WordCount adalah processLine, yang mengambil sebuah String
dan menambah kata katanya pada Map, dan print, yang bertugas mencetak hasil
pemrosesan.
ProcessLine memecah String menjadi kata kata menggunakan StringTokenizer (untuk
mennggunakan StringTokenizer anda harus mengimpornya terlebih dahulu, dengan
menambahkan baris import java.util.* sebelum deklarasi class) dan melewatkan setiap
kata pada processWord.
public void processLine(String s){
409
StringTokenizer st = new StringTokenizer(s,”,.”);
while(st.hasMoreTokens()){
String word = st.nextToken();
processWord(word.toLowerCase());
}
}
public void processWord (String word) {
if (Map.containskunci (word)) {
integer i = (integer) Map.get (word);
integer j = new integer (i.intValue() + 1);
Map.put (word, j);
} else {
Map.put (word, new integer (1));
}
}
Jika kata telah ada dalam Map, maka kita mengambil variabel pencacahnya,
menambahkan satu, dan memasukkannya (put) kembali pada Map. Jika tidak, kita
hanya akan menaruh entri baru pada Map dengan pencacah di beri nilai satu.
Untuk mencetak semua entri pada Map, kita harus bisa menjelajahi seluruh kunci dalam
Map. Untungnya, HashMap menyediakan metode keySet, yang mengembalikan sebuah
objek Set, dan Set menyediakan sebuah metode iterator yang mengembalikan objek
Iterator.
410
Berikut ini cara untuk menggunakan keySet untuk mencetak isi dari HashMap:
//catatan agar kode bisa fungsional anda harus menambahkan baris import java.util.*
sebelum //deklarasi kelas
public void print () {
Set set = Map.keySet();
Iterator it = set.iterator();
while (it.hasNext ()) {
String kunci = (String) it.next ();
integer value = (integer) Map.get (kunci);
System.out.println ("{ " + kunci + ", " + value + " }");
}
}
Setiap elemen pada iterator adalah sebuah Objek, tetapi karena kita tahu bahwa mereka
adalah kunci, kita bisa mengcastingnya menjadi String. Ketika kita mengambil sebuah
nilai pada Map, mereka juga bertipe Objek, tetapi kita tahu bahwa mereka adalah
pencacah, jadi kita bisa mengcastingnya menjadi bertipe integer.
Berikut ini cara untuk menghitung kata kata pada sebuah kalimat:
WordCount wc = new WordCount ();
wc.processLine ("you spin me right round baby " +
"right round like a record baby " +
"right round round round");
wc.print ();
411
Keluarannya adalah
{ you, 1 }
{ round, 5 }
{ right, 3 }
{ me, 1 }
{ like, 1 }
{ baby, 2 }
{ spin, 1 }
{ record, 1 }
{ a, 1 }
Elemen elemen pada Iterator tidak diurutkan berdasarkan aturan tertentu. Satu satunya
jaminan adalah semua kunci pada Map akan muncul.
Berikut ini kode kelas WordCount secara keseluruhan:
import java.util.*;
public class WordCount {
HashMap Map;
public WordCount () {
Map = new HashMap ();
}
public void processLine (String s) {
StringTokenizer st = new StringTokenizer (s, " ,.");
412
while (st.hasMoreTokens()) {
String word = st.nextToken();
processWord (word.toLowerCase ());
}
}
public void processWord (String word) {
if (Map.containsKey (word)) {
integer i = (integer) Map.get (word);
integer j = new integer (i.intValue() + 1);
Map.put (word, j);
} else {
Map.put (word, new integer (1));
}
}
public void print () {
Set set = Map.keySet();
Iterator it = set.iterator();
while (it.hasNext ()) {
String kunci = (String) it.next ();
integer value = (integer) Map.get (kunci);
System.out.println ("{ " + kunci + ", " + value + " }");
}
}
public static void main(String [] args){
WordCount wc = new WordCount ();
413
wc.processLine ("you spin me right round baby " +
"right round like a record baby " +
"right round round round");
wc.print ();
}
}
19.4 Implementasi Dengan Vector
Cara yang mudah untuk mengimplementasikan Map ADT adalah menggunakan entri
dengan struktur data Vector, dimana setiap entri adalah sebuah objek yang berisi sebuah
kunci dan sebuah nilai. Definisi kelas dari Entry seperti di bawah ini:
class Entry {
Object kunci, value;
public Entry (Object kunci, Object value) {
this.kunci = kunci;
this.value = value;
}
public String toString () {
return "{ " + kunci + ", " + value + " }";
}
}
Lalu implementasi dari Map seperti di bawah ini:
414
public class MyMap {
Vector entries;
public Map () {
entries = new Vector ();
}
}
Untuk menaruh entri baru pada Map, kita hanya menambah sebuah objek Entry pada
Vector:
public void put (Object kunci, Object value) {
entries.add (new Entry (kunci, value));
}
Kemudian untuk melihat sebuah kunci pada Map kita harus menjelajahi Vector dan
mencari sebuah Entry dengan kunci yang sesuai.
public Object get (Object kunci) {
Iterator it = entries.iterator ();
while (it.hasNext ()) {
Entry entry = (Entry) it.next ();
if (kunci.equals (entry.kunci)) {
return entry.value;
}
}
return null;
415
}
Cara untuk menjelajahi Vector telah kita lihat pada bagian 17.10. Ketika kita
membandingakan kunci, kita menggunakan metode equal (deep quality) daripada
operator == (shallow quality). Hal ini membuat diperbolehkannya tipe data kunci untuk
menspesifikasikan definisi persamaan yang digunakan, Pada contoh di atas, tipe data
kunci adalah String, jadi metode get akan menggunakan metode equals yang ada pada
kelas String.
Karena equals adalah metode objek. Implementasi dari get tidak bekerja jika kunci
berisi null, Untuk mengatasi kunci yang berisi null, kita harus menambahkan kasus
khusus untuk mengambil dan menulis sebuah metode pada kelas yang membandingkan
kunci dan mengurusi parameter null dengan aman. Tapi hal ini tidak akan kita bahas
secara detail.
Sekarang kita telah memiliki metode get, kita bisa menulis versi dari put yang lebih
lengkap. Jika telah terdapat entri pada Map dengan kunci yang diberikan, put akan
memperbaharuinya( memberinya nilai yang baru), dan mengembalikan nilai yang lama(
atau null jika belum ada). ini adalah implementasi metode yang menyediakan fitur ini:
public Object put (Object kunci, Object value) {
Object result = get (kunci);
if (result == null) {
Entry entry = new Entry (kunci, value);
entries.add (entry);
} else {
update (kunci, value);
416
}
return result;
}
private void update (Object kunci, Object value) {
Iterator it = entries.iterator ();
while (it.hasNext ()) {
Entry entry = (Entry) it.next ();
if (kunci.equals (entry.kunci)) {
entry.value = value;
break;
}
}
Metode update bukan merupakan bagian dari Map ADT, jadi ia dideklarasikan private.
Ia menjelajahi Vector sampai mendapatkan objek Entry yang tepat lalu memparbaharui
atribut nilai nya. Perlu diperhatikan bahwa kita tidak memodifikasi Vector, kita hanya
salah satu objek yang ada di dalamnya.
Metode yang belum kita implementasikan adalah containsKey dan keySet. Metode
containsKey hampir sama dengan metode get kecuali ia mengembalikan nilai true atau
false sebagai ganti objek atau null.
Latihan 19.1 Implementasikan metode keySet dengan membuat dan mengembalikan
objek TreeSet. Gunakan implementasi TreeSet anda pada bab 17.7 atau implementasi
yang telah disediakan pada paket java.util.TreeSet
417
Implementasi lengkap Map menggunakan Vector:
import java.util.*;
public class MyMap {
Vector entries;
public MyMap () {
entries = new Vector ();
}
public Object get (Object kunci) {
Iterator it = entries.iterator ();
while (it.hasNext ()) {
Entry entry = (Entry) it.next ();
if (kunci.equals (entry.kunci)) {
return entry.value;
}
}
return null;
}
public Object put (Object kunci, Object value) {
Object result = get (kunci);
if (result == null) {
Entry entry = new Entry (kunci, value);
entries.add (entry);
} else {
418
update (kunci, value);
}
return result;
}
private void update (Object kunci, Object value) {
Iterator it = entries.iterator ();
while (it.hasNext ()) {
Entry entry = (Entry) it.next ();
if (kunci.equals (entry.kunci)) {
entry.value = value;
break;
}
}
}
public Set keySet(){
Iterator it = entries.iterator ();
Set s = new TreeSet();
while (it.hasNext ()) {
Entry entry = (Entry) it.next ();
s.add(entry.kunci);
}
return s;
}
public boolean containsKey(Object kunci){
if(get(kunci)==null)
419
return false;
else
return true;
}
}
class Entry {
Object kunci, value;
public Entry (Object kunci, Object value) {
this.kunci = kunci;
this.value = value;
}
public String toString () {
return "{ " + kunci + ", " + value + " }";
}
}
Contoh kelas WordCount menggunakan implementasi Map dengan Vector
import java.util.*;
public class WordCountVector {
MyMap Map;
public WordCountVector () {
Map = new MyMap ();
}
420
public void processLine (String s) {
StringTokenizer st = new StringTokenizer (s, " ,.");
while (st.hasMoreTokens()) {
String word = st.nextToken();
processWord (word.toLowerCase ());
}
}
public void processWord (String word) {
if (Map.containsKey (word)) {
integer i = (integer) Map.get (word);
integer j = new integer (i.intValue() + 1);
Map.put (word, j);
} else {
Map.put (word, new integer (1));
}
}
public void print () {
Set set = Map.keySet();
Iterator it = set.iterator();
while (it.hasNext ()) {
String kunci = (String) it.next ();
integer value = (integer) Map.get (kunci);
System.out.println ("{ " + kunci + ", " + value + " }");
}
}
421
public static void main(String [] args){
WordCountVector wc = new WordCountVector ();
wc.processLine ("you spin me right round baby " +
"right round like a record baby " +
"right round round round");
wc.print ();
}
}
19.5 Kelas Abstrak List
Paket java.util mendefinisikan sebuah kelas abstrak yang menspesifikasikan himpunan
operasi yang sebuah kelas harus mengimplementasikan agar dapat disebut List, Ini tidak
berarti bahwa setiap kelas yang mengimplementasikan List harus merupkan struktur
data linked list.
Tidak aneh bahwa kelas bawaan LinkedList adalah implementasi dari List. Dan yang
mengejutkan kelas Vector juga merupakan implementasi dari kelas List.
Metode metode pada definisi List merliputi add, get, dan iterator. Pada kenyataannya,
semua metode dari kelas Vector yang digunakan untuk mengimplementasikan Map
didefinisikan pada kelas abstrak List. Ini berarti sebagai ganti kelas Vector, kita dapat
menggunakan implementasi kelas abstrak List apa pun. Pada implementasi Map yang
telah kita buat kita dapat mengganti Vector dengan LinkedList dan program masih bisa
422
bekerja dengan baik.
Tipe umum seperti ini dapat berguna untuk meningkatkan performa sebuah program.
Anda dapat membuat program dalam bentuk kelas abstrak seperti List lalu mengetes
program tersebut dengan beberapa implementasi yang berbeda untuk melihat
implementas mana yang menghasilkan performa terbaik.
19.6 Implementasi dengan HashMap
Alasan implementasi bawaan Map ADT disebut HashMap karena ia menggunakan
sebuah implementasi Map yang efisien menggunakan tabel hash.
Untuk lebih memahami implementasi HashMap dan mengapa ia dianggap efisien. Kita
akan memulai dengan menganalisa performa dari implementasi List.
Dengan melihat pada implementasi put, kita melihat bahwa terdapat dua kasus. Jika
kunci belum terdapat dalam Map, kita lalu hanya akan membuat entri baru dan
menambahkannya pada Map, kedua operasi ini merupakan operasi dengan waktu yang
konstan.
Pada kasus lain, kita harus menjelajahi List untuk mencari suatu entri. Yang merupakan
operasi dengan waktu linear. Untuk alasan yang sama get dan containsKey juga
memiliki waktu yang linear.
Meskipun operasi linear sering sudah dianggap mencukupi , tetapi kita dapat
melakukannya lebih baik. Ada sebuah cara untuk mengimplementasikan Map ADT
sehingga put dan get dilakukan pada waktu yang konstan.
423
Kuncinya adalah pengetahuan tentang waktu penjelajahan List yang linear dengan
panjang List. Jika kita dapat meletakkan batas atas pada panjang List maka kita dapat
meletakkan batas atas pada waktu penjelajahan, maka apapun dengan batas atas yang
pasti adalah waktu konstan yang bisa diperhitungkan.
Tetapi bagaimana kita dapat membatasi panjang List tanpa membatasi banyak item pada
Map? Solusinya adalah dengan menambah jumlah List. Sebagai ganti satu List yang
panjang kita akan menggunakan banyak List pendek.
Sepanjang kita tahu List mana yang akan dicari, kita dapat meletakkan batas pada
jumlah pencarian.
19.7 Fungsi Hash
Dan itulah dimana fungsi hash bisa dipergunakan. Kita membutuhkan cara untuk
melihat sebuah kunci dan tahu tanpa mencari, pada List mana ia berada. Kita akan
mengasumsikan bahwa List ada di dalam array (atau Vector) jadi kita bisa
mereferensinya dengan dengan index.
Solusi yang bisa dilakukan adalah dengan menggunakan teknik pemetaan. Untuk setiap
kemungkinan kunci dipastikan terdapat sebuah indeks tunggal, tetapi ada kemungkinan
banyak kunci yang dipetakan pada index yang sama.
Sebagai contoh, Sebuah array dengan 8 List dan sebuah Map yang dibuat dengan kunci
bertipe integer dan nilai yang bertipe String. Untuk mendapatkan indeks digunakan
metode intValue pada kelas integer sebagai indeks, yang merupakan tipe yang benar,
424
tetapi ada banyak bilangan integer (bulat) yang tidak berada antara 0 dan 7, padahal
indeks yang legal harus berada antara 0 dan 7.
Operator modulus menyediakan cara yang sederhana dan efisien untuk memetakan
semua bilangan bulat (integer) antara 0 dan 7. Perhitungan
kunci.intValue % 8
dijamin menghasilkan nilai dalam rentang 7 sampai 7 (termasuk keduanya). Jika anda
mengambil nilai absolut (menggunakan Math.abs) anda akan mendapatkan indeks yang
sah.
Untuk tipe yang lain, dapat diterapkan cara yang sama. Misalnya untuk mengkonversi
variabel bertipe Character menjadi bilangan bulat, kita dapat menggunakan metode
bawaan Character.getNumericValue dan untuk tipe Double dapat digunakan metode
intValue.
Untuk String kita bisa mendapatkan nilai bilangan tiap karakter dan menambahkannya,
atau kita mungkin bisa menggunakan operasi shifted sum. Untuk menghitung nilai
shifted sum, anda secara bergiliran menambahkan nilai baru kedalam akumulator dan
menggeser akumulator ke kiri. “geser ke kiri” berarti “menambahkan dengan bilangan
konstan.”
Untuk melihat bagaimana ini bekerja, ambil daftar bilangan 1, 2, 3, 4, 5, 6 dan hitung
shifted sum dengan cara sebagai berikut, pertama inisialisasi akumulator dengan 0, lalu
1. Kalikan akumulator dengan 10.
2. Tambahkan elemen berikut dari daftar pada akumulator.
3. Ulangi sampai semua bilangan pada daftar telah dikalikan semua.
425
Sebagai latihan, tulis sebuah metode yang menghitung shifted sum nilai bilangan semua
karakter pada String menggunakan pengali 16.
Untuk setiap tipe, kita bisa membuat fungsi yang mengambil nilai dengan tipe tersebut
dan menghasilkan nilai bulat yang bersesuaian. Fungsi ini dinamakan fungsi hash,
karena mereka sering terlibat membuat sebuah hash pada komponen sebuah objek. Nilai
hash dari sebuah objek disebut hash code.
Ada satu cara lain yang dapat dilakukan untuk mengasilkan hash code untuk objek Java.
Setiap objek java menyediakan metode hashCode yang mengembalikan integer yang
bersesuaian dengan objek tersebut. Untuk tipe bawaan java, metode hashCode
diimplementasikan sehingga jika dua objek yang memiliki data yang sama, mereka akan
memiliki hash code yang sama. Dokumentasi dari metode ini menjelaskan apa itu
fungsi hash. Anda lebih baik mengeceknya untuk mengetahui lebih lanjut.
Untuk tipe yang didefinisikan pengguna, hal ini tergantung pada pengimplementasi
untuk menyediakan fungsi hash yang sesuai. Fungsi hash default, yang disediakan pada
kelas Object, Sering menggunakan lokasi objek untuk mengasilkan hash code, jadi
notasi nya untuk suatu kesamaan adalah “shallow equality”. Sering ketika kita mencari
sebuah kunci pada Map kesamaan yang menggunakan shallow equality bukan
merupakan yang kita inginkan.
Terlepas dari bagaimana cara menghasilkan hash code, langkah terakhir adalah
menggunakan modulus dan fungsi nilai absolut untuk memetakan hash code pada
426
rentang indeks yang legal.
19.8 Mengubah Ukuran Hash Map.
Sebuah hash table berisi sebuah array (atau Vector) yang elemennya merupakan sebuah
List. Dimana setiap List berisi jumlah entri yang kecil. Untuk menambah entri baru
pada Map, kita menghitung hash code pada kunci yang baru dan menambah entri
tersebut pada List yang sesuai.
Untuk mencari sebuah kunci, kita menghitung hash lagi dan mencarinya pada List yang
sesuai. Jika panjang List terbatas maka waktu pencarian juga terbatas.
Jadi bagaimana cara membuat List tetap pendek? Salah satu tujuan adalah bagaimana
sebisa mungkin semua List tersebut tetap seimbang. Jadi tidak akan ada List yang sangat
panjang dan pada saat yang sama List lain kosong. Hal ini bukan hal yang mudah untuk
dilakukan secara sempurna, ia tergantung pada seberapa baik kita memilih fungsi hash.
Bahkan dengan keseimbangan yang sempurna, rata rata panjang List berkembang secara
linear dengan jumlah entri. Dan kita harus meletakkan titik henti pada saat itu.
Solusinya adalah dengan mengikuti rata rata jumlah entri untuk setiap List, yang disebut
load factor. Jika load factor terlalu tinggi kita harus mengubah ukuran hash table.
Untuk mengubah ukuran, kita membuat hash table baru, biasanya dua kali lebih besar
dari yang asli, ambil semua entri yang ada pada hash table yang lama. Mengenainya
dengan fungsi hash. Dan meletakkannya kembali pada hash table yang baru. Biasanya
kita bisa menggunaka fungsi hash yang sama. Kita hanya menggunakan nilai yang
427
berbeda untuk operator modulus.
19.9 Kinerja dari pengubahan ukuran
Berapa lama waktu yang dibutuhkan untuk mengubah ukuran hash table? Yang jelas ia
sebanding dengan banyak entri. Itu berarti sebagian besar waktu put membutuhkan
waktu yang konstan, tetapi sesekali ketika kita melakukan pengubahan ukuran ia akan
membutuhkan waktu yang linear.
Pada awalnya hal ini memang buruk. Bukankah ini menyalahi klaim saya bahwa kita
melakukan put dengan waktu yang konstan? Terus terang ya. Tetapi dengan sedikit
pengaitan, kita bisa memperbaikinya.
Beberapa operasi put membutuhkan waktu yang lebih lama dibandingkan dengan yang
lain, mari kita gambarkan waktu rata rata dalam operasi put. Rata rata dilambangkan
dengan c yaitu waktu konstan untuk put yang sederhana. Ditambah dengan p,
prosentase waktu sebelum dilakukan pengubahan ukuran, di kali dengan kn, maka biaya
untuk mengubah ukuran adalah.
t(n) = c + p ∙ kn
(19.1)
Kita tidak tahu melambangkan apa c dan k, tetapi kita bisa menggambarkan apakah p
itu. Bayangkan kita baru saja mengubah ukuran hash Map dengan memperbesar dua
kali ukurannya. Jika terdapat n entri lalu kita dapat menambahkan tambahan n entri
sebelum kita harus mengubah ukrannya lagi. Jadi persentasi waktu sebelum harus
428
dilakukan pengubahan ukuran kembali adalah 1/n.
Dengan dimasukkan pada persamaan maka kita dapatkan
t(n) = c + 1/n ∙ kn = c + k
(19.2)
Dengan kata lain, t(n) adalah waktu yang konstan.
19.10 Daftar Kata
map : Sebuah ADT(Abstract Data Type) yang mendefinisikan operasi pada kumpulan
entri.
entri : Elemen pada map yang berisi sebuah kunci dan sebuah nilai.
kunci : Indeks , dapat berupa objek apapun, digunakan untuk mencari nilai dalam map.
value : Elemen, dapat berupa objek apapun, disiman dalam map.
dictionary : nama lain dari map.
array assosiatif : nama lain dari dictionary.
hash map : Sebuah implementasi efisien dari map.
fungsi hash : fungsi yang memetakan nilai pada tipe tertentu kepada bilangan bulat.
hash code: bilangan bulat yang berhubungan dengan sebuah nilai.
shifted sum : fungsi hash sederhana yang sering digunakan untuk objek majemuk
seperti String
load factor : banyak entri dalam hashmap dibagi dengan banyak List dalam hashmap;
sebagai contoh rata rata banyak entri tiap List.
429
19.11 Latihan
latihan 19.2
a. Hitung shift sum dari bilangan bilangan 1, 2, 3, 4, 5, 6 menggunakan pengali 10
b. Hitung shift sum dari bilangan bilangan 11,1 2,1 3,1 4,15, 16 menggunakan
pengali 100
c. Hitung shift sum dari bilangan bilangan 11,1 2,1 3,1 4,15, 16 menggunakan
pengali 10
latihan 19.3
Bayangkan anda memiliki sebuah hash table yang berisi 100 List. Jika anda ingin
mengambil sebuah nilai yang bersesuaian dengan kunci tertentu, dan kode hash dari
kunci tersebut adalah 654321, pada indeks List keberapakah kunci tersebut akan berada
(jika berada dalam map)?
Latihan 19.4
Jika terdapat 100 List dan terdapat 89 entri pada map dan List terpanjang berisi 3 entri,
dan panjang list tengah (median) adalah 1 dan 19% list adalah kosong, berapakah load
factor nya?
Latihan 19.5
Bayangkan terdapat banyak orang pada sebuah pesta. Anda ingin mengetahui apakah
ada diantara mereka yang memiliki hari ulang tahun yang sama. Pertama anda
mendesain sebuah objek Java yang mewakili hari ulang tahun dengan dua variabel
instance: bulan yang merupakan bilangan bulat antara 1 dan 12, dan hari yang
merupakan bilangan bulat antara 1 dan 31.
430
Selanjutnya, anda membuat sebuah array dari objek hariulangtahun yang berisi satu
objek untuk tiap orang pada pesta.
a. Tulis sebuah metode equals untuk hariulangtahun sehingga hari ulang tahun
dengan bulan dan hari yang sama adalah sama (equal).
b. Tulis sebuah metode bernama hasDuplicate yang mengambil sebuah array
objek hariulangtahun dan mengembalikan true jika terdapat dua atau lebih orang
yang memiliki hari ulang tahun sama. Algoritma anda harus hanya menjelajahi
array objek hariulangtahun sekali.
c. Tulis sebuah metode yang bernama randomBirthdays dengan parameter integer
n dan mengembalikan array dengan n objek hariulangtahun yang atribut atribut
nya diisi secara acak. Agar lebih sederhana, anda bisa mengganggap semua
bulan adalah 30 hari.
d. Bangkitkan 100 array acak, masing masing array 10 objek hariulangtahun, dan
lihat berapa banyak array yang berisi hari ulang tahun yang sama.
e. Jika terdapat 20 orang pada pesta, berapa kemungkinan satu atau lebih peserta
pesta memiliki hari ulang tahun yang sama.
Latihan 19.6
Sebuah Map disebut invertible jika setiap kunci dan setiap nilai hanya muncul sekali.
Pada HashMap selalu benar bahwa setiap kunci hanya muncul sekali, tetapi ada
memnungkinkan nilai yang sama muncul beberapa kali. Itulah mengapa, beberapa
HashMap invertible, dan beberapa tidak.
Tulis sebuah metode yang bernama isInvertible dengan parameter HashMap dan
mengembalikan true jika map invertible dan false jika tidak.
431
Latihan 19.7
Tujuan latihan ini adalah untuk mencari 20 kata kata yang paling sering dipakai pada
buku ini (buku ini yang masih dalam bahasa inggris). Tulislah kode yang menggunakan
HashMap untuk menghitung frekuensi kemunculan kata. Download teks buku pada
alamat http://kleq.web.ugm.ac.id/images/thinkapjava.txt. Pikirkan bagaimana
seharusnya program anda menangani tanda baca dan hal hal lain yang muncul pada
berkas.
Tulis program untuk menjawab pertanyaan pertanyaan di bawah ini:
a. Berapa banyak kata yang ada pada buku ini?
b. Berapa banyak kata yang berbeda muncul pada buku ini?
c. Berapa kali kata “encapsulation” muncul?
d. Apa saja 20 kata kata yang paling sering muncul? Petunjuk: Ada struktur data
lain yang mungkin bisa membantu untuk bagian latihan ini.
Latihan 19.8
Tulis sebuah metode untuk LinkedList yang mengecek apakah list mengandung loop.
Anda tidak boleh mengasumsikan bahwa panjang List telah benar.
Latihan 19.9
Tulis implementasi Map ADT yang menggunaka hash table(seperti yang didefinisikan
pada bagian 19.2). Ujilah dengan program program yang telah anda buat sebelumnya.
a. Mulailah dengan implementasi Vector pada buku ini
b. Ubah implementasi dengan menggunakan array dari LinkedList atau Vector
yang berisi LinkedList.
Latihan 19.10
432
Buat implementasi Set menggunakan HashMap
Latihan 19.11
Paket java.util menyediakan dua implementasi Map, yaitu HashMap dan TreeMap,
HashMap dibangun dengan meniru hash table seperti yang dijelaskan pada bab ini.
TreeMap dibuat berdasarkan redblack tree, yang mirip dengan pohon pencarian pada
latihan 17.6.
Meskipun kedua implementasi ini meyediakan antar muka yang sama, kita
mengharapkannya mencapai kinerja yang berbeda. Sejalan dengan banyak entri n
bertambah, kita berharap add dan contains menggunakan waktu konstan untuk
implementasi hash table. Dan waktu yang logaritmik untuk implementasi dengan
pohon.
Lakukan percobaan untuk mengkonfirmasi (atau membantah) prediksi ini, Buat sebuah
program yang menambah n entri pada HashMap atau TreeMap, lalu panggil contains
untuk setiap kunci pada entri tersebut. Catat waktu pengeksekusian program terhadap
nilai n dan buat grafiknya. Apakah kinerja sesuai dengan perkiraan kita?
433
BAB 20
Kode Huffman
20.1 Variabellength codes
Jika anda familiar dengan kode morse, anda mengetahui bahwa morse adalah sistem
untuk mengkodekan huruf pada alfabet menjadi titik dan garis. Misalnya sinyal terkenal
...... mewakili huruf SOS yang termasuk kode panggilan internasional untuk meminta
pertolongan.
Tabel berikut memperlihatkan kode tersebut:
Perhatikan bahwa beberapa kode lebih panjang dibandingkan dengan yang lain. Pada
perancangan, huruf yang paling sering digunakan memiliki kode terpendek. Karena
terbatasnya kode pendek sehingga huruf huruf dan simbol yang lain memiliki kode yang
lebih panjang. Suatu pesan akan memiliki lebih banyak kode pendek dibandingkan kode
yang panjang, sehingga meminimalisasi rata rata transmisi per huruf.
434
Kode seperti ini disebut variablelength codes. Pada bab ini kita akan melihat algoritma
untuk membangkitkan variablelength codes yang disebut kode Huffman. Huffman
merupakan algoritma yang menarik, tetapi juga merupakan latihan yang berguna karena
implementasinya menggunakan banyak struktur data yang telah kita pelajari.
Di bawah ini garis besar untuk beberapa bagian berikutnya:
● Pertama, kita akan menggunakan sebuah contoh teks dalam bahas inggris untuk
menghasilkan tabel frekuensi. Tabel frekuensi mirip seperti histogram, ia
menghitung berapa kali setiap huruf muncul pada teks contoh.
● Inti dari kode huffman adalah pohon huffman. Kita akan menggunakan tabel
frekuensi untuk membangun pohon huffman, lalu menggunakan pohon untuk
mengkodekan dan memecah kode dari rangkaian yang ada.
● Langkah terakhir, kita akan menjelajahi phon huffman dan memangun tabel
kode, yang berisi rangkaian titik dan garis untuk setiap pohon.
20.2 Tabel frekuensi
Karena tujuannya adalah memberikan kode pendek pada kata yang umum, kita harus
tahu berapa kali setiap huruf muncul. Pada cerita pendek Edgar Allen Poe “The Gold
Bug”, salah satu karakter menggunakan frekuensi huruf untuk memecahkan chiper. Dia
menjelaskan
“Sekarang dalam bahasa inggris, huruf yang paling sering muncul adalah e,
setelah itu a o i d h n r s t u y c f g l m w b k p q x z”.
Jadi misi pertama kita adalah melihat apakah Poe benar.
435
Latihan 20.12
Tulis sebuah kelas yang bernama FreqTab yang menghitung berapa kali setiap huruf
muncul pada teks contoh. Download contoh teks cerita pendek favorit anda, dan analisa
frekuensi masing masing huruf.
Saya menemukan bahwa lebih tepat membuat kelas FreqTab diturunkan dari HashMap.
Lalu menulis metode increment yang mengambil huruf sebagai parameter dan
menambah atau memperbaharui entri dalam HashMap untuk setiap huruf.
Anda dapat menggunakan keySet untuk mengambil entri pada HashMap, dan mencetak
daftar huruf dan frekuensinya. Sayangnya, mereka tidak akan tampil secara berurutan.
Latihan berikut menyelesaikan masalah ini.
Latihan 20.13
Tulis sebuah kelas yang bernama Pair yang mewakili pasangan huruf dan frekuensi.
Objek pair harus berisi sebuah huruf dan frekuensi sebagai variabel instance. Pair harus
mengimplementasikan Comparable sehingga objek Pair dengan frekuensi lebih
tinggilah yang menang.
Sekarang urutkan pasangan huruf dan frekuensi dari HashMap dengan menjelajahi
himpunan kunci, buat objek Pair, tambahkan Pair pada PriorityQueue, ambil Pair dari
PriorityQueue dan cetak dengan urutan mundur berdasarkan frekuensi.
436
20.3 Pohon Huffman
Langkah berikutnya adalah membangun pohon huffman. Setiap simpul pada pohon
berisi sebuah huruf dan frekuensinya, serta penunjuk ke simpul kiri dan kanan. Untuk
membangun pohon Huffman, kita mulai dengan membuat himpunan pohon tunggal,
satu dari setiap entri pada tabel frekuensi. Lalu kita membangun pohon dari bawah ke
atas, dimulai dari huruf dengan frekuensi terendah dan secara berulang menggabungkan
subphon sampai mendapatkan pohon tunggal yang berisi semua huruf.
Di bawah ini algoritma untuk membangun pohon Huffman secara lebih detil.
1. Untuk setiap entri pada tabel frekuensi, buat pohon Huffman tunggal dan
tambahkan pada PriorityQueue. Sehingga ketika kita mengambil pohon dari
PriorityQueue, kita mendapatkan yang memiliki frekuensi terendah.
2. Ambil dua pohon dari PriorityQueue dan gabungkan dengan membuat sebuah
simpul orang tua yang menunjuk pada pohon yang diambil tadi. Frekuensi
simpul orang tua adalah jumlahan dari frekuensi anak anaknya.
3. Jika PriorityQueue kosong, maka algoritma selesai, jika tidak letakkan pohon
baru pada PriorityQueue kembali ke langkah 2.
Sebagai contoh kita akan menggunakan teks di bawah untuk membentuk pohon
Huffman.
Eastern Tennessee anteaters ensnare and eat red ants, detest ant
antennae (a tart taste) and dread Antarean anteatereaters. Rare
Andean deer eat tender sea reeds, aster seeds and rats’ ears. Dessert?
Rats’ asses.
437
Teks di atas menghasilkan tebel frekuensi:
e 40
a 32
t 24
s 22
n 20
r 19
d 13
Jadi setelah langkah 1, PriorityQueue akan terlihat seperti:
Pada langkah 2, kita mengambil dua pohon dengan frekuensi terendah (r dan d) dan
menggabungkannya dengan membuat simpul orang tua dengan frekuensi 32. Nilai
huruf untuk simpul internal kurang relevan, sehingga dihapus dari gambar. Ketika kita
meletakkan pohon baru kembali ke PriorityQueue. Hasilnnya akan seperti di bawah:
Sekarang ulangi langkah sebelumnya, dengan menggabungkan s dan n:
Setelah iterasi berikutnya, kita memiliki kumpulan pohon seperti di bawah, Sebagai
438
informasi kumpulan pohon biasa disebut hutan.
Setelah dua iterasi lagi, hanya akan ada tiga pohon yang tertinggal:
Ini adalah pohon Huffman untuk teks contoh. Sebenarnya, ia bukan satu satunya
karena setiap kali kita menggabungkan dua pohon, kita memilih yang mana menjadi
simpul kanan dan yang mana di kiri, dan ketika ada pohon yang sama pada
PriorityQueue, kita memilih tergantung selera. Jadi akan ada banyak kemungkinan
pohon pada contoh di atas.
Jadi bagaimana kita mengambil sebuah kode dari pohon Huffman? Kode untuk
setiap huruf dihitung dengan menelusuri jalan dari akar pohon sampai daun yang berisi
huruf yang dimaksud. Misalnya jalan dari akar sampai s adalah kirikanankiri. Jika kita
mengganti kiri dengan “.” dan kanan dengan “” kita mendapatkan tabel kode di bawah:
e .
a
439
t ..
s ..
n .
r ....
d ...
Perhatikan bahwa kita telah mencapai tujuan: huruf dengan frekuensi terbanyak
memiliki kode paling pendek.
Latihan 20.14
Dengan perhitungan manual, gambarkan pohon Huffman untuk tabel frekuensi berikut:
e 93
s 71
r 57
t 53
n 49
i 44
d 43
o 37
20.4 Metode super
Salah satu cara untuk mengimplementasikan pohon Huffman adalah dengan
menurunkannya dari kelas Pair dari latihan 20.13
public class HuffTree extends Pair implements Comparable {
HuffTree left, right;
public HuffTree (int freq, String letter,
HuffTree left, HuffTree right) {
440
this.freq = freq;
this.letter = letter;
this.left = left;
this.right = right;
}
}
HuffTree mengimplementasikan Comparable sehingga kita bisa meletakkan pohon
Huffman ke dalam PriorityQueue. Untuk mengimplementasikan Comparable, kita
harus menyediakan metode compareTo. Kita bisa menuliskannya dari awal, tetapi akan
lebih mudah jika menggunakan versi compareTo dari kelas Pair.
Sayangnya, metode tersebut tidak melakukan apa tepatnya yang kita inginkan. Untuk
Pair kita memberikan prioritas untuk frekuensi yang lebih tinggi. Sedang pada pohon
Huffman, kita menginginkan memberikan prioritas pada frekuensi yang lebih rendah.
Tentu saja, kita bisa menuliskan versi baru dari compareTo tetapi itu tentunya akan
menutupi (override) versi pada kelas di atasnya (kelas orang tua), padahal kita ingin
bisa memanggil versi metode yang ada pada kelas orang tua.
Kata kunci super mengijinkan kita untuk memanggil metode yang telah ditutup. Ia
disebut super karena kadang kadang kelas orang tua juga disebut superclasses.
Berikut contoh dari implementasi HuffTree:
public int compareTo (Object obj) {
return super.compareTo (obj);
}
441
Ketika compareTo dipanggil pada HuffTree, ia akan memanggil versi compareTo pada
kelas orang tua, dan menegatifkan hasilnya. Sehingga urutan prioritas menjadi terbalik.
Ketika kelas anak (juga disebut subclass) menutupi constructor, ia bisa memanggil
konstruktor kelas orang tua dengan menggunakan super:
public HuffTree (int freq, String letter,
HuffTree left, HuffTree right) {
super (freq, letter);
this.left = left;
this.right = right;
}
Pada contoh ini, konstruktor orang tua menginisialisasi freq dan letter, dan lalu
konstruktor anak menginisialisasi left dan right.
Meskipun fitur ini berguna, ia juga memilliki kecenderungan untuk salah. Ada beberapa
keterbatasan seperti misal konstruktor orang tua harus dipanggil pertama, sebelum
variabel instance lain diinisialisasi, dan ada beberapa hal yang tidak ingin anda ketahui.
Secara umum, mekanisme ini seperti perlengkapan p3k. Ketika anda mengalami
masalah ia bisa membantu anda. Tetapi anda tahu apa yang lebih baik? Jangan masuk
ke dalam masalah. Pada masalah ini, lebih mudah untuk menginisialisasi kesemua
empat variabel instance pada konstruktor anak.
Latihan 20.15
Tulis definisi kelas HuffTree dari bagian ini dan sebuah kelas dengan metode build yang
mengambil sebuah FreqTab dan mengembalikan sebuah HuffTree. Gunakan algoritma
442
pada bagian 20.3.
20.5 Pemecahan Kode
Ketika kita menerima pesan yang dikodekan, kita menggunakan pohon Huffman untuk
memecah kodenya. Di bawah ini algoritma untuk melakukan hal tersebut:
1. Mulai dari akar pohon Huffman
2. Jika simbol berikutnya adalah “.”, maka berlanjut ke anak sebelah kiri, jika tidak
maka berlanjut ke anak sebelah kanan.
3. Jika anda berada pada simpul daun. Ambil hurufnya dan tambahkan pada hasil.
Lalu kembali ke akar.
4. Ke langkah 2.
Perhatikan kode ...., sebagai contoh. Mulai dari puncak pohon, kita menuju kirikiri
kanan dan mendapatkan huruf t. kemudian kita mulai dari akar lagi, kanankiri dan
mendapatkan huruf e, kembali ke puncak, lalu kanankiri dan kita mendapatkan huruf a.
jika kodenya dibentuk dengan baik, kita seharusnya ada pada simpul daun ketika kode
berakhir. Pada kasus ini pesannnya adalah tea.
Latihan 20.16
Gunakan contoh pohon Huffman untuk memecahkan kode berikut:
a. .....
b. ............
c. .........
d. .....
Perhatikan ketika anda mulai memecahkan kode, anda tidak dapat mengetahui berapa
443
banyak huruf pada kode atau berada di mana batas batas hurufnya.
Latihan 20.17
Tulis definisi kelas Huffman. Konstuktor memiliki parameter String yang mengandung
contoh teks. Kelas harus bisa membuat tabel frekuensi dan pohon Huffman.
Buat metode yang bernama decode. Metode ini menggunakan parameter dalam bentuk
titik dan garis dan menggunakan pohon Huffman untuk memecahkan kode dan
mengembalikan hasilnya.
Catatan: Walaupun anda menggunakan contoh kalimat pada bagian 20.2, anda tidak
akan mendapatkan pohon Huffman yang sama seperti pada bagian 20.3, jadi
kemungkinan anda tidak akan bisa menggunakan program anda untuk memecahkan
kode pada contoh latihan sebelumnya.
20.6 Pengkodean
Biasanya, pengkodean lebih sulit dibandingkan dengan pemecahan kode, karena untuk
suatu huruf kita harus mencari simpul daun pada pohon yang berisi huruf tersebut dan
dilanjutkan dengan menggambarkan lintasan dari akar sampai simpul tersebut.
Proses ini akan lebih efisien jika kita menjelajahi pohon sekali, memproses semua kode,
dan membangun Map dari huruf ke kode (huruf sebagai kunci dan kode sebagai nilai).
Dari sekarang kita telah melihat banyak pohon penjelajahan, tetapi yang satu ini tidak
biasa karena saat kita menjelajahi pohin, kita juga ingin menyimpan lintasan/jalan
dimana kita berada. Awalnya ini memang kelihatan sulit, tetapi ada cara yang lebih
alami untuk melakukan perhitungan secara rekursif. Kuncinya adalalah jika lintasan dari
444
akar ke suatu simpul disimbolkan dengan rangkaian titik dan garis yang disebut
lintasan, maka lintasan ke anak kiri pada simpul adalah lintasan + '' dan lintasan ke
anak sebelah kanan adalah lintasan + '.'.
Latihan 20.18
1. Tulis definisi kelas dari CodeTab, yang diturunkan dari HashMap.
2. Pada definisi CodeTab, tulis metode rekursif yang bernama getCodes. Metode
ini menjelajahi pohon Huffman. Ketika ia mencapai simpul daun, ia akan
mencetak huruf pada simpul dan kode yang menyimbolkan lintasan dari akar ke
simpul tersebut.
3. Tulis konstruktor untuk CodeTab yang mengambil HuffTree sebagai parameter
dan mengeksekusi getCodes untuk membangun tabel kode.
4. Pada kelas Huffman, tulis metode encode yang menjelajahi sebuah kalimat,
mencari setiap karakter pada tabel kode dan mengembalikan kalimat yang telah
dikodekan. Uji metode ini dengan melewatkan hasilnya pada decode dan lihat
apakah ia mengembalikan kalimat yang asli.
20.7 Daftar Kata
hutan : kumpulan pohon
superclass : nama lain dari kelas orang tua
subclass : nama lain dari kelas anak.
super : kata kunci yang digunakan untuk memanggil metode yang ditutupi pada
superclass.
445
Lampiran Foreach dan Generik
Generik
Generik adalah kemampuan baru pada Java 1.5 yang digunakan dalam pembentukan
Collection (List, Set, dll). Para pengguna Java tentu mengetahui salah satu kelemahan
penggunaan Collection sebelumnya adalah adanya keharusan untuk mengcasting Objek
saat diambil dari Collection. Proses pengcastingan ini cukup berbahaya karena
pengecekan apakah Casting bisa dilakukan dengan benar dilakukan saat runtime,
sehingga berisiko menghasilkan RuntimeException.
Untuk mengatasi hal di atas maka saat pembentukan Collection, telah diberitahu lebih
dahulu tipe apa yang akan ada di dalam Collection inilah yang disebut generik. Generik
dilakukan dengan menambahkan tipe data/tipe objek saat pendeklarasian Collection.
Contoh deklarasi lama:
List l = new ArrayList();
Set s = new HashSet();
Contoh deklarasi dengan generik:
List <Integer>l = new ArrayList<Integer>();
Set<Double> s = new HashSet<Double>();
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<Integer> l = new ArrayList<Integer>();
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<Integer> l = new ArrayList<Integer>();
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<Integer> l = new ArrayList<Integer>();
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<Integer> l = new ArrayList<Integer>();
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<Integer> l = new ArrayList<Integer>();
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(<tipedatadalam Collection atay array> <namavariabel> : <Collection atau array yang akan
dijelajahi>)
<statemen>
Dalam penggunaannya dalam array, foreach tidak terlalu banyak berguna karena
dengan menggunakan perulangan for biasa penjelajahan array bisa dilakukan dengan
449
maksimal. Salah satu kelebihan foreach dibandingkan dengan for dalam penjelajahan
array adalah syntax yang sederhana dan tidak perlunya menggunakan indeks saat
mengakses elemen dalam array.
import java.util.*;
class Test{
public static void main (String [] args){
int [] arr={1,4,5};
for(int i : arr)
System.out.println(i);
}
}
Tetapi dalam menjelajahi array foreach memiliki keterbatasan, bahwa di dalam foreach
kita tidak bisa melakukan pemanipulasian terhadap isi array. Karena foreach tidak
mereferensi langsung pada isi array tersebut.
import java.util.*;
class Test{
public static void main (String [] args){
int [] arr={1,4,5};
for(int i :arr)
i=i+1;
for(int i : arr)
System.out.println(i);
}
}
Kode di atas sama sekali tidak akan mengubah isi array array. Sehingga saat anda
mencetaknya yang tampil tetap array yang asli.
450
Daftar Kata
Collection : Tipe umum Struktur data Java, struktur data List, Set, Map, dan lain lain
diturunkan dari kelas ini. Biasanya digunakan untuk mengacu pada keseluruhan
struktur data pada Java.
RuntimeException : Eksepsi yang dilempar Java ketika terjadi kesalahan saat program
Java dijalankan
451
Jawaban Latihan
Bab 2
Latihan 2.1
class Date{
public static void main(String [] args){
String hari;
hari="Sabtu";
int tanggal;
tanggal=16;
String bulan;
bulan="September";
int tahun;
tahun=2006;
System.out.println("Format Amerika:");
System.out.println(hari+", "+bulan+" "+tanggal+", "+tahun);
System.out.println("Format Eropa:");
System.out.println(hari+" "+tanggal+" "+bulan+", "+tahun);
}
}
Latihan 2.2
class Time{
public static void main(String [] args){
452
int jam,menit,detik;
jam=6;
menit =26;
detik=20;
int detik_saat_ini;
detik_saat_ini=6*3600+26*60+20;
System.out.println("Detik saat ini sejak tengah malam adalah
"+detik_saat_ini+" detik");
int jumlah_detik_dalam_sehari;
jumlah_detik_dalam_sehari=24*3600;
int sisa_detik=jumlah_detik_dalam_seharidetik_saat_ini;
System.out.println("Sisa detik saat ini = "+sisa_detik+" detik");
int prosentase_sisa_detik;
prosentase_sisa_detik=sisa_detik*100/jumlah_detik_dalam_sehari;
System.out.println("Prosentase sisa detik saat ini =
"+prosentase_sisa_detik+" %");
}
}
Bab 3
Latihan 3.1
453
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
11
59
mainHour
Minute
11
59
Hour
printTimeMinute
Latihan 3.4
class Date{
public static void printAmerican(String hari, int tanggal, String bulan, int
tahun){
System.out.println(hari+", "+bulan+" "+tanggal+", "+tahun);
}
public static void printEuropean(String hari, int tanggal, String bulan, int
tahun){
System.out.println(hari+" "+tanggal+" "+bulan+", "+tahun);
}
public static void main(String [] args){
String hari;
hari="Sabtu";
int tanggal;
tanggal=16;
String bulan;
bulan="September";
int tahun;
tahun=2006;
System.out.println("Format Amerika:");
printAmerican(hari,tanggal,bulan,tahun);
System.out.println("Format Eropa:");
printEuropean(hari,tanggal,bulan,tahun);
455
}
}
Latihan 3.5
class Multadd{
public static void multadd(double a, double b, double c){
System.out.println(a*b+c);
}
public static void main(String [] args){
multadd(1.0,2.0,3.0);
//phi=180'
//Math.log10 ada pada java versi 5(1.5)
multadd(Math.cos(180/4),0.5,Math.sin(180/4));
multadd(1,Math.log10(10),Math.log10(20));
yikes(3);
}
public static void yikes(double x){
multadd(x,Math.exp(x),+(Math.sqrt(1Math.exp(x))));
}
}
Bab 4
Latihan 4.1
456
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
n
2n
1n
nLines
nLines
nLines
nLines
main
n 4
3n
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 10b.
Loop tak berhingga
10
5
6
4
3
2
1
2
1
.
Latihan 6.2.
a.public static int fooMethod (String s) {
int len = s.length ();
458
int i = 0;
int count = 0;
while (i < len) {
char c = s.charAt(i);
if (c == '(') {
count = count + 1;
} else if (c == ')') {
count = count 1;
}
i = i + 1;
}
return count;
}
b. Metode yang jika terdapat kurung buka menambah 1 isi variabel counter dan jika
terdapat kurung tutup akan mengurangi 1 nilai counter. Metode ini berguna untuk
menentukan apakah suatu ekspresi yang menggunakan tanda kurung telah valid (masing
masing memiliki kurung pasangan) atau tidak.
c. Pasangan karakter yang akan dicek sebaiknya dapat diubah ubah sesuai keperluan
class Test{
public static void main (String[] args) {
System.out.println(fooMethod("((3 + 7) * 2)",'(',')'));
459
}
public static int fooMethod (String s, char kar1, char kar2) {
int len = s.length ();
int i = 0;
int count = 0;
while (i < len) {
char c = s.charAt(i);
if (c == kar1) {
count = count + 1;
} else if (c == kar2) {
count = count 1;
}
i = i + 1;
}
return count;
}
}
Latihan 6.3
public static double squareRoot (double number) {
double x0=number/2;
460
double x2=100;
double x1=90;
while(Math.abs(x2x1)>=0.0001){
x1 = (x0 +number/x0)/2;
x2= (x1 +number/x1)/2;
x0=x2;
}
return x2;
}
Latihan 6.4
public static double power(double x, int n){
double result=1;
for(int i=1; i<=n; i++)
result*=x;
return result;
}
Latihan 6.5
public static int faktorial(int x){
int result=1;
while(x!=1){
result*=x;
x=1;
}
461
return result;
}
Latihan 6.6
a)
public class Latihan66 {
public static int faktorial(int x){
int result=1;
while(x!=1){
result*=x;
x=1;
}
return result;
}
public static double myexp(double x,int n){
double result=1;
for(int i=1; i<=n; i++){
result+=power(x,i)/faktorial(i);
}
return result;
}
public static double power(double x, int n){
double result=1;
462
for(int i=1; i<=n; i++)
result*=x;
return result;
}
}
b.
public class Latihan66 {
public static int faktorial(int x){
int result=1;
while(x!=1){
result*=x;
x=1;
}
return result;
}
public static double myexp(double x,int n){
double result=1;
double numerator=1;
double denominator=1;
for(int i=1; i<=n; i++){
numerator*=x;
denominator*=i;
result+=numerator/denominator;
463
}
return result;
}
public static double power(double x, int n){
double result=1;
for(int i=1; i<=n; i++)
result*=x;
return result;
}
}
c)
public static void check(double x){
System.out.println(x+"\t"+Math.exp(x)+"\t"+myexp(x, 100));
}
Latihan 6.7
public static double gauss(double x,int n){
double result=1;
double xvalue=1;
double denominator=1;
int sign=1;
for(int i=1; i<=n; i++){
sign=sign*(1);
464
xvalue*=x;
denominator*=i;
result+=sign*(i+1)*xvalue/denominator;
}
return result;
}
Bab 7
Latihan Tambahan
public static void printBackward(String s){
int index = s.length()1;
while (index >= 0) {
char letter = s.charAt (index);
System.out.print(letter);
index = index 1;
}
System.out.println();
}
Latihan 7.2
Metode bing mencetak setiap karakter pada kalimat beserta indeksnya mulai dari huruf
paling akhir.
Latihan 7.3
Program tidak akan mencetak bilangan secara terbalik tetapi akan mencetak
465
penjumlahan digit pertama dan digit terakhir. Program harusnya seperti berikut:
int number = 17;int lastDigit = number%10;int firstDigit = number/10;System.out.println (lastDigit +”“+firstDigit);
Latihan 7.4Program akan mencetak mengkonversi bilangan basis 10 menjadi bilangan biner.
Latihan 7.5class 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<length/2; i++){
if(str.charAt(i)!=str.charAt(length1i))return false;
}return true;
}}
Latihan 7.6public static boolean isAbcedarian(String str){
char [] charArr=str.toLowerCase().toCharArray();
for(int i=1; i<charArr.length; i++){
if(charArr[i]<=charArr[i1])
return false;
}
return true;
}
Latihan 7.7
public static boolean isDupledrome(String str){
char [] charArr=str.toLowerCase().toCharArray();
for(int i=0; i<charArr.length; i+=2){
if(i+1>=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<charArr.length; i++){
if(charArr[i]==',')
return true;
}
return false;
}
public static String convertName(String name){
if(hasComma(name))
return name;
else if(name.split(" ").length==1)
return name;
else{
String backname="";
int i=0;
for(i=name.length()1; i>=0; i){
468
if(name.charAt(i)==' ')
break;
else
backname=name.charAt(i)+backname;
}
return backname+", "+name.substring(0,i);
}
}
public static int compareName(String name1, String name2){
name1=convertName(name1.toLowerCase());
name2=convertName(name2.toLowerCase());
for(int i=0; i<Math.min(name1.length(), name2.length()); i++){
if(name1.charAt(i)==name2.charAt(i))
continue;
else if(name1.charAt(i) > name2.charAt(i))
return 1;
else
return 1;
}
if(name2.length()>name1.length())
return 1;
else if(name2.length() < name1.length())
return 1;
469
else
return 0;
}
}
Latihan 7.9
public static char add(char c, int addition){
if(Character.isLowerCase(c)){
if(addition>26 || addition<26 )
addition%=26;
int ascii=(char)c;
ascii+=addition;
if(ascii>122)
ascii=96+(ascii122);
else if(ascii<97)
ascii=122+(96ascii);
return (char)ascii;
}
else if(Character.isUpperCase(c)){
if(addition>26 || addition<26 )
addition%=26;
int ascii=(char)c;
ascii+=addition;
if(ascii>90)
470
ascii=64+(ascii90);
else if(ascii<65)
ascii=90+(64ascii);
return (char)ascii;
}
return c;
}
Bab 8
latihan 8.1
b) 5
1
2
Latihan 8.2
5
Latihan 8.3
(5,8)
(5,8)
471
p1 dan p2 bukan merupakan alias,
karena masing masing objek dibuat saat pemanggilang "findCenter"
import java.math.BigInteger;
Latihan 8.4
class Big{
public static int factorial(int n){
int result=1;
for(int i=n; i>=1; i){
result*=i;
}
return result;
}
public static BigInteger bigfactorial(int n){
BigInteger result=BigInteger.valueOf(1);
for(int i=n; i>=1; i){
result=result.multiply(BigInteger.valueOf(i));
}
return result;
}
472
public static void main(String [] args){
for(int i=1; i<=20; i++){
System.out.println("factorial "+i+" = "+Big.bigfactorial(i));
}
}
}
Latihan 8.5
public static BigInteger pow(int x, int n){
if(n==0)
return BigInteger.valueOf(1);
BigInteger t =pow(x,n/2);
if(n%2 == 0)
return t.multiply(t);
else
return t.multiply(t).multiply(BigInteger.valueOf(x));
}
Bab 9
Latihan 9.1
public class Tile{
473
int value;
char letter;
public Tile(char c, int v){
letter=c;
value=v;
}
public static void print(Tile t){
System.out.println("letter "+t.letter+" has value "+t.value);
}
public static void testTile(){
Tile t = new Tile('z',10);
Tile.print(t);
}
}
latihan 9.2
public class Date{
int date;
int month;
int year;
public Date(){}
474
public Date(int d, int m, int y){
date=d;
month=m;
year=y;
}
public static void main(String [] args){
Date d = new Date(11, 7, 1986);
Date d1 = new Date();
d1.date=11;
d1.month=7;
d1.year=1986;
}
}
Latihan 9.3
public class Rational{
int numerator;
int denominator;
public Rational(){
numerator=0;
denominator=0;
}
public Rational(int a, int b){
475
numerator=a;
denominator=b;
}
public static void printRational(Rational r){
System.out.println(r.numerator+"/"+r.denominator);
}
public void negate(){
numerator=numerator;
}
public void invert(){
int temp=numerator;
numerator=denominator;
denominator=temp;
}
public static double toDouble(Rational r){
return (double)r.numerator/r.denominator;
}
public static int gcd(int a, int b){
if(b>a){
int temp=a;
a=b;
476
b=temp;
}
while(b>1){
int temp=b;
b=a%b;
a=temp;
}
if(b==1)
return 1;
else
return a;
}
public static Rational reduce(Rational r){
int gcd=gcd(r.numerator,r.denominator);
System.out.println("gcd ="+gcd);
Rational number = new Rational();
number.numerator=r.numerator/gcd;
number.denominator=r.denominator/gcd;
return number;
}
public static Rational add(Rational r1, Rational r2){
477
return new
Rational(r1.numerator*r2.denominator+r2.numerator*r1.denominator,r2.denominator*r
1.denominator);
}
public static void main(String [] args){
Rational r = new Rational(36,20);
Rational.printRational(r);
r.negate();
Rational.printRational(r);
r.invert();
Rational.printRational(r);
System.out.println(Rational.toDouble(r));
r = Rational.reduce(new Rational(36,20));
Rational.printRational(r);
}
}
Bab 10
Latihan 10.1 dan 10.2
class RandomClass {
public static double randomDouble(double low, double high){
478
return low+(Math.random()*(high1));
}
public static int randomInt(int low, int high){
return low+(int)(Math.random()*(high1));
}
public static void main(String [] args){
System.out.println(randomDouble(0.6,7.8));
System.out.println(randomInt(7,8));
}
}
Latihan 10.3
public static int[] scoreHist(int [] scores){
int [] counts= new int[100];
for(int i=0; i<scores.length; i++){
int index=scores[i];
counts[index]++;
}
return counts;
}
latihan 10.4
public static boolean areFactors(int number, int [] factors){
for(int i=0; i<factors.length; i++){
479
if(number%factors[i]==0)
continue;
else
return false;
}
return true;
}
latihan 10.5
public static int findInRange(int [] arr, int bil, int low, int high){
if(low==high){
if( arr[low]==bil)
return low;
else
return 1;
}
int mid=(low+high)/2;
int result=findInRange(arr,bil,low,mid);
if(result==1){
int result1=findInRange(arr,bil,mid+1,high);
return result1;
}else
return result;
}
480
Latihan 10.6
public static int[] arrayHist(int [] scores){
int [] counts= new int[11];
for(int i=0; i<scores.length; i++){
if(scores[i]<=0)
counts[0]++;
else if(scores[i]>=10)
counts[10]++;
else
counts[scores[i]]++;
}
return counts;
}
Latihan 10.8
a. 30
b. bob ==> 0>2
1>4
2>6
3>8
4>10
c. menjumlahkan semua elemen array dan mengembalikan nilainya
481
Latihan 10.9
class Array {
public static int maxInRange(int [] arr, int low, int high){
if(low==high)
return arr[low];
if(low==high1)
return Math.max(arr[low],arr[high]);
int mid=(low+high)/2;
return
Math.max(maxInRange(arr,mid+1,high),maxInRange(arr,low,mid));
}
public static int max(int [] arr){
return maxInRange(arr,0,arr.length1);
}
public static int findInRange(int [] arr, int bil, int low, int high){
if(low==high){
if( arr[low]==bil)
return low;
else
return 1;
}
int mid=(low+high)/2;
482
int result=findInRange(arr,bil,low,mid);
if(result==1){
int result1=findInRange(arr,bil,mid+1,high);
return result1;
}else
return result;
}
public static void main(String [] args){
int [] arr={1,4,5,7};
System.out.println(findInRange(arr,71,0,3));
}
}
Latihan 10.10
class Sorter {
public static int indexOfMaxInRange(int [] arr, int low, int high){
int max=low;
for(int i=low+1; i<=high; i++)
if(arr[i]>arr[max])
max=i;
return max;
}
483
public static void swap(int [] arr, int index, int index1){
int swap=arr[index];
arr[index]=arr[index1];
arr[index1]=swap;
}
public static int[] sortArrays(int [] arr){
for(int i=0; i<arr.length1; i++){
int index=indexOfMaxInRange(arr,i,arr.length1);
swap(arr,i,index);
}
return arr;
}
public static void main(String [] args){
int [] arr={4,5,6,0,23,12};
arr=sortArrays(arr);
for(int i=0; i<arr.length; i++)
System.out.println(arr[i]);
}
}
latihan 10.11
//blank tiles diwakili oleh spasi(' ')
class LetterHist {
484
public static int [] letterHist(String kal){
int [] counts = new int[27];
char [] kar = kal.toCharArray();
for(int i=0; i<kar.length; i++){
if(kar[i]==' '){
counts[26]++;
continue;
}
kar[i]=Character.toLowerCase(kar[i]);
counts[kar[i]97]++;
}
return counts;
}
public static void main(String [] args){
int count[]=letterHist("afathkFFUhskldh");
for(int i=0; i<count.length; i++)
System.out.println(count[i]);
}
}
latihan 10.12
class Doubloon{
public static boolean isDoubloon(String word){
int count[]=LetterHist.letterHist(word);
for(int i=0; i<count.length; i++)
485
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; i<card.length; i++){
int score=card[i].getRank();
if(score>10)
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<card.length; i++)
hist[card[i].getSuit()]++;
return hist;
}
public static boolean isFlush(Card [] card) {
int [] hist=suitHist(card);
for(int i=0; i<hist.length; i++){
if(hist[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<low)
return 1;
492
int mid=(high+low)/2;
int comp=d.cards[mid].compareCard(c);
if(comp==0)
return mid;
else if(comp>0){
Deck sub=Deck.subDeck(d,low,mid1);
return findBisect(sub,c,low,mid1);
}else{
Deck sub=Deck.subDeck(d,mid+1,high);
return findBisect(sub,c,mid+1,high);
}
}
}
latihan 12.5
public class Poker{
public Deck[] getHands(){
Deck d= new Deck();
Deck.shuffleDeck(d);
Deck[] arr = new Deck[4];
arr[0]=Deck.subdeck(d,0,4);
arr[1]=Deck.subdeck(d,5,9);
arr[2]=Deck.subdeck(d,10,14);
arr[3]=Deck.subdeck(d,15,19);
493
return arr;
}
public static boolean isFlush(Deck d){
return CardUtility.isFlush(d.cards);
}
public static boolean isThreeKind(Deck d){
Card[] cards=d.cards;
int [] counter = new int[13];
for(int i=0; i<cards.length; i++){
counter[cards[i].getRank()]++;
}
for(int i=0; i<counter.length; i++){
if(counter[i]==3)
return true;
else
continue;
}
return false;
}
public static boolean isFourKind(Deck d){
Card[] cards=d.cards;
494
int [] counter = new int[13];
for(int i=0; i<cards.length; i++){
counter[cards[i].getRank()]++;
}
for(int i=0; i<counter.length; i++){
if(counter[i]==4)
return true;
else
continue;
}
return false;
}
public static boolean isPair(Deck d){
Card[] cards=d.cards;
int [] counter = new int[13];
for(int i=0; i<cards.length; i++){
counter[cards[i].getRank()]++;
}
for(int i=0; i<counter.length; i++){
if(counter[i]==2)
return true;
else
495
continue;
}
return false;
}
public static boolean isTwoPair(Deck d){
Card[] cards=d.cards;
int [] counter = new int[13];
for(int i=0; i<cards.length; i++){
counter[cards[i].getRank()]++;
}
int pair=0;
for(int i=0; i<counter.length; i++){
if(pair==2)
return true;
if(counter[i]==2)
pair++;
else
continue;
}
return false;
}
496
public static boolean isStraight(Deck d){
Card[] cards=d.cards;
Card [] temp = new Card[cards.length];
for(int i=0; i<cards.length; i++)
temp[i]=cards[i];
for(int i=0; i<temp.length; i++){
int index=getLowestCard(temp,i,temp.length1);
Deck.swapCards(temp,i,index);
if(i>=1){
if(temp[i].getRank()!=temp[i1].getRank()+1)
return false;
}
}
return true;
}
public static boolean isFullHouse(Deck d){
Card[] cards=d.cards;
int [] counter = new int[13];
for(int i=0; i<cards.length; i++){
counter[cards[i].getRank()]++;
}
//return true if three & two = true
boolean three=false;
boolean two=false;
for(int i=0; i<counter.length; i++){
497
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()<c[min].getRank())
min=i;
}
return min;
}
}
498
latihan 12.2 s/d 12.7 kecuali 12.5
class Deck{
Card[] cards;
public Deck(int n){
cards = new Card[n];
}
public Deck(){
cards = new Card[52];
int index=0;
for(int suit=0; suit<=3; suit++){
for(int rank=1; rank<=13; rank++){
cards[index]=new Card(suit,rank);
index++;
}
}
}
public static Deck subdeck(Deck deck, int low, int high){
Deck sub = new Deck(highlow+1);
for(int i=0; i<sub.cards.length; i++)
sub.cards[i]=deck.cards[low+i];
return sub;
}
499
public static void swapCards(Card [] c, int i, int j){
Card temp=c[i];
c[i]=c[j];
c[j]=temp;
}
public static void shuffleDeck(Card[] c){
for(int i=0; i<c.length; i++){
int random=i+(int)(Math.random()*(c.length1));
if(random<=51 && random >=0)
swapCards(c,i,random);
}
}
public static void shuffleDeck(Deck d){
for(int i=0; i<d.cards.length; i++){
int random=i+(int)(Math.random()*(d.cards.length1));
if(random<=51 && random >=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<c.length; i++){
int swap=findLowestCard(c,i,c.length1);
if(swap!=99)
swapCards(c,i,swap);
}
}
public static void incrementalShuffle(Deck d, Card c){
int random=0+(int)(Math.random()*(d.cards.length1));
d.cards[random]=c;
}
public static Deck merge(Deck d1, Deck d2){
Deck result = new Deck(d1.cards.length+d2.cards.length);
int i=0;
int j=0;
501
for(int k=0; k<result.cards.length; k++){
//d1 empty
if(i==d1.cards.length){
result.cards[k]=d2.cards[j];
j++;
//d2 empty
}else if(j==d2.cards.length){
result.cards[k]=d1.cards[i];
i++;
}else if(d1.cards[i].compareCard(d2.cards[j])==1){
result.cards[k]=d1.cards[i];
i++;
}else{
result.cards[k]=d2.cards[j];
j++;
}
}
return result;
}
502
public static Deck mergeSort1(Deck deck){
int mid=(deck.cards.length1)/2;
Deck sub1=subdeck(deck,0,mid);
Deck sub2=subdeck(deck,mid+1,deck.cards.length1);
sort(sub1.cards);
sort(sub2.cards);
Deck d = merge(sub1,sub2);
return d;
}
public static Deck mergeSort2(Deck deck){
if(deck.cards.length<=1)
return deck;
int mid=(deck.cards.length1)/2;
Deck sub1=subdeck(deck,0,mid);
Deck sub2=subdeck(deck,mid+1,deck.cards.length1);
sub1=mergeSort2(sub1);
sub2=mergeSort2(sub2);
Deck d = merge(sub1,sub2);
return d;
}
503
public static void main(String [] args){
Deck d = new Deck(5);
Card [] c = new Card[5];
c[0]= new Card(3,5);
c[1]= new Card(0,5);
c[2]= new Card(0,4);
c[3]= new Card(4,4);
c[4]= new Card(1,5);
d.cards=c;
Deck d1 = mergeSort2(d);
for(int i=0; i<d1.cards.length; i++){
System.out.println(d1.cards[i].getSuit()+"
"+d1.cards[i].getRank());
}
}
}
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<index){
list=list.next;
i++;
}
list.cargo=cargo;
}
}
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<index){
list=list.next;
i++;
}
newNode.next=list.next;
list.next=newNode;
length++;
}
}
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<index){
list=list.next;
i++;
}
newNode.next=list.next;
list.next=newNode;
length++;
}
}
public static LinkedList split(String sentence){
String [] word=sentence.split(" ");
Node [] node = new Node[word.length];
for(int i=0; i<word.length; i++){
525
node[i] = new Node();
node[i].cargo=word[i];
}
LinkedList list = new LinkedList();
list.head=node[0];
for(int i=1; i<node.length; i++)
node[i1].next=node[i];
node[node.length1]=null;
list.length=word.length;
return list;
}
public static String join(LinkedList list){
Node node = list.head;
String result="";
while(node!=null){
result+=node.cargo+" ";
node=node.next;
}
return result;
}
public String toString(){
return LinkedList.join(this);
}
526
public static void main (String[] args) {
// note: the following is a really bad way to build a list.
// warning signs of badness: allocating two different kinds
// of objects, accessing the instance variables of another class,
// using the constant 3 to set the length
// create an empty list
LinkedList list = new LinkedList ();
// create three new nodes, unlinked
Node node1 = new Node ();
node1.cargo = new Integer(1);
Node node2 = new Node ();
node2.cargo = new Integer(2);
Node node3 = new Node ();
node3.cargo = new Integer(3);
// next up the nodes
node1.next = node2;
node2.next = node3;
node3.next = null;
527
list.head = node1;
list.length = 3;
list.print ();
list.printBackward ();
LinkedList linkedList=LinkedList.split("aaa akbfkjfb lij ndnd");
linkedList.print();
System.out.println(LinkedList.join(linkedList));
System.out.println("Test toString method");
System.out.println(linkedList.toString());
}
}
class Node {
Object cargo;
Node next;
// default constructor
public Node () {
cargo = new Integer(0);
next = null;
}
// other constructor
public Node (Object cargo, Node next) {
528
this.cargo = cargo;
this.next = next;
}
public String toString () {
return cargo + "";
}
public static void printBackward (Node list) {
if (list == null) return;
Node head = list;
Node tail = list.next;
printBackward (tail);
System.out.print (head + ", ");
}
}
Latihan 15.3
class NewStack {
private LinkedList list;
public NewStack(){
list = new LinkedList();
list.head=null;
529
list.length=0;
}
public boolean isEmpty(){
return list.head==null;
}
public void push(Object o){
list.addFirst(o);
}
public Object pop(){
Node node = list.removeFirst();
return node.cargo;
}
public static void main(String [] args){
NewStack stack = new NewStack();
stack.push(Integer.valueOf(1));
stack.push(Integer.valueOf(2));
stack.push(Integer.valueOf(3));
while(!stack.isEmpty()){
Object o = stack.pop();
System.out.println(o);
}
}
530
}
latihan 15.4
import java.util.*;
import java.io.*;
class Balance {
private Stack parenthesStack;
private Stack bracketStack;
private Stack squigStack;
public Balance(){
parenthesStack = new Stack();
bracketStack = new Stack();
squigStack = new Stack();
}
public boolean read(String filename){
BufferedReader reader=null;
try{
reader = new BufferedReader(new FileReader(filename));
while(true){
String s = reader.readLine();
531
if(s==null)
break;
char [] kar = s.toCharArray();
for(int i=0; i<kar.length; i++){
if(kar[i]=='(')
parenthesStack.push(new Character('('));
else if(kar[i]==')'){
if(parenthesStack.isEmpty())
return false;
else
parenthesStack.pop();
}
else if(kar[i]=='[')
bracketStack.push(new Character('['));
else if(kar[i]==']'){
if(bracketStack.isEmpty())
return false;
else
bracketStack.pop();
}
else if(kar[i]=='{'){
squigStack.push(new Character('{'));
}
else if(kar[i]=='}'){
if(squigStack.isEmpty())
532
return false;
else
squigStack.pop();
}
}
}syst
if(squigStack.isEmpty() && bracketStack.isEmpty() &&
parenthesStack.isEmpty())
return true;
else
return false;
}catch(Exception e){
e.printStackTrace();
return false;
}finally{
try{
reader.close();
}catch(IOException ioe){
ioe.printStackTrace();
}
}
}
public static void main(String [] args){
533
System.out.println(new Balance().read("balance"));
}
}
latihan 15.5 dan 15.6
import java.util.*;
import java.io.*;
class EvalPostfix{
public static double evalPostFix(String postfix){
StringTokenizer token = new StringTokenizer(postfix);
Stack stack = new Stack();
while(token.hasMoreTokens()){
String operand=token.nextToken();
System.out.println(operand);
try{
Double angka=Double.valueOf(operand);
stack.push(angka);
}catch (NumberFormatException e){
double angka1=((Double)stack.pop()).doubleValue();
double angka2=((Double)stack.pop()).doubleValue();
if(operand.equals("+"))
stack.push(Double.valueOf(angka1+angka2));
else if(operand.equals(""))
stack.push(Double.valueOf(angka1angka2));
534
else if(operand.equals("*"))
stack.push(Double.valueOf(angka1*angka2));
else if(operand.equals("/"))
stack.push(Double.valueOf(angka1/angka2));
}
}
return ((Double)stack.pop()).doubleValue();
}
public static String inputLoop() throws IOException{
BufferedReader reader = new BufferedReader(new
InputStreamReader(System.in));
String result="";
while(true){
System.out.print("=>");
String s = reader.readLine();
if(s==null)
break;
else if(isOperator(s) || isOperand(s)){
System.out.println(s);
result+=s+" ";
}
else if(s.equals("quit"))
break;
}
return result;
535
}
public static boolean isOperator(String operand){
if(operand.equals("+"))
return true;
else if(operand.equals(""))
return true;
else if(operand.equals("*"))
return true;
else if(operand.equals("/"))
return true;
else
return false;
}
public static boolean isOperand(String s){
try{
Integer.parseInt(s);
return true;
}catch (NumberFormatException e){
return false;
}
}
536
public static void main(String [] args){
String s="";
try{
s = inputLoop();
}catch(IOException e){
e.printStackTrace();
}
System.out.println(s);
System.out.println(evalPostFix(s));
}
}
Bab 16
latihan 16.1
class Queue16_1 {
public Object[] array;
public int first,next;
public Queue16_1(){
array = new Object[128];
first=0;
next=0;
}
537
public boolean isEmpty(){
return first==next;
}
public void add(Object item){
array[next]=item;
next++;
}
public Object remove(){
if(isEmpty())
return null;
else{
Object result=array[first];
first++;
return result;
}
}
}
latihan 16.2
class Queue16_2 {
public Object[] array;
public int first,next;
public Queue16_2(){
538
array = new Object[3];
first=0;
next=0;
}
public boolean isEmpty(){
return first==next;
}
public void add(Object item){
array[next]=item;
if(isFull())
resize_array();
next=(next+1)%array.length;
}
private boolean isFull(){
return ((next+1)%array.length==first);
}
private void resize_array(){
Object [] temp=array;
array = new Object[2*temp.length];
for(int i=0; i<temp.length; i++){
array[i]=temp[i];
}
539
}
public Object remove(){
if(isEmpty())
return null;
else{
Object result=array[first];
first++;
return result;
}
}
public static void main(String [] args){
Queue16_2 queue = new Queue16_2();
queue.add("a");
queue.add("b");
queue.add("b");
queue.remove();
queue.add("a");
queue.add("c");
}
}
latihan 16.4
import java.util.*;
540
class SortedList extends LinkedList{
public SortedList(){
super();
}
public void add(Comparable o){
Node node=super.head;
int indeks=1;
while(node!=null){
if(o.compareTo(node)!=1)
break;
indeks++;
node=node.next;
}
Node n = (Node)o;
//jika indeks satu maka gunakan method addFirst
if(indeks==1)
super.addFirst(n.cargo);
else
super.add(indeks1,n.cargo);
}
public Node removeLast(){
return super.removeLast();
}
541
public static void main(String [] args){
SortedList list = new SortedList();
list.add(new Node(6));
list.add(new Node(4));
list.add(new Node(5));
list.print();
}
}
latihan 16.5
public Object maximum(){
Node node=head;
Node max=head;
while (node != null) {
if(max.compareTo(node) == 1)
max=node;
node = node.next;
}
return max;
}
latihan 16.6
//descending priority
class PriorityQueue extends SortedList{
542
public boolean isEmpty(){
return super.isEmpty();
}
public void add(int a){
Node n = new Node(a);
super.add(n);
}
public int remove(){
Node node=super.removeLast();
return ((Integer)node.cargo).intValue();
}
public static void main(String [] args){
PriorityQueue queue = new PriorityQueue();
queue.add(6);
queue.add(4);
queue.add(7);
while(!queue.isEmpty()){
System.out.println(queue.remove());
}
}
543
}
latihan 16.7
import java.util.PriorityQueue;
import java.util.Date;
class Event{
private PriorityQueue queue;
public boolean isEmpty(){
return queue.size()==0;
}
public Event(){
queue = new PriorityQueue();
}
public void add(Date d){
queue.add(d);
}
public Date nextTime(){
if(queue.isEmpty())
return null;
else{
return (Date)queue.peek();
544
}
}
public Date nextEvent(){
if(queue.isEmpty())
return null;
else{
return (Date)queue.poll();
}
}
public static void main(String [] args){
Event event = new Event();
event.add(new Date(2006,10,6));
event.add(new Date(2006,11,6));
event.add(new Date(2006,10,7));
while(!event.isEmpty()){
System.out.println(event.nextTime().toString());
System.out.println(event.nextEvent().toString());
}
}
}
545
Bab 17
latihan 17.1
public static void visitPreorder(Tree tree){
if(tree==null)
return;
tree.node.visit();
visitPreorder(tree.left);
visitPreorder(tree.right);
}
latihan 17.2
import java.util.*;
class PreIterator implements Iterator{
private Iterator iter;
public PreIterator(Tree t){
iter=buildVector(t).iterator();
}
public void remove(){
iter.remove();
}
546
public boolean hasNext(){
return iter.hasNext();
}
public Object next(){
return iter.next();
}
private Vector buildVector(Tree t){
Vector v = new Vector();
v.add(t.getNode().getCargo());
if(t.left!=null)
v.addAll(buildVector(t.left));
if(t.right!=null)
v.addAll(buildVector(t.right));
return v;
}
}
import java.util.*;
class Tree{
public Node node;
public Tree left;
547
public Tree right;
public Tree(Node node, Tree left, Tree right){
this.node=node;
this.left=left;
this.right=right;
}
public Node getNode(){
return node;
}
public static void visitPreorder(Tree tree){
if(tree==null)
return;
tree.node.visit();
visitPreorder(tree.left);
visitPreorder(tree.right);
}
public PreIterator preorderIterator(){
return new PreIterator(this);
}
public static int longestPath(Tree tree){
548
if(tree==null)
return 0;
return 1+Math.max(longestPath(tree.left),longestPath(tree.right));
}
public static void main(String [] args){
Tree t1 = new Tree(new Node(2),null,null);
Tree t2 = new Tree(new Node(3),null,null);
Tree t = new Tree(new Node(1),t1,t2);
Tree t3 = new Tree(new Node(4),null,null);
Tree root = new Tree(new Node(5),t,t3);
visitPreorder(root);
PreIterator iter=root.preorderIterator();
System.out.println();
while(iter.hasNext()){
System.out.println(iter.next());
}
System.out.println("panjang tree "+longestPath(root));
}
}
549
class Node implements Visitable,Comparable{
private Object cargo;
public Node(Object o){
cargo=o;
}
public void visit(){
System.out.print(cargo+" ");
}
public Object getCargo(){
return cargo;
}
public int compareTo(Object obj){
Node node=(Node)obj;
int a=((Integer)this.cargo).intValue();
int b=((Integer)node.cargo).intValue();
if(a>b)
return 1;
if(a<b)
return 1;
return 0;
}
}
550
interface Visitable{
public void visit();
}
latihan 17.3
a.9
b.2 3 * 1 +
c.3
latihan 17.4
public static int longestPath(Tree tree){
if(tree==null)
return 0;
return 1+Math.max(longestPath(tree.left),longestPath(tree.right));
}
latihan 17.5
import java.util.*;
class ComparableTree{
Comparable node;
public Tree left;
public Tree right;
public ComparableTree(Comparable node, Tree left, Tree right){
551
this.node=node;
this.left=left;
this.right=right;
}
public static Node findMax(Tree tree){
Node max=tree.node;
if(tree.left!=null){
Node left=findMax(tree.left);
if(max.compareTo(left)==1)
max=left;
}
if(tree.right!=null){
Node right=findMax(tree.right);
if(max.compareTo(right)==1)
max=right;
}
return max;
}
public static void main(String [] args){
Tree t1 = new Tree(new Node(2),null,null);
Tree t2 = new Tree(new Node(78),null,null);
552
Tree t = new Tree(new Node(1),t1,t2);
Tree t3 = new Tree(new Node(4),null,null);
Tree root = new Tree(new Node(5),t,t3);
System.out.println("max= "+findMax(root).getCargo());
}
}
latihan 17.6
import java.util.*;
class SearchTree {
Comparable node;
public SearchTree left;
public SearchTree right;
public SearchTree(Comparable node, SearchTree left, SearchTree right){
this.node=node;
this.left=left;
this.right=right;
}
public boolean contains(Object o){
return contains(o,this);
553
}
public Comparable getNode(){
return node;
}
public static boolean contains(Object o, SearchTree t){
Node n =(Node)o;
if(n.compareTo(t.getNode())==0)
return true;
if(n.compareTo(t.getNode())==1){
if(t.left==null)
return false;
return contains(o,t.left);
}
else{
if(t.right==null)
return false;
return contains(o,t.right);
}
}
public static void main(String [] args){
SearchTree t1 = new SearchTree(new Node(2),null,null);
SearchTree t2 = new SearchTree(new Node(4),null,null);
SearchTree t = new SearchTree(new Node(3),t1,t2);
554
SearchTree t3 = new SearchTree(new Node(6),null,null);
SearchTree root = new SearchTree(new Node(5),t,t3);
System.out.println(root.contains(new Node(5)));
}
}
latihan 17.817.9
import java.util.*;
class TreeSetModify extends TreeSet {
public static TreeSet union(Set set1, Set set2){
Iterator iter1 = set1.iterator();
Iterator iter2 = set2.iterator();
TreeSet newset = new TreeSet();
while(iter1.hasNext()){
newset.add(iter1.next());
}
while(iter2.hasNext()){
newset.add(iter2.next());
}
return newset;
}
555
public static TreeSet intersection(Set set1, Set set2){
Iterator iter1 = set1.iterator();
TreeSet newset = new TreeSet();
while(iter1.hasNext()){
Object o =(Object)iter1.next();
if(set2.contains(o))
newset.add(o);
}
return newset;
}
}
Bab 18
latihan 18.1 18.2 dan 18.7
import java.util.*;
class Heap {
int [] array;
int size;
public Heap(){
556
array = new int[128];
size=0;
}
public void print(){
for(int i=0; i<array.length; i++){
if(array[i]!=0)
System.out.println(array[i]);
}
}
public Object getCargo(int i){
return array[i];
}
public void setCargo(int i, int o){
array[i]=o;
}
public int getLeft(int i){
return 2*i;
}
public int getRight(int i){
return 2*i+1;
}
557
public int getParent(int i){
if(i % 2 == 1)
return i/2;
else
return (i1)/2;
}
public void add(int o){
array[size]=o;
int indeks=size;
int parent=getParent(size);
while(indeks!=0){
if(array[parent]<array[indeks]){
int temp=array[parent];
array[parent]=array[indeks];
array[indeks]=temp;
indeks=parent;
parent=getParent(indeks);
}
else
break;
}
size++;
}
558
public void remove(int indeks){
int right=getRight(indeks);
int left=getLeft(indeks);
//indeks is tree's leaf
if(array[right]==0)
array[indeks]=array[right];
array[right]=array[size1];
if(array[indeks]<array[left]){
int temp=array[indeks];
array[indeks]=array[left];
array[left]=temp;
}
reheap(left);
reheap(right);
}
private void reheap(int indeks){
if(array[indeks]==0)
return;
int right=getRight(indeks);
int left=getLeft(indeks);
if(array[indeks]<array[left]){
int temp=array[indeks];
559
array[indeks]=array[left];
array[left]=temp;
}
reheap(left);
if(array[indeks]<array[right]){
int temp=array[indeks];
array[indeks]=array[right];
array[right]=temp;
}
reheap(right);
}
public static boolean isComplete(Tree tree){
if(tree.left==null && tree.right==null)
return true;
int leftHeight=Tree.longestPath(tree.left);
int rightHeight=Tree.longestPath(tree.right);
if(leftHeight==1 && rightHeight==1)
return true;
if(leftHeight==rightHeight || leftHeight==rightHeight+1){
if(isComplete(tree.left)){
if(isComplete(tree.right)){
return true;
}
}
560
}
return false;
}
public static boolean isHeapProperty(Tree tree){
if(tree.left==null || tree.right==null)
return true;
Node cargo=tree.getNode();
Node left=tree.left.getNode();
Node right=tree.right.getNode();
if(cargo.compareTo(left)!=1 && cargo.compareTo(right)!=1)
if(isHeapProperty(tree.left))
if(isHeapProperty(tree.right))
return true;
return false;
}
public static void main(String [] args){
Heap heap = new Heap();
heap.add(79);
heap.add(60);
heap.add(65);
561
heap.add(56);
heap.add(57);
heap.add(18);
heap.add(20);
heap.add(14);
heap.add(28);
heap.add(20);
heap.print();
heap.add(68);
heap.print();
heap.remove(68);
}
}
import java.util.*;
class Tree{
public Node node;
public Tree left;
public Tree right;
public Tree(Node node, Tree left, Tree right){
this.node=node;
this.left=left;
this.right=right;
562
}
public Node getNode(){
return node;
}
public static void visitPreorder(Tree tree){
if(tree==null)
return;
tree.node.visit();
visitPreorder(tree.left);
visitPreorder(tree.right);
}
public static int longestPath(Tree tree){
if(tree==null)
return 0;
return 1+Math.max(longestPath(tree.left),longestPath(tree.right));
}
public static void main(String [] args){
Tree t1 = new Tree(new Node(2),null,null);
563
Tree t2 = new Tree(new Node(3),null,null);
Tree t = new Tree(new Node(1),t1,t2);
Tree t3 = new Tree(new Node(4),null,null);
Tree root = new Tree(new Node(5),t,t3);
visitPreorder(root);
System.out.println("panjang tree "+longestPath(root));
}
}
class Node implements Visitable,Comparable{
private Object cargo;
public Node(Object o){
cargo=o;
}
public void visit(){
System.out.print(cargo+" ");
}
public Object getCargo(){
return cargo;
}
564
public int compareTo(Object obj){
Node node=(Node)obj;
int a=((Integer)this.cargo).intValue();
int b=((Integer)node.cargo).intValue();
if(a>b)
return 1;
if(a<b)
return 1;
return 0;
}
}
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<n; i++){
int day=1+(int)(Math.random()*30);
int month=1+(int)(Math.random()*12);
bdays[i] = new BirthDay(day,month);
}
return bdays;
}
public static void main(String [] args){
for(int i=1; i<100; i++){
BirthDay[] bday = randomBirthdays(10);
571
System.out.println(hasDuplicate(bday));
}
}
}
Latihan 19.6
import java.util.*;
class Invertible{
public static boolean invertible(HashMap map){
Set set=map.keySet();
Iterator iter=set.iterator();
List list = new ArrayList();
while(iter.hasNext()){
Object o=map.get(iter.next());
if(list.contains(o))
return false;
else
list.add(o);
}
return true;
}
}
Latihan 19.7
572
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.StringTokenizer;
public class Excercise197 {
private BufferedReader reader;
private Map map = new HashMap();
private int numOfWord;
public Excercise197(String filename){
try{
reader = new BufferedReader(new InputStreamReader(new
FileInputStream(filename)));
}catch(Exception e){
e.printStackTrace();
573
}
while(true){
try{
String content=reader.readLine();
if(content==null)
break;
StringTokenizer st = new StringTokenizer(content);
while(st.hasMoreTokens()){
numOfWord++;
String str=st.nextToken();
if(str.length()==1)
continue;
if(map.containsKey(str)){
int
counter=((Integer)map.get(str)).intValue();
map.put(str, Integer.valueOf(counter+1));
}else
map.put(str, Integer.valueOf(1));
}
}catch(Exception e){
e.printStackTrace();
try{
reader.close();
}catch(IOException ioe){
574
ioe.printStackTrace();
}
}
}
}
public int getNumOfDifferentWord(){
return map.size();
}
public int getNumOfWord(){
return numOfWord;
}
public int getNumOfWord(String s){
Object o=map.get(s);
if(o==null)
return 0;
return ((Integer)o).intValue();
}
public String[] get20MostCommonWord(){
Set s=map.keySet();
Iterator iter=s.iterator();
PriorityQueue queue = new PriorityQueue();
575
while(iter.hasNext()){
String key=(String)iter.next();
Pair p = new Pair(key,((Integer)map.get(key)).intValue());
queue.add(p);
}
String [] arr = new String[20];
for(int i=0; i<20; i++){
arr[i]=((Pair)queue.poll()).getWord();
}
return arr;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Autogenerated method stub
Excercise197 e = new Excercise197("../../thinkapjava.txt");
//jawaban a
System.out.println(e.getNumOfWord());
//jawaban b
System.out.println(e.getNumOfDifferentWord());
//jawaban c
System.out.println(e.getNumOfWord("encapsulation"));
//jawaban d
576
String [] arr=e.get20MostCommonWord();
for(int i=0; i<arr.length; i++)
System.out.println(arr[i]);
}
}
class Pair implements Comparable{
String word;
int frequency;
public Pair(String w, int freq){
word=w;
frequency=freq;
}
public int getFrequency(){
return frequency;
}
public String getWord(){
return word;
}
public int compareTo(Object o){
577
Pair p=(Pair)o;
if(this.getFrequency() > p.getFrequency())
return 1;
else if(this.getFrequency() < p.getFrequency())
return 1;
else
return 0;
}
}
Latihan 19.8
import java.util.*;
public class LinkedList {
int length;
Node head;
public LinkedList () {
length = 0;
head = null;
}
//metode yang mengecek apakah suatu list mengandung loop
public boolean containLoop(){
Node node=head;
578
Map map = new HashMap();
while(node != null){
if(map.containsKey(node))
return true;
else
map.put(node, new Integer(1));
node=node.next;
}
return false;
}
public static void main(String [] args){
LinkedList list = new LinkedList ();
// create three new nodes, unlinked
Node node1 = new Node ();
node1.cargo = new Integer(1);
Node node2 = new Node ();
node2.cargo = new Integer(5);
Node node3 = new Node ();
node3.cargo = new Integer(3);
579
// next up the nodes
node1.next = node2;
node2.next = node3;
node3.next = node1;
list.head = node1;
list.length = 3;
System.out.println(list.containLoop());
}
}
class Node {
Object cargo;
Node next;
// default constructor
public Node () {
cargo = new Integer(0);
next = null;
}
}
580
Latihan 19.9
import java.util.*;
class NewMap{
Vector entries;
public NewMap(){
entries = new Vector();
}
public Object get(Object key){
Iterator it = entries.iterator();
while(it.hasNext()){
java.util.LinkedList entry =(java.util.LinkedList)it.next();
if(key.equals(entry.getFirst())){
return entry.getLast();
}
}
return null;
}
public Object put(Object key, Object value){
Object result=get(key);
581
if(result==null){
List l = new java.util.LinkedList();
l.add(key);
l.add(value);
entries.add(l);
}else{
update(key,value);
}
return result;
}
private void update(Object key, Object value){
Iterator it = entries.iterator();
while(it.hasNext()){
java.util.LinkedList entry =(java.util.LinkedList)it.next();
if(key.equals(entry.getFirst())){
entry.set(1, value);
break;
}
}
}
public Set keySet(){
Iterator it = entries.iterator();
TreeSet set = new TreeSet();
582
while(it.hasNext()){
java.util.LinkedList entry =(java.util.LinkedList)it.next();
set.add(entry.getFirst());
}
return set;
}
public static void main(String [] args){
NewMap map = new NewMap();
map.put("aa","dsdd");
map.put("saa","sdfdd");
map.put("saa","test");
Set set=map.keySet();
Iterator it = set.iterator();
while(it.hasNext()){
String key=(String)it.next();
System.out.println(key);
System.out.println(map.get(key));
}
}
}
Pada implementasi di atas digunakan Vector yang berisi LinkedList. Dimana isi dari
Map disimpan dalam LinkedList. LinkedList yang digunakan adalah LinkedList bawaan
dari Java yang pada paket java.util. Elemen pertama LinkedList adalah kunci entri
583
sedang Elemen terakhir LinkedList adalah nilai yang disimpan dalam Map.
Latihan 19.10
import java.util.*;
class NewSet
{
HashMap map ;
public NewSet(){
map = new HashMap();
}
public void clear(){
map = new HashMap();
}
public boolean remove(Object o){
if(map.remove(o) != null)
return true;
else
return false;
}
public boolean removeAll(Collection c){
Iterator it = c.iterator();
584
while(it.hasNext()){
if(remove(it.next()))
continue;
else
return false;
}
return true;
}
public void add(Object o){
map.put(o,null);
}
}
Latihan 19.11
Untuk melakukan pengujian saya menyediakan kelas BenchMark yang mengambil
argumen banyak data (n) yang akan di test dan mencetak waktu yang digunakan oleh
HashMap dan TreeMap dalam melakukan operasi add dan contains.
Untuk menggunakan kelas anda harus memberi argumen saat anda mengeksekusi kelas
pada konsole atau commandprompt
Contoh penggunaan:
[fuad@fuad bab19]$ java Bencmark 200
TES METODE ADD
585
waktu hash map 1
waktu tree map 2
TES METODE CONTAINS
waktu hash map 1
waktu tree map 1
Eksekusi di atas akan mengetes HashMap dan TreeMap dengan data sebanyak 200.
waktu yang dihasilkan adalah waktu eksekusi masing masing metode baik add maupun
contains.
import java.util.*;
public class Bencmark {
HashMap hashMap = new HashMap();
TreeMap treeMap = new TreeMap();
public void add(int n){
System.out.println("TES METODE ADD");
long time=System.currentTimeMillis();
for(int i=1; i<=n; i++)
hashMap.put(new Integer(i),"test");
long time1=System.currentTimeMillis();
System.out.println("waktu hash map "+(time1time));
time=System.currentTimeMillis();
586
for(int i=1; i<=n; i++)
treeMap.put(new Integer(i),"test");
time1=System.currentTimeMillis();
System.out.println("waktu tree map "+(time1time));
}
public void testContains(){
System.out.println();
System.out.println();
System.out.println("TES METODE CONTAINS");
Iterator keys = hashMap.keySet().iterator();
long time=System.currentTimeMillis();
while(keys.hasNext()){
hashMap.containsKey(keys.next());
}
long time1=System.currentTimeMillis();
System.out.println("waktu hash map "+(time1time));
keys = treeMap.keySet().iterator();
time=System.currentTimeMillis();
while(keys.hasNext()){
treeMap.containsKey(keys.next());
}
time1=System.currentTimeMillis();
System.out.println("waktu tree map "+(time1time));
587
}
public static void main(String [] args){
int n = Integer.parseInt(args[0]);
Bencmark b = new Bencmark();
b.add(n);
b.testContains();
}
}
BAB 20
Latihan 20.12
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class FreqTab extends HashMap {
588
BufferedReader reader;
Map map = new HashMap();
public FreqTab(String filename){
try{
reader = new BufferedReader(new InputStreamReader(new
FileInputStream(filename)));
}catch(Exception e){
e.printStackTrace();
}
}
private void buildFrequencyTable(){
String content="";
while(true){
try{
content=reader.readLine();
if(content==null)
break;
char [] arr=content.toCharArray();
for(int i=0; i<arr.length; i++){
if(Character.isLetter(arr[i])){
increment(arr[i]);
}
}
589
}catch(Exception e){
e.printStackTrace();
try{
reader.close();
}catch(IOException ioe){
ioe.printStackTrace();
}
}
}
}
private void increment(char c){
Character key = Character.valueOf(Character.toLowerCase(c));
Object o=map.get(key);
if(o==null)
map.put(key,new Integer(1));
else{
int oldValue=((Integer)o).intValue();
map.put(key,new Integer(++oldValue));
}
}
public void print(){
590
Iterator iter=map.keySet().iterator();
while(iter.hasNext()){
Object key=iter.next();
System.out.println(key+" "+map.get(key));
}
}
public static void main(String [] args){
FreqTab ft = new FreqTab("FreqTab.java");
ft.buildFrequencyTable();
ft.print();
}
}
Kelas di atas mengambil parameter nama file pada konstruktor nya, membaca dan
menghitung frekuensi kemunculan tiap tiap huruf. Sebagai contoh saya membaca file
kode sumber kelas ini lagi.
Anda bisa mengubahnya dengan teks lain yang anda inginkan.
Latihan 20.13
import java.util.*;
public class pair implements Comparable{
int frequency;
591
char karakter;
public pair(char c, int a){
karakter=c;
frequency=a;
}
public pair(){
}
public int getFrequency(){
return frequency;
}
public char getKarakter(){
return karakter;
}
public int compareTo(Object o){
pair p=(pair)o;
if(this.getFrequency() > p.getFrequency())
return 1;
else if(this.getFrequency() < p.getFrequency())
return 1;
else
592
return 0;
}
}
Definisi FreqTab yang menggunakan pair
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.PriorityQueue;
public class FreqTab extends HashMap {
BufferedReader reader;
Map map = new HashMap();
public FreqTab(String filename){
try{
reader = new BufferedReader(new InputStreamReader(new
FileInputStream(filename)));
}catch(Exception e){
e.printStackTrace();
}
593
}
public FreqTab(){
}
public void buildFrequencyTable(String teks){
char [] arr=teks.toCharArray();
for(int i=0; i<arr.length; i++){
if(Character.isLetter(arr[i])){
increment(arr[i]);
}
}
}
private void buildFrequencyTable(){
String content="";
while(true){
try{
content=reader.readLine();
if(content==null)
break;
char [] arr=content.toCharArray();
for(int i=0; i<arr.length; i++){
if(Character.isLetter(arr[i])){
594
increment(arr[i]);
}
}
}catch(Exception e){
e.printStackTrace();
try{
reader.close();
}catch(IOException ioe){
ioe.printStackTrace();
}
}
}
}
private void increment(char c){
Character key = Character.valueOf(Character.toLowerCase(c));
Object o=map.get(key);
if(o==null)
map.put(key,new Integer(1));
else{
int oldValue=((Integer)o).intValue();
map.put(key,new Integer(++oldValue));
}
595
}
public void print(){
Iterator iter=map.keySet().iterator();
PriorityQueue queue = new PriorityQueue();
while(iter.hasNext()){
Character key=(Character)iter.next();
pair p = new
pair(key.charValue(),((Integer)map.get(key)).intValue());
queue.add(p);
}
while(!queue.isEmpty()){
Object o = queue.remove();
pair pairobj = (pair)o;
System.out.println(pairobj.getKarakter()+"
"+pairobj.getFrequency());
}
}
public static void main(String [] args){
FreqTab ft = new FreqTab("FreqTab.java");
ft.buildFrequencyTable();
ft.print();
596
}
}
Latihan 20.14
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
e93
s71
r57
t53
n49
i44
d43
o37
8093110
151
186261
447
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<charArr.length; i++){
Object codePart=ct.get(Character.valueOf(charArr[i]));
if(codePart!=null)
code+=(String)codePart;
}
return code;
}
public static void main(String [] args){
String teks="";
for(int i=1; i<=40; i++)
teks+="e";
for(int i=1; i<=32; i++)
teks+="a";
for(int i=1; i<=24; i++)
teks+="t";
for(int i=1; i<=22; i++)
teks+="s";
for(int i=1; i<=20; i++)
teks+="n";
for(int i=1; i<=19; i++)
teks+="r";
604
for(int i=1; i<=13; i++)
teks+="d";
HuffMan hm = new HuffMan(teks);
String code=hm.encode("desta");
System.out.println(code);
System.out.println(hm.decode(code));
}
}
605
History
1994, Tulisan Original Oleg Allen Downey
2007, Penerjemahan dan Penambahan lampiran “For each dan Generik” oleh Wim
Permana, Muhammad Fuad Dwi Rizki, dan Agus Juliardi
GNU Free Documentation License
Version 1.2, November 2002
Copyright (C) 2000,2001,2002 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
0. PREAMBLE
The purpose of this License is to make a manual, textbook, or other functional and
useful document "free" in the sense of freedom: to assure everyone the effective
freedom to copy and redistribute it, with or without modifying it, either commercially or
noncommercially. Secondarily, this License preserves for the author and publisher a
way to get credit for their work, while not being considered responsible for
modifications made by others.
This License is a kind of "copyleft", which means that derivative works of the document
must themselves be free in the same sense. It complements the GNU General Public
License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software, because
606
free software needs free documentation: a free program should come with manuals
providing the same freedoms that the software does. But this License is not limited to
software manuals; it can be used for any textual work, regardless of subject matter or
whether it is published as a printed book. We recommend this License principally for
works whose purpose is instruction or reference.
1. APPLICABILITY AND DEFINITIONS
This License applies to any manual or other work, in any medium, that contains a notice
placed by the copyright holder saying it can be distributed under the terms of this
License. Such a notice grants a worldwide, royaltyfree license, unlimited in duration,
to use that work under the conditions stated herein. The "Document", below, refers to
any such manual or work. Any member of the public is a licensee, and is addressed as
"you". You accept the license if you copy, modify or distribute the work in a way
requiring permission under copyright law.
A "Modified Version" of the Document means any work containing the Document or a
portion of it, either copied verbatim, or with modifications and/or translated into
another language.
A "Secondary Section" is a named appendix or a frontmatter section of the Document
that deals exclusively with the relationship of the publishers or authors of the Document
to the Document's overall subject (or to related matters) and contains nothing that could
fall directly within that overall subject. (Thus, if the Document is in part a textbook of
mathematics, a Secondary Section may not explain any mathematics.) The relationship
could be a matter of historical connection with the subject or with related matters, or of
legal, commercial, philosophical, ethical or political position regarding them.
The "Invariant Sections" are certain Secondary Sections whose titles are designated,
607