Closure adalah blok kode yang dapat disimpan dalam variabel, dipanggil kemudian, dan membawa lingkup asalnya bersamaan. Topik ini membedah anatomi closure Groovy secara menyeluruh, yaitu cara mendeklarasikan parameternya, parameter implisit it, parameter variadik (varargs), serta variabel lokal yang ditangkap oleh lingkupnya. Setelah membaca topik ini, closure dapat dipahami sebagai objek yang memiliki properti dan aturan pencarian nama sendiri, bukan sekadar penulis pendek untuk metode.
Closure sebagai objek#
Setiap closure yang ditulis adalah instance dari kelas groovy.lang.Closure, sehingga closure dapat ditugaskan ke variabel, dikirim sebagai argumen, dan dikembalikan dari metode sebagaimana objek lain. Pemanggilannya dilakukan dengan tanda kurung sebagaimana metode biasa, atau secara eksplisit melalui metode call. Karena berstatus objek, closure juga memuat properti yang dapat diperiksa, di antaranya maximumNumberOfParameters dan parameterTypes.
// Closure adalah objek: disimpan, dipanggil, dan diperiksa
def jumlah = { a, b -> a + b }
println "Panggilan : ${jumlah(2, 3)}"
println "Melalui call : ${jumlah.call(10, 20)}"
println "Closure? : ${jumlah instanceof Closure}"
println "Nama kelas : ${jumlah.getClass().simpleName}"
println "Param maks : ${jumlah.maximumNumberOfParameters}"
println "Param terdaftar: ${jumlah.parameterTypes.size()}"
println "owner == delegate: ${jumlah.owner.is(jumlah.delegate)}"Panggilan : 5
Melalui call : 30
Closure? : true
Nama kelas : _run_closure1
Param maks : 2
Param terdaftar: 2
owner == delegate: trueNama kelas _run_closure1 memperlihatkan bahwa kompilator menghasilkan kelas closure tersendiri untuk setiap closure yang ditulis. Baris terakhir memperkenalkan dua properti penting, yaitu owner dan delegate, yang pada tahap ini menunjuk objek yang sama. Keduanya baru berbeda ketika delegate diarahkan ke objek lain secara manual. Perlu dicatat pula bahwa closure selalu mengembalikan nilai, yaitu nilai pernyataan terakhirnya, sehingga pernyataan return bersifat opsional.
Bedanya dengan metode biasa baru terasa ketika blok kode tersebut perlu berpindah tempat. Metode melekat pada kelasnya dan dipanggil melalui objeknya, sedangkan closure dapat dikirim sebagai argumen, disimpan di dalam map, atau dikembalikan dari fungsi lain. Dua closure yang isinya sama pun merupakan dua objek yang berbeda, karena masing-masing membawa lingkup asalnya sendiri. Sifat inilah yang membuat metode koleksi seperti findAll dan collect dapat menerima kriteria yang ditulis langsung di tempat pemanggilannya.
Parameter dan nilai implisit it#
Closure yang tidak mendeklarasikan daftar parameternya secara eksplisit selalu memiliki satu parameter implisit bernama it, yang menerima argumen pertama pemanggilannya. Penulisan { it * 2 } dengan demikian setara persis dengan { it -> it * 2 }. Apabila closure memang tidak boleh menerima argumen apa pun, daftar parameternya wajib ditulis kosong dengan bentuk { -> }, dan pemanggilan dengan argumen pada closure semacam itu akan gagal.
def gandakan = { it * 2 }
println gandakan(21)
println gandakan.maximumNumberOfParameters42
1Properti maximumNumberOfParameters bernilai satu, karena closure tanpa daftar parameter memang mendeklarasikan satu parameter implisit. Angka semacam ini memungkinkan kode yang menerima closure sebagai argumen memeriksa batas parameternya sebelum memanggilnya. Sebaliknya, bentuk { -> } menolak seluruh argumen dan cocok untuk closure yang dipanggil tanpa data masukan.
def nomorUrut = { -> 'tiket nomor satu' }
println nomorUrut()tiket nomor satuParameter variadik#
Sebagaimana metode, closure dapat mendeklarasikan parameter variadik, yaitu parameter terakhir yang menampung sejumlah nilai berbibit tanda tiga (...) atau berbentuk array. Groovy mengemas seluruh argumen tambahan menjadi array di dalam closure, sehingga metode array seperti join dapat langsung dipakai. Parameter variadik juga dapat dicampur dengan parameter tetap di depannya.
// Varargs: parameter terakhir menampung banyak nilai
def catat = { String... kata -> kata.join(' | ') }
println catat('piket', 'senin', 'ali')
println "Tanpa argumen: [${catat()}]"
def beriAwalan = { String awalan, String... kata -> awalan + kata.join(', ') }
println beriAwalan('Kelas A: ', 'Ali', 'Budi')piket | senin | ali
Tanpa argumen: []
Kelas A: Ali, BudiPemanggilan catat() tanpa argumen sah dan menghasilkan array kosong, bukan error. Parameter variadik dengan demikian tidak menuntut minimal satu nilai, dan validasi jumlah argumen dilakukan sendiri oleh isi closure bila diperlukan. Pada closure kedua, parameter tetap awalan menampung argumen pertama, sedangkan sisa argumen dikemas ke dalam parameter variadik kata.
Lingkup yang ditangkap#
Closure menangkap variabel lokal yang terlihat pada saat closure itu dideklarasikan, dan penangkapan itu berlangsung secara referensi, bukan salinan nilai. Konsekuensinya berlaku dua arah. Perubahan variabel dari luar terlihat oleh closure, dan perubahan yang dilakukan closure terlihat oleh kode di luarnya. Perilaku ini berbeda dari aturan effectively final pada lambda Java, yang melarang variabel lokal yang ditangkap untuk diubah.
// Variabel lokal ditangkap closure secara referensi
def penghitung = 0
def tambah = { penghitung += 1 }
tambah()
tambah()
tambah()
println "Penghitung : $penghitung"
def nama = 'Citra'
def sambut = { "Halo, $nama" }
println "Sebelum : ${sambut()}"
nama = 'Dewi'
println "Sesudah : ${sambut()}"Penghitung : 3
Sebelum : Halo, Citra
Sesudah : Halo, DewiKeluaran tersebut menunjukkan dua arah penangkapan. Closure tambah mengubah variabel penghitung milik skrip, dan hasilnya bertahan setelah pemanggilan selesai. Closure sambut membaca variabel nama, dan penugasan ulang nama setelah closure dibuat tetap memengaruhi hasil pemanggilannya.
Selain variabel lokal, closure juga membawa owner, yaitu objek yang melingkupinya secara langsung. Lingkup itu berupa objek skrip ketika closure ditulis di tingkat atas, atau kelas dan closure lain ketika closure dideklarasikan di dalamnya. Pada closure yang bersarang, owner closure dalam adalah closure luarnya, dan hal itu dapat diperiksa dengan perbandingan identitas is.
def luar = {
def dalam = { owner }
dalam()
}
println luar().is(luar)truePemanggilan luar() mengembalikan hasil dalam(), yaitu objek closure dalam itu sendiri. Pembanding is menghasilkan true, yang berarti owner milik closure dalam adalah closure luar, bukan objek skrip yang melingkupi keduanya. Di titik ini owner berbeda dari thisObject, karena thisObject closure dalam tetap menunjuk objek skrip, sedangkan owner berhenti pada closure yang langsung membungkusnya.
Kesalahan umum#
Kesalahan pertama adalah memberikan argumen pada closure yang daftar parameternya dideklarasikan kosong. Groovy memeriksa jumlah argumen lebih dahulu dan melempar MissingMethodException, karena tidak ada bentuk pemanggilan yang cocok. Perbaikannya adalah menghapus argumen yang berlebih atau mengganti daftar { -> } dengan daftar parameter yang sesuai kebutuhan.
def tanpaParameter = { -> 'cukup' }
try {
tanpaParameter(1)
} catch (MissingMethodException e) {
println "Gagal: ${e.getClass().simpleName}"
}Gagal: MissingMethodExceptionKesalahan kedua adalah memakai it di dalam closure yang parameternya dideklarasikan kosong. Karena daftar { -> } menghapus parameter implisit, nama it tidak lagi terdefinisi, sehingga pemanggilan closure melempar MissingPropertyException. Perbaikannya adalah mendeklarasikan parameter secara eksplisit bila nilai masukan memang diperlukan.
def rusak = { -> it }
try {
rusak()
} catch (MissingPropertyException e) {
println "Gagal: ${e.getClass().simpleName}"
}Gagal: MissingPropertyExceptionRingkasan#
- Closure adalah objek
groovy.lang.Closureyang dapat disimpan, dipanggil dengan()ataucall, dan selalu mengembalikan nilai pernyataan terakhirnya. - Closure tanpa daftar parameter memiliki parameter implisit
it, sedangkan{ -> }menolak seluruh argumen. - Parameter terakhir berbentuk
Tipe...atau array menampung jumlah argumen yang tidak tetap. - Variabel lokal ditangkap secara referensi, sehingga perubahan dua arah antara closure dan lingkupnya saling terlihat, dan
ownerclosure bersarang adalah closure yang melingkupinya.
Referensi#
- Closures — Apache Groovy
— dokumentasi resmi closure: sintaks parameter, parameter implisit
it, varargs, serta properti owner dan delegate.