Konstanta #
Konstanta adalah nilai yang tidak berubah selama program berjalan. Di Java, konstanta bukan tipe khusus tersendiri — melainkan kombinasi dua kata kunci: static dan final. final mencegah nilai diubah setelah inisialisasi, sementara static memastikan hanya ada satu salinan untuk seluruh kelas. Menggunakan konstanta dengan nama yang bermakna — bukan angka atau string literal yang tersebar di seluruh kode — membuat kode lebih mudah dipahami, diubah, dan diuji. Artikel ini membahas cara mendeklarasikan dan menggunakan konstanta, kapan enum adalah pilihan yang lebih baik, anti-pattern yang harus dihindari, serta konstanta bawaan yang sudah tersedia di standard library Java.
Deklarasi Konstanta #
Konstanta di Java dideklarasikan dengan kombinasi public static final dan nama ditulis dalam UPPER_SNAKE_CASE. Konvensi penamaan ini adalah sinyal universal bagi developer Java bahwa nilai tersebut tidak akan berubah.
public class KonfigurasiAplikasi {
// Konstanta numerik
public static final int MAKS_PERCOBAAN_LOGIN = 3;
public static final int TIMEOUT_KONEKSI_MS = 5_000;
public static final double TARIF_PAJAK = 0.11; // PPN 11%
public static final long BATAS_UKURAN_FILE = 10 * 1024 * 1024; // 10 MB
// Konstanta String
public static final String CHARSET_DEFAULT = "UTF-8";
public static final String FORMAT_TANGGAL = "yyyy-MM-dd";
public static final String VERSI_API = "v2";
// Konstanta boolean
public static final boolean MODE_DEBUG = false;
}
Penggunaan di kelas lain:
// Akses via nama kelas — jelas dari mana konstanta berasal
if (percobaanLogin >= KonfigurasiAplikasi.MAKS_PERCOBAAN_LOGIN) {
kunciAkun(username);
}
long ukuranFile = file.length();
if (ukuranFile > KonfigurasiAplikasi.BATAS_UKURAN_FILE) {
throw new IllegalArgumentException("Ukuran file melebihi batas");
}
Mengapa Konstanta, Bukan Magic Number #
Magic number adalah angka atau string literal yang muncul langsung dalam kode tanpa penjelasan. Ini adalah salah satu anti-pattern paling umum di Java — dan konstanta adalah solusinya.
// ANTI-PATTERN: magic number — apa arti 3, 5000, 0.11?
public void prosesLogin(String user, String pass) {
if (percobaanGagal >= 3) { // ✗ kenapa 3?
kunciAkun(user);
}
}
public double hitungTotal(double harga) {
return harga + (harga * 0.11); // ✗ 0.11 adalah apa?
}
public boolean validasiFile(File f) {
return f.length() <= 10485760; // ✗ 10485760 bytes = berapa MB?
}
// BENAR: konstanta dengan nama yang bermakna
public void prosesLogin(String user, String pass) {
if (percobaanGagal >= MAKS_PERCOBAAN_LOGIN) { // ✓ jelas
kunciAkun(user);
}
}
public double hitungTotal(double harga) {
return harga + (harga * TARIF_PAJAK); // ✓ jelas
}
public boolean validasiFile(File f) {
return f.length() <= BATAS_UKURAN_FILE; // ✓ jelas
}
Keuntungan nyata dari konstanta dibanding magic number:
| Aspek | Magic Number | Konstanta |
|---|---|---|
| Keterbacaan | Pembaca harus menebak makna 0.11 | TARIF_PAJAK langsung menjelaskan diri |
| Pemeliharaan | Harus cari-ganti di seluruh codebase | Ubah di satu tempat, berlaku ke mana-mana |
| Keamanan | Rawan typo (0.011 vs 0.11) | Compiler tangkap jika nama salah |
| Dokumentasi | Tidak ada konteks | Nama + Javadoc bisa menjelaskan detail |
Inisialisasi: Tiga Cara #
Ada tiga tempat di mana final field bisa diinisialisasi, masing-masing untuk kebutuhan berbeda.
1. Langsung saat Deklarasi (Paling Umum) #
public class Matematika {
public static final double PI = 3.141592653589793;
public static final double E = 2.718281828459045;
public static final double SQRT2 = 1.4142135623730951;
}
2. Di Static Initializer Block #
Digunakan ketika nilai konstanta membutuhkan logika atau komputasi yang tidak bisa ditulis dalam satu ekspresi:
public class KonfigurasiSistem {
public static final String HOSTNAME;
public static final int PORT;
static {
// Baca dari environment variable, fallback ke default
String host = System.getenv("APP_HOST");
HOSTNAME = (host != null && !host.isEmpty()) ? host : "localhost";
String port = System.getenv("APP_PORT");
PORT = (port != null) ? Integer.parseInt(port) : 8080;
}
}
3. Di Konstruktor (Blank Final) #
final instance field yang tidak diinisialisasi saat deklarasi disebut blank final — wajib diisi di konstruktor, dan setelah itu tidak bisa diubah. Berguna untuk nilai yang beda per objek tapi tidak berubah seumur hidup objek:
public class Token {
// Setiap token punya nilai unik yang tidak pernah berubah
private final String nilai;
private final long waktuDibuat;
private final long waktuKedaluwarsa;
public Token(String nilai, long durasiMs) {
this.nilai = nilai;
this.waktuDibuat = System.currentTimeMillis();
this.waktuKedaluwarsa = this.waktuDibuat + durasiMs;
}
public String getNilai() { return nilai; }
public boolean sudahKedaluwarsa() {
return System.currentTimeMillis() > waktuKedaluwarsa;
}
// Tidak ada setter — nilai token tidak bisa diubah setelah dibuat
}
flowchart TD
A{Nilai konstanta\nbutuh logika\natau komputasi?} -- Ya --> B{Bergantung pada\nkondisi runtime\nseperti env var?}
A -- Tidak --> C["Inisialisasi langsung\npublic static final X = nilai"]
B -- Ya --> D["Static initializer block\nstatic { ... }"]
B -- Tidak --> C
A -- "Beda per objek\ntapi tidak berubah" --> E["Blank final di konstruktor\nprivate final X;\nthis.x = nilai"]
style C fill:#16a34a,color:#fff
style D fill:#3b82f6,color:#fff
style E fill:#7c3aed,color:#fffenum sebagai Konstanta Terstruktur
#
Untuk sekelompok konstanta yang saling berkaitan, enum adalah pilihan yang jauh lebih baik daripada sekumpulan static final. enum memberi type safety — compiler menolak nilai yang bukan anggota enum — dan bisa membawa data serta metode.
Konstanta Biasa vs Enum #
// ANTI-PATTERN: konstanta int untuk status — tidak ada type safety
public class StatusPesanan {
public static final int MENUNGGU = 1;
public static final int DIPROSES = 2;
public static final int DIKIRIM = 3;
public static final int SELESAI = 4;
public static final int DIBATALKAN = 5;
}
// Tidak ada yang mencegah ini dari compile:
void updateStatus(int status) { ... }
updateStatus(999); // ✗ nilai tidak valid, tapi lolos compile
updateStatus(-1); // ✗ sama
// BENAR: enum dengan type safety
public enum StatusPesanan {
MENUNGGU, DIPROSES, DIKIRIM, SELESAI, DIBATALKAN
}
void updateStatus(StatusPesanan status) { ... }
updateStatus(StatusPesanan.DIKIRIM); // ✓
// updateStatus(999); // ✗ COMPILE ERROR — harus StatusPesanan
Enum dengan Data dan Metode #
enum bisa membawa data dan logika yang terkait langsung dengan setiap konstanta:
public enum PriorityLevel {
RENDAH ("Rendah", 1, 72), // label, bobot, sla jam
SEDANG ("Sedang", 2, 24),
TINGGI ("Tinggi", 3, 4),
KRITIS ("Kritis", 4, 1);
private final String label;
private final int bobot;
private final int slaJam;
PriorityLevel(String label, int bobot, int slaJam) {
this.label = label;
this.bobot = bobot;
this.slaJam = slaJam;
}
public String getLabel() { return label; }
public int getBobot() { return bobot; }
public int getSlaJam() { return slaJam; }
public boolean melebihiDeadline(long jamBerlalu) {
return jamBerlalu > slaJam;
}
}
// Penggunaan
PriorityLevel p = PriorityLevel.KRITIS;
System.out.println(p.getLabel()); // Kritis
System.out.println(p.getSlaJam()); // 1
System.out.println(p.melebihiDeadline(2)); // true
// Iterasi semua nilai enum
for (PriorityLevel level : PriorityLevel.values()) {
System.out.printf("%-8s bobot=%d sla=%d jam%n",
level.getLabel(), level.getBobot(), level.getSlaJam());
}
Perbandingan Pendekatan Konstanta #
| Pendekatan | Type Safety | Bisa Bawa Data | Iterasi | Cocok Untuk |
|---|---|---|---|---|
static final primitif | ✗ | ✗ | ✗ | Nilai tunggal tidak berkaitan |
static final String | ✗ | ✗ | ✗ | Konfigurasi, format string |
enum sederhana | ✓ | ✗ | ✓ | Status, kategori, pilihan |
enum dengan field | ✓ | ✓ | ✓ | Konstanta yang punya atribut |
Constant Interface Anti-Pattern #
Versi lama artikel ini menunjukkan konstanta di dalam interface sebagai sesuatu yang umum. Ini sebenarnya adalah anti-pattern yang dikenal sebagai Constant Interface Anti-Pattern dan sudah lama tidak direkomendasikan.
// ANTI-PATTERN: Constant Interface — jangan lakukan ini
public interface KonstantaGlobal {
double PI = 3.14159; // implisit public static final
int MAX_UMUR = 100;
String CHARSET = "UTF-8";
}
// Kelas mengimplementasikan interface hanya untuk akses konstanta
public class Kalkulator implements KonstantaGlobal {
public double hitungLuas(double r) {
return PI * r * r; // ✗ terkesan seperti PI adalah kontrak kelas ini
}
}
Masalahnya: implements seharusnya menyatakan kontrak perilaku (is-a relationship), bukan akses ke konstanta. Kalkulator bukan “sebuah KonstantaGlobal”. Selain itu, semua konstanta interface menjadi bagian dari public API kelas — sulit untuk menghapusnya di masa depan tanpa breaking change.
// BENAR: gunakan utility class dengan konstruktor private
public final class Konstanta {
private Konstanta() { } // cegah instantiasi
// Kelompokkan berdasarkan domain
public static final class Http {
private Http() { }
public static final int TIMEOUT_MS = 5_000;
public static final int MAKS_RETRY = 3;
public static final String USER_AGENT = "MyApp/1.0";
}
public static final class Validasi {
private Validasi() { }
public static final int MAKS_PANJANG_NAMA = 100;
public static final int MIN_PANJANG_SANDI = 8;
public static final String REGEX_EMAIL = "^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$";
}
}
// Penggunaan — jelas asal-usulnya
if (nama.length() > Konstanta.Validasi.MAKS_PANJANG_NAMA) {
throw new IllegalArgumentException("Nama terlalu panjang");
}
Immutability: final pada Tipe Referensi
#
Perlu dipahami bahwa final pada tipe referensi hanya mengunci referensinya, bukan isinya. Ini sumber kebingungan yang umum.
public class ContohFinalReferensi {
// final pada List: referensi tidak bisa diganti, tapi isi bisa diubah
public static final List<String> DAFTAR_KOTA = new ArrayList<>();
static {
DAFTAR_KOTA.add("Jakarta");
DAFTAR_KOTA.add("Surabaya");
}
public static void main(String[] args) {
// DAFTAR_KOTA = new ArrayList<>(); // ✗ COMPILE ERROR — referensi tidak bisa diganti
DAFTAR_KOTA.add("Bandung"); // ✓ isi bisa diubah — ini mungkin tidak diinginkan!
System.out.println(DAFTAR_KOTA); // [Jakarta, Surabaya, Bandung]
}
}
Untuk koleksi yang benar-benar tidak bisa dimodifikasi, gunakan koleksi immutable:
public static final List<String> DAFTAR_KOTA = List.of(
"Jakarta", "Surabaya", "Bandung", "Medan"
);
// DAFTAR_KOTA.add("Bali"); // ✗ UnsupportedOperationException saat runtime
public static final Map<String, Integer> KODE_PROVINSI = Map.of(
"DKI Jakarta", 31,
"Jawa Barat", 32,
"Jawa Tengah", 33
);
public static final Set<String> EKSTENSI_GAMBAR = Set.of(
".jpg", ".jpeg", ".png", ".webp", ".gif"
);
flowchart LR
subgraph "final List<String> list"
A["Variabel list\n(referensi)"] -->|"tidak bisa diganti"| B["Objek ArrayList\ndi heap"]
B -->|"bisa diubah"| C["'Jakarta'\n'Surabaya'\n'Bandung'"]
end
subgraph "List.of(...)"
D["Variabel list2\n(referensi)"] -->|"tidak bisa diganti"| E["ImmutableList\ndi heap"]
E -->|"tidak bisa diubah"| F["'Jakarta'\n'Surabaya'"]
end
style A fill:#f59e0b,color:#000
style D fill:#16a34a,color:#fffKonstanta Bawaan Standard Library #
Java menyediakan banyak konstanta yang sudah didefinisikan di standard library — tidak perlu mendefinisikan ulang.
| Konstanta | Lokasi | Nilai |
|---|---|---|
Math.PI | java.lang.Math | 3.141592653589793 |
Math.E | java.lang.Math | 2.718281828459045 |
Integer.MAX_VALUE | java.lang.Integer | 2,147,483,647 |
Integer.MIN_VALUE | java.lang.Integer | -2,147,483,648 |
Long.MAX_VALUE | java.lang.Long | 9,223,372,036,854,775,807 |
Double.MAX_VALUE | java.lang.Double | ~1.8 × 10³⁰⁸ |
Double.NaN | java.lang.Double | Not a Number |
Double.POSITIVE_INFINITY | java.lang.Double | ∞ |
Integer.MAX_VALUE | java.lang.Integer | Batas atas int |
System.lineSeparator() | java.lang.System | \n atau \r\n |
// ANTI-PATTERN: mendefinisikan ulang konstanta yang sudah ada
public static final double PI = 3.14; // ✗ kurang presisi dari Math.PI
public static final int MAX_INT = 2147483647; // ✗ gunakan Integer.MAX_VALUE
// BENAR: pakai yang sudah tersedia
double keliling = 2 * Math.PI * jariJari; // ✓
int[] arr = new int[Integer.MAX_VALUE / 2]; // ✓ lebih readable dari angka literal
// Cek NaN dengan benar
double hasil = 0.0 / 0.0;
// ANTI-PATTERN: membandingkan NaN dengan ==
if (hasil == Double.NaN) { } // ✗ selalu false, NaN != NaN
// BENAR: gunakan Double.isNaN()
if (Double.isNaN(hasil)) { } // ✓
Ringkasan #
public static final+UPPER_SNAKE_CASE— kombinasi standar untuk konstanta kelas di Java;staticagar hanya satu salinan,finalagar tidak bisa diubah, penamaan kapital sebagai sinyal visual.- Hilangkan magic number — setiap angka atau string literal yang muncul lebih dari sekali dalam kode adalah kandidat konstanta; nama yang bermakna jauh lebih mudah dipahami dan diubah daripada nilai literal.
enumuntuk grup konstanta berkaitan — gunakanenumbukanstatic final intketika konstanta mewakili satu set pilihan;enummemberikan type safety dan bisa membawa data serta metode.- Tiga cara inisialisasi — langsung saat deklarasi (paling umum), di static initializer block (untuk logika/env var), atau di konstruktor sebagai blank final (untuk nilai unik per objek yang tidak berubah).
- Constant Interface adalah anti-pattern — jangan
implementsinterface hanya untuk akses konstanta; gunakan utility class final dengan konstruktor private dan inner class per domain.finalpada referensi ≠ immutable —final Listtidak mencegah.add()atau.remove(); gunakanList.of(),Map.of(),Set.of()untuk koleksi yang benar-benar tidak bisa dimodifikasi.- Jangan definisikan ulang konstanta standard library —
Math.PI,Integer.MAX_VALUE,Double.NaN, dan lainnya sudah tersedia; gunakan yang ada daripada mendefinisikan versi yang lebih tidak presisi.