Class, Object, dan Constructor di Java

Hai, teman-teman! Pemilik Warteg Canggih baru saja menambah menu perkedel. Dia membuka kode kasir versi 10, menyisipkan "Perkedel" di urutan ketiga array namaMenu, lalu lupa menyisipkan harganya di hargaMenu. Begitu kasir dijalankan, daftar menunya jadi seperti ini:

1. Nasi putih (Rp5000)
2. Tempe orek (Rp4000)
3. Perkedel (Rp3000)
4. Es teh (Rp6000)
5. Telur balado (Rp4000)
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5

Es teh mendadak seharga telur balado, dan sayur asem malah bikin program berhenti. Penyebabnya bukan salah ketik angka, tapi cara menyimpan datanya: nama dan harga satu menu tinggal di dua array yang berbeda, dan cuma disatukan oleh urutan. Di artikel pembuka Tahap 2 ini, kita belajar class dan object di Java, supaya nama dan harga satu menu tidak bisa terpisah lagi.

Sampul seri belajar Java Studi Kasus Warteg Canggih bagian 11: Class dan Object di Java, dengan ilustrasi struk kasir warteg

TL;DR: Class adalah cetakan, object adalah barang jadi dari cetakan itu. Class Menu menyatukan field nama dan harga, lalu setiap menu dibuat sebagai object dengan new. Constructor mengisi field saat object dibuat, dan this membedakan field dari parameter yang namanya sama. Method tanpa static adalah milik object dan bisa memakai field-nya, sedangkan method static milik class. Di akhir artikel, kasir versi 11 memakai Menu[] dan class Kasir, sehingga sebagian besar static hilang.

Kenalan Dulu: Apa Itu Class dan Object di Java?

Class adalah rancangan yang menjelaskan data apa yang dimiliki sesuatu dan apa yang bisa dilakukannya. Object adalah wujud nyata dari rancangan itu, yang dibuat dengan new. Satu class bisa dipakai untuk membuat object sebanyak apa pun, dan setiap object menyimpan datanya sendiri.

Coba bayangin cetakan kue di dapur warteg. Cetakannya cuma satu, tapi kue yang dihasilkan bisa puluhan, dan isiannya boleh beda-beda. Cetakan itu class, kuenya object.

Sebenarnya kita sudah menulis class sejak artikel pertama: public class WartegCanggih. Hanya saja selama ini class itu dipakai sebagai wadah method main dan teman-temannya, tidak pernah dibuat object-nya. Gaya menyusun program dengan class dan object seperti ini disebut pemrograman berorientasi objek (object-oriented programming), atau OOP. Penjelasan resminya ada di tutorial Classes dari Oracle.

Membuat Class Menu: Nama dan Harga dalam Satu Paket

Untuk membuat class sendiri, tulis class diikuti namanya, lalu daftarkan variabel yang dimiliki setiap object di dalam kurung kurawal. Variabel milik object ini disebut field. Object dibuat dengan new Menu(), dan field-nya diakses lewat titik, seperti tempe.nama.

Masalah di pembuka tadi muncul karena nama dan harga satu menu tidak punya "rumah" bersama. Jadi kita buatkan rumahnya:

public class WartegCanggih {
    public static void main(String[] args) {
        Menu tempe = new Menu();
        tempe.nama = "Tempe orek";
        tempe.harga = 4000;

        Menu esTeh = new Menu();
        esTeh.nama = "Es teh";
        esTeh.harga = 3000;

        System.out.println(tempe.nama + " Rp" + tempe.harga);
        System.out.println(esTeh.nama + " Rp" + esTeh.harga);
    }
}

class Menu {
    String nama;
    int harga;
}

Hasilnya:

Tempe orek Rp4000
Es teh Rp3000

Ada tiga hal yang perlu diperhatikan:

  • class Menu ditulis di file yang sama, di bawah WartegCanggih, dan tanpa public. Satu file Java hanya boleh punya satu class public, dan namanya harus sama dengan nama file. Di proyek sungguhan, setiap class biasanya punya file sendiri (Menu.java), tapi satu file lebih praktis untuk latihan dan tetap bisa dijalankan dengan java WartegCanggih.java.
  • Menu tempe artinya variabel tempe bertipe Menu. Jadi Menu sekarang tipe data baru, sejajar dengan String dan int[].
  • tempe dan esTeh adalah dua object yang terpisah. Mengubah tempe.harga tidak menyentuh esTeh.harga sama sekali. Proses new ini dijelaskan lebih rinci di tutorial Objects dari Oracle.

Field Punya Nilai Bawaan

Kalau field belum diisi, isinya tidak acak. Coba cetak object yang baru dibuat:

Menu tempe = new Menu();
System.out.println(tempe.nama + " " + tempe.harga);   // null 0

Aturannya sama dengan sekat array yang belum diisi di artikel Array: 0 untuk angka, false untuk boolean, dan null untuk object seperti String. Ini berbeda dengan variabel lokal di dalam method, yang wajib diisi dulu sebelum dipakai.

Nilai bawaan ini praktis, tapi juga berbahaya. Menu dengan nama null dan harga 0 itu menu yang tidak masuk akal, dan Java tidak protes sedikit pun kalau kita lupa mengisinya.

Constructor di Java: Object Langsung Siap Pakai

Constructor adalah blok kode khusus yang otomatis dijalankan saat object dibuat dengan new. Namanya harus sama persis dengan nama class dan tidak punya tipe return. Dengan constructor, data wajib bisa langsung dikirim saat object dibuat, jadi tidak ada lagi object yang setengah jadi.

Mengisi field satu per satu seperti di atas butuh tiga baris untuk setiap menu, dan gampang sekali ada yang terlewat. Coba bayangin warung yang memesan kue ke toko: pesanannya disebut lengkap di depan ("kue lapis, isi cokelat"), bukan diambil polosan lalu dihias sendiri. Constructor bekerja seperti itu:

class Menu {
    String nama;
    int harga;

    Menu(String nama, int harga) {
        this.nama = nama;
        this.harga = harga;
    }
}

Sekarang satu menu cukup satu baris:

Menu tempe = new Menu("Tempe orek", 4000);

Bentuknya mirip method yang dibahas di artikel Method: ada parameter di dalam kurung, dan nilainya dikirim lewat argumen. Bedanya, constructor tidak punya tipe return (bahkan void pun tidak), dan hanya bisa dipanggil lewat new. Detailnya ada di tutorial Providing Constructors dari Oracle.

Default Constructor dan Jebakannya

Lalu kenapa contoh sebelumnya, yang belum punya constructor, tetap bisa memanggil new Menu()? Karena kalau sebuah class tidak menulis constructor sama sekali, Java diam-diam membuatkan satu constructor tanpa parameter yang tidak melakukan apa-apa. Constructor ini disebut default constructor, dan aturannya tercantum di spesifikasi bahasa Java bagian 8.8.9.

Kata kuncinya: hanya kalau tidak ada constructor sama sekali. Begitu kita menulis Menu(String nama, int harga), constructor bawaan itu tidak dibuat lagi. Kode lama yang masih memanggil new Menu() langsung gagal di-compile:

WartegCanggih.java:3: error: constructor Menu in class Menu cannot be applied to given types;
        Menu tempe = new Menu();
                     ^
  required: String,int
  found:    no arguments
  reason: actual and formal argument lists differ in length

Pesannya sebenarnya jelas: yang dibutuhkan (required) adalah String dan int, yang dikirim (found) tidak ada. Di kasir kita, error ini justru menguntungkan, karena tidak ada lagi cara membuat menu tanpa nama dan harga.

Keyword this: Membedakan Field dan Parameter

Di dalam constructor tadi, parameter dan field sama-sama bernama nama dan harga. Di situ ada dua nama yang berbeda, dan Java memilih yang paling dekat, yaitu parameter. this dipakai untuk menunjuk object yang sedang dibuat, jadi this.nama adalah field, sedangkan nama adalah parameter.

Coba lihat apa yang terjadi kalau this dihapus:

Menu(String nama, int harga) {
    nama = nama;      // parameter diisi dengan dirinya sendiri
    harga = harga;
}

Kode ini lolos compile tanpa peringatan apa pun. Tapi saat dijalankan:

Menu tempe = new Menu("Tempe orek", 4000);
System.out.println(tempe.nama + " Rp" + tempe.harga);   // null Rp0

Field-nya tetap kosong, karena yang diisi cuma parameter, dan parameter itu hilang begitu constructor selesai. Ini salah satu bug yang paling susah dicari, karena tidak ada error yang muncul. Penjelasan lengkapnya ada di tutorial Using the this Keyword dari Oracle.

Method Milik Object

Class tidak hanya menyimpan data, tapi juga perilaku. Sekarang Menu punya nama dan harga, jadi pekerjaan yang hanya butuh dua data itu lebih cocok diserahkan ke Menu sendiri:

class Menu {
    String nama;
    int harga;

    Menu(String nama, int harga) {
        this.nama = nama;
        this.harga = harga;
    }

    int hitungHarga(int porsi) {
        return harga * porsi;
    }

    void tampilkan(int nomor) {
        System.out.println(nomor + ". " + nama + " (Rp" + harga + ")");
    }
}

Coba perhatikan, dua method ini tidak memakai static. Di dalamnya, harga dan nama langsung bisa dipakai tanpa dikirim lewat parameter, karena method ini menempel ke satu object tertentu:

Menu telur = new Menu("Telur balado", 6000);
System.out.println(telur.hitungHarga(2));   // 12000
telur.tampilkan(4);                         // 4. Telur balado (Rp6000)

Saat telur.hitungHarga(2) dipanggil, harga di dalam method itu adalah harga milik telur. Kalau dipanggil lewat object lain, yang dipakai harga object lain itu.

Bandingkan dengan versi 10. Dulu tampilkanMenu(namaMenu, hargaMenu) harus menerima dua array, dan method perhitungan selalu minta hargaMenu dikirim. Sekarang data dan cara mengolahnya tinggal di tempat yang sama, jadi parameter yang perlu dikirim ke sana-sini jauh berkurang.

Jadi, static Itu Sebenarnya Apa?

static menandai sesuatu sebagai milik class, bukan milik object. Method static bisa dipanggil tanpa membuat object, tapi karena itu juga tidak bisa memakai field milik object. Method tanpa static adalah milik object, jadi harus dipanggil lewat object dan bebas memakai field-nya.

Di artikel Method, kita sepakat menempelkan static di setiap method tanpa penjelasan lengkap. Sekarang janjinya dilunasi.

Analogi di warteg: papan harga di dinding itu milik warteg, satu untuk semua orang, dan bisa dibaca tanpa harus jadi pembeli dulu. Itu static. Piring di meja makan itu milik masing-masing pembeli, dan isinya beda-beda. Itu field object. Kalau ada yang bertanya "piringnya isi apa?" tanpa menyebut piring siapa, pertanyaan itu tidak bisa dijawab.

Java juga tidak bisa menjawabnya. Coba panggil method tanpa static langsung dari main:

public class WartegCanggih {
    int omzet;

    public static void main(String[] args) {
        tampilkanOmzet();
    }

    void tampilkanOmzet() {
        System.out.println(omzet);
    }
}
WartegCanggih.java:4: error: non-static method tampilkanOmzet() cannot be referenced from a static context
        tampilkanOmzet();
        ^

main itu static, jadi dia tidak sedang "memegang" object apa pun. Kalau tampilkanOmzet() dipanggil dari sana, Java tidak tahu omzet milik object yang mana. Itulah alasan semua method di Tahap 1 harus static: kita belum punya object, jadi semuanya harus jadi milik class. Pembahasan resmi soal milik class dan milik object ada di tutorial Understanding Class Members dari Oracle.

Lalu kapan static masih pantas dipakai? Saat method itu memang tidak butuh data milik object mana pun. hitungDiskon(int subtotal) contohnya. Hasilnya hanya bergantung pada parameter, jadi di kasir versi 11 method ini tetap static.

Array of Object: Satu Array Menggantikan Dua

Array tidak hanya bisa berisi int atau String, tapi juga object. Dengan Menu[], satu sekat array berisi satu menu lengkap dengan nama dan harganya, jadi bug di pembuka artikel tidak mungkin terjadi lagi.

Bandingkan sebelum dan sesudahnya:

// versi 10: dua array, dijaga urutannya secara manual
String[] namaMenu = {"Nasi putih", "Tempe orek", "Es teh", "Telur balado", "Sayur asem"};
int[] hargaMenu = {5000, 4000, 3000, 6000, 4000};

// versi 11: satu array, nama dan harga tidak bisa terpisah
Menu[] daftarMenu = {
    new Menu("Nasi putih", 5000),
    new Menu("Tempe orek", 4000),
    new Menu("Es teh", 3000),
    new Menu("Telur balado", 6000),
    new Menu("Sayur asem", 4000)
};

Kalau pemilik warteg mau menambah perkedel, cukup sisipkan new Menu("Perkedel", 3000) di baris mana pun. Harganya ikut di baris yang sama, jadi tidak ada yang bisa bergeser.

Mengakses isinya pakai dua langkah: ambil object-nya lewat index, lalu ambil field-nya lewat titik, misalnya daftarMenu[1].nama. Kalau mau lebih enak dibaca, simpan dulu ke variabel:

Menu menu = daftarMenu[1];
System.out.println(menu.nama + " Rp" + menu.harga);

Baris pertama tidak membuat salinan menu. Sama seperti array di artikel Array, variabel bertipe object menyimpan rujukan, jadi menu dan daftarMenu[1] menunjuk ke object yang sama.

Jebakan: Array Berisi null

Ada satu cara membuat array of object yang sering menjebak:

Menu[] daftarMenu = new Menu[3];
System.out.println(daftarMenu[0]);        // null
System.out.println(daftarMenu[0].nama);   // error

new Menu[3] hanya menyiapkan tiga sekat, belum membuat satu object Menu pun. Isinya tiga null. Begitu kita mencoba membaca .nama dari null, program berhenti:

Exception in thread "main" java.lang.NullPointerException: Cannot read field "nama" because "<local2>[0]" is null

Sejak Java 15, pesan NullPointerException sudah menyebutkan bagian mana yang null, berkat JEP 358. Tulisan <local2> itu muncul karena secara bawaan nama variabel lokal tidak ikut disimpan saat compile. Kalau di-compile dengan javac -g, pesannya jadi lebih jelas: because "daftarMenu[0]" is null. Solusinya, isi setiap sekat dengan new Menu(...) sebelum dipakai.

Kasir Warteg Canggih Versi 11 dengan Class dan Object di Java

Sekarang Menu sudah beres, tapi masih ada satu sisa Tahap 1 yang mengganjal. Di versi 10, porsiHarian, omzet, dan jumlahPembeli dibuat di main lalu dioper ke method yang membutuhkannya. Data itu sebenarnya milik satu hal, yaitu kasir yang sedang buka hari ini. Jadi kita buatkan juga class Kasir, dan data itu kita jadikan field-nya.

Begitu data harian jadi field, method yang memakainya tidak perlu static lagi, dan parameternya ikut berkurang. Isi main sekarang tinggal menyiapkan menu, membuat kasir, lalu membukanya:

import java.util.Scanner;

public class WartegCanggih {
    public static void main(String[] args) {
        Menu[] daftarMenu = {
            new Menu("Nasi putih", 5000),
            new Menu("Tempe orek", 4000),
            new Menu("Es teh", 3000),
            new Menu("Telur balado", 6000),
            new Menu("Sayur asem", 4000)
        };

        Scanner input = new Scanner(System.in);
        Kasir kasir = new Kasir("Warteg Canggih", daftarMenu, input);
        kasir.buka();
        input.close();
    }
}

// satu object Menu = satu baris di papan menu: nama dan harga selalu satu paket
class Menu {
    String nama;
    int harga;

    Menu(String nama, int harga) {
        this.nama = nama;
        this.harga = harga;
    }

    int hitungHarga(int porsi) {
        return harga * porsi;
    }

    void tampilkan(int nomor) {
        System.out.println(nomor + ". " + nama + " (Rp" + harga + ")");
    }
}

// data yang harus bertahan sepanjang hari sekarang jadi field milik Kasir
class Kasir {
    String namaWarteg;
    Menu[] daftarMenu;
    Scanner input;
    int[] porsiHarian;
    int omzet;
    int jumlahPembeli;

    Kasir(String namaWarteg, Menu[] daftarMenu, Scanner input) {
        this.namaWarteg = namaWarteg;
        this.daftarMenu = daftarMenu;
        this.input = input;
        this.porsiHarian = new int[daftarMenu.length];
    }

    void buka() {
        System.out.println("=".repeat(28));
        System.out.println("  KASIR " + namaWarteg.toUpperCase());
        System.out.println("=".repeat(28));

        int pilihan;
        do {
            System.out.println();
            System.out.println("1. Layani pembeli");
            System.out.println("2. Lihat daftar menu");
            System.out.println("0. Tutup kasir");
            System.out.print("Pilih: ");
            pilihan = bacaAngka();

            switch (pilihan) {
                case 1 -> layaniPembeli();
                case 2 -> tampilkanMenu();
                case 0 -> System.out.println("Kasir ditutup.");
                default -> System.out.println("Pilihan " + pilihan + " tidak ada.");
            }
        } while (pilihan != 0);

        cetakRekapHarian();
    }

    // ---------- melayani satu pembeli ----------

    void layaniPembeli() {
        input.nextLine();                         // buang sisa Enter dari "Pilih"
        System.out.print("Atas nama siapa? ");
        String namaPembeli = input.nextLine().trim();
        if (namaPembeli.isEmpty()) {
            namaPembeli = "Pembeli";
        }

        tampilkanMenu();
        int[] porsi = ambilPesanan();

        int subtotal = hitungSubtotal(porsi);
        if (subtotal == 0) {
            System.out.println("Tidak ada pesanan untuk " + namaPembeli + ".");
            return;
        }
        int diskon = hitungDiskon(subtotal);
        int total = subtotal - diskon;

        cetakStruk(namaPembeli, porsi, subtotal, diskon, total);
        terimaPembayaran(total);
        catatKeRekap(porsi, total);
    }

    int[] ambilPesanan() {
        int[] porsi = new int[daftarMenu.length];
        int pilihan;
        do {
            System.out.print("Nomor menu (0 = selesai): ");
            pilihan = bacaAngka();
            if (pilihan == 0) {
                continue;
            }
            int index = pilihan - 1;
            if (index < 0 || index >= daftarMenu.length) {
                System.out.println("Menu nomor " + pilihan + " tidak ada.");
                continue;
            }
            System.out.print("Berapa porsi? ");
            int jumlah = bacaAngka();
            if (jumlah <= 0) {
                System.out.println("Jumlah porsi minimal 1.");
                continue;
            }
            porsi[index] += jumlah;
            System.out.println("Dicatat: " + daftarMenu[index].nama + " x" + jumlah);
        } while (pilihan != 0);
        return porsi;
    }

    void terimaPembayaran(int total) {
        System.out.print("Uang pembeli: ");
        int uangPembeli = bacaAngka();
        while (uangPembeli < total) {
            System.out.print("Uang kurang Rp" + (total - uangPembeli) + ". Masukkan ulang: ");
            uangPembeli = bacaAngka();
        }
        cetakKembalian(uangPembeli - total);
    }

    void catatKeRekap(int[] porsi, int total) {
        omzet += total;
        jumlahPembeli++;
        for (int i = 0; i < porsi.length; i++) {
            porsiHarian[i] += porsi[i];
        }
    }

    // ---------- perhitungan ----------

    int hitungSubtotal(int[] porsi) {
        int subtotal = 0;
        for (int i = 0; i < daftarMenu.length; i++) {
            subtotal += daftarMenu[i].hitungHarga(porsi[i]);
        }
        return subtotal;
    }

    // tidak memakai field apa pun, jadi tetap static
    static int hitungDiskon(int subtotal) {
        if (subtotal >= 20000) {
            return 2000;
        }
        if (subtotal >= 10000) {
            return 1000;
        }
        return 0;
    }

    // ---------- tampilan ----------

    void tampilkanMenu() {
        System.out.println("Daftar menu:");
        for (int i = 0; i < daftarMenu.length; i++) {
            daftarMenu[i].tampilkan(i + 1);
        }
    }

    void cetakStruk(String namaPembeli, int[] porsi, int subtotal, int diskon, int total) {
        System.out.println();
        System.out.println("Pesanan atas nama " + namaPembeli);
        for (int i = 0; i < daftarMenu.length; i++) {
            if (porsi[i] > 0) {
                Menu menu = daftarMenu[i];
                int hargaBaris = menu.hitungHarga(porsi[i]);
                System.out.println(String.format("%-14s%3s%11s", menu.nama, "x" + porsi[i], "Rp" + hargaBaris));
            }
        }
        cetakGaris(28);
        cetakBaris("Subtotal", subtotal);
        cetakBaris("Diskon", diskon);
        cetakBaris("TOTAL", total);
    }

    static void cetakKembalian(int sisa) {
        System.out.println("Kembalian: Rp" + sisa);
        int[] pecahan = {10000, 5000, 2000, 1000};
        for (int lembar : pecahan) {
            while (sisa >= lembar) {
                System.out.println("  Rp" + lembar);
                sisa -= lembar;
            }
        }
    }

    void cetakRekapHarian() {
        System.out.println();
        System.out.println("REKAP HARI INI");
        cetakGaris(28);
        for (int i = 0; i < daftarMenu.length; i++) {
            System.out.println(String.format("%-14s%14s", daftarMenu[i].nama, porsiHarian[i] + " porsi"));
        }
        cetakGaris(28);
        System.out.println(String.format("%-14s%14s", "Pembeli", jumlahPembeli + " orang"));
        cetakBaris("Omzet", omzet);

        int terlaris = 0;
        for (int i = 1; i < porsiHarian.length; i++) {
            if (porsiHarian[i] > porsiHarian[terlaris]) {
                terlaris = i;
            }
        }
        if (porsiHarian[terlaris] > 0) {
            System.out.println("Menu terlaris: " + daftarMenu[terlaris].nama);
        }
    }

    static void cetakGaris(int panjang) {
        System.out.println("-".repeat(panjang));
    }

    static void cetakBaris(String label, int nominal) {
        System.out.println(String.format("%-17s%11s", label, "Rp" + nominal));
    }

    int bacaAngka() {
        while (!input.hasNextInt()) {
            String salah = input.next();          // buang input yang bukan angka
            System.out.print("\"" + salah + "\" bukan angka, coba lagi: ");
        }
        return input.nextInt();
    }
}

Apa Saja yang Berubah dari Versi 10

Kalau dijalankan dengan pesanan yang sama seperti contoh di artikel Program Kasir Warteg, hasilnya persis sama, sampai ke rekap hariannya:

REKAP HARI INI
----------------------------
Nasi putih           1 porsi
Tempe orek           2 porsi
Es teh               2 porsi
Telur balado         1 porsi
Sayur asem           0 porsi
----------------------------
Pembeli              2 orang
Omzet                Rp23000
Menu terlaris: Tempe orek

Perilakunya sama, tapi isinya berubah cukup banyak. Mengubah susunan kode tanpa mengubah hasilnya seperti ini disebut refactoring, dan hasil yang sama persis itu justru tanda refactoring-nya berhasil. Ini perubahan yang paling terasa:

  • Parameter jauh lebih sedikit. cetakStruk dulu menerima tujuh parameter, sekarang lima. tampilkanMenu() dan cetakRekapHarian() tidak butuh parameter sama sekali, karena datanya sudah ada di field.
  • omzet dan jumlahPembeli tidak diisi 0 secara manual. Keduanya field, jadi otomatis bernilai 0 saat object Kasir dibuat.
  • Constructor Kasir menyiapkan porsiHarian. Panjangnya mengikuti jumlah menu yang dikirim, jadi menambah menu tidak perlu mengubah kode lain.
  • static tinggal di empat method: hitungDiskon, cetakKembalian, cetakGaris, dan cetakBaris. Keempatnya tidak memakai field apa pun. Sisanya milik object Kasir.
  • hitungTotal hilang. Di versi 10, main harus menghitung ulang total untuk rekap. Sekarang layaniPembeli() langsung mencatatnya lewat catatKeRekap, karena method ini bisa mengubah field milik kasirnya sendiri.

Cara menjalankannya masih sama seperti di artikel pertama: java WartegCanggih.java. Ketiga class ada di satu file, dan Java menjalankan main di class pertama.

Error yang Sering Muncul Saat Belajar Class dan Object di Java

Ada empat kesalahan yang paling sering muncul saat pertama kali membuat class. Dua ketahuan saat compile, satu baru meledak saat program berjalan, dan satu lagi tidak menampilkan error sama sekali.

Gejala Penyebab Perbaikan
constructor Menu in class Menu cannot be applied to given types Memanggil new Menu() padahal class hanya punya constructor dengan parameter Kirim argumen yang diminta, misalnya new Menu("Es teh", 3000)
non-static method ... cannot be referenced from a static context Memanggil method milik object langsung dari main Buat object-nya dulu, lalu panggil lewat object itu, seperti kasir.buka()
NullPointerException saat membaca field Array dibuat dengan new Menu[3] tapi sekatnya belum diisi object Isi setiap sekat dengan new Menu(...)
Tidak ada error, tapi field tetap null dan 0 Constructor menulis nama = nama; tanpa this Tulis this.nama = nama;

Baris terakhir itu yang paling berbahaya, karena program terlihat baik-baik saja sampai struknya mencetak null. Kebiasaan yang aman: setiap kali membuat class baru, cetak satu object-nya dulu sebelum dipakai di mana-mana.

Penutup

Yang berubah di artikel ini sebenarnya cara bertanya. Di Tahap 1, pertanyaannya selalu "langkah apa yang harus dijalankan berikutnya?". Sekarang pertanyaannya jadi "data ini milik siapa?". Nama dan harga milik Menu, omzet dan rekap milik Kasir. Begitu jawabannya jelas, letak method-nya biasanya ikut jelas.

Sebagai latihan, coba tambahkan field stok ke class Menu, isi lewat constructor, lalu kurangi stoknya setiap kali pesanan dicatat. Kalau stok tidak cukup, tolak pesanannya. Kamu akan sadar satu hal: siapa pun bisa menulis menu.stok = -5 dari luar class, dan Java diam saja. Melindungi field dari perubahan seperti ini disebut encapsulation, dan aturan "stok tidak boleh minus" nanti akan dirapikan di artikel Custom Exception.

Artikel berikutnya adalah Stack dan Queue di Java: Konsep LIFO dan FIFO, tempat pesanan yang sudah dicatat akhirnya bisa dibatalkan, dan antrian pembeli dilayani sesuai urutan datangnya.

Daftar semua artikel seri ini ada di label Warteg Canggih Series. Kalau kamu menambahkan field stok tadi, method mana yang menurutmu paling pas untuk mengurangi stok, ambilPesanan atau catatKeRekap? Tulis alasanmu di kolom komentar.

Source code lengkap artikel ini bisa dilihat di GitHub: 11-class-object-constructor-java

FAQ

Apa perbedaan class dan object di Java? Class adalah rancangan yang menentukan field dan method apa saja yang dimiliki. Object adalah wujud nyata dari rancangan itu, yang dibuat dengan new dan menyimpan datanya sendiri. Satu class bisa menghasilkan banyak object yang isinya berbeda-beda.

Apa itu constructor di Java? Constructor adalah blok kode yang otomatis dijalankan saat object dibuat dengan new, biasanya untuk mengisi field. Namanya harus sama dengan nama class dan tidak punya tipe return, bahkan void pun tidak. Kalau class tidak punya constructor sama sekali, Java membuatkan default constructor tanpa parameter.

Kenapa new Menu() error setelah saya menulis constructor sendiri? Default constructor hanya dibuat otomatis kalau class tidak punya constructor sama sekali. Begitu kita menulis constructor dengan parameter, constructor tanpa parameter tidak tersedia lagi. Solusinya, kirim argumen yang diminta, atau tulis sendiri constructor tanpa parameter kalau memang dibutuhkan.

Kapan harus memakai keyword this di Java? this dipakai untuk menunjuk object yang sedang aktif, terutama saat nama parameter sama dengan nama field. Tanpa this, penulisan nama = nama; hanya mengisi parameter dengan dirinya sendiri, dan field-nya tetap kosong. Kode seperti itu lolos compile, jadi bug-nya baru terlihat saat program berjalan.

Apa bedanya method static dan non-static di Java? Method static adalah milik class, jadi bisa dipanggil tanpa object, tapi tidak bisa memakai field milik object. Method non-static adalah milik object, jadi harus dipanggil lewat object dan bebas memakai field-nya. Pakai static hanya untuk method yang hasilnya cukup ditentukan dari parameternya, seperti menghitung diskon dari subtotal.

Referensi

Posting Komentar untuk "Class, Object, dan Constructor di Java"