Tampilan
Kenapa penting
Contoh-contoh sejauh ini ditulis dalam satu file tanpa package. Aplikasi backend nyata berisi ratusan sampai ribuan class — controller, service, repository, DTO — yang ditulis banyak orang. Tanpa pengelompokan, nama class mudah bentrok, detail internal sebuah fitur bisa dipakai sembarangan oleh fitur lain, dan developer baru tersesat mencari kode. Package menyelesaikan ketiga masalah itu sekaligus. Begitu kamu membuka project Spring Boot pertama, hal yang pertama terlihat adalah struktur package-nya, jadi bab ini bekal penting sebelum masuk ke collections dan framework.
Konsep inti
Package: namespace dan batas akses
Package adalah "nama keluarga" untuk class. Nama lengkap (fully qualified name) sebuah class adalah nama package ditambah nama class, misalnya java.util.Arrays atau id.jalurjava.m02.BankAccount dari latihanmu. Package punya dua fungsi:
- Namespace — dua class boleh bernama sama asalkan package-nya berbeda (
java.util.Datedanjava.sql.Date). - Batas akses — class, method, atau field tanpa access modifier (package-private) hanya terlihat dari package yang sama. Ini alat encapsulation di level fitur, bukan hanya di level class.
Konvensinya: huruf kecil semua, diawali domain terbalik milik organisasi agar unik di seluruh dunia — id.jalurjava, com.example.shop. Baris package ditulis paling atas file, dan folder harus sama dengan nama package: class id.toko.order.OrderService disimpan di .../id/toko/order/OrderService.java.
Contoh multi-package
Struktur berikut memisahkan katalog produk dari pemesanan:
text
src/id/toko/catalog/Product.java
src/id/toko/catalog/PromoRules.java
src/id/toko/catalog/Catalog.java
src/id/toko/order/OrderService.javajava
// src/id/toko/catalog/Product.java
package id.toko.catalog;
public class Product {
private final String sku;
private final long price;
public Product(String sku, long price) {
this.sku = sku;
this.price = price;
}
public String sku() {
return sku;
}
public long price() {
return price;
}
long priceAfterPromo() { // package-private: hanya untuk package catalog
return PromoRules.apply(price);
}
}java
// src/id/toko/catalog/PromoRules.java
package id.toko.catalog;
class PromoRules { // package-private: detail internal katalog
static long apply(long price) {
return price >= 100_000 ? price - 10_000 : price;
}
}java
// src/id/toko/catalog/Catalog.java
package id.toko.catalog;
public class Catalog {
public static Product find(String sku) {
return new Product(sku, 125_000);
}
public static long sellingPrice(Product product) {
return product.priceAfterPromo(); // boleh: masih satu package
}
}java
// src/id/toko/order/OrderService.java
package id.toko.order;
import id.toko.catalog.Catalog;
import id.toko.catalog.Product;
public class OrderService {
public String checkout(String sku, int quantity) {
Product product = Catalog.find(sku);
long total = Catalog.sellingPrice(product) * quantity;
return "Pesanan " + product.sku() + " x" + quantity + " = Rp" + total;
}
public static void main(String[] args) {
OrderService service = new OrderService();
System.out.println(service.checkout("KOPI-250", 2));
}
}Program multi-file dengan package dikompilasi ke folder output, lalu dijalankan dengan classpath (-cp) yang menunjuk folder itu dan nama lengkap class yang punya main:
bash
javac -d out $(find src -name '*.java') # kompilasi semua file ke folder out/
java -cp out id.toko.order.OrderService # → Pesanan KOPI-250 x2 = Rp230000javac -d out membuat folder out/id/toko/catalog/ dan out/id/toko/order/ berisi file .class — struktur package ikut terbentuk. Classpath adalah daftar tempat JVM mencari class; Maven dan IDE mengaturnya otomatis untukmu.
Akses lintas package
OrderService hanya bisa memakai bagian public dari package catalog. Kalau ia mencoba menyentuh detail internal:
java
// Potongan: OrderService versi salah
import id.toko.catalog.PromoRules; // class package-private
// ...
return product.priceAfterPromo(); // method package-privatecompiler menolak keduanya:
text
error: PromoRules is not public in id.toko.catalog; cannot be accessed from outside package
error: priceAfterPromo() is not public in Product; cannot be accessed from outside packageInilah kekuatannya: tim katalog bebas mengubah PromoRules tanpa takut merusak fitur lain, karena tidak ada yang bisa memakainya dari luar. Ringkasan aturan akses dari bab Encapsulation, kini dengan sudut pandang package:
| Modifier | Class sama | Package sama | Subclass di package lain | Package lain |
|---|---|---|---|---|
public | ya | ya | ya | ya |
protected | ya | ya | ya (lewat pewarisan) | tidak |
| (package-private) | ya | ya | tidak | tidak |
private | ya | tidak | tidak | tidak |
import tunggal, wildcard, dan static import
import hanya menyingkat penulisan nama — ia tidak mengubah akses dan tidak "memuat" apa pun saat runtime.
- Import tunggal
import id.toko.catalog.Product;— jelas dan paling umum; IDE menambahkannya otomatis. - Wildcard
import java.util.*;— semua class di satu package (tidak termasuk sub-package). Banyak tim menghindarinya karena sumber sebuah nama jadi tidak jelas dan rawan bentrok. - Static import — memakai anggota
statictanpa awalan class:
java
import static java.lang.Math.max;
import static java.lang.Math.min;
public class StaticImportDemo {
static int clampPercent(int percent) {
return max(0, min(100, percent)); // tanpa awalan Math.
}
public static void main(String[] args) {
System.out.println(clampPercent(-5) + " " + clampPercent(40) + " " + clampPercent(150));
}
}Output: 0 40 100. Static import wajar untuk nama yang sangat dikenal, misalnya assertThat di file test latihan (import static org.assertj.core.api.Assertions.assertThat;). Di kode aplikasi, pakai seperlunya agar pembaca tetap tahu asal method.
Class di package java.lang (String, Math, Integer) tidak perlu di-import sama sekali.
Konflik nama
Bila dua class bernama sama dibutuhkan sekaligus, import salah satunya dan tulis yang lain dengan nama lengkap:
java
import java.util.Date;
public class NameConflict {
public static void main(String[] args) {
Date createdAt = new Date(); // java.util.Date dari import
java.sql.Date dueDate = java.sql.Date.valueOf("2026-11-30"); // nama lengkap (fully qualified)
System.out.println(createdAt.getClass().getName());
System.out.println(dueDate.getClass().getName());
System.out.println(dueDate);
}
}Output: java.util.Date, java.sql.Date, lalu 2026-11-30. Kalau kamu menulis import java.util.*; dan import java.sql.*; lalu memakai Date, compiler menolak dengan reference to Date is ambiguous.
Module import (Java 25) dan JPMS sekilas
Java 25 memfinalkan module import declaration (JEP 511): import module java.base; mengimpor semua package publik milik modul java.base sekaligus — praktis untuk skrip dan eksperimen.
java
import module java.base; // Java 25: semua package publik milik modul java.base
public class ModuleImportDemo {
public static void main(String[] args) {
BigDecimal price = new BigDecimal("19999.50"); // java.math
BigDecimal rounded = price.setScale(0, RoundingMode.HALF_UP); // java.math
int[] stock = {5, 1, 3};
Arrays.sort(stock); // java.util
System.out.println(rounded + " " + Arrays.toString(stock));
}
}Output: 20000 [1, 3, 5]. Modul sendiri (JPMS, module-info.java) adalah lapisan di atas package: kumpulan package yang menyatakan apa yang diekspor dan apa yang dibutuhkan. JDK sudah terbagi menjadi modul sejak Java 9, tetapi kebanyakan aplikasi Spring Boot masih berjalan di classpath biasa tanpa module-info.java. Cukup kenali istilahnya untuk sekarang.
Struktur project Maven
Project nyata (termasuk exercises/ milikmu) mengikuti tata letak standar Maven:
text
exercises/m02-oop/
├── pom.xml ← konfigurasi build & dependency
├── src/main/java/id/jalurjava/m02/BankAccount.java ← kode aplikasi
└── src/test/java/id/jalurjava/m02/BankAccountTest.java ← test, package samaTest berada di package yang sama dengan class yang diuji, tetapi di folder src/test/java. Akibatnya test bisa memakai anggota package-private tanpa membukanya menjadi public, sementara kode test tidak ikut terbawa ke aplikasi yang di-deploy. Detail Maven dibahas di M05.
Contoh di dunia backend
Ada dua cara umum membagi package aplikasi backend:
text
By layer By feature
id.toko.controller id.toko.order
OrderController OrderController
ProductController OrderService
id.toko.service OrderRepository
OrderService PriceCalculator ← package-private
ProductService id.toko.catalog
id.toko.repository ProductController
OrderRepository ProductService
ProductRepository ProductRepositoryBy layer terlihat rapi di awal, tetapi setiap fitur tersebar di tiga package dan semua class terpaksa public agar bisa saling memanggil. By feature mengelompokkan class yang berubah bersama: menambah fitur pembayaran cukup menambah package id.toko.payment, dan helper seperti PriceCalculator bisa dibuat package-private sehingga tidak bocor ke fitur lain. Banyak tim memakai by feature sebagai default dan baru memecah per layer di dalam fitur yang sudah besar. Pola ini juga memudahkan langkah berikutnya: fitur yang batasnya sudah jelas lebih mudah dipisah menjadi modul atau bahkan service tersendiri — topik di level Microservices.
Pertanyaan interview
1. Apa fungsi package di Java?
Package adalah namespace yang mencegah bentrok nama class dan sekaligus batas akses: anggota package-private hanya terlihat dari package yang sama. Package juga mengatur struktur folder project dan menjadi cara utama mengelompokkan kode berdasarkan fitur.
2. Apa beda package-private dan protected?
Package-private (tanpa modifier) hanya bisa diakses dari package yang sama. protected menambahkan akses dari subclass di package lain, tetapi hanya lewat pewarisan — class lain di package berbeda yang bukan subclass tetap tidak bisa.
3. Package by layer atau by feature — mana yang kamu pilih?
Untuk aplikasi yang akan tumbuh, by feature: class yang berubah bersama berada di satu tempat, detail internal bisa package-private, dan batas fitur terlihat jelas. By layer masih wajar untuk aplikasi kecil atau bila tim sudah punya konvensi; yang penting konsisten.
Jebakan umum
- Folder tidak sesuai nama package.
package id.toko.order;di folder lain membuat build gagal atau class tidak ditemukan. Biarkan IDE yang membuat package dan memindahkan file. - Membuat semua class
public"biar gampang". Detail internal jadi bisa dipakai fitur lain dan sulit diubah. Mulai dari package-private, buka seperlunya. - Wildcard import yang bertabrakan (
java.util.*+java.sql.*) →reference to Date is ambiguous. - Menjalankan class dengan nama pendek.
java -cp out OrderServicegagal; JVM butuh nama lengkapid.toko.order.OrderService. - Mengira
importmemuat kode atau memperlambat program. Import hanya urusan compiler untuk menyingkat nama.
Ringkasan
- Package = namespace + batas akses; nama huruf kecil, domain terbalik, dan folder harus sama dengan package.
importhanya menyingkat nama; pakai import tunggal, wildcard dengan hati-hati, static import seperlunya.- Konflik nama diselesaikan dengan nama lengkap;
java.langtidak perlu di-import. - Package-private melindungi detail fitur;
protectedmenambah akses untuk subclass di package lain. - Maven: kode di
src/main/java, test disrc/test/javadengan package yang sama; kompilasi manual denganjavac -ddanjava -cp. - Untuk aplikasi yang tumbuh, utamakan package by feature.