Groovy memungkinkan skrip ditulis tanpa satu deklarasi tipe pun, sehingga baris pertama berkas bisa langsung berisi logika program. Topik ini membahas pengetikan opsional (optional typing) pada skrip, mulai dari def yang menyimpulkan tipe dari nilai, tipe eksplisit yang memperketat penugasan, sampai penolakan kompilasi pada variabel final. Kelonggaran sintaks turut diuraikan, yaitu return, titik koma, dan kurung yang dapat dihilangkan beserta syaratnya. Bagian akhir membahas lingkup variabel skrip: perbedaan antara variabel def yang lokal dan penugasan tanpa def yang masuk ke binding. Setelah membacanya, skrip dapat ditulis dengan gaya Groovy yang ringkas tanpa kehilangan kendali atas tipe.
def dan tipe yang disimpulkan#
Kata def mendeklarasikan variabel tanpa tipe tertulis, dan tipenya mengikuti nilai yang diisikan. Variabel tetap menyimpan nilai bertipe nyata, hanya saja tipe itu ditentukan oleh data, bukan oleh tulisan di depan namanya. Pada penugasan berikutnya, variabel yang sama boleh diisi nilai dengan tipe berbeda, dan tipenya ikut berganti. Perilaku inilah yang disebut pengetikan dinamis (dynamic typing), yaitu pemeriksaan tipe yang dilakukan saat program berjalan, bukan saat dikompilasi, dan menjadi perilaku bawaan Groovy.
def stok = 24
println "Stok awal : $stok (${stok.getClass().simpleName})"
stok = 'habis'
println "Stok baru : $stok (${stok.getClass().simpleName})"
def rataRata = 78.5
println "Rata-rata : $rataRata (${rataRata.getClass().simpleName})"Stok awal : 24 (Integer)
Stok baru : habis (String)
Rata-rata : 78.5 (BigDecimal)Keluaran tersebut memperlihatkan ketiga perilaku itu pada stok buku dan rata-rata nilai ujian. Variabel stok menyimpan bilangan bulat 24, lalu berganti menjadi String setelah diisi teks ‘habis’, tanpa peringatan apa pun. Nilai 78.5 dikenali sebagai BigDecimal, karena literal desimal pada Groovy memang dibuat sebagai BigDecimal dan bukan sebagai bilangan pecahan biner. Kebebasan seperti ini nyaman untuk skrip kecil, tetapi kesalahan tipe baru terasa saat program berjalan, dan bagian selanjutnya menunjukkan apa yang berubah ketika tipe ditulis secara eksplisit.
Metode def, return, dan kelonggaran sintaks#
Kata def juga mendeklarasikan metode tanpa tipe kembalian maupun tipe parameter. Nilai kembaliannya adalah ekspresi terakhir di dalam tubuh metode, sehingga kata return bersifat opsional dan lazim dihilangkan pada metode pendek. Titik koma di akhir baris dapat dihilangkan selama baris itu hanya berisi satu pernyataan, sedangkan dua pernyataan yang berbagi satu baris wajib dipisahkan titik koma. Kurung pada pemanggilan boleh dihilangkan bila ada minimal satu argumen dan pemanggilannya tidak ambigu, sedangkan pemanggilan tanpa argumen tetap memerlukan kurung.
Skrip piket.groovy di bawah ini memakai seluruh kelonggaran tersebut untuk menyusun jadwal piket kelas. Metode baris mengembalikan teks hasil interpolasi tanpa return, tiga pemanggilannya menyusun daftar jadwal, dan println dipanggil tanpa kurung.
// piket.groovy: menyusun jadwal piket kelas
def baris(hari, nama) {
"$hari: $nama"
}
def jadwal = [
baris('Senin', 'Ali'),
baris('Selasa', 'Citra'),
baris('Rabu', 'Budi')
]
println jadwal[0]
println jadwal[1]
println jadwal[2]
println "Jumlah petugas: ${jadwal.size()}"Senin: Ali
Selasa: Citra
Rabu: Budi
Jumlah petugas: 3# tidak-dijalankan
# menjalankan skrip jadwal piket dari folder berkasnya
groovy piket.groovyKelonggaran berikutnya adalah bentuk penulisan yang setara pada satu pernyataan. Dua baris pertama blok berikut menghasilkan keluaran yang sama persis, sedangkan baris ketiga memaksa titik koma dipakai karena berisi dua pernyataan. Kurung tetap disarankan ketika argumennya panjang atau bersarang, karena pemisahan antara argumen dan ekspresi yang menyusulnya mudah tertukar, dan jebakan itu dibedah pada bagian Kesalahan umum.
println 'tanpa kurung'
println('dengan kurung')
println 'satu baris'; println 'dua pernyataan'tanpa kurung
dengan kurung
satu baris
dua pernyataanTipe eksplisit yang memperketat#
Menuliskan tipe secara eksplisit mengubah aturan penugasannya. Nilai yang ditugaskan melewati konversi (cast) otomatis, dan keberhasilannya bergantung pada arah konversi. Bilangan ke teks dimungkinkan, sehingga String nama = 42 sah dan menghasilkan teks “42”. Konversi teks ’tujuh’ ke int tidak dimungkinkan, dan penolakannya baru terjadi saat runtime dalam bentuk GroovyCastException. Kelas pengecualian itu berada di luar impor bawaan Groovy, sehingga blok catch membutuhkan satu baris import.
import org.codehaus.groovy.runtime.typehandling.GroovyCastException
String nama = 42
println "Nama : $nama (${nama.getClass().simpleName})"
try {
int stok = 'tujuh'
println stok
} catch (GroovyCastException e) {
println "Gagal: ${e.getClass().simpleName}"
}Nama : 42 (String)
Gagal: GroovyCastExceptionPerhatikan bahwa baris pertama keluaran berhasil dicetak. Konversi diam-diam dari bilangan ke teks tidak menimbulkan pesan apa pun, sehingga kesalahan tipe pada penugasan semacam itu lolos tanpa sinyal. Penolakan konversi teks ke bilangan tidak dapat dicegah kompilator, karena kecocokan nilainya baru diketahui saat program berjalan. Karena itu, tipe eksplisit bukan alat validasi, dan pemeriksaan nilai tetap menjadi tanggung jawab kode yang menulisnya.
final dan waktu pemeriksaan#
Variabel final menutup celah yang tersisa, karena penugasan ulangnya ditolak saat kompilasi. Pemeriksaan ini satu-satunya pada topik ini yang berlangsung sebelum program berjalan, sehingga pelanggarannya mustahil lolos ke runtime. Untuk memperagakannya, kelas GroovyShell mengevaluasi kode berbentuk teks, dan kegagalan kompilasinya dilempar sebagai CompilationFailedException yang dapat ditangkap blok catch.
import org.codehaus.groovy.control.CompilationFailedException
def shell = new GroovyShell()
try {
shell.evaluate('final int stok = 10\nstok = 20')
} catch (CompilationFailedException e) {
println "Gagal kompilasi: ${e.getClass().simpleName}"
println e.message.find(/The variable \[\w+\] is declared final but is reassigned/)
}Gagal kompilasi: MultipleCompilationErrorsException
The variable [stok] is declared final but is reassignedPesan yang tercetak berasal dari kompilator dan menyebut variabel yang melanggar, bukan dari program yang sedang berjalan. Dengan tambahan final, tiga tingkat kekakuan tipe pada Groovy kini terlihat utuh, sebagaimana dirangkum tabel berikut.
| Penulisan | Waktu pemeriksaan | Hasil |
|---|---|---|
def stok = 'habis' | saat runtime | tipe mengikuti nilai, penugasan ulang bebas |
String nama = 42 | saat runtime | konversi diam-diam menjadi “42” |
int stok = 'tujuh' | saat runtime | GroovyCastException |
stok = 20 setelah final int stok = 10 | saat kompilasi | kompilasi ditolak |
Aturan emas jalur ini menempatkan kelancaran dinamis terlebih dahulu dan tipe eksplisit sebagai alat ketatanan. Tulis skrip dengan def selama tipenya jelas dari konteks, lalu naikkan ke tipe eksplisit atau final pada bagian yang menjadi kontrak, misalnya parameter metode yang dipanggil bagian lain atau batasan yang tidak boleh berubah. Panduan gaya resmi Groovy menyarankan arah yang sama, yaitu tipe yang kuat untuk kode yang dipakai orang lain karena memperjelas kontrak dan membantu pemeriksaan statis.
Skrip sebagai kelas dan lingkup variabelnya#
Setiap berkas skrip dijalankan sebagai kelas turunan groovy.lang.Script, dan pernyataan tingkat atasnya menjadi isi metode run() milik kelas tersebut. Di dalam struktur itu ada dua tempat variabel disimpan. Penugasan tanpa def tidak membuat variabel lokal, melainkan menyimpan nilainya ke binding, yaitu wadah variabel milik skrip yang menurut dokumentasinya dapat diubah dari luar objek skrip. Adapun def membuat variabel lokal yang hidup hanya di tubuh skrip. Perbedaannya terasa ketika metode ikut membaca variabel tersebut, karena metode dapat mengakses variabel binding, sedangkan variabel def milik tubuh skrip tidak terlihat oleh metode mana pun.
kalimat = 'cicak di dinding'
def hitungKata() {
kalimat.split(/\s+/).size()
}
def bacaPenghitung() {
penghitung
}
println "Kelas induk : ${this.getClass().superclass.name}"
println "Jumlah kata : ${hitungKata()}"
def penghitung = 0
try {
bacaPenghitung()
} catch (MissingPropertyException e) {
println "Gagal : ${e.getClass().simpleName}"
}Kelas induk : groovy.lang.Script
Jumlah kata : 3
Gagal : MissingPropertyExceptionPada keluaran itu, metode hitungKata berhasil menghitung dari variabel binding kalimat, sedangkan pemanggilan bacaPenghitung gagal meskipun penghitung sudah diisi sebelum pemanggilannya. Nama penghitung dicari di lingkup metode lalu di binding skrip, dan tidak ditemukan di keduanya, sehingga dilempar MissingPropertyException. Akibat praktisnya, variabel tanpa def tersebar ke lingkup yang lebih luas daripada yang biasanya dibutuhkan, sedangkan def membatasi variabel pada tubuh skrip.
Kesalahan umum#
Kesalahan pertama adalah menjumlahkan teks berisi angka dengan bilangan. Kode ‘101’ yang berasal dari berkas data terlihat seperti bilangan, tetapi operator + pada operan String dan bilangan melakukan penggabungan, bukan penjumlahan, sehingga hasilnya ‘1011’ tanpa pesan error apa pun. Jebakan ini mudah terlewat karena teks dan bilangan memang tampil sama di keluaran. Perbaikannya mengubah teks menjadi bilangan lebih dahulu dengan metode toInteger(), sebagaimana baris kedua keluaran berikut.
def kode = '101'
println "Kode berikutnya: ${kode + 1}"
println "Perbaikan : ${kode.toInteger() + 1}"Kode berikutnya: 1011
Perbaikan : 102Kesalahan kedua menyangkut kurung. Pernyataan println (1 + 2) * 3 mencetak 3 lebih dahulu, lalu menggagalkan program dengan NullPointerException. Kurung yang menyusul nama metode hanya menampung argumennya, sehingga println (1 + 2) dianggap pemanggilan lengkap, sedangkan sisa * 3 dioperasikan pada nilai kembalian println yang bernilai null. Perbaikannya membungkus seluruh ekspresi dalam satu kurung, yaitu println((1 + 2) * 3), sehingga seluruh hasil perkalian menjadi satu argumen.
try {
println (1 + 2) * 3
} catch (NullPointerException e) {
println "Gagal: ${e.getClass().simpleName}"
}3
Gagal: NullPointerExceptionRingkasan#
defmembuat tipe mengikuti nilai, dan penugasan ulang dengan tipe lain tetap sah karena pengetikan dinamis menjadi perilaku bawaan Groovy.- Tipe eksplisit memperketat saat runtime, yaitu konversi bilangan ke teks terjadi diam-diam, konversi teks yang tak sah melempar
GroovyCastException, dan penugasan ulang variabelfinalditolak saat kompilasi. return, titik koma, dan kurung bersifat opsional dengan syarat, yaitu kurung wajib pada pemanggilan tanpa argumen dan titik koma wajib memisahkan dua pernyataan dalam satu baris.- Skrip berjalan sebagai kelas turunan
groovy.lang.Script, penugasan tanpadefmasuk binding dan tetap terjangkau dari metode, sedangkan variabeldefhanya hidup di tubuh skrip.
Referensi#
- Semantics — Apache Groovy
— dokumentasi resmi semantik Groovy yang mencakup konversi tipe serta bagian Optionality tentang kurung, titik koma, dan
returnyang opsional. - Style guide — Apache Groovy
— panduan resmi pemakaian
defdan tipe eksplisit, termasuk saran kapan pengetikan kuat lebih diutamakan. - Script — Apache Groovy
— kelas induk setiap skrip Groovy beserta metode
run()dan akses ke binding-nya. - Binding — Apache Groovy
— wadah variabel skrip yang menampung penugasan tanpa
defdan dapat diubah dari luar skrip.