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).
| Tipe | Ukuran | Rentang Nilai | Nilai Default | Literal |
|---|---|---|---|---|
byte | 8-bit | -128 s/d 127 | 0 | byte b = 42; |
short | 16-bit | -32.768 s/d 32.767 | 0 | short s = 1000; |
int | 32-bit | ±2,147,483,647 | 0 | int i = 100; |
long | 64-bit | ±9,223,372,036,854,775,807 | 0L | long l = 100L; |
float | 32-bit | ±3.4×10³⁸, presisi ~7 digit | 0.0f | float f = 3.14f; |
double | 64-bit | ±1.7×10³⁰⁸, presisi ~15 digit | 0.0d | double d = 3.14; |
char | 16-bit | ‘\u0000’ s/d ‘\uffff’ (Unicode) | '\u0000' | char c = 'A'; |
boolean | 1-bit* | true atau false | false | boolean 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:#fffBilangan 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
doubleuntuk kalkulasi finansial (uang).doublemenggunakan representasi biner yang tidak bisa merepresentasikan desimal seperti0.1secara 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 perbandinganGunakan
BigDecimaluntuk 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:#fffString 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>).
| Primitif | Wrapper Class |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
// 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
nullmenyebabkanNullPointerException— 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:#fffNarrowing (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
BigDecimaldari literaldouble— kamu mewarisi ketidaktepatandoubleke dalamBigDecimal:// 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:#fffRingkasan #
- Delapan tipe primitif —
byte,short,int,long,float,double,char,boolean; gunakanintuntuk bilangan bulat dandoubleuntuk desimal sebagai default.longbutuh suffixL,floatbutuhf—100Ladalah long,3.14fadalah float; tanpa suffix, integer literal adalahintdan desimal literal adalahdouble.- Integer overflow terjadi diam-diam —
Integer.MAX_VALUE + 1menjadi negatif tanpa exception; gunakanMath.addExact()ataulongjika ada risiko overflow.doubletidak cocok untuk uang —0.1 + 0.2 ≠ 0.3karena representasi biner; gunakanBigDecimaldengan konstruktorStringuntuk semua kalkulasi finansial.Stringimmutable,StringBuildermutable — setiap operasi padaStringmenghasilkan objek baru; gunakanStringBuilderuntuk konkatenasi dalam loop.==pada String/wrapper tidak andal — selalu gunakan.equals()untuk membandingkan isi;==hanya membandingkan alamat memori.- Autoboxing
null→NullPointerException— unboxing wrapper yangnullke primitif langsung melempar NPE; cek null atau gunakan nilai default sebelum unboxing.- Narrowing casting memotong, bukan membulatkan —
(int) 9.99menghasilkan9, bukan10; dan nilai bisa wrap-around jika melebihi rentang tipe tujuan.