Fungsi

Fungsi #

Di Java, semua fungsi disebut metode — mereka selalu menjadi bagian dari sebuah kelas, tidak bisa berdiri sendiri seperti di Python atau Go. Metode adalah unit terkecil dari logika yang bisa diberi nama dan dipanggil ulang. Menulis metode yang baik adalah salah satu keterampilan terpenting dalam Java: metode yang terlalu panjang sulit dipahami, metode dengan terlalu banyak parameter sulit dipanggil, dan metode yang melakukan terlalu banyak hal sulit diuji. Artikel ini membahas anatomi metode, berbagai jenis dan modifier, perilaku pass-by-value yang sering disalahpahami, overloading, varargs, rekursi, dan pengenalan lambda sebagai cara modern menulis fungsi anonim.

Anatomi Metode #

Setiap metode di Java terdiri dari beberapa bagian yang masing-masing punya peran:

//    [1]    [2]    [3]       [4]      [5]          [6]
public static int  hitungLuas(int panjang, int lebar) throws IllegalArgumentException {
    //                                                   [7]
    if (panjang <= 0 || lebar <= 0) {
        throw new IllegalArgumentException("Dimensi harus positif");
    }
    return panjang * lebar; // [8]
}
BagianContohKeterangan
[1] Access modifierpublicSiapa yang bisa memanggil metode ini
[2] Non-access modifierstaticPerilaku tambahan (static, final, abstract, dll)
[3] Return typeintTipe nilai yang dikembalikan; void jika tidak ada
[4] Nama metodehitungLuascamelCase, dimulai kata kerja
[5] Parameterint panjang, int lebarInput yang diterima metode
[6] Throws clausethrows IllegalArgumentExceptionException checked yang mungkin dilempar
[7] Bodyblok {}Logika metode
[8] returnreturn panjang * lebarNilai yang dikembalikan ke pemanggil

Modifier Metode #

Access Modifier #

Access modifier menentukan dari mana metode bisa dipanggil:

public class ContohModifier {
    public    void metodePubilk()    { } // dari mana saja
    protected void metodeProtected() { } // kelas ini + subclass + package yang sama
              void metodeDefault()   { } // hanya package yang sama
    private   void metodePrivate()   { } // hanya kelas ini
}

static vs Instance #

static artinya metode milik kelas, bukan objek. Bisa dipanggil tanpa membuat objek:

public class MathUtils {
    // static — milik kelas, tidak butuh objek
    public static int kuadrat(int n) {
        return n * n;
    }

    // instance — butuh objek, bisa akses field instance
    private double faktor;

    public double skala(double nilai) {
        return nilai * this.faktor; // akses field instance via this
    }
}

// Pemanggilan
int hasil = MathUtils.kuadrat(5);       // langsung via nama kelas
MathUtils util = new MathUtils();
double skala   = util.skala(3.14);      // butuh objek

final pada Metode #

public class Induk {
    // final — tidak bisa di-override di subclass
    public final void validasi() {
        // logika validasi yang tidak boleh diubah
    }

    // Tanpa final — bisa di-override
    public void proses() { }
}

public class Anak extends Induk {
    // @Override public void validasi() { } // ✗ COMPILE ERROR — final
    @Override
    public void proses() { } // ✓ boleh
}

Return Type dan void #

Metode bisa mengembalikan nilai apa pun termasuk array, objek, dan tipe generik — atau tidak mengembalikan apa-apa (void).

// void — tidak mengembalikan nilai
public void cetak(String pesan) {
    System.out.println(pesan);
    // return; — boleh, tapi opsional untuk void
}

// Primitif
public int tambah(int a, int b)       { return a + b; }
public boolean isGenap(int n)         { return n % 2 == 0; }

// Objek
public String format(double harga)    { return String.format("Rp%.2f", harga); }
public List<String> getNama()         { return new ArrayList<>(namaList); }

// Array
public int[] urutkan(int[] arr)       { Arrays.sort(arr); return arr; }

// Tipe generik
public <T> List<T> bungkus(T item)   { return List.of(item); }

Beberapa Titik return #

Metode boleh punya lebih dari satu return, tapi perlu digunakan dengan bijak:

// ANTI-PATTERN: return di tengah logika kompleks — sulit dilacak
public String kategori(int nilai) {
    String hasil = "";
    if (nilai >= 90) {
        hasil = "A";
        return hasil; // return terlalu awal, tidak konsisten
    }
    // ... lebih banyak kondisi
    return hasil;
}

// BENAR: guard clause di awal (return cepat untuk error/edge case)
public String kategori(int nilai) {
    if (nilai < 0 || nilai > 100) {
        throw new IllegalArgumentException("Nilai harus 0-100, dapat: " + nilai);
    }
    if (nilai >= 90) return "A";
    if (nilai >= 80) return "B";
    if (nilai >= 70) return "C";
    if (nilai >= 60) return "D";
    return "E";
}

Parameter: Pass-by-Value #

Java selalu menggunakan pass-by-value — artinya salinan nilai yang dikirim ke metode, bukan variabel aslinya. Untuk tipe primitif ini jelas, tapi untuk tipe referensi sering disalahpahami.

// Primitif: salinan nilai dikirim — perubahan di dalam metode tidak mempengaruhi luar
public static void coba(int x) {
    x = 100; // hanya mengubah salinan lokal
}

int n = 5;
coba(n);
System.out.println(n); // masih 5 — tidak berubah

// Referensi: salinan ALAMAT dikirim — bisa modifikasi objek yang ditunjuk,
// tapi tidak bisa mengganti referensi itu sendiri
public static void tambahItem(List<String> list) {
    list.add("baru");     // ✓ modifikasi objek berhasil
    list = new ArrayList<>(); // ✗ hanya mengubah salinan referensi lokal
}

List<String> daftar = new ArrayList<>();
daftar.add("awal");
tambahItem(daftar);
System.out.println(daftar); // [awal, baru] — add berhasil, tapi reassign tidak
flowchart LR
    subgraph "Pass-by-value: Primitif"
        A["int n = 5\n(Stack)"] -->|"salinan nilai"| B["parameter x = 5\n(Stack lokal metode)"]
        B --> C["x = 100\nhanya lokal"]
        A --> D["n masih 5"]
    end

    subgraph "Pass-by-value: Referensi"
        E["List daftar\n→ alamat 0x1234"] -->|"salinan alamat"| F["parameter list\n→ alamat 0x1234"]
        F --> G["list.add() → modifikasi\nobjek di 0x1234 ✓"]
        F --> H["list = new ArrayList()\nhanya ganti salinan ✗"]
    end

Method Overloading #

Overloading memungkinkan beberapa metode dengan nama yang sama asalkan signature berbeda — jumlah, tipe, atau urutan parameter berbeda. Return type saja tidak cukup untuk membedakan overload.

public class Kalkulator {
    // Overloading berdasarkan tipe parameter
    public int tambah(int a, int b)        { return a + b; }
    public double tambah(double a, double b) { return a + b; }
    public String tambah(String a, String b) { return a + b; }

    // Overloading berdasarkan jumlah parameter
    public int tambah(int a, int b)              { return a + b; }
    public int tambah(int a, int b, int c)       { return a + b + c; }
    public int tambah(int a, int b, int c, int d){ return a + b + c + d; }
}

// Compiler memilih overload yang paling cocok secara otomatis
Kalkulator k = new Kalkulator();
k.tambah(1, 2);          // panggil tambah(int, int)
k.tambah(1.0, 2.0);      // panggil tambah(double, double)
k.tambah("Hello", " World"); // panggil tambah(String, String)
k.tambah(1, 2, 3);       // panggil tambah(int, int, int)

Overloading yang terlalu agresif bisa membingungkan. Jika dua overload melakukan hal yang berbeda secara semantik, lebih baik beri nama yang berbeda:

// ANTI-PATTERN: overload yang membingungkan — keduanya "cetak" tapi berbeda
public void cetak(String teks)       { System.out.println(teks); }
public void cetak(String teks, boolean uppercase) {
    System.out.println(uppercase ? teks.toUpperCase() : teks);
}

// BENAR: nama yang mencerminkan perbedaan
public void cetak(String teks)         { System.out.println(teks); }
public void cetakKapital(String teks)  { System.out.println(teks.toUpperCase()); }

Varargs (Variable Arguments) #

Varargs memungkinkan metode menerima jumlah argumen yang tidak ditentukan. Di belakang layar, varargs adalah array biasa — hanya sintaksnya yang lebih ringkas.

// Sintaks: tipe... nama — harus parameter terakhir
public static int jumlah(int... angka) {
    int total = 0;
    for (int n : angka) {
        total += n;
    }
    return total;
}

// Pemanggilan — bisa nol hingga banyak argumen
jumlah();              // 0 — angka = int[]{}
jumlah(1);             // 1
jumlah(1, 2, 3);       // 6
jumlah(1, 2, 3, 4, 5); // 15

// Varargs dengan parameter lain — varargs harus di akhir
public static String format(String template, Object... args) {
    return String.format(template, args);
}
format("Halo %s, umur %d", "Budi", 25);

// Bisa juga kirim array langsung
int[] arr = {10, 20, 30};
jumlah(arr); // ✓ array langsung diterima varargs

Rekursi #

Rekursi adalah teknik di mana metode memanggil dirinya sendiri. Setiap metode rekursif harus punya dua bagian: base case (kondisi berhenti) dan recursive case (pemanggilan diri sendiri dengan input yang lebih kecil).

// Faktorial: n! = n × (n-1)!
public static long faktorial(int n) {
    if (n < 0) throw new IllegalArgumentException("n harus >= 0");
    if (n == 0 || n == 1) return 1;  // base case
    return (long) n * faktorial(n - 1); // recursive case
}

// Fibonacci
public static int fibonacci(int n) {
    if (n <= 1) return n;             // base case
    return fibonacci(n - 1) + fibonacci(n - 2); // recursive case
}
sequenceDiagram
    participant Main
    participant F5 as faktorial(5)
    participant F4 as faktorial(4)
    participant F3 as faktorial(3)
    participant F2 as faktorial(2)
    participant F1 as faktorial(1)

    Main->>F5: faktorial(5)
    F5->>F4: faktorial(4)
    F4->>F3: faktorial(3)
    F3->>F2: faktorial(2)
    F2->>F1: faktorial(1)
    F1-->>F2: return 1
    F2-->>F3: return 2
    F3-->>F4: return 6
    F4-->>F5: return 24
    F5-->>Main: return 120

Rekursi vs Iterasi #

Rekursi lebih ekspresif untuk masalah yang secara alami rekursif (pohon, graf, divide-and-conquer), tetapi punya overhead per pemanggilan dan berisiko StackOverflowError untuk input besar:

// Rekursi — ekspresif tapi berisiko untuk n besar
public static long faktorialRekursif(int n) {
    if (n <= 1) return 1;
    return n * faktorialRekursif(n - 1);
}

// Iterasi — lebih aman untuk input besar
public static long faktorialIteratif(int n) {
    long hasil = 1;
    for (int i = 2; i <= n; i++) {
        hasil *= i;
    }
    return hasil;
}

// faktorialRekursif(100_000) → StackOverflowError
// faktorialIteratif(100_000) → berjalan normal (hasilnya overflow long, tapi tidak crash)

Lambda dan Method Reference #

Sejak Java 8, fungsi bisa diperlakukan sebagai nilai menggunakan lambda expression — cara ringkas untuk membuat implementasi functional interface (interface dengan tepat satu metode abstrak).

import java.util.function.*;

// Lambda: (parameter) -> ekspresi atau blok
Runnable r = () -> System.out.println("Halo dari lambda");
r.run();

// Dengan parameter
Function<Integer, Integer> kuadrat = n -> n * n;
kuadrat.apply(5); // 25

// Dengan dua parameter
BiFunction<Integer, Integer, Integer> tambah = (a, b) -> a + b;
tambah.apply(3, 4); // 7

// Predicate — kondisi boolean
Predicate<String> tidakKosong = s -> !s.isEmpty();
tidakKosong.test("");    // false
tidakKosong.test("halo"); // true

// Consumer — melakukan sesuatu, tidak mengembalikan nilai
Consumer<String> cetak = s -> System.out.println(s);
cetak.accept("Halo");

// Supplier — menghasilkan nilai tanpa input
Supplier<String> salam = () -> "Selamat pagi";
salam.get(); // "Selamat pagi"

Method Reference #

Method reference adalah versi lebih ringkas dari lambda ketika lambda hanya memanggil metode yang sudah ada:

List<String> nama = List.of("Andi", "Budi", "Cici");

// Lambda
nama.forEach(n -> System.out.println(n));

// Method reference — lebih ringkas
nama.forEach(System.out::println); // instance method dari System.out

// Static method reference
nama.stream()
    .map(String::toUpperCase)   // instance method via referensi kelas
    .forEach(System.out::println);

// Constructor reference
Supplier<ArrayList<String>> buat = ArrayList::new;
ArrayList<String> list = buat.get();

Anti-Pattern Metode #

Beberapa pola yang harus dihindari saat menulis metode:

// ✗ ANTI-PATTERN 1: metode terlalu panjang (> 30 baris)
public void prosesSeluruhAplikasi() {
    // 200 baris kode melakukan segalanya
    // validasi, konversi, simpan, kirim email, update cache, log...
}

// ✓ BENAR: pecah menjadi metode kecil dengan tanggung jawab tunggal
public void prosesPesanan(Pesanan p) {
    validasiPesanan(p);
    simpanPesanan(p);
    kirimKonfirmasi(p);
    updateInventori(p);
}

// ✗ ANTI-PATTERN 2: terlalu banyak parameter (> 3-4)
public void buat(String nama, int umur, String email, String telepon,
                 String alamat, String kota, String kodePos) { }

// ✓ BENAR: bungkus parameter terkait dalam objek
public void buat(Pengguna pengguna) { }
// atau gunakan Builder pattern

// ✗ ANTI-PATTERN 3: nama tidak mencerminkan perilaku
public boolean cek(Pengguna u) { /* sebenarnya mengupdate database */ }
public void proses(Order o)     { /* nama terlalu umum */ }

// ✓ BENAR: nama = kata kerja + konteks yang jelas
public boolean isEmailTerdaftar(String email) { }
public void bayarPesanan(Order pesanan) { }

// ✗ ANTI-PATTERN 4: metode melakukan lebih dari satu hal (violates SRP)
public User loginDanUpdateLastSeen(String email, String pass) {
    User u = autentikasi(email, pass);
    u.setLastSeen(LocalDateTime.now()); // side effect tersembunyi
    simpan(u);
    return u;
}

// ✓ BENAR: pisahkan tanggung jawab
public User autentikasi(String email, String pass) { ... }
public void updateLastSeen(User u) { ... }

Ringkasan #

  • Anatomi metode — modifier + return type + nama + parameter + throws + body; nama metode sebaiknya dimulai kata kerja yang menjelaskan apa yang dilakukan.
  • static vs instancestatic milik kelas dan bisa dipanggil tanpa objek; instance method bisa mengakses this dan field instance.
  • Java selalu pass-by-value — untuk primitif, salinan nilai dikirim; untuk referensi, salinan alamat dikirim — bisa modifikasi objek yang ditunjuk tapi tidak bisa ganti referensi aslinya.
  • Overloading berdasarkan signature — beda jumlah, tipe, atau urutan parameter; return type saja tidak cukup; hindari overload yang melakukan hal semantik berbeda.
  • Varargs untuk argumen fleksibeltipe... nama harus parameter terakhir; di belakang layar adalah array biasa.
  • Rekursi butuh base case — tanpa base case yang benar, rekursi tak terbatas menyebabkan StackOverflowError; gunakan iterasi untuk input besar.
  • Lambda untuk functional interface(param) -> ekspresi membuat implementasi interface satu metode secara ringkas; gunakan method reference Kelas::metode ketika lambda hanya meneruskan ke satu metode.
  • Single Responsibility — setiap metode sebaiknya melakukan tepat satu hal; metode panjang dan banyak parameter adalah sinyal bahwa metode perlu dipecah.

← Sebelumnya: Perulangan   Berikutnya: Kelas →

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