Setiap class kini hidup rapi di dalam package, tetapi konsekuensinya muncul segera: class dari package lain tidak lagi bisa dipanggil dengan nama sederhananya. Java menyediakan tiga cara untuk merujuk class lintas package — single-type import, on-demand import, dan fully qualified name — dan ketiganya punya tempat masing-masing dalam kode sehari-hari. Pada Chapter ini kita akan membahas cara kerja ketiganya, batasan on-demand import yang sering mengejutkan pemula, serta cara keluar dari konflik nama ketika dua package menyediakan class bernama sama.

Cara Import Bekerja #

Pernyataan import tidak menyalin kode apa pun ke berkas kita — import hanyalah instruksi untuk compiler javac agar nama sederhana dipetakan ke nama lengkap tertentu saat resolusi nama. Ketika compiler membaca List, import java.util.List membuat compiler menerjemahkannya menjadi java.util.List di bytecode hasil kompilasi. Dengan demikian, import murni urusan waktu kompilasi dan tidak memengaruhi ukuran maupun kecepatan program saat berjalan.

Satu pengecualian membuat hidup kita mudah: isi package java.lang (String, Integer, System, Math, dan kawan-kawannya) diimpor secara otomatis ke setiap berkas sumber. Itulah sebabnya kita bisa menulis System.out.println sejak program pertama tanpa satu baris import. Seluruh package lain — termasuk java.util dan java.io — harus diimpor secara eksplisit.

Single-Type Import #

Bentuk paling lazim menyebut satu class secara utuh: import java.util.ArrayList;. Satu berkas boleh memuat banyak single-type import, dan kebiasaan umum di komunitas Java adalah mengimpornya satu per satu, bukan memakai tanda bintang. Contoh berikut memakai dua single-type import untuk membangun daftar belanja:

import java.util.ArrayList;
import java.util.List;

public class DaftarBelanja {
    public static void main(String[] args) {
        List<String> belanja = new ArrayList<>();
        belanja.add("Beras 5 kg");
        belanja.add("Minyak goreng 1 L");
        belanja.add("Gula 1 kg");

        System.out.println("Daftar belanja Budi:");
        for (int i = 0; i < belanja.size(); i++) {
            System.out.println((i + 1) + ". " + belanja.get(i));
        }
    }
}

Output yang Dihasilkan:

text
Daftar belanja Budi:
1. Beras 5 kg
2. Minyak goreng 1 L
3. Gula 1 kg

Mari kita telaah baris-baris kuncinya:

  1. Baris 1-2 (import java.util.ArrayList; dan import java.util.List;): dua single-type import — nama List dipetakan ke java.util.List, dan ArrayList ke java.util.ArrayList. Compiler menolak dua single-type import yang memetakan nama sederhana sama.
  2. Baris 6 (List<String> belanja = new ArrayList<>();): tipe List pada sisi kiri dan ArrayList pada sisi kanan dikenali berkat pemetaan import; tanpa import, baris ini harus ditulis java.util.List<String> belanja = new java.util.ArrayList<>();.
  3. Baris 12 (belanja.get(i)): method get milik interface List mengembalikan elemen sesuai indeks — pola import memudahkan kode tetap fokus pada logika, bukan pada nama panjang.

On-Demand Import dan Batasannya #

Bentuk kedua mengimpor seluruh class dalam satu package sekaligus dengan tanda bintang: import java.util.*;.Compiler kemudian mencari nama sederhana yang belum terdefinisi di berkas tersebut di seluruh class package java.util. Bentuk ini memangkas baris import, tetapi ada satu batasan yang wajib dipahami: on-demand import tidak mengikutsertakan subpackage. Package java.util.function adalah package tersendiri, bukan bagian dari java.util, sehingga class Predicate tidak ikut terimpor. Contoh berikut sengaja memuat kesalahan itu:

java ImporMassal.java
import java.util.*;

public class ImporMassal {
    public static void main(String[] args) {
        List<String> nama = new ArrayList<>();
        nama.add("Budi");

        Predicate<String> panjang = s -> s.length() > 2;
        System.out.println(nama + " -> " + panjang.test("Siti"));
    }
}

Kompilasi berkas tersebut gagal dengan pesan yang kita verifikasi sendiri:

text
ImporMassal.java:8: error: cannot find symbol
        Predicate<String> panjang = s -> s.length() > 2;
               ^
  symbol:   class Predicate
  location: class ImporMassal

Nama List dan ArrayList lolos karena memang anggota java.util, sedangkan Predicate tidak ditemukan — kita perlu satu baris tambahan import java.util.function.Predicate;. Inilah alasan utama banyak tim melarang on-demand import: pembaca kode tidak bisa melihat dari package mana sebuah class berasal hanya dengan membaca daftar import.

Nama Memenuhi dan Konflik Nama #

Ketika dua package menyediakan class dengan nama sederhana sama, single-type import hanya boleh memilih salah satu. Kasus klasiknya adalah java.util.Date versus java.sql.Date — keduanya bernama Date tetapi digunakan untuk keperluan berbeda. Solusinya: impor salah satunya, dan rujuk yang lain dengan fully qualified name alias nama memenuhi yang dituliskan langsung di tempat pemakaian:

import java.time.LocalDate;
import java.util.Date;

public class KonflikTanggal {
    public static void main(String[] args) {
        LocalDate lahir = LocalDate.of(2001, 5, 17);

        Date tanggalUtil = Date.from(
                lahir.atStartOfDay(java.time.ZoneId.of("Asia/Jakarta")).toInstant());
        java.sql.Date tanggalSql = java.sql.Date.valueOf(lahir);

        System.out.println("java.util.Date : " + tanggalUtil);
        System.out.println("java.sql.Date  : " + tanggalSql);
    }
}

Output yang Dihasilkan:

text
java.util.Date : Thu May 17 00:00:00 WIB 2001
java.sql.Date  : 2001-05-17

Mari kita bedah baris-baris pentingnya:

  1. Baris 2 (import java.util.Date;): nama sederhana Date kini resmi merujuk java.util.Date di seluruh berkas ini.
  2. Baris 11 (java.sql.Date tanggalSql = java.sql.Date.valueOf(lahir);): class Date kedua dipakai dengan nama lengkapnya secara langsung — penulisan nama memenuhi tidak memerlukan import dan selalu sah di posisi mana pun.
  3. Dua baris System.out.println: hasil cetak memperlihatkan perbedaan representasi — java.util.Date mencetak momen waktu lengkap dengan zona, sedangkan java.sql.Date (turunan java.util.Date untuk kolom tanggal SQL) hanya mencetak bagian tanggalnya.

Nama memenuhi juga berguna untuk pemakaian sekali pakai tanpa menambah baris import, misalnya java.util.concurrent.TimeUnit.SECONDS.sleep(500) di satu titik kode.

Import untuk Member Static #

Selain class dan interface, import juga bisa menyasar member static — method dan konstanta. Bentuknya import static java.lang.Math.sqrt; (single) atau import static java.lang.Math.*; (on-demand) sehingga sqrt(2) bisa ditulis tanpa awalan Math.. Karena topik ini berkaitan erat dengan member static dan keterbacaan kode, kita bahas tuntas pada Chapter Static Import.

Aturan Penulisan yang Rapi #

Konvensi penulisan import yang dipegang luas: letakkan seluruh import setelah deklarasi package, urutkan berdasarkan package dengan import static di kelompok terpisah, dan hapus import yang tidak terpakai — IDE modern melakukannya otomatis. Compiler hanya menolak import yang bentrok nama sederhananya, bukan import yang tidak terpakai; kode yang bersih tetap tanggung jawab kita.

Kita sudah bisa memanggil class lintas package dengan tiga gaya. Namun nama package sendiri tidak boleh asal pilih — ada konvensi domain terbalik yang dipakai seluruh ekosistem Java, dan itulah topik Chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

DaftarBelanja.java

Unduh berkas
java
import java.util.ArrayList;
import java.util.List;

public class DaftarBelanja {
    public static void main(String[] args) {
        List<String> belanja = new ArrayList<>();
        belanja.add("Beras 5 kg");
        belanja.add("Minyak goreng 1 L");
        belanja.add("Gula 1 kg");

        System.out.println("Daftar belanja Budi:");
        for (int i = 0; i < belanja.size(); i++) {
            System.out.println((i + 1) + ". " + belanja.get(i));
        }
    }
}

ImporMassal.java

Unduh berkas
java
import java.util.*;

public class ImporMassal {
    public static void main(String[] args) {
        List<String> nama = new ArrayList<>();
        nama.add("Budi");

        Predicate<String> panjang = s -> s.length() > 2;
        System.out.println(nama + " -> " + panjang.test("Siti"));
    }
}

KonflikTanggal.java

Unduh berkas
java
import java.time.LocalDate;
import java.util.Date;

public class KonflikTanggal {
    public static void main(String[] args) {
        LocalDate lahir = LocalDate.of(2001, 5, 17);

        Date tanggalUtil = Date.from(
                lahir.atStartOfDay(java.time.ZoneId.of("Asia/Jakarta")).toInstant());
        java.sql.Date tanggalSql = java.sql.Date.valueOf(lahir);

        System.out.println("java.util.Date : " + tanggalUtil);
        System.out.println("java.sql.Date  : " + tanggalSql);
    }
}