Sintaks Utama

Sintaks Utama #

Java adalah bahasa yang sangat terstruktur — setiap baris kode harus berada dalam konteks yang jelas: di dalam kelas, di dalam metode, dengan tipe data yang eksplisit. Bagi developer yang datang dari Python atau JavaScript, ketatnya aturan sintaks Java terasa berlebihan di awal, tetapi justru itulah yang membuat kode Java mudah dibaca dan diprediksi di skala besar. Artikel ini membahas fondasi sintaks Java yang perlu kamu pahami sebelum menulis program apapun: bagaimana kode dikompilasi dan dijalankan, struktur file yang wajib dipenuhi, sistem package dan import, modifier akses, serta konvensi penamaan yang digunakan seluruh ekosistem Java.

Bagaimana Java Mengompilasi dan Menjalankan Kode #

Sebelum melihat sintaks, penting memahami alur dari source code hingga program berjalan. Java menggunakan pendekatan dua tahap yang membedakannya dari bahasa compiled seperti C++ maupun interpreted seperti Python:

flowchart LR
    A["HelloWorld.java\n(Source Code)"] -->|"javac"| B["HelloWorld.class\n(Bytecode)"]
    B -->|"java"| C["JVM\n(Java Virtual Machine)"]
    C -->|"JIT Compilation"| D["Kode Mesin\n(Native)"]
    D --> E([Output Program])

    style A fill:#f59e0b,color:#000
    style B fill:#3b82f6,color:#fff
    style C fill:#7c3aed,color:#fff
    style E fill:#16a34a,color:#fff

javac mengompilasi source code .java menjadi bytecode .class — format perantara yang bukan kode mesin native. JVM kemudian menjalankan bytecode ini di platform apapun (Windows, macOS, Linux) tanpa perlu kompilasi ulang. Inilah arti dari slogan Java: “Write once, run anywhere”.

# Kompilasi: source code → bytecode
javac HelloWorld.java      # menghasilkan HelloWorld.class

# Jalankan bytecode di JVM
java HelloWorld            # perhatikan: tanpa ekstensi .class

Struktur Program Java #

Setiap program Java dimulai dari struktur yang sama. Berikut adalah program lengkap paling sederhana beserta penjelasan tiap bagiannya:

// 1. Deklarasi package — opsional, tapi direkomendasikan
package com.example.app;

// 2. Import kelas dari package lain
import java.util.List;
import java.util.ArrayList;

// 3. Deklarasi kelas — nama file harus sama dengan nama kelas public
public class HelloWorld {

    // 4. Entry point — JVM mencari metode ini untuk memulai program
    public static void main(String[] args) {

        // 5. Statement diakhiri titik koma
        System.out.println("Halo, Dunia!");

        // 6. Variabel harus dideklarasikan dengan tipe
        String pesan = "Java " + Runtime.version().feature();
        System.out.println(pesan);
    }
}

Aturan yang wajib dipenuhi dan menyebabkan compile error jika dilanggar:

✓ Nama file harus sama persis dengan nama kelas public
  → file: HelloWorld.java, kelas: public class HelloWorld
  → file: UserService.java, kelas: public class UserService

✓ Setiap statement diakhiri titik koma (;)

✓ Semua kode harus berada di dalam kelas

✓ Satu file hanya boleh punya satu kelas public
  (boleh punya beberapa kelas non-public)

Deklarasi Kelas #

Kelas adalah unit terkecil organisasi kode di Java. Semua kode — variabel, metode, logika — harus berada di dalam kelas.

// Anatomi deklarasi kelas
[modifier] class NamaKelas [extends KelasInduk] [implements Interface1, Interface2] {
    // field (variabel)
    // konstruktor
    // metode
}

Variasi Deklarasi Kelas #

// Kelas publik — bisa diakses dari package lain
public class Pengguna {
    String nama;
    int umur;
}

// Kelas yang mewarisi kelas lain
public class Admin extends Pengguna {
    String levelAkses;
}

// Kelas yang mengimplementasikan interface
public class EmailService implements NotificationService {
    @Override
    public void kirim(String pesan) {
        // implementasi
    }
}

// Kelas final — tidak bisa diwarisi
public final class Konstanta {
    public static final double PI = 3.14159;
}

// Kelas abstrak — tidak bisa diinstansiasi langsung
public abstract class Bentuk {
    public abstract double hitungLuas(); // metode abstrak
}
classDiagram
    class Bentuk {
        <<abstract>>
        +hitungLuas() double
    }
    class Lingkaran {
        -double jariJari
        +hitungLuas() double
    }
    class Persegi {
        -double sisi
        +hitungLuas() double
    }
    class Drawable {
        <<interface>>
        +gambar() void
    }
    Bentuk <|-- Lingkaran
    Bentuk <|-- Persegi
    Drawable <|.. Lingkaran

Metode main #

Metode main adalah pintu masuk setiap program Java. JVM mencari metode dengan signature tepat ini untuk memulai eksekusi:

public static void main(String[] args) {
    // kode program
}

Setiap kata kunci dalam signature ini punya alasan:

public   JVM (dari luar kelas) harus bisa memanggilnya
static   JVM memanggil tanpa membuat objek terlebih dahulu
void     tidak mengembalikan nilai ke JVM
String[] args  menerima argumen dari command line

Membaca Argumen Command Line #

public class Sapa {
    public static void main(String[] args) {
        // java Sapa Budi 25
        if (args.length < 2) {
            System.out.println("Penggunaan: java Sapa <nama> <umur>");
            return;
        }

        String nama = args[0];       // "Budi"
        int umur    = Integer.parseInt(args[1]); // 25

        System.out.println("Halo, " + nama + "! Umur kamu " + umur + " tahun.");
    }
}
Sejak Java 21, ada fitur Unnamed Main Method dan Instance Main Methods (preview) yang memungkinkan menulis program tanpa boilerplate public static void main. Tapi untuk saat ini, signature standar di atas masih digunakan di hampir semua codebase production.

Package dan Import #

Package adalah mekanisme Java untuk mengelompokkan kelas yang saling berkaitan sekaligus menghindari konflik nama. Bayangkan package seperti folder di filesystem.

Deklarasi Package #

// Baris pertama file (sebelum import apapun)
package com.example.ecommerce.service;

// Konvensi penamaan: nama domain dibalik + nama modul
// com.namaPerusahaan.namaAplikasi.namaModul

Import #

// Import kelas spesifik — direkomendasikan
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;

// ANTI-PATTERN: wildcard import — mengimpor semua kelas dalam package
import java.util.*;   // ✗ tidak jelas kelas mana yang digunakan

// Import static — untuk menggunakan anggota static tanpa nama kelas
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;

public class Lingkaran {
    double hitungKeliling(double r) {
        return 2 * PI * r;  // langsung pakai PI, bukan Math.PI
    }
    double hitungDiagonal(double a, double b) {
        return sqrt(a*a + b*b); // langsung pakai sqrt
    }
}

Struktur Direktori Sesuai Package #

Nama package harus mencerminkan struktur direktori file:

src/
  └── com/
      └── example/
          └── ecommerce/
              ├── service/
              │   ├── UserService.java      → package com.example.ecommerce.service
              │   └── OrderService.java     → package com.example.ecommerce.service
              ├── model/
              │   ├── User.java             → package com.example.ecommerce.model
              │   └── Order.java            → package com.example.ecommerce.model
              └── repository/
                  └── UserRepository.java   → package com.example.ecommerce.repository

Modifier Akses #

Modifier akses mengontrol dari mana sebuah kelas, metode, atau field bisa diakses. Java punya empat level akses:

public class ContohModifier {

    public    String namaPublik;    // bisa diakses dari mana saja
    protected String namaProtected; // package + subclass di luar package
    String    namaDefault;          // hanya dalam package yang sama (tanpa keyword)
    private   String namaPrivate;   // hanya dalam kelas ini

    // Metode dengan berbagai modifier
    public    void metodePubilk()    { }
    protected void metodeProtected() { }
              void metodeDefault()   { }  // package-private
    private   void metodePrivate()   { }
}
graph TD
    subgraph "Jangkauan Akses"
        A["private\nHanya kelas ini"]
        B["(default)\nKelas dalam package yang sama"]
        C["protected\nDefault + subclass di luar package"]
        D["public\nSemua kelas di semua package"]
    end
    A --> B --> C --> D
    style A fill:#e05252,color:#fff
    style B fill:#f59e0b,color:#000
    style C fill:#3b82f6,color:#fff
    style D fill:#16a34a,color:#fff

Prinsip Enkapsulasi #

Gunakan modifier akses sesempit mungkin — ini adalah prinsip least privilege yang membuat kode lebih aman dan mudah di-refactor:

// ANTI-PATTERN: semua field public — kelas tidak punya kontrol atas datanya
public class Rekening {
    public double saldo;       // ✗ siapapun bisa ubah langsung
    public String nomorRek;    // ✗ tidak ada validasi
}

// BENAR: field private, akses via metode yang mengontrol validasi
public class Rekening {
    private double saldo;
    private String nomorRek;

    public Rekening(String nomorRek, double saldoAwal) {
        if (saldoAwal < 0) throw new IllegalArgumentException("Saldo tidak boleh negatif");
        this.nomorRek = nomorRek;
        this.saldo    = saldoAwal;
    }

    public double getSaldo()    { return saldo; }
    public String getNomorRek() { return nomorRek; }

    public void tarik(double jumlah) {
        if (jumlah > saldo) throw new IllegalStateException("Saldo tidak mencukupi");
        saldo -= jumlah;
    }

    public void setor(double jumlah) {
        if (jumlah <= 0) throw new IllegalArgumentException("Jumlah setor harus positif");
        saldo += jumlah;
    }
}

Tipe Data Primitif vs Referensi #

Java membedakan dua kategori tipe data yang punya perilaku sangat berbeda:

// Tipe PRIMITIF — disimpan langsung di stack, bukan objek
byte    b = 127;              // 8-bit, -128 s/d 127
short   s = 32767;            // 16-bit
int     i = 2_147_483_647;    // 32-bit (paling umum)
long    l = 9_223_372_036L;   // 64-bit, tambahkan L di akhir
float   f = 3.14f;            // 32-bit, tambahkan f di akhir
double  d = 3.14159265358979; // 64-bit (paling umum untuk desimal)
char    c = 'A';              // 16-bit Unicode
boolean flag = true;

// Tipe REFERENSI — menyimpan alamat memori ke objek di heap
String     nama   = "Budi";          // objek String
int[]      angka  = {1, 2, 3};       // array
List<String> list = new ArrayList<>(); // Collection

Perbedaan Kritis: Assignment dan Perbandingan #

// PRIMITIF: assignment menyalin nilai
int a = 10;
int b = a;
b = 20;
System.out.println(a); // 10 — a tidak berubah

// REFERENSI: assignment menyalin alamat (bukan nilai)
int[] arrA = {1, 2, 3};
int[] arrB = arrA;    // arrB menunjuk objek yang SAMA
arrB[0] = 99;
System.out.println(arrA[0]); // 99 — arrA ikut berubah!

// Perbandingan referensi
String s1 = new String("halo");
String s2 = new String("halo");

// ANTI-PATTERN: membandingkan referensi dengan ==
if (s1 == s2) { }          // ✗ false — alamat berbeda meski isinya sama

// BENAR: bandingkan nilai dengan .equals()
if (s1.equals(s2)) { }     // ✓ true
Kesalahan membandingkan String dengan == alih-alih .equals() adalah salah satu bug paling umum di Java. Operator == pada tipe referensi membandingkan alamat memori, bukan nilai. Selalu gunakan .equals() untuk membandingkan isi objek.

Konvensi Penamaan #

Java memiliki konvensi penamaan yang diikuti hampir universal oleh seluruh ekosistem. Melanggar konvensi ini tidak menyebabkan error, tetapi akan membuat kode kamu terlihat asing bagi developer Java lain:

// KELAS & INTERFACE: PascalCase
public class UserService      { }
public class HttpClient       { }
public interface Serializable { }
public @interface Override    { }  // anotasi

// METODE & VARIABEL: camelCase
public void hitungTotalHarga() { }
int jumlahProduk    = 0;
String namaLengkap  = "";
boolean sudahLogin  = false;

// KONSTANTA: UPPER_SNAKE_CASE
public static final int    MAX_RETRY_COUNT = 3;
public static final String DEFAULT_CHARSET = "UTF-8";
public static final double BATAS_DISKON    = 0.5;

// PACKAGE: semua huruf kecil, tanpa underscore
package com.example.userservice;
package org.apache.commons.lang3;

// GENERIC TYPE PARAMETER: satu huruf kapital
class Box<T>          { }  // T = Type
class Pair<K, V>      { }  // K = Key, V = Value
class Repository<E>   { }  // E = Entity

Statement, Blok, dan Ekspresi #

Pemahaman tentang tiga konsep ini membantu membaca struktur kode Java dengan lebih baik:

public class KonsepDasar {
    public static void main(String[] args) {

        // STATEMENT: instruksi tunggal, diakhiri titik koma
        int x = 10;
        System.out.println(x);
        x++;

        // BLOK: kelompok statement dalam kurung kurawal
        {
            int y = 20;  // y hanya ada di dalam blok ini
            System.out.println(x + y);
        }
        // System.out.println(y); // ERROR: y sudah di luar scope

        // EKSPRESI: kombinasi nilai dan operator yang menghasilkan nilai
        int hasil  = x * 2 + 5;        // ekspresi aritmatika
        boolean ok = hasil > 10;        // ekspresi boolean
        String  s  = "nilai: " + hasil; // ekspresi string concatenation

        // Ekspresi bisa berdiri sebagai statement jika punya efek samping
        x = x + 1;   // assignment
        x++;          // increment
        metode();     // pemanggilan metode
    }

    static void metode() { }
}

Output ke Konsol #

Java menyediakan beberapa cara untuk mencetak output, masing-masing dengan kegunaan berbeda:

// System.out.println — cetak dengan newline di akhir
System.out.println("Halo");          // "Halo\n"
System.out.println(42);              // konversi otomatis ke String
System.out.println(3.14);
System.out.println(true);

// System.out.print — cetak tanpa newline
System.out.print("Nama: ");
System.out.print("Budi");
System.out.println();                // newline manual

// System.out.printf — format seperti C printf
System.out.printf("Nama: %s, Umur: %d%n", "Budi", 25);
System.out.printf("Harga: Rp%.2f%n", 99999.5);

// String.format — format ke String tanpa langsung cetak
String pesan = String.format("ID: %05d, Status: %s", 42, "aktif");
System.out.println(pesan); // "ID: 00042, Status: aktif"

// Format specifier yang umum:
// %s  → String
// %d  → integer
// %f  → float/double
// %.2f → 2 desimal
// %n  → newline (platform-independent, lebih baik dari "\n")
// %05d → integer dengan padding nol, lebar 5

Ringkasan #

  • Dua tahap eksekusijavac mengompilasi .java menjadi bytecode .class, kemudian JVM menjalankan bytecode; ini yang membuat Java write once, run anywhere.
  • Nama file = nama kelas — nama file .java harus identik dengan nama kelas public di dalamnya; pelanggaran ini langsung menyebabkan compile error.
  • Package mencerminkan direktori — deklarasi package com.example.service mengharuskan file berada di direktori com/example/service/; konvensi nama domain dibalik mencegah konflik antar library.
  • Modifier akses sesempit mungkin — gunakan private untuk field dan buka akses hanya via metode yang memvalidasi; ini adalah fondasi enkapsulasi OOP.
  • == vs .equals()== membandingkan alamat memori untuk tipe referensi; selalu gunakan .equals() untuk membandingkan isi objek seperti String.
  • Tipe primitif vs referensi — primitif (int, double, boolean, dll) disalin saat assignment; referensi menyimpan alamat sehingga dua variabel bisa menunjuk objek yang sama.
  • Konvensi penamaan — PascalCase untuk kelas/interface, camelCase untuk metode/variabel, UPPER_SNAKE_CASE untuk konstanta; konvensi ini diikuti seluruh ekosistem Java.

← Sebelumnya: Instalasi   Berikutnya: Komentar →

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