Seluruh kelas pada JDK (Java Development Kit) dapat dipanggil langsung dari kode Clojure tanpa lapisan pembungkus. Topik ini membahas sintaks pemanggilan Java dari sisi Clojure: form titik untuk metode instance, akses field instance, pemanggilan member static, dua bentuk pemanggilan konstruktor, pernyataan import yang meringkas nama kelas, macro doto untuk perantaian metode, serta konversi dua arah antara koleksi Clojure dan koleksi Java. Contoh terakhir menyusun kalkulator kecil yang menggabungkan Integer/parseInt dengan kelas Math sehingga seluruh sintaks digunakan dalam satu program utuh.
Form titik untuk metode instance#
Metode instance dipanggil dengan form titik, yaitu list yang elemen pertamanya tanda titik diikuti nama metode, elemen keduanya objek target, dan elemen berikutnya argumen metode. Bentuk umumnya (.metode objek arg1 arg2), sehingga pemanggilan substring atas string Java ditulis (.substring "kalkulator" 0 4). Objek target selalu berada di posisi kedua, dan jumlah argumen mengikuti signature metode pada kelasnya. Tiga pemanggilan berikut menggunakan metode milik kelas String.
(println (.toUpperCase "teks"))
(println (.substring "kalkulator" 0 4))
(println (.toUpperCase (.substring "kalkulator" 0 4)))TEKS
kalk
KALKForm ketiga menunjukkan perantaian lewat penyarangan: hasil (.substring "kalkulator" 0 4) menjadi target (.toUpperCase ...), sama seperti penulisan "kalkulator".substring(0, 4).toUpperCase() pada Java. Setiap form titik mengembalikan nilai kembalian metode yang dipanggil, sehingga hasilnya dapat langsung digunakan oleh form lain tanpa variabel perantara. Nama metode ditulis apa adanya sesuai kelas Java, termasuk bentuk seperti toUpperCase atau parseInt yang memang ditulis tanpa pemisah kata.
Member static#
Member static, baik field maupun metode, dipanggil dengan sintaks garis miring Kelas/member. Field static diakses tanpa kurung, misalnya Math/PI, sedangkan metode static dipanggil seperti fungsi biasa dengan nama kelas menggantikan posisi nama fungsi, misalnya (Math/sqrt 16). Sintaks ini membedakan member milik kelas dari fungsi Clojure pada pembacaan pertama, karena garis miring tidak pernah muncul pada nama fungsi inti. Kelas pada paket java.lang seperti Math dan Integer cukup ditulis dengan nama pendeknya karena paket tersebut dimuat sejak awal, sedangkan kelas lain ditulis lengkap dengan paketnya atau diimpor terlebih dahulu.
(println Math/PI)
(println (Math/sqrt 16))
(println (Math/pow 2 10))
(println (Integer/parseInt "42"))3.141592653589793
4.0
1024.0
42Math/sqrt dan Math/pow mengembalikan double, sehingga hasilnya tampil dengan titik desimal seperti 4.0 dan 1024.0 meskipun nilai yang dimasukkan bulat sempurna. Integer/parseInt mengubah teks menjadi bilangan int dan menjadi utilitas yang sering digunakan ketika masukan program datang dalam bentuk string. Ketiga pemanggilan di atas berjalan tanpa deklarasi apa pun karena Math dan Integer berada di java.lang, dan inilah bentuk interop yang paling sering muncul pada kode sehari-hari.
Field instance dan konstruktor#
Field instance publik diakses dengan form titik tanpa argumen, yaitu (.field objek). Objek Java baru dibuat melalui konstruktor dengan dua bentuk yang setara: nama kelas diakhiri titik seperti (java.util.ArrayList.), atau form new seperti (new java.util.ArrayList). Bentuk titik akhir lebih ringkas dan menjadi konvensi yang lazim. Konstruktor berargumen menerima argumennya tepat seperti pemanggilan fungsi.
(def p (java.awt.Point. 3 4))
(println "koordinat x:" (.x p))
(println "koordinat y:" (.y p))
(println (str (java.util.ArrayList. [1 2 3])))
(println (str (new java.util.ArrayList [1 2 3])))koordinat x: 3
koordinat y: 4
[1, 2, 3]
[1, 2, 3]Kelas java.awt.Point menyimpan sepasang koordinat pada field publik x dan y, sehingga keduanya terbaca langsung dengan (.x p) dan (.y p). Konstruktor ArrayList menerima java.util.Collection, dan vector Clojure mengimplementasikan interface tersebut, sehingga vector [1 2 3] dapat dikirim begitu saja ke konstruktornya. Fungsi str digunakan pada dua baris terakhir karena str memanggil toString milik objek Java dan menghasilkan cetakan gaya Java yang stabil.
Import ringan#
Kelas dengan nama paket lengkap dapat langsung digunakan tanpa deklarasi apa pun, seperti java.util.ArrayList pada bagian sebelumnya. Pernyataan import meringkas penulisan berulang dengan mengikat nama pendek kelas ke namespace saat ini. Satu kelas diimpor dengan (import java.util.ArrayList), sedangkan beberapa kelas dari satu paket diimpor sekaligus dengan bentuk (import (java.util ArrayList HashMap)). Import hanya kenyamanan penulisan dan tidak memuat pustaka tambahan, karena seluruh kelas JDK sudah tersedia pada classpath sejak JVM dimulai.
(import (java.util ArrayList HashMap))
(println (count (ArrayList.)))
(println (str (HashMap. {"satu" 1 "dua" 2})))0
{dua=2, satu=1}Setelah import, ArrayList. dan HashMap. ditulis tanpa awalan paket. Perhatikan urutan cetak HashMap: entri ditampilkan mengikuti susunan internal tabel hash milik Java, bukan urutan penulisannya, sehingga dua muncul sebelum satu. Perilaku ini dibawa apa adanya dari Java karena HashMap memang tidak menjamin urutan entri. Bila urutan penyisipan penting, kelas java.util.LinkedHashMap yang menjamin urutan sesuai penyisipan dapat digunakan dengan sintaks yang sama.
Perantaian metode dengan doto#
Perantaian lewat penyarangan hanya nyaman untuk metode yang mengembalikan objek berikutnya. Metode mutator seperti ArrayList.add mengembalikan boolean, sehingga hasil penyarangannya bukan lagi objek daftar yang dapat dilanjutkan. Macro doto menjawab kebutuhan ini: setiap form di dalam doto menerima objek target sebagai argumen pertamanya secara otomatis, dan doto mengembalikan objek target itu sendiri setelah seluruh form selesai dijalankan.
(def antrian
(doto (java.util.ArrayList.)
(.add "A-101")
(.add "A-102")
(.add "A-103")))
(println (str antrian))
(println (seq antrian))[A-101, A-102, A-103]
(A-101 A-102 A-103)Di dalam doto, form (.add "A-101") dijabarkan menjadi (.add antrian "A-101"), sehingga tiga pemanggilan add mengisi antrian tanpa satu pun nilai boolean yang perlu ditampung. Baris terakhir memanggil seq atas ArrayList untuk menghasilkan seq Clojure yang dicetak sebagai list. Pola doto berlaku untuk kelas Java apa pun yang memerlukan deretan perintah pengaturan, dan objek targetnya cukup disebut satu kali di awal.
Konversi antara koleksi Clojure dan Java#
Koleksi Java dikonversi ke sisi Clojure melalui fungsi yang menghasilkan koleksi persistent, yaitu into, vec, atau seq. Hasilnya adalah koleksi Clojure biasa, sehingga map, filter, dan reduce langsung berlaku atasnya. Arah sebaliknya juga mudah: konstruktor kelas koleksi Java menerima koleksi Clojure apa pun yang mengimplementasikan java.util.Collection, dan fungsi to-array mengubah koleksi menjadi array Object ketika API Java menuntut array. Fungsi java.util.Arrays/asList melengkapi dengan membungkus array menjadi java.util.List dalam satu panggilan.
(def daftar-java
(doto (java.util.ArrayList.)
(.add 3)
(.add 1)
(.add 2)))
(println (into [] daftar-java))
(println (seq daftar-java))
(def daftar-clj [3 1 2])
(println (str (java.util.ArrayList. daftar-clj)))
(println (str (java.util.Arrays/asList (to-array daftar-clj))))[3 1 2]
(3 1 2)
[3, 1, 2]
[3, 1, 2]Dua baris pertama menunjukkan arah Java ke Clojure: into menghasilkan vector dan seq menghasilkan list, keduanya immutable. Dua baris terakhir menunjukkan arah sebaliknya, yaitu vector dikirim ke konstruktor dan ke to-array. Konversi menghasilkan salinan, bukan penghubung dua arah: perubahan pada ArrayList setelah into tidak tercermin pada vector hasilnya, dan sebaliknya. Salinan eksplisit seperti ini mempertahankan sifat immutable pada sisi Clojure, sehingga kedua sisi tidak saling mengubah isi secara diam-diam.
Contoh kecil: kalkulator teks#
Contoh penutup menggabungkan pemanggilan static dengan alur program kecil yang utuh. Fungsi hitung menerima nama operasi dan dua bilangan dalam bentuk teks, mengubah keduanya dengan Integer/parseInt, lalu memilih rumusnya dengan case. Operasi pangkat dan akar diambil dari kelas Math, sedangkan jumlah dan selisih menggunakan aritmetika Clojure biasa atas int hasil parse. Bentuk seperti ini lazim ditemui ketika program menerima masukan teks dari berkas atau layanan lain.
(defn hitung
[operasi x y]
(let [a (Integer/parseInt x)
b (Integer/parseInt y)]
(case operasi
"jumlah" (+ a b)
"selisih" (- a b)
"pangkat" (Math/pow a b)
"akar" (Math/sqrt (+ a b)))))
(println "48 + 13 =" (hitung "jumlah" "48" "13"))
(println "48 - 13 =" (hitung "selisih" "48" "13"))
(println "2 pangkat 10 =" (hitung "pangkat" "2" "10"))
(println "akar 61 =" (hitung "akar" "48" "13"))48 + 13 = 61
48 - 13 = 35
2 pangkat 10 = 1024.0
akar 61 = 7.810249675906654Form case mencocokkan string operasi satu per satu dan mengembalikan hasil pemanggilan yang sesuai. Dua baris terakhir keluaran memperlihatkan akibat tipe kembalian Java: Math/pow menghasilkan 1024.0, dan Math/sqrt atas 61 menghasilkan desimal panjang karena double memang menyimpan nilai mendekati, bukan persis. Apabila operasi tidak dikenal, case melempar error karena tidak ada klausa cadangan, dan klausa default dapat ditambahkan sebagai ekspresi terakhir bila diperlukan.
Kesalahan umum#
- Menulis pemanggilan metode tanpa titik, misalnya
(toUpperCase "teks"). Clojure mencarinya sebagai fungsi biasa dan melaporkan errorUnable to resolve symbol: toUpperCase. Metode instance selalu ditulis dengan titik, dan member static dengan garis miring. - Memanggil metode instance atas nil, misalnya
(.toUpperCase nil). Tidak ada objek target sehingga JVM melempar NullPointerException. Pastikan targetnya ada sebelum dipanggil, atau tangani exception dengan try. - Menulis kelas di luar java.lang tanpa paketnya, misalnya
(ArrayList.)tanpa import. Compiler melaporkan errorUnable to resolve classname: ArrayList. Gunakan nama lengkap dengan paketnya atau tambahkan import terlebih dahulu. - Memanggil
Integer/parseIntatas teks yang bukan angka, misalnya"12a". JVM melempar NumberFormatException dan program berhenti pada baris itu. Validasi bentuk masukan terlebih dahulu atau tangkap exception secara eksplisit.
Ringkasan#
- Metode instance dipanggil dengan form titik
(.metode objek args), member static denganKelas/member, dan field instance dengan(.field objek). - Konstruktor ditulis dengan titik akhir
(java.util.ArrayList.)atau formnew, keduanya setara, dan import mengikat nama pendek kelas pada namespace. - doto menyuntikkan objek target ke setiap form dan mengembalikan objek itu sendiri, sehingga cocok untuk deretan perintah yang nilai baliknya tidak terpakai.
- Koleksi Java dikonversi ke koleksi Clojure dengan into, vec, atau seq, sedangkan koleksi Clojure dikirim ke konstruktor Java atau diubah dengan to-array.
Referensi#
- Java Interop — Clojure — Referensi resmi seluruh sintaks interop, meliputi form titik, member static, konstruktor, import, dan doto.
- Java SE 21 API Specification — Oracle — Dokumentasi kelas JDK seperti String, Math, Integer, dan ArrayList yang dipanggil pada topik ini.