Kita telah melihat dua dunia: classpath yang menyatukan seluruh JAR dalam satu kantong class, dan module path yang menyusun module bernama dengan kontrak eksplisit. Kabar baiknya, keduanya tidak saling meniadakan — JVM modern menjalankan keduanya sekaligus, dalam satu proses yang sama, dengan aturan pergaulan yang jelas. Bab ini membedah dua mode tersebut: bagaimana kode classpath hidup di unnamed module, mengapa strong encapsulation tetap melindungi JDK dari kode classpath, bagaimana classpath dan module path bisa dicampur, dan bagaimana proyek lawas berpindah ke module system selangkah demi selangkah.
Dua Mode Pemuatan Class #
Perbedaannya berawal dari opsi launcher. Opsi -cp/--class-path memuat class dan JAR ke unnamed module — mode lawas yang tetap didukung penuh. Opsi --module-path (-p) menyusun JAR menjadi module graph; JAR ber-module-info menjadi module bernama, dan peluncuran menyasar module utama lewat --module. Sisi perbandingannya:
| Aspek | Classpath | Module path |
|---|---|---|
| Unit organisasi | JAR dan direktori class | module bernama |
| Visibilitas | semua class public terbuka | hanya package yang diekspor |
| Dependensi | tidak dinyatakan | requires eksplisit, dicek saat launch |
| Package ganda | dimungkinkan (yang dimuat menang) | ditolak sejak kompilasi |
| Reflection ke internal | bebas | ditolak kecuali opens |
Mode aktif ditentukan cara peluncuran: java -cp kelas Aplikasi berjalan di mode classpath, java --module-path mods --module m/a.C berjalan di mode module. Keduanya bisa hadir bersamaan — detail di bagian akhir chapter.
Modul Tak Bernama #
Ketika JVM memuat class dari classpath, class tersebut ditempatkan di unnamed module — satu module khusus tanpa nama yang dibuat class loader aplikasi. Program berikut memeriksa kedudukan class-nya sendiri:
public class ModulTakBernama {
public static void main(String[] args) {
Module sendiri = ModulTakBernama.class.getModule();
Module javaBase = String.class.getModule();
System.out.println("class ini dijalankan lewat classpath");
System.out.println("nama module sendiri : " + sendiri.getName());
System.out.println("module sendiri bernama? " + sendiri.isNamed());
System.out.println("String berasal dari : " + javaBase.getName());
System.out.println("java.lang diekspor? : " + javaBase.isExported("java.lang"));
}
}Output yang Dihasilkan:
class ini dijalankan lewat classpath
nama module sendiri : null
module sendiri bernama? false
String berasal dari : java.base
java.lang diekspor? : trueBedah hasilnya: getName() mengembalikan null karena unnamed module memang tak bernama; isNamed() menegaskan hal yang sama secara eksplisit; sementara String tetap berasal dari java.base yang mengekspor java.lang — kode classpath tidak kehilangan akses ke API standar.
Hak istimewa unnamed module cukup lapang: module ini membaca semua module lain dalam graf, sehingga kode classpath bebas memakai seluruh API yang diekspor module platform. Namun unnamed module sendiri tidak mengekspor apa pun secara formal — tidak ada module bernama yang bisa requires module tanpa nama. Arahnya searah: classpath boleh memanggil dunia module, dunia module tidak memanggil classpath.
Strong Encapsulation Tetap Berlaku #
Sisi pelindungnya tetap aktif untuk kode classpath. Package internal JDK tidak diekspor java.base kepada siapa pun, termasuk unnamed module — dan compiler menegakkannya sejak kompilasi. Bukti kecil yang gagal dikompilasi:
public class AksesInternalGagal {
public static void main(String[] args) {
System.out.println(jdk.internal.misc.VM.isBooted());
}
}Pesan gagalnya kita verifikasi langsung di JDK 25:
AksesInternalGagal.java:3: error: package jdk.internal.misc is not visible
System.out.println(jdk.internal.misc.VM.isBooted());
^
(package jdk.internal.misc is declared in module java.base, which does not export it to the unnamed module)Sebelum era module, kode bisa menjamah sun.misc.* dan detail internal JDK lainnya — pemasangan patch tidak resmi yang patah setiap JDK membarui dirinya. Strong encapsulation menutup pintu itu bagi semua mode, termasuk classpath. Jalur yang tersisa bagi API internal resmi (sun.misc.Unsafe dan beberapa tetangganya) sengaja dibuka hanya ke module tertentu di JDK, bukan ke kode kita.
Mencampur Classpath dan Module Path #
Aplikasi classpath tetap boleh memanfaatkan JAR modular dari pihak lain. Skenario uji kita: perpus-inti.jar berisi class com.perpus.inti.Buku dengan module-info — JAR modular sejati — lalu aplikasi biasa di classpath mencoba memakainya:
java --module-path perpus-inti.jar AplikasiKelas.javaSumber AplikasiKelas.java mengimpor com.perpus.inti.Buku dan mencetak hasilnya. Percobaan pertama di atas gagal dikompilasi:
AplikasiKelas.java:1: error: package com.perpus.inti is not visible
import com.perpus.inti.Buku;
^
(package com.perpus.inti is declared in module perpus.inti, which is not in the module graph)Alasannya: peluncuran mode classpath hanya meresolverkan himpunan module standar — JAR di module path belum diminta siapa pun sehingga tidak masuk graf. Tambahkan permintaan eksplisit dan semuanya berjalan:
java --module-path perpus-inti.jar --add-modules perpus.inti AplikasiKelas.javaOutput yang Dihasilkan:
Buku di classpath membaca modul: perpus.inti
Judul: Belajar JavaOpsi --add-modules memaksa module perpus.inti ikut terresolver, unnamed module membacanya, dan class Buku pun bisa diimpor. Sebaliknya, arah yang jauh lebih umum: JAR tanpa module-info diletakkan di module path — JVM menaikkannya menjadi automatic module: nama module diturunkan dari nama berkas (tanda hubung menjadi titik, bagian versi dibuang — perpus-inti.jar menjadi perpus.inti), seluruh package-nya dianggap diekspor dan dibuka, sehingga module modular lain bisa requires perpus.inti kepadanya. Inilah jembatan yang membuat dua dunia saling menoleh.
Jalur Migrasi Bertahap #
JEP 261 dirancang agar migrasi bisa bertahap, bukan serba sekaligus. Resep yang lazim di proyek nyata: pertama, biarkan seluruh aplikasi di classpath dan pastikan berjalan di JDK modern. Kedua, pecah kode menjadi JAR-JAR per bagian (biasanya sudah dilakukan build tool). Ketiga, mulai dari bagian paling bawah — library utilitas tanpa dependensi berat — beri module-info.java, taruh di module path; sisanya tetap di classpath dan otomatis menjadi unnamed module yang membacanya. Keempat, naik lapisan demi lapisan; JAR pihak ketiga yang belum modular cukup dibiarkan sebagai automatic module. Tool jdeps membantu memetakan ketergantungan sebelum setiap langkah, termasuk memberi rekomendasi requires untuk module-info yang akan dibuat.
Dengan kedua mode di tangan, kita sampai di ujung perjalanan Section ini: merangkai semua keping — package, access control, JAR, module — menjadi satu library Java yang pantas dibagikan ke dunia. Chapter penutup berikutnya mempraktikkan seluruh alur dari kode sampai rilis bernomor versi.
Referensi #
- JEP 261: Module System (classpath, unnamed module, automatic module)
- Oracle: java — Launcher JDK 25 (opsi –module-path, –add-modules)
- dev.java: Java Modules
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
AksesInternalGagal.java
Unduh berkaspublic class AksesInternalGagal {
public static void main(String[] args) {
System.out.println(jdk.internal.misc.VM.isBooted());
}
}
ModulTakBernama.java
Unduh berkaspublic class ModulTakBernama {
public static void main(String[] args) {
Module sendiri = ModulTakBernama.class.getModule();
Module javaBase = String.class.getModule();
System.out.println("class ini dijalankan lewat classpath");
System.out.println("nama module sendiri : " + sendiri.getName());
System.out.println("module sendiri bernama? " + sendiri.isNamed());
System.out.println("String berasal dari : " + javaBase.getName());
System.out.println("java.lang diekspor? : " + javaBase.isExported("java.lang"));
}
}