Pandangan Mendalam tentang C++ vs. Java

Diterbitkan: 2022-07-22

Banyak 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?

Bagan alur dengan gelembung "Mulai" berwarna biru tua di sudut kiri atas yang akhirnya terhubung ke salah satu dari tujuh kotak kesimpulan biru muda di bawahnya, melalui serangkaian persimpangan keputusan putih dengan cabang biru tua untuk "Ya" dan opsi lainnya, dan cabang biru muda untuk "Tidak." Yang pertama adalah "Aplikasi GUI lintas platform?" dari mana "Ya" menunjukkan kesimpulan, "Pilih lingkungan pengembangan lintas platform dan gunakan bahasa utamanya." A "Tidak" menunjuk ke "Aplikasi Android Asli?" dari mana "Ya" menunjuk ke pertanyaan sekunder, "Apakah ini permainan?" Dari pertanyaan sekunder, jawaban "Tidak" menunjukkan kesimpulan, "Gunakan Java (atau Kotlin)", dan jawaban "Ya" menunjukkan kesimpulan yang berbeda, "Pilih mesin game lintas platform dan gunakan bahasa yang disarankan." Dari "Aplikasi Android Asli?" pertanyaan, "Tidak" menunjuk ke "Aplikasi Windows asli?" dari mana "Ya" menunjuk ke pertanyaan sekunder, "Apakah ini permainan?" Dari pertanyaan sekunder, jawaban "Ya" menunjukkan kesimpulan, "Pilih mesin permainan lintas platform dan gunakan bahasa yang disarankan," dan jawaban "Tidak" menunjukkan kesimpulan yang berbeda, "Pilih lingkungan GUI Windows dan gunakan bahasa (biasanya C++ atau C#)." Dari "aplikasi Windows Asli?" pertanyaan, "Tidak" menunjuk ke "Aplikasi server?" dari mana "Ya" menunjuk ke pertanyaan sekunder, "Jenis pengembang?" Dari pertanyaan sekunder, keputusan "Keterampilan Menengah" mengarah pada kesimpulan, "Gunakan Java (atau C# atau TypeScript)," dan keputusan "Terampil" mengarah ke pertanyaan tersier, "Prioritas utama?" Dari pertanyaan tersier, keputusan "Produktivitas pengembang" mengarah ke kesimpulan, "Gunakan Java (atau C# atau TypeScript)," dan keputusan "Kinerja" menunjukkan kesimpulan yang berbeda, "Gunakan C++ (atau Rust)." Dari "Aplikasi server?" pertanyaan, "Tidak" menunjuk ke pertanyaan sekunder, "Pengembangan driver?" Dari pertanyaan sekunder, "Ya" menunjukkan kesimpulan, "Gunakan C++ (atau Rust)," dan "Tidak" mengarah ke pertanyaan tersier, "Pengembangan IoT?" Dari pertanyaan tersier, "Ya" menunjukkan kesimpulan, "Gunakan C++ (atau Rust)," dan "Tidak" menunjukkan pertanyaan kuaterner, "Perdagangan berkecepatan tinggi?" Dari pertanyaan kuartener, "Ya" menunjukkan kesimpulan, "Gunakan C++ (atau Rust)," dan "Tidak" menunjukkan kesimpulan terakhir yang tersisa, "Tanyakan seseorang yang akrab dengan domain target Anda."
Panduan yang diperluas untuk memilih bahasa terbaik untuk berbagai jenis proyek.

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.