Tipe Data

Tipe Data #

Java adalah bahasa statically typed — setiap variabel, parameter, dan return value harus punya tipe yang dideklarasikan secara eksplisit, dan compiler memverifikasi konsistensinya sebelum program berjalan. Ini berbeda dari Python atau JavaScript yang menentukan tipe di runtime. Java membagi tipe data menjadi dua kategori besar: primitif (nilai disimpan langsung di stack) dan referensi (variabel menyimpan alamat ke objek di heap). Memahami perbedaan ini — terutama implikasinya pada assignment, perbandingan, dan performa — adalah fondasi untuk menulis kode Java yang benar dan efisien.

Tipe Data Primitif #

Java memiliki tepat delapan tipe primitif yang sudah ditentukan di spesifikasi bahasa dan tidak bisa diubah. Semua tipe lain di Java adalah tipe referensi (objek).

TipeUkuranRentang NilaiNilai DefaultLiteral
byte8-bit-128 s/d 1270byte b = 42;
short16-bit-32.768 s/d 32.7670short s = 1000;
int32-bit±2,147,483,6470int i = 100;
long64-bit±9,223,372,036,854,775,8070Llong l = 100L;
float32-bit±3.4×10³⁸, presisi ~7 digit0.0ffloat f = 3.14f;
double64-bit±1.7×10³⁰⁸, presisi ~15 digit0.0ddouble d = 3.14;
char16-bit‘\u0000’ s/d ‘\uffff’ (Unicode)'\u0000'char c = 'A';
boolean1-bit*true atau falsefalseboolean ok = true;

*ukuran aktual boolean di JVM tergantung implementasi, biasanya 1 byte.

flowchart TD
    A[Tipe Data Java] --> B[Primitif\nnilai disimpan di stack]
    A --> C[Referensi\nalamat objek di heap]
    B --> D[Bilangan Bulat\nbyte short int long]
    B --> E[Bilangan Desimal\nfloat double]
    B --> F[Karakter\nchar]
    B --> G[Logika\nboolean]
    C --> H[String]
    C --> I[Array]
    C --> J[Objek / Kelas]
    C --> K[Interface]

    style B fill:#3b82f6,color:#fff
    style C fill:#7c3aed,color:#fff

Bilangan Bulat #

int adalah pilihan default untuk bilangan bulat — digunakan untuk hampir semua kebutuhan umum. Gunakan long hanya ketika nilai melebihi batas int, dan byte/short hanya untuk array besar di mana penghematan memori signifikan.

// int — paling umum, cukup untuk sebagian besar kebutuhan
int jumlahPengguna = 1_000_000; // underscore sebagai pemisah ribuan (Java 7+)
int tahun          = 2025;

// long — untuk nilai di atas ~2 miliar, tambahkan L di akhir literal
long populasiDunia  = 8_100_000_000L; // L wajib, tanpanya compiler error
long timestampMs    = System.currentTimeMillis();

// byte dan short — jarang digunakan, kecuali array data biner besar
byte[] dataGambar = new byte[1920 * 1080 * 3]; // hemat memori untuk buffer besar

Integer overflow terjadi diam-diam — tidak ada exception, hasilnya wrap-around. Ini sumber bug yang sulit ditemukan:

int maks = Integer.MAX_VALUE; // 2,147,483,647
int hasil = maks + 1;
System.out.println(hasil);    // -2,147,483,648 — wrap ke negatif!

// Untuk operasi yang mungkin overflow, gunakan long atau Math.addExact()
long aman = (long) maks + 1;  // 2,147,483,648 — benar
Math.addExact(maks, 1);       // melempar ArithmeticException jika overflow

Bilangan Desimal #

double adalah pilihan default untuk bilangan desimal. float hanya digunakan untuk array besar yang butuh penghematan memori, atau saat berinteraksi dengan API yang memang membutuhkan float (seperti OpenGL).

double suhu     = 36.6;       // default — tidak perlu suffix
double pi       = Math.PI;    // 3.141592653589793
float  koordinat = 120.5f;    // f wajib, tanpanya dianggap double

// Operasi desimal biasa
double luas = Math.PI * 5.0 * 5.0;
System.out.println(luas); // 78.53981633974483

Jangan gunakan double untuk kalkulasi finansial (uang). double menggunakan representasi biner yang tidak bisa merepresentasikan desimal seperti 0.1 secara tepat:

System.out.println(0.1 + 0.2);         // 0.30000000000000004 — bukan 0.3!
System.out.println(1.03 - 0.42);        // 0.6099999999999999 — bukan 0.61!

double harga = 19.99;
double total = harga * 3;
System.out.println(total);              // 59.97 — kebetulan oke
System.out.println(harga * 3 == 59.97); // false — tidak aman untuk perbandingan

Gunakan BigDecimal untuk semua kalkulasi yang melibatkan uang. Ini dibahas lebih lanjut di bagian tersendiri.

char dan boolean #

char menyimpan satu karakter Unicode 16-bit. Karena Java menggunakan Unicode secara native, char bisa menyimpan karakter dari hampir semua bahasa di dunia.

char huruf    = 'A';
char angka    = '5';       // karakter '5', bukan int 5
char unicode  = '\u00e9';  // 'é' — karakter beraksent
char newline  = '\n';      // escape character

// char bisa dioperasikan secara aritmatika (nilai Unicode-nya)
char berikut = (char) ('A' + 1); // 'B'
System.out.println((int) 'A');   // 65 — nilai Unicode

boolean aktif    = true;
boolean sudahBayar = false;
boolean hasil    = (5 > 3) && (10 != 20); // true

Tipe Referensi Utama #

Tipe referensi adalah semua tipe selain delapan primitif. Variabel bertipe referensi menyimpan alamat objek di heap, bukan nilai objeknya secara langsung.

String #

String adalah tipe referensi paling sering digunakan di Java. Meskipun terlihat seperti tipe primitif karena bisa dibuat tanpa new, String adalah objek dari kelas java.lang.String.

// Dua cara membuat String
String s1 = "halo";              // string literal — dari String pool
String s2 = new String("halo");  // objek baru di heap — hindari kecuali perlu

// String bersifat IMMUTABLE — setiap "modifikasi" menghasilkan objek baru
String nama = "Budi";
nama.toUpperCase();               // tidak mengubah 'nama'!
String namaBesar = nama.toUpperCase(); // ✓ simpan hasilnya

// Operasi String umum
String s = "Hello, Java!";
s.length();          // 12
s.toUpperCase();     // "HELLO, JAVA!"
s.substring(7, 11);  // "Java"
s.contains("Java");  // true
s.replace("Java", "World"); // "Hello, World!"
s.split(", ");       // ["Hello", "Java!"]
s.trim();            // hapus spasi di awal/akhir
s.isEmpty();         // false
s.startsWith("He");  // true

String Pool dan Perbandingan #

Java memiliki String pool — area memori khusus tempat string literal yang identik berbagi satu objek untuk menghemat memori.

flowchart LR
    subgraph Stack
        A["s1"]
        B["s2"]
        C["s3"]
    end
    subgraph "Heap — String Pool"
        D['"halo"']
    end
    subgraph "Heap — Luar Pool"
        E['"halo" objek baru']
    end

    A -->|"referensi"| D
    B -->|"referensi"| D
    C -->|"referensi"| E

    style D fill:#16a34a,color:#fff
    style E fill:#e05252,color:#fff
String s1 = "halo";
String s2 = "halo";              // s1 dan s2 menunjuk objek SAMA dari pool
String s3 = new String("halo");  // objek BARU di luar pool

// ANTI-PATTERN: membandingkan String dengan ==
if (s1 == s2) { }     // true — kebetulan karena pool, tapi tidak andal
if (s1 == s3) { }     // false — objek berbeda meski isinya sama

// BENAR: selalu gunakan .equals() untuk membandingkan isi String
if (s1.equals(s2)) { }     // ✓ true
if (s1.equals(s3)) { }     // ✓ true
if ("halo".equals(s1)) { } // ✓ pola "Yoda condition" — aman jika s1 null

StringBuilder untuk Konkatenasi Berulang #

// ANTI-PATTERN: konkatenasi String dalam loop — membuat banyak objek sementara
String hasil = "";
for (int i = 0; i < 1000; i++) {
    hasil += i + ", "; // ✗ setiap += membuat objek String baru
}

// BENAR: gunakan StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i).append(", "); // ✓ modifikasi in-place
}
String hasil = sb.toString();

Wrapper Class #

Setiap tipe primitif memiliki wrapper class yang membungkusnya menjadi objek. Wrapper diperlukan ketika API mengharuskan tipe referensi — seperti List<Integer> (tidak bisa List<int>).

PrimitifWrapper Class
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean
// Wrapper menyediakan method utilitas yang berguna
int maks = Integer.MAX_VALUE;      // 2,147,483,647
int dari  = Integer.parseInt("42"); // konversi String → int
String ke = Integer.toString(42);   // konversi int → String
int min   = Integer.min(10, 20);    // 10

// Cek tipe
Character.isLetter('A');  // true
Character.isDigit('5');   // true
Character.isWhitespace(' '); // true
Character.toUpperCase('a');  // 'A'

Autoboxing dan Unboxing #

Java secara otomatis mengkonversi antara primitif dan wrapper — disebut autoboxing (primitif → wrapper) dan unboxing (wrapper → primitif).

// Autoboxing — otomatis terjadi
List<Integer> daftar = new ArrayList<>();
daftar.add(42);        // int 42 otomatis dibungkus ke Integer(42)

Integer x = 100;       // autoboxing dari int ke Integer

// Unboxing — otomatis terjadi
int nilai = daftar.get(0); // Integer otomatis dibuka ke int
int y = x + 1;             // x di-unbox ke int sebelum operasi +

Unboxing dari null menyebabkan NullPointerException — jebakan yang sering tidak disadari:

Integer wrapper = null;
int primitif = wrapper; // ✗ NullPointerException saat unboxing null!

// Selalu cek null sebelum unboxing
if (wrapper != null) {
    int primitif = wrapper; // ✓ aman
}

// Atau gunakan Objects.requireNonNullElse
int primitif = Objects.requireNonNullElse(wrapper, 0);

Selain itu, perbandingan == pada wrapper bisa menyesatkan karena Integer hanya meng-cache nilai -128 hingga 127:

Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true — dari cache

Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false — objek berbeda, di luar cache!
System.out.println(c.equals(d)); // true — gunakan equals()

Type Casting #

Type casting adalah konversi eksplisit dari satu tipe ke tipe lain. Java membedakan dua jenis casting berdasarkan apakah informasi bisa hilang.

Widening (Implicit) — Otomatis, Aman #

Konversi dari tipe yang lebih kecil ke yang lebih besar terjadi otomatis tanpa kehilangan data:

byte  b = 42;
short s = b;      // byte → short, otomatis
int   i = s;      // short → int, otomatis
long  l = i;      // int → long, otomatis
float f = l;      // long → float, otomatis (presisi mungkin berkurang)
double d = f;     // float → double, otomatis
flowchart LR
    A["byte\n8-bit"] --> B["short\n16-bit"] --> C["int\n32-bit"] --> D["long\n64-bit"] --> E["float\n32-bit"] --> F["double\n64-bit"]
    style A fill:#e05252,color:#fff
    style F fill:#16a34a,color:#fff

Narrowing (Explicit) — Manual, Bisa Kehilangan Data #

Konversi dari tipe yang lebih besar ke yang lebih kecil harus ditulis eksplisit — compiler memaksa kamu sadar bahwa data mungkin hilang:

double d = 9.99;
int    i = (int) d;     // 9 — bagian desimal dipotong (bukan dibulatkan!)
long   l = 123456789L;
byte   b = (byte) l;    // hanya 8-bit bawah yang diambil, hasilnya -128 s/d 127

// ANTI-PATTERN: lupa narrowing bisa memotong nilai
int besar = 300;
byte kecil = (byte) besar; // 44 — bukan 300! nilai wrap-around

// BENAR: cek rentang sebelum narrowing jika nilai tidak pasti
if (besar >= Byte.MIN_VALUE && besar <= Byte.MAX_VALUE) {
    byte kecil = (byte) besar; // aman
}

Casting Tipe Referensi #

// Upcasting — otomatis, selalu aman
Object obj = "ini sebuah String"; // String adalah subclass Object

// Downcasting — harus eksplisit, bisa gagal
String s = (String) obj;          // ✓ aman karena obj memang String

Object angka = Integer.valueOf(42);
// String x = (String) angka;    // ✗ ClassCastException saat runtime!

// BENAR: gunakan instanceof sebelum downcast
if (obj instanceof String str) {  // pattern matching instanceof (Java 16+)
    System.out.println(str.toUpperCase());
}

BigDecimal untuk Kalkulasi Finansial #

Untuk semua perhitungan yang melibatkan uang, pajak, atau nilai desimal yang harus tepat, gunakan BigDecimal — bukan double.

import java.math.BigDecimal;
import java.math.RoundingMode;

// ANTI-PATTERN: double untuk uang
double harga1 = 19.99;
double harga2 = 29.99;
System.out.println(harga1 + harga2); // 49.980000000000004 — tidak tepat!

// BENAR: BigDecimal untuk uang
BigDecimal h1 = new BigDecimal("19.99"); // gunakan String, bukan double!
BigDecimal h2 = new BigDecimal("29.99");
BigDecimal total = h1.add(h2);
System.out.println(total); // 49.98 — tepat

// Operasi BigDecimal
BigDecimal harga = new BigDecimal("100.00");
BigDecimal pajak = new BigDecimal("0.11");

BigDecimal nilaiPajak = harga.multiply(pajak);       // 11.0000
BigDecimal totalBayar = harga.add(nilaiPajak);       // 111.0000

// Pembulatan — selalu tentukan RoundingMode secara eksplisit
BigDecimal dibulatkan = nilaiPajak.setScale(2, RoundingMode.HALF_UP); // 11.00

// Perbandingan BigDecimal
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
a.equals(b);      // false — equals memperhatikan scale
a.compareTo(b);   // 0 — compareTo hanya bandingkan nilai numerik

Jangan buat BigDecimal dari literal double — kamu mewarisi ketidaktepatan double ke dalam BigDecimal:

// ANTI-PATTERN: BigDecimal dari double literal
new BigDecimal(0.1);  // ✗ "0.1000000000000000055511151231257827021181583404541015625"

// BENAR: BigDecimal dari String
new BigDecimal("0.1"); // ✓ tepat 0.1

Memilih Tipe yang Tepat #

flowchart TD
    A{Tipe data apa\nyang dibutuhkan?} --> B[Bilangan bulat]
    A --> C[Bilangan desimal]
    A --> D[Teks]
    A --> E[Logika benar/salah]
    A --> F[Karakter tunggal]

    B --> B1{Nilai > 2 miliar?}
    B1 -- Ya --> B2["long"]
    B1 -- Tidak --> B3["int (default)"]

    C --> C1{Untuk uang\natau kalkulasi tepat?}
    C1 -- Ya --> C2["BigDecimal"]
    C1 -- Tidak --> C3["double (default)"]

    D --> D1{Konkatenasi\nbanyak dalam loop?}
    D1 -- Ya --> D2["StringBuilder"]
    D1 -- Tidak --> D3["String"]

    E --> E1["boolean"]
    F --> F1["char"]

    style B2 fill:#3b82f6,color:#fff
    style B3 fill:#16a34a,color:#fff
    style C2 fill:#e05252,color:#fff
    style C3 fill:#16a34a,color:#fff
    style D2 fill:#3b82f6,color:#fff
    style D3 fill:#16a34a,color:#fff

Ringkasan #

  • Delapan tipe primitifbyte, short, int, long, float, double, char, boolean; gunakan int untuk bilangan bulat dan double untuk desimal sebagai default.
  • long butuh suffix L, float butuh f100L adalah long, 3.14f adalah float; tanpa suffix, integer literal adalah int dan desimal literal adalah double.
  • Integer overflow terjadi diam-diamInteger.MAX_VALUE + 1 menjadi negatif tanpa exception; gunakan Math.addExact() atau long jika ada risiko overflow.
  • double tidak cocok untuk uang0.1 + 0.2 ≠ 0.3 karena representasi biner; gunakan BigDecimal dengan konstruktor String untuk semua kalkulasi finansial.
  • String immutable, StringBuilder mutable — setiap operasi pada String menghasilkan objek baru; gunakan StringBuilder untuk konkatenasi dalam loop.
  • == pada String/wrapper tidak andal — selalu gunakan .equals() untuk membandingkan isi; == hanya membandingkan alamat memori.
  • Autoboxing nullNullPointerException — unboxing wrapper yang null ke primitif langsung melempar NPE; cek null atau gunakan nilai default sebelum unboxing.
  • Narrowing casting memotong, bukan membulatkan(int) 9.99 menghasilkan 9, bukan 10; dan nilai bisa wrap-around jika melebihi rentang tipe tujuan.

← Sebelumnya: Konstanta   Berikutnya: Operator →

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