Pandangan Mendalam tentang C++ vs. Java
Diterbitkan: 2022-07-22Banyak artikel yang membandingkan fitur teknis C++ dan Java, tetapi perbedaan mana yang paling penting untuk dipertimbangkan? Ketika perbandingan menunjukkan, misalnya, bahwa Java tidak mendukung multiple inheritance dan C++ mendukung, apa artinya? Dan apakah itu hal yang baik? Beberapa berpendapat bahwa ini adalah keuntungan dari Java, sementara yang lain menyatakannya sebagai masalah.
Mari kita telusuri situasi di mana pengembang harus memilih C++, Java, atau bahasa lain sekaligus—dan, yang lebih penting, mengapa keputusan itu penting.
Meneliti Dasar-Dasarnya: Bahasa dan Ekosistemnya
C++ diluncurkan pada tahun 1985 sebagai ujung depan untuk kompiler C, mirip dengan bagaimana TypeScript mengkompilasi ke JavaScript. Kompiler C++ modern biasanya dikompilasi ke kode mesin asli. Meskipun beberapa mengklaim kompiler C++ mengurangi portabilitasnya, dan mereka memang memerlukan pembangunan kembali untuk arsitektur target baru, kode C++ berjalan di hampir setiap platform prosesor.
Pertama kali dirilis pada tahun 1995, Java tidak membangun langsung ke kode asli. Sebagai gantinya, Java membangun bytecode, representasi biner perantara yang berjalan di Java Virtual Machine (JVM). Dengan kata lain, output kompiler Java membutuhkan executable native khusus platform untuk dijalankan.
Baik C++ dan Java termasuk dalam keluarga bahasa mirip-C, karena mereka umumnya menyerupai C dalam sintaksnya. Perbedaan yang paling signifikan adalah ekosistemnya: Meskipun C++ dapat dengan mulus memanggil pustaka berdasarkan C atau C++, atau API sistem operasi, Java paling cocok untuk pustaka berbasis Java. Anda dapat mengakses pustaka C di Java menggunakan Java Native Interface (JNI) API, tetapi ini rawan kesalahan dan memerlukan beberapa kode C atau C++. C++ juga berinteraksi dengan perangkat keras lebih mudah daripada Java, karena C++ adalah bahasa tingkat rendah.
Pertukaran Detail: Generik, Memori, dan Lainnya
Kita dapat membandingkan C++ dengan Java dari banyak perspektif. Dalam beberapa kasus, keputusan antara C++ dan Java sudah jelas. Aplikasi Android asli biasanya harus menggunakan Java kecuali aplikasi tersebut adalah game. Sebagian besar pengembang game harus memilih C++ atau bahasa lain untuk animasi waktu-nyata yang sehalus mungkin; Manajemen memori Java sering menyebabkan kelambatan selama bermain game.
Aplikasi lintas platform yang bukan game berada di luar cakupan diskusi ini. Baik C++ maupun Java tidak ideal dalam hal ini karena terlalu bertele-tele untuk pengembangan GUI yang efisien. Untuk aplikasi berperforma tinggi, yang terbaik adalah membuat modul C++ untuk melakukan pekerjaan berat, dan menggunakan bahasa yang lebih produktif bagi pengembang untuk GUI.
Aplikasi lintas platform yang bukan game berada di luar cakupan diskusi ini. Baik C++ maupun Java tidak ideal dalam hal ini karena terlalu bertele-tele untuk pengembangan GUI yang efisien.
Menciak
Untuk beberapa proyek, pilihannya mungkin tidak jelas, jadi mari kita bandingkan lebih lanjut:
Fitur | C++ | Jawa |
---|---|---|
Ramah pemula | Tidak | Ya |
Performa runtime | Terbaik | Bagus |
Latensi | Dapat diprediksi | Tak terduga |
Pointer pintar penghitungan referensi | Ya | Tidak |
Pengumpulan sampah tandai dan sapu global | Tidak | Yg dibutuhkan |
Alokasi memori tumpukan | Ya | Tidak |
Kompilasi ke executable asli | Ya | Tidak |
Kompilasi ke bytecode Java | Tidak | Ya |
Interaksi langsung dengan API sistem operasi tingkat rendah | Ya | Memerlukan kode C |
Interaksi langsung dengan pustaka C | Ya | Memerlukan kode C |
Interaksi langsung dengan perpustakaan Java | Melalui JNI | Ya |
Pembuatan standar dan manajemen paket | Tidak | Maven |
Selain fitur yang dibandingkan dalam tabel, kami juga akan fokus pada fitur pemrograman berorientasi objek (OOP) seperti pewarisan berganda, generik/templat, dan refleksi. Perhatikan bahwa kedua bahasa mendukung OOP: Java mengamanatkannya, sementara C++ mendukung OOP bersama dengan fungsi global dan data statis.
Banyak Warisan
Dalam OOP, pewarisan adalah ketika kelas anak mewarisi atribut dan metode dari kelas induk. Salah satu contoh standar adalah kelas Rectangle
yang mewarisi dari kelas Shape
yang lebih umum:
// Note that we are in a C++ file class Shape { // Position int x, y; public: // The child class must override this pure virtual function virtual void draw() = 0; }; class Rectangle: public Shape { // Width and height int w, h; public: void draw(); };
Warisan berganda adalah ketika kelas anak mewarisi dari banyak orang tua. Berikut adalah contoh, menggunakan kelas Rectangle
dan Shape
dan kelas Clickable
tambahan:
// Not recommended class Shape {...}; class Rectangle: public Shape {...}; class Clickable { int xClick, yClick; public: virtual void click() = 0; }; class ClickableRectangle: public Rectangle, public Clickable { void click(); };
Dalam hal ini kita memiliki dua tipe dasar: Shape
(tipe dasar Rectangle
) dan Clickable
. ClickableRectangle
mewarisi dari keduanya untuk menyusun dua tipe objek.
C++ mendukung pewarisan berganda; Jawa tidak. Warisan berganda berguna dalam kasus tepi tertentu, seperti:
- Membuat bahasa khusus domain (DSL) tingkat lanjut.
- Melakukan perhitungan canggih pada waktu kompilasi.
- Meningkatkan keamanan jenis proyek dengan cara yang tidak mungkin dilakukan di Jawa.
Namun, menggunakan pewarisan berganda umumnya tidak disarankan. Ini dapat memperumit kode dan memengaruhi kinerja kecuali jika digabungkan dengan metaprogramming template—sesuatu yang paling baik dilakukan hanya oleh programmer C++ yang paling berpengalaman.
Generik dan Template
Versi generik dari kelas yang bekerja dengan tipe data apa pun praktis untuk digunakan kembali kode. Kedua bahasa menawarkan dukungan ini—Java melalui generik, C++ melalui templat—namun fleksibilitas templat C++ dapat membuat pemrograman tingkat lanjut lebih aman dan tangguh. Kompiler C++ membuat kelas atau fungsi baru yang disesuaikan setiap kali Anda menggunakan tipe yang berbeda dengan template. Selain itu, template C++ dapat memanggil fungsi kustom berdasarkan jenis parameter fungsi tingkat atas, yang memungkinkan tipe data tertentu memiliki kode khusus. Ini disebut spesialisasi template. Java tidak memiliki fitur yang setara.
Sebaliknya, ketika menggunakan obat generik, kompiler Java membuat objek umum tanpa tipe melalui proses yang disebut penghapusan tipe. Java melakukan pengecekan tipe selama kompilasi, tetapi pemrogram tidak dapat mengubah perilaku kelas atau metode generik berdasarkan parameter tipenya. Untuk memahami ini dengan lebih baik, mari kita lihat contoh singkat dari fungsi generik std::string format(std::string fmt, T1 item1, T2 item2)
yang menggunakan template, template<class T1, class T2>
, dari C++ perpustakaan yang saya buat:
std::string firstParameter = "A string"; int secondParameter = 123; // Format printed output as an eight-character-wide string and a hexadecimal value format("%8s %x", firstParameter, secondParameter); // Format printed output as two eight-character-wide strings format("%8s %8s", firstParameter, secondParameter);
C++ akan menghasilkan fungsi format
sebagai std::string format(std::string fmt, std::string item1, int item2)
, sedangkan Java akan membuatnya tanpa string
spesifik dan tipe objek int
untuk item1
dan item2
. Dalam hal ini, template C++ kami mengetahui parameter masuk terakhir adalah int
dan oleh karena itu dapat melakukan konversi std::to_string
yang diperlukan dalam panggilan format
kedua. Tanpa templat, pernyataan printf
C++ yang mencoba mencetak angka sebagai string seperti pada panggilan format
kedua akan memiliki perilaku yang tidak terdefinisi dan dapat membuat aplikasi macet atau mencetak sampah. Fungsi Java hanya akan dapat memperlakukan angka sebagai string dalam panggilan format
pertama dan tidak akan memformatnya sebagai bilangan bulat heksadesimal secara langsung. Ini adalah contoh sepele, tetapi ini menunjukkan kemampuan C++ untuk memilih templat khusus untuk menangani objek kelas arbitrer apa pun tanpa memodifikasi kelasnya atau fungsi format
. Kami dapat menghasilkan output dengan benar di Java menggunakan refleksi alih-alih generik, meskipun metode ini kurang dapat diperluas dan lebih rawan kesalahan.
Cerminan
Di Java, dimungkinkan untuk mengetahui (saat runtime) detail struktural seperti anggota mana yang tersedia di kelas atau tipe kelas. Fitur ini disebut refleksi, mungkin karena seperti mengangkat cermin ke objek untuk melihat apa yang ada di dalamnya. (Informasi lebih lanjut dapat ditemukan di dokumentasi refleksi Oracle.)
C++ tidak memiliki refleksi penuh, tetapi C++ modern menawarkan informasi tipe runtime (RTTI). RTTI memungkinkan deteksi runtime dari jenis objek tertentu, meskipun tidak dapat mengakses informasi seperti anggota objek.
Manajemen memori
Perbedaan penting lainnya antara C++ dan Java adalah manajemen memori, yang memiliki dua pendekatan utama: manual, di mana pengembang harus melacak dan melepaskan memori secara manual; dan otomatis, di mana perangkat lunak melacak objek mana yang masih digunakan untuk mendaur ulang memori yang tidak digunakan. Di Jawa, contohnya adalah pengumpulan sampah.
Java membutuhkan memori yang dikumpulkan dari sampah, menyediakan manajemen memori yang lebih mudah daripada pendekatan manual dan menghilangkan kesalahan pelepasan memori yang biasanya berkontribusi pada kerentanan keamanan. C++ tidak menyediakan manajemen memori otomatis secara native, tetapi mendukung bentuk pengumpulan sampah yang disebut smart pointer. Pointer pintar menggunakan penghitungan referensi dan aman serta berkinerja jika digunakan dengan benar. C++ juga menawarkan destruktor yang membersihkan atau melepaskan sumber daya pada penghancuran objek.
Sementara Java hanya menawarkan alokasi tumpukan, C++ mendukung alokasi tumpukan (menggunakan fungsi malloc
C new
dan delete
atau yang lebih lama) dan alokasi tumpukan. Alokasi tumpukan bisa lebih cepat dan lebih aman daripada alokasi tumpukan karena tumpukan adalah struktur data linier sementara tumpukan berbasis pohon, sehingga memori tumpukan jauh lebih mudah untuk dialokasikan dan dilepaskan.
Keuntungan lain dari C++ terkait dengan alokasi stack adalah teknik pemrograman yang dikenal sebagai Resource Acquisition Is Initialization (RAII). Di RAII, sumber daya seperti referensi terkait dengan siklus hidup objek pengontrolnya; sumber daya akan dihancurkan pada akhir siklus hidup objek itu. RAII adalah cara kerja penunjuk pintar C++ tanpa dereferensi manual—penunjuk pintar yang direferensikan di bagian atas suatu fungsi secara otomatis direferensikan setelah keluar dari fungsi. Memori yang terhubung juga dilepaskan jika ini adalah referensi terakhir ke penunjuk pintar. Meskipun Java menawarkan pola yang serupa, ini lebih canggung daripada RAII C++, terutama jika Anda perlu membuat beberapa sumber daya dalam blok kode yang sama.
Performa Waktu Proses
Java memiliki kinerja runtime yang solid, tetapi C++ masih memegang mahkota karena manajemen memori manual lebih cepat daripada pengumpulan sampah untuk aplikasi dunia nyata. Meskipun Java dapat mengungguli C++ dalam kasus sudut tertentu karena kompilasi JIT, C++ memenangkan sebagian besar kasus non-sepele.
Secara khusus, pustaka memori standar Java membebani pengumpul sampah dengan alokasinya dibandingkan dengan pengurangan penggunaan alokasi heap oleh C++. Namun, Java masih relatif cepat dan dapat diterima kecuali latensi menjadi perhatian utama—misalnya, dalam game atau aplikasi dengan batasan waktu nyata.
Bangun dan Manajemen Paket
Apa yang kurang dalam kinerja Java, itu membuatnya mudah digunakan. Salah satu komponen yang memengaruhi efisiensi pengembang adalah manajemen pembangunan dan paket—bagaimana kita membangun proyek dan membawa dependensi eksternal ke dalam aplikasi. Di Java, alat yang disebut Maven menyederhanakan proses ini menjadi beberapa langkah mudah dan terintegrasi dengan banyak IDE seperti IntelliJ IDEA.
Dalam C++, bagaimanapun, tidak ada repositori paket standar. Bahkan tidak ada metode standar untuk membangun kode C++ dalam aplikasi: Beberapa pengembang lebih memilih Visual Studio, sementara yang lain menggunakan CMake atau seperangkat alat khusus lainnya. Lebih lanjut menambah kompleksitas, pustaka C++ komersial tertentu diformat biner, dan tidak ada cara yang konsisten untuk mengintegrasikan pustaka tersebut ke dalam proses pembuatan. Selain itu, variasi dalam pengaturan build atau versi kompiler dapat menyebabkan tantangan dalam membuat pustaka biner berfungsi.
Keramahan pemula
Gesekan manajemen pembangunan dan paket bukan satu-satunya alasan C++ jauh lebih tidak ramah bagi pemula daripada Java. Seorang programmer mungkin mengalami kesulitan men-debug dan menggunakan C++ dengan aman kecuali mereka terbiasa dengan C, bahasa rakitan, atau cara kerja komputer tingkat rendah. Pikirkan C++ seperti alat listrik: Ini dapat mencapai banyak hal tetapi berbahaya jika disalahgunakan.
Pendekatan manajemen memori Java yang disebutkan di atas juga membuatnya jauh lebih mudah diakses daripada C++. Pemrogram Java tidak perlu khawatir melepaskan memori objek karena bahasa akan menanganinya secara otomatis.
Waktu Keputusan: C++ atau Java?
Sekarang setelah kita menjelajahi perbedaan antara C++ dan Java secara mendalam, kita kembali ke pertanyaan awal kita: C++ atau Java? Bahkan dengan pemahaman mendalam tentang kedua bahasa tersebut, tidak ada jawaban yang cocok untuk semua.
Insinyur perangkat lunak yang tidak terbiasa dengan konsep pemrograman tingkat rendah mungkin lebih baik memilih Java saat membatasi keputusan ke C++ atau Java, kecuali untuk konteks waktu nyata seperti bermain game. Pengembang yang ingin memperluas cakrawala mereka, di sisi lain, dapat belajar lebih banyak dengan memilih C++.
Namun, perbedaan teknis antara C++ dan Java mungkin hanya menjadi faktor kecil dalam pengambilan keputusan. Jenis produk tertentu memerlukan pilihan tertentu. Jika Anda masih tidak yakin, Anda dapat melihat diagram alur—tetapi perlu diingat bahwa pada akhirnya dapat mengarahkan Anda ke bahasa ketiga.