OOP di Java: Kelas, Objek, Enkapsulasi, Pewarisan, dan Abstraksi
Pelajari Pemrograman Berorientasi Objek di Java dengan contoh praktis. Kuasai kelas, objek, pewarisan, enkapsulasi, dan kelas abstrak menggunakan sistem menu restoran.
Java secara konsisten menjadi salah satu dari tiga bahasa paling populer di dunia. Adopsinya di bidang seperti pengembangan perangkat lunak enterprise, aplikasi seluler Android, dan aplikasi web skala besar tidak tertandingi. Sistem tipe yang kuat, ekosistem yang luas, dan kemampuan “tulis sekali, jalankan di mana saja” menjadikannya sangat menarik untuk membangun sistem yang tangguh dan skalabel. Dalam artikel ini, kita akan mengeksplorasi bagaimana fitur pemrograman berorientasi objek Java memungkinkan pengembang memanfaatkan kemampuan ini secara efektif, sehingga mereka dapat membangun aplikasi yang mudah dipelihara dan dapat diskalakan melalui pengorganisasian dan pemakaian ulang kode yang tepat.
Catatan tentang Mengorganisir dan Menjalankan Kode Java
Sebelum kita mulai menulis kode, mari lakukan sedikit penyiapan.
Seperti halnya sintaksnya, Java memiliki aturan ketat tentang pengorganisasian kode.
Pertama, setiap kelas public harus berada di file tersendiri, dinamai persis seperti nama kelasnya tetapi dengan ekstensi .java. Jadi, jika saya ingin menulis kelas Laptop, nama filenya harus Laptop.java—peka huruf besar-kecil. Anda dapat memiliki kelas non-public dalam file yang sama, tetapi sebaiknya dipisah. Saya tahu kita sedikit melangkah ke depan—membahas pengorganisasian kelas bahkan sebelum menulisnya—tetapi memiliki gambaran kasar tentang di mana menempatkan sesuatu sejak awal adalah ide yang baik.
Semua proyek Java harus memiliki file Main.java dengan kelas Main. Di sinilah Anda menguji kelas-kelas Anda dengan membuat objek darinya.
Untuk menjalankan kode Java, kita akan menggunakanIntelliJ IDEA, IDE Java yang populer. Setelah memasang IntelliJ:
Buat proyek Java baru (File > New > Project)
Klik kanan folder src untuk membuat file Main.java dan tempelkan konten berikut:
publicclassMain{publicstaticvoidmain(String[] args){// Create and tests objects here}}Didukung oleh
Setiap kali kita membahas kelas, kita menulis kode di file lain selain file Main.java. Namun jika kita membahas pembuatan dan pengujian objek, kita beralih ke Main.java.
Untuk menjalankan program, Anda dapat mengeklik tombol putar hijau di sebelah metode main:
Keluaran akan ditampilkan di jendela alat Run di bagian bawah.
Jika Anda benar-benar baru di Java, silakan lihatkursus Introduction to Java kami, yang membahas dasar-dasar tipe data Java dan alur kontrol sebelum melanjutkan.
Jika tidak, mari kita langsung masuk.
Kelas dan Objek di Java
Jadi, sebenarnya apa itu kelas?
Kelas adalah konstruksi pemrograman di Java untuk merepresentasikan konsep dunia nyata. Misalnya, perhatikan kelas MenuItem ini (buat sebuah file untuk menulis kelas ini di IDE Anda):
publicclassMenuItem{publicString name;publicdouble price;}Didukung oleh
Kelas memberi kita cetak biru atau templat untuk merepresentasikan berbagai item menu di restoran. Dengan mengubah dua atribut kelas, name, dan price, kita dapat membuat tak terhitung banyaknya objek menu seperti burger atau salad.
Jadi, untuk membuat kelas di Java, Anda memulai baris yang menjelaskan tingkat akses kelas (private, public, atau protected) diikuti oleh nama kelas. Segera setelah kurung kurawal, Anda uraikan atribut-atribut kelas Anda.
Namun bagaimana kita membuat objek yang termasuk dalam kelas ini? Java memungkinkannya melalui metode konstruktor:
Konstruktor adalah metode khusus yang dipanggil ketika kita membuat objek baru dari sebuah kelas. Konstruktor menginisialisasi atribut objek dengan nilai yang kita berikan. Pada contoh di atas, konstruktor menerima parameter name dan price lalu menetapkannya ke field objek menggunakan kata kunci ‘this’ untuk merujuk pada instance objek di masa mendatang.
Sintaks konstruktor berbeda dari metode kelas lainnya karena tidak mengharuskan Anda menentukan tipe pengembalian. Selain itu, konstruktor harus memiliki nama yang sama dengan kelas, dan sebaiknya memiliki jumlah atribut yang sama dengan yang Anda deklarasikan setelah definisi kelas. Di atas, konstruktor membuat dua atribut karena kita mendeklarasikan dua setelah definisi kelas: name dan price.
Setelah Anda menulis kelas dan konstruktornya, Anda dapat membuat instance (objek) di metode main Anda:
Di atas, kita membuat dua objek MenuItem ke dalam variabel burger dan salad. Sesuai ketentuan di Java, tipe variabel harus dideklarasikan, yaitu MenuItem. Lalu, untuk membuat instance dari kelas kita, tuliskan kata kunci new diikuti dengan pemanggilan metode konstruktor.
Selain konstruktor, Anda dapat membuat metode biasa yang memberi perilaku pada kelas Anda. Misalnya, di bawah ini, kita menambahkan metode untuk menghitung total harga setelah pajak:
publicclassMenuItem{publicString name;publicdouble price;// ConstructorpublicMenuItem(String name,double price){this.name = name;this.price = price;}// Method to calculate price after taxpublicdoublegetPriceAfterTax(){double taxRate =0.08;// 8% tax ratereturn price +(price * taxRate);}}Didukung oleh
Sekarang kita dapat menghitung total harga termasuk pajak:
publicclassMain{publicstaticvoidmain(String[] args){MenuItem burger =newMenuItem("Burger",3.5);System.out.println("Price after tax: $"+ burger.getPriceAfterTax());}}Didukung oleh
Tujuan kelas adalah menyediakan cetak biru untuk membuat objek. Objek-objek ini kemudian akan digunakan oleh skrip atau program lain. Misalnya, objek MenuItem kita dapat digunakan oleh antarmuka pengguna yang menampilkan nama, harga, dan gambarnya di layar.
Untuk alasan ini, kita harus merancang kelas sedemikian rupa sehingga instance-nya hanya dapat digunakan sesuai yang kita maksudkan. Saat ini, kelas MenuItem kita sangat dasar dan rawan kesalahan. Seseorang mungkin membuat objek dengan atribut yang tidak masuk akal, seperti pai apel berharga negatif atau sandwich bernilai jutaan dolar:
// Inside Main.javaMenuItem applePie =newMenuItem("Apple Pie",-5.99);// Negative price!MenuItem sandwich =newMenuItem("Sandwich",1000000);// Unreasonably expensiveSystem.out.println("Apple pie price: $"+ applePie.price);System.out.println("Sandwich price: $"+ sandwich.price);Didukung oleh
Jadi, langkah pertama setelah menulis sebuah kelas adalah melindungi atributnya dengan membatasi bagaimana atribut tersebut dibuat dan diakses. Untuk mulai, kita ingin hanya mengizinkan nilai positif untuk price dan menetapkan nilai maksimum untuk menghindari menampilkan item yang sangat mahal secara tidak sengaja.
Java memungkinkan kita mencapai ini dengan menggunakan metode setter:
publicclassMenuItem{privateString name;privatedouble price;privatestaticfinaldoubleMAX_PRICE=100.0;publicMenuItem(String name,double price){this.name = name;setPrice(price);}publicvoidsetPrice(double price){if(price <0){thrownewIllegalArgumentException("Price cannot be negative");}if(price >MAX_PRICE){thrownewIllegalArgumentException("Price cannot exceed $"+MAX_PRICE);}this.price = price;}}Didukung oleh
Mari kita telaah apa yang baru pada blok kode di atas:
1. Kita menjadikan atribut private dengan menambahkan kata kunci private. Ini berarti atribut hanya dapat diakses di dalam kelas MenuItem. Enkapsulasi dimulai dari langkah krusial ini.
2. Kita menambahkan konstanta baru MAX_PRICE yang bersifat:
private (hanya dapat diakses dalam kelas)
static (dibagikan di seluruh instance)
final (tidak dapat diubah setelah inisialisasi)
ditetapkan $100.0 sebagai harga maksimum yang wajar
3. Kita menambahkan metode setPrice() yang:
Menerima parameter harga
Memvalidasi bahwa harga tidak negatif
Memvalidasi bahwa harga tidak melebihi MAX_PRICE
Melempar IllegalArgumentException dengan pesan deskriptif jika validasi gagal
Hanya menetapkan harga jika semua validasi lolos
4. Kita memodifikasi konstruktor untuk menggunakan setPrice() alih-alih langsung menetapkan harga. Ini memastikan validasi harga terjadi saat pembuatan objek.
Kita baru saja menerapkan salah satu pilar inti desain OOP yang baik — enkapsulasi. Paradigma ini menegakkan penyembunyian data dan akses terkontrol ke atribut objek, memastikan detail implementasi internal terlindungi dari campur tangan eksternal dan hanya dapat diubah melalui antarmuka yang terdefinisi dengan baik.
Mari kita pertegas dengan menerapkan enkapsulasi pada atribut name. Bayangkan kita memiliki kedai kopi yang hanya menyajikan latte, cappuccino, espresso, americano, dan mocha.
Jadi, nama item menu kita hanya bisa salah satu dari daftar ini. Berikut cara kita menegakkannya dalam kode:
// Rest of the class here...privatestaticfinalString[]VALID_NAMES={"latte","cappuccino","espresso","americano","mocha"};privateString name;publicvoidsetName(String name){String lowercaseName = name.toLowerCase();for(String validName :VALID_NAMES){if(validName.equals(lowercaseName)){this.name = name;return;}}thrownewIllegalArgumentException("Invalid drink name. Must be one of: "+String.join(", ",VALID_NAMES));}Didukung oleh
Kode di atas menerapkan validasi nama untuk item menu di kedai kopi. Mari kita uraikan:
1. Pertama, didefinisikan array private static final VALID_NAMES yang berisi satu-satunya nama minuman yang diizinkan: latte, cappuccino, espresso, americano, dan mocha. Array ini bersifat:
private: hanya dapat diakses dalam kelas
static: dibagikan di seluruh instance
final: tidak dapat diubah setelah inisialisasi
2. Dideklarasikan field String name privat untuk menyimpan nama minuman
3. Metode setName() menerapkan logika validasi:
Menerima parameter String name
Mengonversinya ke huruf kecil agar perbandingan tidak peka huruf besar-kecil
Melakukan iterasi pada array VALID_NAMES
Jika ada kecocokan, menetapkan name dan keluar
Jika tidak ada kecocokan, melempar IllegalArgumentException dengan pesan deskriptif yang mencantumkan semua opsi valid
Berikut kelas lengkap sejauh ini:
publicclassMenuItem{privateString name;privatedouble price;privatestaticfinalString[]VALID_NAMES={"latte","cappuccino","espresso","americano","mocha"};privatestaticfinaldoubleMAX_PRICE=100.0;publicMenuItem(String name,double price){setName(name);setPrice(price);}publicvoidsetName(String name){String lowercaseName = name.toLowerCase();for(String validName :VALID_NAMES){if(validName.equals(lowercaseName)){this.name = name;return;}}thrownewIllegalArgumentException("Invalid drink name. Must be one of: "+String.join(", ",VALID_NAMES));}publicvoidsetPrice(double price){if(price <0){thrownewIllegalArgumentException("Price cannot be negative");}if(price >MAX_PRICE){thrownewIllegalArgumentException("Price cannot exceed $"+MAX_PRICE);}this.price = price;}}Didukung oleh
Setelah kita melindungi cara atribut dibuat, kita juga ingin melindungi cara atribut diakses. Ini dilakukan dengan menggunakan metode getter:
publicclassMenuItem{// Rest of the code here...publicStringgetName(){return name;}publicdoublegetPrice(){return price;}}Didukung oleh
Metode getter menyediakan akses terkontrol ke atribut privat dari sebuah kelas. Metode ini menyelesaikan masalah akses atribut langsung yang dapat menyebabkan modifikasi yang tidak diinginkan dan merusak enkapsulasi.
Sebagai contoh, tanpa getter, kita mungkin mengakses atribut secara langsung:
MenuItem item =newMenuItem("Latte",4.99);String name = item.name;// Direct access to attribute
item.name ="INVALID";// Can modify directly, bypassing validationDidukung oleh
Dengan getter, kita menegakkan akses yang benar:
MenuItem item =newMenuItem("Latte",4.99);String name = item.getName();// Controlled access through getter// item.name = "INVALID"; // Not allowed - must use setName() which validatesDidukung oleh
Enkapsulasi ini:
Melindungi integritas data dengan mencegah modifikasi yang tidak valid
Memungkinkan kita mengubah implementasi internal tanpa memengaruhi kode yang menggunakan kelas
Menyediakan satu titik akses yang dapat menyertakan logika tambahan bila diperlukan
Membuat kode lebih mudah dipelihara dan lebih kecil kemungkinannya menimbulkan bug
Pewarisan
Kelas kita mulai terlihat baik tetapi masih ada cukup banyak masalah. Misalnya, untuk restoran besar yang menyajikan banyak jenis hidangan dan minuman, kelas ini tidak cukup fleksibel.
Jika kita ingin menambahkan berbagai jenis makanan, kita akan menghadapi beberapa tantangan. Beberapa hidangan bisa disiapkan untuk dibawa pulang, sementara yang lain perlu dikonsumsi segera. Item menu mungkin memiliki harga dan diskon yang bervariasi. Hidangan mungkin memerlukan pelacakan suhu atau penyimpanan khusus. Minuman bisa panas atau dingin dengan bahan yang dapat disesuaikan. Item mungkin memerlukan informasi alergen dan opsi porsi. Sistem saat ini tidak menangani kebutuhan yang beragam ini.
Pewarisan memberikan solusi elegan untuk semua masalah ini. Pewarisan memungkinkan kita membuat versi khusus dari item menu dengan mendefinisikan kelas dasar MenuItem dengan atribut umum lalu membuat kelas turunan yang mewarisi dasar-dasar ini sambil menambahkan fitur unik.
Sebagai contoh, kita bisa memiliki kelas Drink untuk minuman dengan opsi suhu, kelas Food untuk item yang perlu segera dikonsumsi, dan kelas Dessert untuk item dengan kebutuhan penyimpanan khusus — semuanya mewarisi fungsionalitas inti item menu.
Memperluas kelas
Mari implementasikan ide-ide tersebut dimulai dengan Drink:
Untuk mendefinisikan kelas anak yang mewarisi dari induk, kita menggunakan kata kunci extends setelah nama kelas anak diikuti kelas induk. Setelah definisi kelas, kita mendefinisikan atribut baru apa pun yang dimiliki anak ini dan mengimplementasikan konstruktornya.
Namun perhatikan bagaimana kita harus mengulang inisialisasi name dan price bersama dengan isCold. Itu tidak ideal karena kelas induk mungkin memiliki ratusan atribut. Selain itu, kode di atas akan menghasilkan error saat Anda mengompilasinya karena itu bukan cara yang benar untuk menginisialisasi atribut kelas induk. Cara yang benar adalah menggunakan kata kunci super:
Kata kunci super digunakan untuk memanggil konstruktor kelas induk. Dalam hal ini, super(name, price) memanggil konstruktor MenuItem untuk menginisialisasi atribut tersebut, menghindari duplikasi kode. Kita hanya perlu menginisialisasi atribut baru isCold yang spesifik untuk kelas Drink.
Kata kunci ini sangat fleksibel karena Anda dapat menggunakannya untuk merujuk pada kelas induk di bagian mana pun dari kelas anak. Misalnya, untuk memanggil metode induk, Anda gunakan super.methodName() sementara super.attributeName untuk atribut.
Method overriding
Sekarang, anggaplah kita ingin menambahkan metode baru ke kelas-kelas kita untuk menghitung total harga setelah pajak. Karena item menu yang berbeda mungkin memiliki tarif pajak yang berbeda (misalnya, makanan siap saji vs. minuman kemasan), kita dapat menggunakan method overriding untuk menerapkan perhitungan pajak khusus di setiap kelas turunan sambil mempertahankan nama metode yang sama di kelas induk.
Berikut tampilannya:
publicclassMenuItem{// Rest of the MenuItem classpublicdoublecalculateTotalPrice(){// Default tax rate of 10%return price *1.10;}}publicclassFoodextendsMenuItem{privateboolean isVegetarian;publicFood(String name,double price,boolean isVegetarian){super(name, price);this.isVegetarian = isVegetarian;}@OverridepublicdoublecalculateTotalPrice(){// Food has 15% taxreturnsuper.getPrice()*1.15;}}publicclassDrinkextendsMenuItem{privateboolean isCold;publicDrink(String name,double price,boolean isCold){super(name, price);this.isCold = isCold;}@OverridepublicdoublecalculateTotalPrice(){// Drinks have 8% taxreturnsuper.getPrice()*1.08;}}Didukung oleh
Dalam contoh ini, method overriding memungkinkan setiap subclass menyediakan implementasi sendiri untuk calculateTotalPrice():
Kelas dasar MenuItem mendefinisikan perhitungan pajak default sebesar 10%.
Ketika kelas Food dan Drink memperluas MenuItem, mereka override metode ini untuk menerapkan tarif pajak masing-masing:
Item makanan memiliki tarif pajak lebih tinggi yaitu 15%
Minuman memiliki tarif pajak lebih rendah yaitu 8%
Anotasi @Override digunakan untuk secara eksplisit menunjukkan bahwa metode ini menimpa metode kelas induk. Ini membantu menangkap kesalahan jika tanda tangan metode tidak cocok dengan kelas induk.
Setiap subclass masih dapat mengakses harga kelas induk menggunakan super.getPrice(), menunjukkan bagaimana metode yang dioverride dapat memanfaatkan fungsionalitas kelas induk sambil menambahkan perilaku mereka sendiri.
Singkatnya, method overriding adalah bagian integral dari pewarisan yang memungkinkan subclass menyediakan implementasi metode mereka sendiri yang didefinisikan di kelas induk, sehingga memungkinkan perilaku yang lebih spesifik sambil mempertahankan tanda tangan metode yang sama.
Kelas Abstrak
Hierarki kelas MenuItem kita berfungsi, tetapi ada masalah: haruskah siapa pun dapat membuat objek MenuItem biasa? Bagaimanapun, di restoran kita, setiap item menu adalah Food atau Drink—tidak ada yang namanya “item menu generik.”
Kita dapat mencegah hal ini dengan menjadikan MenuItem sebagai kelas abstrak. Kelas abstrak hanya menyediakan cetak biru dasar—kelas ini hanya dapat digunakan sebagai kelas induk untuk pewarisan, bukan diinstansiasi langsung.
Untuk membuat MenuItem menjadi abstrak, kita tambahkan kata kunci abstract setelah modifier aksesnya:
publicabstractclassMenuItem{privateString name;privatedouble price;publicMenuItem(String name,double price){setName(name);setPrice(price);}// Existing getters/setters remain the same// Make this method abstract - every subclass MUST implement itpublicabstractdoublecalculateTotalPrice();}Didukung oleh
Kelas abstrak juga dapat memiliki metode abstrak seperti calculateTotalPrice() di atas. Metode abstrak ini berfungsi sebagai kontrak yang memaksa subclass untuk menyediakan implementasinya. Dengan kata lain, metode abstrak apa pun dalam kelas abstrak harus diimplementasikan oleh kelas anak.
Jadi, mari kita tulis ulang Food dan Drink dengan perubahan ini dalam pikiran:
Melalui implementasi sistem menu ini, kita telah melihat bagaimana abstraksi dan pewarisan bekerja bersama untuk menciptakan kode yang fleksibel dan mudah dipelihara yang dapat dengan mudah beradaptasi dengan berbagai kebutuhan bisnis.
Kesimpulan
Hari ini, kita telah melihat sekilas tentang kemampuan Java sebagai bahasa pemrograman berorientasi objek. Kita telah membahas dasar-dasar kelas, objek, dan beberapa pilar utama OOP: enkapsulasi, pewarisan, dan abstraksi melalui sistem menu restoran.
Untuk membuat sistem ini siap produksi, Anda masih memiliki banyak hal untuk dipelajari, seperti interface (bagian dari abstraksi), polimorfisme, dan pola desain OOP. Untuk mempelajari lebih lanjut tentang konsep-konsep ini, lihatkursus Introduction to OOP in Java kami.
Apa prasyarat untuk mengikuti tutorial OOP Java ini?⌄
Anda sebaiknya memiliki pengetahuan dasar pemrograman Java, termasuk tipe data, variabel, alur kontrol (if/else, loop), dan sintaks dasar. Kursus Introduction to Java kami membahas dasar-dasar ini jika Anda perlu penyegaran.
Mengapa menggunakan sistem menu restoran untuk menjelaskan konsep OOP?⌄
Sistem menu restoran adalah contoh intuitif yang menunjukkan penerapan prinsip OOP di dunia nyata. Contoh ini secara alami memperlihatkan pewarisan (berbagai jenis item menu), enkapsulasi (melindungi nilai harga dan nama), dan interface (program loyalitas, kontrol suhu). Kebanyakan orang memahami cara kerja restoran, sehingga konsepnya lebih mudah dipahami.
Apa perbedaan antara kelas abstrak dan interface di Java?⌄
Sebuah kelas hanya dapat memperluas satu kelas abstrak tetapi dapat mengimplementasikan banyak interface.
Kelas abstrak dapat memiliki field dan konstruktor; interface tidak dapat.
Kelas abstrak dapat memiliki metode abstrak dan konkret; secara tradisional interface hanya memiliki metode abstrak (sebelum Java 8).
Kelas abstrak menyediakan implementasi dasar, sementara interface mendefinisikan kontrak.
Saya adalah kreator konten data science dengan pengalaman lebih dari 2 tahun dan salah satu dengan jumlah pengikut terbesar di Medium. Saya suka menulis artikel mendalam tentang AI dan ML dengan sedikit gaya sarkastik—karena harus ada sesuatu untuk membuatnya tidak terlalu membosankan. Saya telah menghasilkan lebih dari 130 artikel dan satu kursus di DataCamp, dengan satu lagi sedang dalam proses. Konten saya telah dilihat oleh lebih dari 5 juta pasang mata, dan 20 ribu di antaranya menjadi pengikut di Medium dan LinkedIn.
Kuasai pertanyaan wawancara basis data, dari konsep SQL dasar hingga skenario desain sistem tingkat lanjut. Panduan mendalam ini mencakup semua yang Anda perlukan untuk sukses di wawancara DBMS dan meraih peran berikutnya.
Temukan alasan mengapa Anda sebaiknya (tidak) menggunakan spaghetti plot untuk menyampaikan ketidakpastian jalur prediksi badai serta dampaknya terhadap interpretasi.
Pelajari konsep-konsep dasar pemrograman berorientasi objek, mulai dari kelas dan objek dasar hingga topik-topik lanjutan seperti pewarisan dan polimorfisme.
Java secara konsisten menjadi salah satu dari tiga bahasa paling populer di dunia. Adopsinya di bidang seperti pengembangan perangkat lunak enterprise, aplikasi seluler Android, dan aplikasi web skala besar tidak tertandingi. Sistem tipe yang kuat, ekosistem yang luas, dan kemampuan “tulis sekali, jalankan di mana saja” menjadikannya sangat menarik untuk membangun sistem yang tangguh dan skalabel. Dalam artikel ini, kita akan mengeksplorasi bagaimana fitur pemrograman berorientasi objek Java memungkinkan pengembang memanfaatkan kemampuan ini secara efektif, sehingga mereka dapat membangun aplikasi yang mudah dipelihara dan dapat diskalakan melalui pengorganisasian dan pemakaian ulang kode yang tepat.
Catatan tentang Mengorganisir dan Menjalankan Kode Java
Sebelum kita mulai menulis kode, mari lakukan sedikit penyiapan.
Seperti halnya sintaksnya, Java memiliki aturan ketat tentang pengorganisasian kode.
Pertama, setiap kelas public harus berada di file tersendiri, dinamai persis seperti nama kelasnya tetapi dengan ekstensi
.java. Jadi, jika saya ingin menulis kelas Laptop, nama filenya harusLaptop.java—peka huruf besar-kecil. Anda dapat memiliki kelas non-public dalam file yang sama, tetapi sebaiknya dipisah. Saya tahu kita sedikit melangkah ke depan—membahas pengorganisasian kelas bahkan sebelum menulisnya—tetapi memiliki gambaran kasar tentang di mana menempatkan sesuatu sejak awal adalah ide yang baik.Semua proyek Java harus memiliki file
Main.javadengan kelas Main. Di sinilah Anda menguji kelas-kelas Anda dengan membuat objek darinya.Untuk menjalankan kode Java, kita akan menggunakan IntelliJ IDEA, IDE Java yang populer. Setelah memasang IntelliJ:
Main.javadan tempelkan konten berikut:Setiap kali kita membahas kelas, kita menulis kode di file lain selain file
Main.java. Namun jika kita membahas pembuatan dan pengujian objek, kita beralih keMain.java.Untuk menjalankan program, Anda dapat mengeklik tombol putar hijau di sebelah metode main:
Keluaran akan ditampilkan di jendela alat Run di bagian bawah.
Jika Anda benar-benar baru di Java, silakan lihat kursus Introduction to Java kami, yang membahas dasar-dasar tipe data Java dan alur kontrol sebelum melanjutkan.
Jika tidak, mari kita langsung masuk.
Kelas dan Objek di Java
Jadi, sebenarnya apa itu kelas?
Kelas adalah konstruksi pemrograman di Java untuk merepresentasikan konsep dunia nyata. Misalnya, perhatikan kelas MenuItem ini (buat sebuah file untuk menulis kelas ini di IDE Anda):
Kelas memberi kita cetak biru atau templat untuk merepresentasikan berbagai item menu di restoran. Dengan mengubah dua atribut kelas,
name, danprice, kita dapat membuat tak terhitung banyaknya objek menu seperti burger atau salad.Jadi, untuk membuat kelas di Java, Anda memulai baris yang menjelaskan tingkat akses kelas (
private,public, atauprotected) diikuti oleh nama kelas. Segera setelah kurung kurawal, Anda uraikan atribut-atribut kelas Anda.Namun bagaimana kita membuat objek yang termasuk dalam kelas ini? Java memungkinkannya melalui metode konstruktor:
Konstruktor adalah metode khusus yang dipanggil ketika kita membuat objek baru dari sebuah kelas. Konstruktor menginisialisasi atribut objek dengan nilai yang kita berikan. Pada contoh di atas, konstruktor menerima parameter name dan price lalu menetapkannya ke field objek menggunakan kata kunci ‘this’ untuk merujuk pada instance objek di masa mendatang.
Sintaks konstruktor berbeda dari metode kelas lainnya karena tidak mengharuskan Anda menentukan tipe pengembalian. Selain itu, konstruktor harus memiliki nama yang sama dengan kelas, dan sebaiknya memiliki jumlah atribut yang sama dengan yang Anda deklarasikan setelah definisi kelas. Di atas, konstruktor membuat dua atribut karena kita mendeklarasikan dua setelah definisi kelas:
namedanprice.Setelah Anda menulis kelas dan konstruktornya, Anda dapat membuat instance (objek) di metode main Anda:
Keluaran:
Di atas, kita membuat dua objek
MenuItemke dalam variabelburgerdansalad. Sesuai ketentuan di Java, tipe variabel harus dideklarasikan, yaituMenuItem. Lalu, untuk membuat instance dari kelas kita, tuliskan kata kunci new diikuti dengan pemanggilan metode konstruktor.Selain konstruktor, Anda dapat membuat metode biasa yang memberi perilaku pada kelas Anda. Misalnya, di bawah ini, kita menambahkan metode untuk menghitung total harga setelah pajak:
Sekarang kita dapat menghitung total harga termasuk pajak:
Keluaran:
Enkapsulasi
Tujuan kelas adalah menyediakan cetak biru untuk membuat objek. Objek-objek ini kemudian akan digunakan oleh skrip atau program lain. Misalnya, objek
MenuItemkita dapat digunakan oleh antarmuka pengguna yang menampilkan nama, harga, dan gambarnya di layar.Untuk alasan ini, kita harus merancang kelas sedemikian rupa sehingga instance-nya hanya dapat digunakan sesuai yang kita maksudkan. Saat ini, kelas
MenuItemkita sangat dasar dan rawan kesalahan. Seseorang mungkin membuat objek dengan atribut yang tidak masuk akal, seperti pai apel berharga negatif atau sandwich bernilai jutaan dolar:Jadi, langkah pertama setelah menulis sebuah kelas adalah melindungi atributnya dengan membatasi bagaimana atribut tersebut dibuat dan diakses. Untuk mulai, kita ingin hanya mengizinkan nilai positif untuk price dan menetapkan nilai maksimum untuk menghindari menampilkan item yang sangat mahal secara tidak sengaja.
Java memungkinkan kita mencapai ini dengan menggunakan metode setter:
Mari kita telaah apa yang baru pada blok kode di atas:
1. Kita menjadikan atribut private dengan menambahkan kata kunci
private. Ini berarti atribut hanya dapat diakses di dalam kelas MenuItem. Enkapsulasi dimulai dari langkah krusial ini.2. Kita menambahkan konstanta baru
MAX_PRICEyang bersifat:3. Kita menambahkan metode
setPrice()yang:4. Kita memodifikasi konstruktor untuk menggunakan
setPrice()alih-alih langsung menetapkan harga. Ini memastikan validasi harga terjadi saat pembuatan objek.Kita baru saja menerapkan salah satu pilar inti desain OOP yang baik — enkapsulasi. Paradigma ini menegakkan penyembunyian data dan akses terkontrol ke atribut objek, memastikan detail implementasi internal terlindungi dari campur tangan eksternal dan hanya dapat diubah melalui antarmuka yang terdefinisi dengan baik.
Mari kita pertegas dengan menerapkan enkapsulasi pada atribut name. Bayangkan kita memiliki kedai kopi yang hanya menyajikan latte, cappuccino, espresso, americano, dan mocha.
Jadi, nama item menu kita hanya bisa salah satu dari daftar ini. Berikut cara kita menegakkannya dalam kode:
Kode di atas menerapkan validasi nama untuk item menu di kedai kopi. Mari kita uraikan:
1. Pertama, didefinisikan array private static final
VALID_NAMESyang berisi satu-satunya nama minuman yang diizinkan: latte, cappuccino, espresso, americano, dan mocha. Array ini bersifat:2. Dideklarasikan field String name privat untuk menyimpan nama minuman
3. Metode
setName()menerapkan logika validasi:VALID_NAMESBerikut kelas lengkap sejauh ini:
Setelah kita melindungi cara atribut dibuat, kita juga ingin melindungi cara atribut diakses. Ini dilakukan dengan menggunakan metode getter:
Metode getter menyediakan akses terkontrol ke atribut privat dari sebuah kelas. Metode ini menyelesaikan masalah akses atribut langsung yang dapat menyebabkan modifikasi yang tidak diinginkan dan merusak enkapsulasi.
Sebagai contoh, tanpa getter, kita mungkin mengakses atribut secara langsung:
Dengan getter, kita menegakkan akses yang benar:
Enkapsulasi ini:
Pewarisan
Kelas kita mulai terlihat baik tetapi masih ada cukup banyak masalah. Misalnya, untuk restoran besar yang menyajikan banyak jenis hidangan dan minuman, kelas ini tidak cukup fleksibel.
Jika kita ingin menambahkan berbagai jenis makanan, kita akan menghadapi beberapa tantangan. Beberapa hidangan bisa disiapkan untuk dibawa pulang, sementara yang lain perlu dikonsumsi segera. Item menu mungkin memiliki harga dan diskon yang bervariasi. Hidangan mungkin memerlukan pelacakan suhu atau penyimpanan khusus. Minuman bisa panas atau dingin dengan bahan yang dapat disesuaikan. Item mungkin memerlukan informasi alergen dan opsi porsi. Sistem saat ini tidak menangani kebutuhan yang beragam ini.
Pewarisan memberikan solusi elegan untuk semua masalah ini. Pewarisan memungkinkan kita membuat versi khusus dari item menu dengan mendefinisikan kelas dasar
MenuItemdengan atribut umum lalu membuat kelas turunan yang mewarisi dasar-dasar ini sambil menambahkan fitur unik.Sebagai contoh, kita bisa memiliki kelas
Drinkuntuk minuman dengan opsi suhu, kelasFooduntuk item yang perlu segera dikonsumsi, dan kelasDessertuntuk item dengan kebutuhan penyimpanan khusus — semuanya mewarisi fungsionalitas inti item menu.Memperluas kelas
Mari implementasikan ide-ide tersebut dimulai dengan
Drink:Untuk mendefinisikan kelas anak yang mewarisi dari induk, kita menggunakan kata kunci
extendssetelah nama kelas anak diikuti kelas induk. Setelah definisi kelas, kita mendefinisikan atribut baru apa pun yang dimiliki anak ini dan mengimplementasikan konstruktornya.Namun perhatikan bagaimana kita harus mengulang inisialisasi
namedanpricebersama denganisCold. Itu tidak ideal karena kelas induk mungkin memiliki ratusan atribut. Selain itu, kode di atas akan menghasilkan error saat Anda mengompilasinya karena itu bukan cara yang benar untuk menginisialisasi atribut kelas induk. Cara yang benar adalah menggunakan kata kuncisuper:Kata kunci
superdigunakan untuk memanggil konstruktor kelas induk. Dalam hal ini,super(name, price)memanggil konstruktorMenuItemuntuk menginisialisasi atribut tersebut, menghindari duplikasi kode. Kita hanya perlu menginisialisasi atribut baruisColdyang spesifik untuk kelasDrink.Kata kunci ini sangat fleksibel karena Anda dapat menggunakannya untuk merujuk pada kelas induk di bagian mana pun dari kelas anak. Misalnya, untuk memanggil metode induk, Anda gunakan
super.methodName()sementarasuper.attributeNameuntuk atribut.Method overriding
Sekarang, anggaplah kita ingin menambahkan metode baru ke kelas-kelas kita untuk menghitung total harga setelah pajak. Karena item menu yang berbeda mungkin memiliki tarif pajak yang berbeda (misalnya, makanan siap saji vs. minuman kemasan), kita dapat menggunakan method overriding untuk menerapkan perhitungan pajak khusus di setiap kelas turunan sambil mempertahankan nama metode yang sama di kelas induk.
Berikut tampilannya:
Dalam contoh ini, method overriding memungkinkan setiap subclass menyediakan implementasi sendiri untuk
calculateTotalPrice():Kelas dasar
MenuItemmendefinisikan perhitungan pajak default sebesar 10%.Ketika kelas
FooddanDrinkmemperluasMenuItem, mereka override metode ini untuk menerapkan tarif pajak masing-masing:Anotasi
@Overridedigunakan untuk secara eksplisit menunjukkan bahwa metode ini menimpa metode kelas induk. Ini membantu menangkap kesalahan jika tanda tangan metode tidak cocok dengan kelas induk.Setiap subclass masih dapat mengakses harga kelas induk menggunakan
super.getPrice(), menunjukkan bagaimana metode yang dioverride dapat memanfaatkan fungsionalitas kelas induk sambil menambahkan perilaku mereka sendiri.Singkatnya, method overriding adalah bagian integral dari pewarisan yang memungkinkan subclass menyediakan implementasi metode mereka sendiri yang didefinisikan di kelas induk, sehingga memungkinkan perilaku yang lebih spesifik sambil mempertahankan tanda tangan metode yang sama.
Kelas Abstrak
Hierarki kelas
MenuItemkita berfungsi, tetapi ada masalah: haruskah siapa pun dapat membuat objekMenuItembiasa? Bagaimanapun, di restoran kita, setiap item menu adalah Food atau Drink—tidak ada yang namanya “item menu generik.”Kita dapat mencegah hal ini dengan menjadikan
MenuItemsebagai kelas abstrak. Kelas abstrak hanya menyediakan cetak biru dasar—kelas ini hanya dapat digunakan sebagai kelas induk untuk pewarisan, bukan diinstansiasi langsung.Untuk membuat
MenuItemmenjadi abstrak, kita tambahkan kata kunciabstractsetelah modifier aksesnya:Kelas abstrak juga dapat memiliki metode abstrak seperti
calculateTotalPrice()di atas. Metode abstrak ini berfungsi sebagai kontrak yang memaksa subclass untuk menyediakan implementasinya. Dengan kata lain, metode abstrak apa pun dalam kelas abstrak harus diimplementasikan oleh kelas anak.Jadi, mari kita tulis ulang
FooddanDrinkdengan perubahan ini dalam pikiran:Melalui implementasi sistem menu ini, kita telah melihat bagaimana abstraksi dan pewarisan bekerja bersama untuk menciptakan kode yang fleksibel dan mudah dipelihara yang dapat dengan mudah beradaptasi dengan berbagai kebutuhan bisnis.
Kesimpulan
Hari ini, kita telah melihat sekilas tentang kemampuan Java sebagai bahasa pemrograman berorientasi objek. Kita telah membahas dasar-dasar kelas, objek, dan beberapa pilar utama OOP: enkapsulasi, pewarisan, dan abstraksi melalui sistem menu restoran.
Untuk membuat sistem ini siap produksi, Anda masih memiliki banyak hal untuk dipelajari, seperti interface (bagian dari abstraksi), polimorfisme, dan pola desain OOP. Untuk mempelajari lebih lanjut tentang konsep-konsep ini, lihat kursus Introduction to OOP in Java kami.
Jika Anda ingin menguji pengetahuan Java Anda, coba jawab beberapa pertanyaan dalam artikel Java Interview Questions kami.