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:

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 color:#fff,stroke:#16a34a,stroke-width:2px
    style D color:#fff,stroke:#3b82f6,stroke-width:2px
    style E color:#fff,stroke:#7c3aed,stroke-width:2px

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 #

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&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 color:#000,stroke:#f59e0b,stroke-width:2px
    style D color:#fff,stroke:#16a34a,stroke-width:2px

Konstanta 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; 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