Konstanta

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:

AspekMagic NumberKonstanta
KeterbacaanPembaca harus menebak makna 0.11TARIF_PAJAK langsung menjelaskan diri
PemeliharaanHarus cari-ganti di seluruh codebaseUbah di satu tempat, berlaku ke mana-mana
KeamananRawan typo (0.011 vs 0.11)Compiler tangkap jika nama salah
DokumentasiTidak ada konteksNama + 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:#fff

enum 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 #

PendekatanType SafetyBisa Bawa DataIterasiCocok Untuk
static final primitifNilai tunggal tidak berkaitan
static final StringKonfigurasi, format string
enum sederhanaStatus, kategori, pilihan
enum dengan fieldKonstanta 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&lt;String&gt; 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:#fff

Konstanta Bawaan Standard Library #

Java menyediakan banyak konstanta yang sudah didefinisikan di standard library — tidak perlu mendefinisikan ulang.

KonstantaLokasiNilai
Math.PIjava.lang.Math3.141592653589793
Math.Ejava.lang.Math2.718281828459045
Integer.MAX_VALUEjava.lang.Integer2,147,483,647
Integer.MIN_VALUEjava.lang.Integer-2,147,483,648
Long.MAX_VALUEjava.lang.Long9,223,372,036,854,775,807
Double.MAX_VALUEjava.lang.Double~1.8 × 10³⁰⁸
Double.NaNjava.lang.DoubleNot a Number
Double.POSITIVE_INFINITYjava.lang.Double
Integer.MAX_VALUEjava.lang.IntegerBatas 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; static agar hanya satu salinan, final agar 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.
  • enum untuk grup konstanta berkaitan — gunakan enum bukan static final int ketika konstanta mewakili satu set pilihan; enum memberikan 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 implements interface hanya untuk akses konstanta; gunakan utility class final dengan konstruktor private dan inner class per domain.
  • final pada referensi ≠ immutablefinal List tidak mencegah .add() atau .remove(); gunakan List.of(), Map.of(), Set.of() untuk koleksi yang benar-benar tidak bisa dimodifikasi.
  • Jangan definisikan ulang konstanta standard libraryMath.PI, Integer.MAX_VALUE, Double.NaN, dan lainnya sudah tersedia; gunakan yang ada daripada mendefinisikan versi yang lebih tidak presisi.

← Sebelumnya: Variabel   Berikutnya: Tipe Data →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact