Fungsi & Method di Java
Di bahasa pemrograman, semua logika bisnis kita ditulis di dalam blok kode. Agar blok kode itu bisa dipakai berulang kali — bukan ditulis ulang setiap saat — Java memberi kita method (kadang disebut fungsi). Menguasai method berarti menguasai cara memecah masalah besar menjadi potongan-potongan kecil yang rapi, bisa diuji, dan mudah dibaca. Artikel ini membahasnya dari dasar sampai yang paling sering dipakai sehari-hari di backend.
Apa Itu Method?
Method adalah blok kode yang diberi nama, menerima input (parameter), dan bisa dipanggil berkali-kali. Setiap kali dipanggil, method menjalankan instruksinya lalu (opsional) mengembalikan hasil.
public class Greeting {
public String sapa(String nama) {
return "Halo, " + nama + "!";
}
}
Bayangkan method seperti resep masakan: ditulis sekali di buku, lalu bisa diikuti setiap kali Anda mau memasak hidangan yang sama. Tanpa method, kode Anda akan penuh duplikasi — satu perubahan kecil berarti mengubah puluhan tempat.
Konsep kunci: method = kode yang diberi nama, bisa menerima input, dan bisa dipanggil ulang. Method adalah unit terkecil dari logika yang bisa diuji dan dirawat.
Method Static vs Method Instance
Method Java dibagi dua berdasarkan cara memanggilnya:
| Jenis | Kata kunci | Dipanggil lewat | Kapan dipakai |
|---|---|---|---|
| Instance method | Tanpa static | Objek (obj.method()) | Butuh membaca/mengubah state objek |
| Static method | static | Nama class (Math.max(...)) | Utilitas, tidak butuh state objek |
public class Kalkulator {
// static: cukup dipanggil lewat nama class
public static int tambah(int a, int b) {
return a + b;
}
// instance: butuh objek, bisa menyimpan state
private int total = 0;
public void akumulasi(int nilai) {
this.total += nilai;
}
public int getTotal() {
return total;
}
}
int hasil = Kalkulator.tambah(2, 3); // static → lewat nama class
Kalkulator k = new Kalkulator();
k.akumulasi(10); // instance → lewat objek
Aturan praktis backend: method yang tidak membaca atau mengubah data objek — misal formatTanggal() — sebaiknya dibuat static; method yang bekerja dengan state objek adalah instance method. Detailnya di dokumentasi tentang variabel class.
Anatomi Deklarasi Method
Deklarasi method di Java terdiri dari beberapa bagian yang masing-masing punya peran:
public static int hitungDiskon (double harga, int persen)
│ │ │ │ └──── parameter (input)
│ │ │ └──── nama method (kata kerja)
│ │ └──── return type (tipe hasil yang dikembalikan)
│ └──── modifier opsional lain
└──── access modifier (hak akses)
| Bagian | Contoh | Penjelasan |
|---|---|---|
| Access modifier | public, private, protected | Siapa saja yang boleh memanggil method ini |
| Modifier lain | static, final | Perilaku tambahan (misal bisa dipanggil tanpa objek) |
| Return type | int, String, void | Tipe nilai yang dikembalikan, atau void kalau tidak ada |
| Nama method | hitungDiskon | Kata kerja yang menggambarkan aksi method |
| Parameter | (double harga, int persen) | Input yang diterima method (boleh kosong) |
| Body | { return ...; } | Blok kode yang dijalankan saat method dipanggil |
public class Checkout {
public double hitungDiskon(double harga, int persen) {
double potongan = harga * persen / 100.0;
return harga - potongan;
}
}
Return Value: void vs Tipe Data
public class OrderService {
// Mengembalikan nilai → return type berupa tipe data
public double hitungTotal(List<Double> itemHarga) {
double total = 0;
for (double h : itemHarga) {
total += h;
}
return total;
}
// Tidak mengembalikan apa pun → return type void
public void kirimKonfirmasi(String email) {
System.out.println("Konfirmasi dikirim ke " + email);
}
}
Konsep kunci:
voidberarti method hanya "melakukan sesuatu" tanpa memberi hasil; tipe data berarti method "memberi nilai" kepada pemanggilnya. Pilih sesuai kebutuhan: method yang menghitung harusreturn, method yang melakukan aksi sampingan cukupvoid.
Early Return & Guard Clause
return tidak harus berada di akhir method. Kita boleh menghentikan eksekusi lebih awal — pola ini disebut guard clause, sangat umum di backend untuk menolak input yang tidak valid sebelum logika utama dijalankan.
public double hitungTotalBayar(double harga, int persenDiskon) {
if (harga < 0) {
return 0; // early return: input tidak valid, langsung berhenti
}
if (persenDiskon > 100) {
throw new IllegalArgumentException("Diskon tidak boleh lebih dari 100%");
}
return harga - (harga * persenDiskon / 100.0);
}
Pola ini membuat kode lebih datar (tidak bertingkat-tingkat if bersarang), validasi yang gagal berhenti lebih awal, dan logika utama hanya berjalan untuk data yang benar.
Parameter & Argument: Java Selalu Pass-by-Value
Istilah yang perlu dibedakan dulu: parameter (variabel yang ditulis di deklarasi method, seperti double harga) dan argument (nilai nyata yang diberikan saat method dipanggil, seperti 99_000).
Pertanyaan klasik yang sering menjebak junior: apakah Java pass-by-value atau pass-by-reference? Jawabannya tegas: Java selalu pass-by-value. Argument di-copy ke parameter, sehingga perubahan di dalam method tidak memengaruhi variabel asli pemanggil.
public static void ubah(int angka) {
angka = 100; // hanya mengubah salinan di parameter
}
public static void main(String[] args) {
int asli = 5;
ubah(asli);
System.out.println(asli); // tetap 5, bukan 100
}
Yang membingungkan adalah untuk objek: yang di-copy adalah nilai reference-nya (alamat objek di memori), bukan objeknya. Jadi method bisa mengubah isi objek, tetapi tidak bisa menukar objek di tempat pemanggil. Penjelasan lengkapnya di dokumentasi passing arguments.
public class User {
public String nama;
}
public static void gantiNama(User u) {
u.nama = "Budi"; // efek terlihat di pemanggil → objek yang sama
}
public static void tukar(User a, User b) {
User tmp = a; // hanya menukar salinan reference
a = b;
b = tmp; // pemanggil tidak terpengaruh
}
public static void main(String[] args) {
User u = new User();
u.nama = "Andi";
gantiNama(u);
System.out.println(u.nama); // "Budi" — isi objek berubah
}
Konsep kunci: "pass-by-value" untuk objek berarti alamat objek disalin. Method bisa mengubah isi objek yang dipegangnya, tetapi tidak bisa mengganti objek milik pemanggil dengan objek baru.
Method Overloading
Overloading adalah teknik menulis beberapa method dengan nama sama tetapi parameter berbeda (jumlah, tipe, atau urutannya). Java memilih method yang tepat berdasarkan argument yang diberikan saat pemanggilan.
public class Formatter {
public String format(int angka) {
return "Rp " + angka;
}
public String format(double angka) {
return "Rp " + String.format("%.2f", angka);
}
public String format(int angka, String kodeMataUang) {
return kodeMataUang + " " + angka;
}
}
Contoh paling terkenal di JDK adalah Integer.parseInt(...) yang memiliki versi overload: parseInt(String), parseInt(String, int radix), dan lainnya. Overloading membuat API terasa nyaman: pemanggil memakai nama yang sama, Java yang mencarikan versi yang cocok.
Syarat overload: parameter harus berbeda; mengubah return type saja tidak dianggap overload dan akan error kompilasi.
Varargs: Argument Berjumlah Tak Tentu
Kadang kita tidak tahu berapa banyak argument yang akan diberikan. Dengan varargs (tanda ...), sebuah parameter bisa menerima jumlah nilai berapa pun, dan di dalam method diperlakukan sebagai array:
public class OrderService {
public static double totalSemua(double... harga) {
double total = 0;
for (double h : harga) {
total += h;
}
return total;
}
}
double t = OrderService.totalSemua(10_000, 20_000); // 2 argumen
double t2 = OrderService.totalSemua(1_000, 2_000, 3_000); // 3 argumen
double t3 = OrderService.totalSemua(); // 0 argumen, total = 0
Perhatikan aturannya: varargs harus menjadi parameter terakhir dan hanya boleh ada satu; di belakang layar Java mengubahnya menjadi array (double[] harga).
Rekursi: Method yang Memanggil Diri Sendiri
Rekursi terjadi ketika sebuah method memanggil dirinya sendiri untuk menyelesaikan versi lebih kecil dari masalah yang sama. Contoh klasiknya adalah faktorial (5! = 5 × 4 × 3 × 2 × 1):
public static int faktorial(int n) {
if (n <= 1) {
return 1; // base case: titik berhenti
}
return n * faktorial(n - 1); // recursive case: panggil diri sendiri
}
Setiap method rekursif wajib punya base case (kondisi berhenti). Tanpa itu, method akan terus memanggil dirinya sendiri sampai memori habis dan memunculkan StackOverflowError — setiap pemanggilan menyimpan konteks di call stack yang terbatas.
public static int tanpaBaseCase(int n) {
return n + tanpaBaseCase(n - 1); // tidak pernah berhenti
}
Konsep kunci: rekursi menambah satu frame ke call stack per pemanggilan; kedalaman terlalu besar →
StackOverflowError. Untuk data besar, loopfor/while(iterasi) lebih hemat memori dan lebih cepat.
Kapan memakai apa
| Situasi | Pilihan |
|---|---|
| Masalah terstruktur bertingkat (tree, folder) | Rekursi lebih ekspresif |
| Operasi pada list/rangkaian data besar | Iterasi (for/while) |
| Depth sangat dalam (ribuan level) | Hindari rekursi, pakai iterasi |
Method String yang Sering Dipakai di Backend
String adalah tipe yang paling sering diolah di backend — email, nama, alamat, JSON snippet. Ini method yang wajib hafal:
| Method | Contoh | Hasil |
|---|---|---|
length() | "user".length() | 4 |
substring(a, b) | "hendro@mail.com".substring(0, 6) | "hendro" |
split(regex) | "a,b,c".split(",") | ["a","b","c"] |
contains(s) | "user@mail.com".contains("@") | true |
toLowerCase() | "ADMIN".toLowerCase() | "admin" |
trim() | " budi ".trim() | "budi" |
format(fmt, args...) | String.format("Halo %s", "Budi") | "Halo Budi" |
String raw = " HEndro@Mail.Com ";
String email = raw.trim().toLowerCase(); // "hendro@mail.com"
boolean valid = email.contains("@") && email.contains(".");
String[] parts = email.split("@"); // ["hendro", "mail.com"]
String label = String.format("Member: %s", parts[0]); // "Member: hendro"
Perhatikan cara berantai: raw.trim().toLowerCase() — method string bisa dipanggil berurutan karena setiap method mengembalikan String baru. Ini disebut method chaining.
Lambda & Functional Interface
Sejak Java 8, kita bisa menulis logika singkat sebagai lambda expression: fungsi tanpa nama dengan sintaks (params) -> body.
// Tanpa lambda (pakai anonymous class)
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
// Dengan lambda — jauh lebih singkat
Collections.sort(list, (a, b) -> a.length() - b.length());
Lambda hanya bisa dipakai pada functional interface — interface yang punya tepat satu method abstrak. Seperti Comparator di atas, atau interface bawaan di package java.util.function:
| Interface | Input → Output | Contoh pemakaian |
|---|---|---|
Function<T, R> | T → R | s -> s.trim() (ubah satu nilai jadi nilai lain) |
Predicate<T> | T → boolean | u -> u.age >= 17 (uji kondisi) |
Supplier<T> | () → T | () -> UUID.randomUUID().toString() (produksi nilai) |
Consumer<T> | T → void | log -> System.out.println(log) (aksi, tanpa hasil) |
Lambda ini berpasangan sangat erat dengan Stream API. Dalam satu kalimat: stream memproses koleksi secara deklaratif, dan lambda adalah "mesin kecil" yang kita selipkan ke dalamnya.
List<User> dewasa = users.stream()
.filter(u -> u.age >= 17) // Predicate
.map(u -> u.email.toUpperCase()) // Function
.toList(); // kumpulkan hasil
Method Reference
Method reference adalah penyederhanaan lambda ketika lambda hanya memanggil method yang sudah ada, dengan sintaks ClassName::methodName.
// Lambda penuh
users.forEach(u -> System.out.println(u.email));
// Method reference — lebih bersih
users.forEach(System.out::println);
// Contoh di backend: urutkan user berdasarkan nama
users.sort(Comparator.comparing(User::getNama));
User::getNama sama artinya dengan u -> u.getNama(). Pakai method reference saat lambda hanya "meneruskan" pemanggilan; kalau body lambda berisi logika, tulis lambda biasa agar tetap jelas.
Contoh Mini Integrasi: Service Validasi & Diskon
Semua konsep di atas saling menguatkan. Berikut contoh kecil yang menyusun method-method menjadi satu service sederhana — pola yang sangat mirip dengan kode backend sungguhan: method satu tanggung jawab, guard clause untuk validasi, method static untuk logika murni, dan lambda untuk pengolahan data.
import java.util.List;
public class OrderService {
// 1. Guard clause — validasi email
public static boolean isValidEmail(String email) {
if (email == null || email.isBlank()) {
return false;
}
return email.contains("@") && email.contains(".");
}
// 2. Static method murni — hitung diskon
public static double hitungDiskon(double harga, int persen) {
if (harga < 0 || persen < 0 || persen > 100) {
throw new IllegalArgumentException("Input diskon tidak valid");
}
return harga - (harga * persen / 100.0);
}
// 3. Instance method — memakai state, memanggil method lain
private double totalPendapatan;
public void prosesPembayaran(String email, double harga, int persen) {
if (!isValidEmail(email)) {
throw new IllegalArgumentException("Email tidak valid");
}
double dibayar = hitungDiskon(harga, persen);
totalPendapatan += dibayar;
System.out.printf("Tagihan dikirim ke %s: Rp %.2f%n", email, dibayar);
}
// 4. Memanfaatkan lambda + method reference
public static List<String> emailValid(List<String> emails) {
return emails.stream()
.map(String::trim)
.filter(OrderService::isValidEmail)
.map(String::toLowerCase)
.toList();
}
}
Lihat bagaimana method-method ini saling memanggil: prosesPembayaran memakai isValidEmail untuk validasi dan hitungDiskon untuk perhitungan, sementara emailValid merangkai lambda untuk memfilter koleksi. Setiap method kecil berdiri sendiri dan mudah diuji — inilah kenapa menguasai method adalah kunci menulis backend yang rapi.
Ringkasan
- Method adalah blok kode bernama yang bisa dipanggil ulang; unit logika terkecil yang bisa diuji.
- Static method dipanggil lewat nama class dan cocok untuk utilitas; instance method bekerja dengan state objek.
- Anatomi method: access modifier, return type, nama, parameter, dan body — masing-masing punya peran jelas.
void= tanpa hasil; tipe data = mengembalikan nilai. Gunakan guard clause untuk menghentikan eksekusi lebih awal saat input tidak valid.- Java selalu pass-by-value; untuk objek, yang disalin adalah reference-nya, jadi isi objek bisa berubah tapi objeknya tidak bisa diganti.
- Overloading = nama method sama, parameter berbeda; varargs (
...) menampung argumen berjumlah tak tentu. - Rekursi elegan untuk masalah bertingkat, tetapi hati-hati
StackOverflowError; iterasi lebih hemat memori untuk data besar. - String method seperti
substring,split,trim, danformatadalah alat sehari-hari backend. - Lambda (
(p) -> body) dan method reference (Class::method) membuat kode koleksi dan stream jauh lebih ringkas. - Service yang baik = kumpulan method kecil, satu tanggung jawab, saling memanggil dengan rapi.