Kode Java Buggy: 10 Kesalahan Paling Umum yang Dilakukan Pengembang Java

Diterbitkan: 2022-03-11

Java adalah bahasa pemrograman yang pada awalnya dikembangkan untuk televisi interaktif, tetapi seiring waktu menjadi tersebar luas di mana-mana perangkat lunak dapat digunakan. Dirancang dengan gagasan pemrograman berorientasi objek, menghapus kompleksitas bahasa lain seperti C atau C++, pengumpulan sampah, dan mesin virtual agnostik arsitektur, Java menciptakan cara pemrograman baru. Selain itu, ia memiliki kurva belajar yang lembut dan tampaknya berhasil mematuhi motonya sendiri - "Tulis sekali, jalankan di mana-mana", yang hampir selalu benar; tapi masalah Java masih ada. Saya akan membahas sepuluh masalah Java yang menurut saya merupakan kesalahan paling umum.

Kesalahan Umum #1: Mengabaikan Perpustakaan yang Ada

Jelas merupakan kesalahan bagi Pengembang Java untuk mengabaikan jumlah pustaka yang tak terhitung banyaknya yang ditulis dalam Java. Sebelum menemukan kembali roda, coba cari perpustakaan yang tersedia - banyak di antaranya telah dipoles selama bertahun-tahun keberadaannya dan bebas untuk digunakan. Ini bisa berupa pustaka logging, seperti logback dan Log4j, atau pustaka terkait jaringan, seperti Netty atau Akka. Beberapa perpustakaan, seperti Joda-Time, telah menjadi standar de facto.

Berikut ini adalah pengalaman pribadi dari salah satu proyek saya sebelumnya. Bagian dari kode yang bertanggung jawab untuk melarikan diri HTML ditulis dari awal. Itu bekerja dengan baik selama bertahun-tahun, tetapi akhirnya menemukan input pengguna yang menyebabkannya berputar menjadi loop tak terbatas. Pengguna, yang menganggap layanan tidak responsif, mencoba mencoba lagi dengan masukan yang sama. Akhirnya, semua CPU di server yang dialokasikan untuk aplikasi ini ditempati oleh infinite loop ini. Jika pembuat alat melarikan diri HTML naif ini telah memutuskan untuk menggunakan salah satu perpustakaan terkenal yang tersedia untuk melarikan diri HTML, seperti HtmlEscapers dari Google Guava, ini mungkin tidak akan terjadi. Setidaknya, berlaku untuk sebagian besar perpustakaan populer dengan komunitas di belakangnya, kesalahan akan ditemukan dan diperbaiki lebih awal oleh komunitas untuk perpustakaan ini.

Kesalahan Umum #2: Kehilangan Kata Kunci 'break' di Blok Switch-Case

Masalah Java ini bisa sangat memalukan, dan terkadang tetap tidak terungkap sampai dijalankan dalam produksi. Perilaku fallthrough dalam pernyataan switch seringkali berguna; namun, melewatkan kata kunci “break” ketika perilaku tersebut tidak diinginkan dapat menyebabkan hasil yang membawa malapetaka. Jika Anda lupa memberi tanda “break” pada “case 0” pada contoh kode di bawah ini, program akan menulis “Zero” diikuti dengan “One”, karena aliran kontrol di dalam sini akan melalui seluruh pernyataan “switch” sampai itu mencapai "istirahat". Sebagai contoh:

 public static void switchCasePrimer() { int caseIndex = 0; switch (caseIndex) { case 0: System.out.println("Zero"); case 1: System.out.println("One"); break; case 2: System.out.println("Two"); break; default: System.out.println("Default"); } }

Dalam kebanyakan kasus, solusi yang lebih bersih adalah dengan menggunakan polimorfisme dan memindahkan kode dengan perilaku tertentu ke dalam kelas yang terpisah. Kesalahan Java seperti ini dapat dideteksi menggunakan penganalisis kode statis, misalnya FindBugs dan PMD.

Kesalahan Umum #3: Melupakan Sumber Daya Gratis

Setiap kali sebuah program membuka file atau koneksi jaringan, penting bagi pemula Java untuk membebaskan sumber daya setelah Anda selesai menggunakannya. Kehati-hatian serupa harus diambil jika ada pengecualian yang dilemparkan selama operasi pada sumber daya tersebut. Orang dapat berargumen bahwa FileInputStream memiliki finalizer yang memanggil metode close() pada acara pengumpulan sampah; namun, karena kami tidak dapat memastikan kapan siklus pengumpulan sampah akan dimulai, aliran input dapat menggunakan sumber daya komputer untuk jangka waktu yang tidak ditentukan. Sebenarnya, ada pernyataan yang sangat berguna dan rapi yang diperkenalkan di Java 7 khususnya untuk kasus ini, yang disebut try-with-resources:

 private static void printFileJava7() throws IOException { try(FileInputStream input = new FileInputStream("file.txt")) { int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); } } }

Pernyataan ini dapat digunakan dengan objek apa pun yang mengimplementasikan antarmuka AutoClosable. Ini memastikan bahwa setiap sumber daya ditutup pada akhir pernyataan.

Terkait: 8 Pertanyaan Wawancara Penting Java

Kesalahan Umum #4: Kebocoran Memori

Java menggunakan manajemen memori otomatis, dan meskipun melupakan pengalokasian dan pengosongan memori secara manual merupakan hal yang melegakan, bukan berarti pengembang Java pemula tidak perlu mengetahui bagaimana memori digunakan dalam aplikasi. Masalah dengan alokasi memori masih mungkin terjadi. Selama sebuah program membuat referensi ke objek yang tidak diperlukan lagi, itu tidak akan dibebaskan. Di satu sisi, kita masih bisa menyebut kebocoran memori ini. Kebocoran memori di Java dapat terjadi dengan berbagai cara, tetapi alasan yang paling umum adalah referensi objek yang kekal, karena pengumpul sampah tidak dapat menghapus objek dari heap saat masih ada referensi ke objek tersebut. Seseorang dapat membuat referensi seperti itu dengan mendefinisikan kelas dengan bidang statis yang berisi beberapa koleksi objek, dan lupa menyetel bidang statis itu ke nol setelah koleksi tidak lagi diperlukan. Bidang statis dianggap sebagai akar GC dan tidak pernah dikumpulkan.

Alasan potensial lain di balik kebocoran memori tersebut adalah sekelompok objek yang saling mereferensikan, menyebabkan ketergantungan melingkar sehingga pengumpul sampah tidak dapat memutuskan apakah objek ini dengan referensi ketergantungan silang diperlukan atau tidak. Masalah lainnya adalah kebocoran memori non-heap saat JNI digunakan.

Contoh kebocoran primitif dapat terlihat seperti berikut:

 final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); final Deque<BigDecimal> numbers = new LinkedBlockingDeque<>(); final BigDecimal divisor = new BigDecimal(51); scheduledExecutorService.scheduleAtFixedRate(() -> { BigDecimal number = numbers.peekLast(); if (number != null && number.remainder(divisor).byteValue() == 0) { System.out.println("Number: " + number); System.out.println("Deque size: " + numbers.size()); } }, 10, 10, TimeUnit.MILLISECONDS); scheduledExecutorService.scheduleAtFixedRate(() -> { numbers.add(new BigDecimal(System.currentTimeMillis())); }, 10, 10, TimeUnit.MILLISECONDS); try { scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); }

Contoh ini membuat dua tugas terjadwal. Tugas pertama mengambil nomor terakhir dari deque yang disebut "angka" dan mencetak nomor dan ukuran deque jika nomor tersebut habis dibagi 51. Tugas kedua memasukkan angka ke dalam deque. Kedua tugas dijadwalkan dengan kecepatan tetap, dan dijalankan setiap 10 md. Jika kode dijalankan, Anda akan melihat bahwa ukuran deque meningkat secara permanen. Ini pada akhirnya akan menyebabkan deque diisi dengan objek yang menghabiskan semua memori heap yang tersedia. Untuk mencegah hal ini sambil mempertahankan semantik program ini, kita dapat menggunakan metode yang berbeda untuk mengambil angka dari deque: "pollLast". Berlawanan dengan metode "peekLast", "pollLast" mengembalikan elemen dan menghapusnya dari deque sementara "peekLast" hanya mengembalikan elemen terakhir.

Untuk mempelajari lebih lanjut tentang kebocoran memori di Java, silakan merujuk ke artikel kami yang mengungkap masalah ini.

Kesalahan Umum #5: Alokasi Sampah Berlebihan

Alokasi sampah yang berlebihan dapat terjadi ketika program membuat banyak objek berumur pendek. Pengumpul sampah bekerja terus menerus, menghapus objek yang tidak dibutuhkan dari memori, yang berdampak negatif pada kinerja aplikasi. Salah satu contoh sederhana:

 String oneMillionHello = ""; for (int i = 0; i < 1000000; i++) { oneMillionHello = oneMillionHello + "Hello!"; } System.out.println(oneMillionHello.substring(0, 6));

Dalam pengembangan Java, string tidak dapat diubah. Jadi, pada setiap iterasi, string baru dibuat. Untuk mengatasi ini kita harus menggunakan StringBuilder yang bisa berubah:

 StringBuilder oneMillionHelloSB = new StringBuilder(); for (int i = 0; i < 1000000; i++) { oneMillionHelloSB.append("Hello!"); } System.out.println(oneMillionHelloSB.toString().substring(0, 6));

Sementara versi pertama membutuhkan sedikit waktu untuk dieksekusi, versi yang menggunakan StringBuilder menghasilkan hasil dalam jumlah waktu yang jauh lebih sedikit.

Kesalahan Umum #6: Menggunakan Referensi Null tanpa Perlu

Menghindari penggunaan null yang berlebihan adalah praktik yang baik. Misalnya, lebih baik mengembalikan array atau koleksi kosong dari metode daripada null, karena dapat membantu mencegah NullPointerException.

Pertimbangkan metode berikut yang melintasi koleksi yang diperoleh dari metode lain, seperti yang ditunjukkan di bawah ini:

 List<String> accountIds = person.getAccountIds(); for (String accountId : accountIds) { processAccount(accountId); }

Jika getAccountIds() mengembalikan null ketika seseorang tidak memiliki akun, maka NullPointerException akan dimunculkan. Untuk memperbaikinya, pemeriksaan nol akan diperlukan. Namun, jika alih-alih nol, ia mengembalikan daftar kosong, maka NullPointerException tidak lagi menjadi masalah. Selain itu, kodenya lebih bersih karena kita tidak perlu memeriksa null variabel accountIds.

Untuk menangani kasus lain ketika seseorang ingin menghindari nol, strategi yang berbeda dapat digunakan. Salah satu strategi ini adalah dengan menggunakan tipe Opsional yang dapat berupa objek kosong atau bungkus dari beberapa nilai:

 Optional<String> optionalString = Optional.ofNullable(nullableString); if(optionalString.isPresent()) { System.out.println(optionalString.get()); }

Faktanya, Java 8 memberikan solusi yang lebih ringkas:

 Optional<String> optionalString = Optional.ofNullable(nullableString); optionalString.ifPresent(System.out::println);

Tipe opsional telah menjadi bagian dari Java sejak versi 8, tetapi telah lama dikenal di dunia pemrograman fungsional. Sebelum ini, tersedia di Google Guava untuk versi Java sebelumnya.

Kesalahan Umum #7: Mengabaikan Pengecualian

Seringkali tergoda untuk membiarkan pengecualian tidak ditangani. Namun, praktik terbaik untuk pengembang Java pemula dan berpengalaman adalah menanganinya. Pengecualian dilemparkan dengan sengaja, jadi dalam banyak kasus kita perlu mengatasi masalah yang menyebabkan pengecualian ini. Jangan abaikan peristiwa-peristiwa ini. Jika perlu, Anda dapat menampilkannya kembali, menampilkan dialog kesalahan kepada pengguna, atau menambahkan pesan ke log. Paling tidak, harus dijelaskan mengapa pengecualian dibiarkan tidak ditangani agar pengembang lain tahu alasannya.

 selfie = person.shootASelfie(); try { selfie.show(); } catch (NullPointerException e) { // Maybe, invisible man. Who cares, anyway? }

Cara yang lebih jelas untuk menyoroti ketidakpentingan pengecualian adalah dengan menyandikan pesan ini ke dalam nama variabel pengecualian, seperti ini:

 try { selfie.delete(); } catch (NullPointerException unimportant) { }

Kesalahan Umum #8: Pengecualian Modifikasi Bersamaan

Pengecualian ini terjadi ketika koleksi dimodifikasi saat mengulanginya menggunakan metode selain yang disediakan oleh objek iterator. Misalnya, kami memiliki daftar topi dan kami ingin menghapus semua yang memiliki penutup telinga:

 List<IHat> hats = new ArrayList<>(); hats.add(new Ushanka()); // that one has ear flaps hats.add(new Fedora()); hats.add(new Sombrero()); for (IHat hat : hats) { if (hat.hasEarFlaps()) { hats.remove(hat); } }

Jika kita menjalankan kode ini, “ConcurrentModificationException” akan dimunculkan karena kode memodifikasi koleksi saat iterasi. Pengecualian yang sama dapat terjadi jika salah satu dari beberapa utas yang bekerja dengan daftar yang sama mencoba mengubah koleksi sementara yang lain mengulanginya. Modifikasi koleksi secara bersamaan di beberapa utas adalah hal yang wajar, tetapi harus diperlakukan dengan alat biasa dari kotak alat pemrograman bersamaan seperti kunci sinkronisasi, koleksi khusus yang diadopsi untuk modifikasi bersamaan, dll. Ada perbedaan halus tentang bagaimana masalah Java ini dapat diselesaikan dalam kasus ulir tunggal dan kasus multithreaded. Di bawah ini adalah diskusi singkat tentang beberapa cara ini dapat ditangani dalam skenario berulir tunggal:

Kumpulkan objek dan hapus di loop lain

Mengumpulkan topi dengan penutup telinga dalam daftar untuk melepaskannya nanti dari dalam lingkaran lain adalah solusi yang jelas, tetapi memerlukan koleksi tambahan untuk menyimpan topi yang akan dilepas:

 List<IHat> hatsToRemove = new LinkedList<>(); for (IHat hat : hats) { if (hat.hasEarFlaps()) { hatsToRemove.add(hat); } } for (IHat hat : hatsToRemove) { hats.remove(hat); }

Gunakan metode Iterator.remove

Pendekatan ini lebih ringkas, dan tidak memerlukan koleksi tambahan untuk dibuat:

 Iterator<IHat> hatIterator = hats.iterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); } }

Gunakan metode ListIterator

Menggunakan daftar iterator tepat ketika koleksi yang dimodifikasi mengimplementasikan antarmuka Daftar. Iterator yang mengimplementasikan antarmuka ListIterator tidak hanya mendukung operasi penghapusan, tetapi juga menambah dan mengatur operasi. ListIterator mengimplementasikan antarmuka Iterator sehingga contohnya akan terlihat hampir sama dengan metode penghapusan Iterator. Satu-satunya perbedaan adalah jenis iterator topi, dan cara kita mendapatkan iterator itu dengan metode “listIterator()”. Cuplikan di bawah ini menunjukkan cara mengganti setiap topi dengan penutup telinga dengan sombrero menggunakan metode “ListIterator.remove” dan “ListIterator.add”:

 IHat sombrero = new Sombrero(); ListIterator<IHat> hatIterator = hats.listIterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.remove(); hatIterator.add(sombrero); } }

Dengan ListIterator, panggilan metode hapus dan tambahkan dapat diganti dengan satu panggilan untuk disetel:

 IHat sombrero = new Sombrero(); ListIterator<IHat> hatIterator = hats.listIterator(); while (hatIterator.hasNext()) { IHat hat = hatIterator.next(); if (hat.hasEarFlaps()) { hatIterator.set(sombrero); // set instead of remove and add } }

Menggunakan metode aliran yang diperkenalkan di Java 8 Dengan Java 8, pemrogram memiliki kemampuan untuk mengubah koleksi menjadi aliran dan memfilter aliran itu menurut beberapa kriteria. Berikut adalah contoh bagaimana stream api dapat membantu kami memfilter topi dan menghindari "ConcurrentModificationException".

 hats = hats.stream().filter((hat -> !hat.hasEarFlaps())) .collect(Collectors.toCollection(ArrayList::new));

Metode "Collectors.toCollection" akan membuat ArrayList baru dengan topi yang difilter. Ini bisa menjadi masalah jika kondisi penyaringan harus dipenuhi oleh sejumlah besar item, menghasilkan ArrayList yang besar; dengan demikian, itu harus digunakan dengan hati-hati. Gunakan metode List.removeIf yang disajikan di Java 8 Solusi lain yang tersedia di Java 8, dan jelas yang paling ringkas, adalah penggunaan metode "removeIf":

 hats.removeIf(IHat::hasEarFlaps);

Itu dia. Di bawah tenda, ia menggunakan "Iterator.remove" untuk menyelesaikan perilaku.

Gunakan koleksi khusus

Jika pada awalnya kami memutuskan untuk menggunakan “CopyOnWriteArrayList” daripada “ArrayList”, maka tidak akan ada masalah sama sekali, karena “CopyOnWriteArrayList” menyediakan metode modifikasi (seperti set, tambah, dan hapus) yang tidak berubah larik pendukung koleksi, melainkan buat versi modifikasi baru darinya. Hal ini memungkinkan iterasi pada versi asli dari koleksi dan modifikasi pada saat yang sama, tanpa risiko "ConcurrentModificationException". Kelemahan dari koleksi itu jelas - generasi koleksi baru dengan setiap modifikasi.

Ada koleksi lain yang disetel untuk kasus yang berbeda, misalnya "CopyOnWriteSet" dan "ConcurrentHashMap".

Kesalahan lain yang mungkin terjadi dengan modifikasi koleksi bersamaan adalah membuat aliran dari koleksi, dan selama iterasi aliran, memodifikasi koleksi pendukung. Aturan umum untuk aliran adalah untuk menghindari modifikasi koleksi yang mendasarinya selama kueri aliran. Contoh berikut akan menunjukkan cara yang salah dalam menangani aliran:

 List<IHat> filteredHats = hats.stream().peek(hat -> { if (hat.hasEarFlaps()) { hats.remove(hat); } }).collect(Collectors.toCollection(ArrayList::new));

Metode mengintip mengumpulkan semua elemen dan melakukan tindakan yang disediakan pada masing-masing dari mereka. Di sini, tindakan mencoba menghapus elemen dari daftar yang mendasarinya, yang salah. Untuk menghindarinya, cobalah beberapa metode yang dijelaskan di atas.

Kesalahan Umum #9: Melanggar Kontrak

Terkadang, kode yang disediakan oleh perpustakaan standar atau oleh vendor pihak ketiga bergantung pada aturan yang harus dipatuhi agar semuanya berfungsi. Misalnya, kode hash dan kontrak equals yang ketika diikuti, membuat kerja dijamin untuk satu set koleksi dari kerangka koleksi Java, dan untuk kelas lain yang menggunakan metode hashCode dan equals. Tidak mematuhi kontrak bukanlah jenis kesalahan yang selalu mengarah pada pengecualian atau merusak kompilasi kode; ini lebih rumit, karena terkadang itu mengubah perilaku aplikasi tanpa tanda bahaya. Kode yang salah dapat masuk ke rilis produksi dan menyebabkan sejumlah besar efek yang tidak diinginkan. Ini dapat mencakup perilaku UI yang buruk, laporan data yang salah, kinerja aplikasi yang buruk, kehilangan data, dan banyak lagi. Untungnya, bug bencana ini tidak sering terjadi. Saya sudah menyebutkan kode hash dan kontrak yang sama. Ini digunakan dalam koleksi yang mengandalkan hashing dan membandingkan objek, seperti HashMap dan HashSet. Sederhananya, kontrak berisi dua aturan:

  • Jika dua objek sama, maka kode hashnya harus sama.
  • Jika dua objek memiliki kode hash yang sama, maka keduanya mungkin sama atau tidak.

Melanggar aturan pertama kontrak menyebabkan masalah saat mencoba mengambil objek dari hashmap. Aturan kedua menandakan bahwa objek dengan kode hash yang sama belum tentu sama. Mari kita periksa efek dari melanggar aturan pertama:

 public static class Boat { private String name; Boat(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Boat boat = (Boat) o; return !(name != null ? !name.equals(boat.name) : boat.name != null); } @Override public int hashCode() { return (int) (Math.random() * 5000); } }

Seperti yang Anda lihat, class Boat telah mengganti metode equals dan hashCode. Namun, itu telah melanggar kontrak, karena kode hash mengembalikan nilai acak untuk objek yang sama setiap kali dipanggil. Kode berikut kemungkinan besar tidak akan menemukan perahu bernama "Perusahaan" di hashset, meskipun kami telah menambahkan perahu semacam itu sebelumnya:

 public static void main(String[] args) { Set<Boat> boats = new HashSet<>(); boats.add(new Boat("Enterprise")); System.out.printf("We have a boat named 'Enterprise' : %b\n", boats.contains(new Boat("Enterprise"))); }

Contoh lain dari kontrak melibatkan metode finalisasi. Berikut adalah kutipan dari dokumentasi Java resmi yang menjelaskan fungsinya:

Kontrak umum finalisasi adalah bahwa itu dipanggil jika dan ketika mesin virtual JavaTM telah menentukan bahwa tidak ada lagi sarana yang dengannya objek ini dapat diakses oleh utas apa pun (yang belum mati), kecuali sebagai akibat dari tindakan yang diambil oleh finalisasi beberapa objek atau kelas lain yang siap untuk diselesaikan. Metode finalisasi dapat mengambil tindakan apa pun, termasuk membuat objek ini tersedia lagi untuk utas lainnya; tujuan biasa dari finalisasi, bagaimanapun, adalah untuk melakukan tindakan pembersihan sebelum objek dibuang secara tidak dapat dibatalkan. Misalnya, metode finalisasi untuk objek yang mewakili koneksi input/output mungkin melakukan transaksi I/O eksplisit untuk memutuskan koneksi sebelum objek dibuang secara permanen.

Seseorang dapat memutuskan untuk menggunakan metode finalisasi untuk membebaskan sumber daya seperti penangan file, tetapi itu akan menjadi ide yang buruk. Ini karena tidak ada jaminan waktu kapan finalize akan dipanggil, karena itu dipanggil selama pengumpulan sampah, dan waktu GC tidak dapat ditentukan.

Kesalahan Umum #10: Menggunakan Jenis Mentah Alih-alih Yang Berparameter

Tipe mentah, menurut spesifikasi Java, adalah tipe yang tidak berparametri, atau anggota kelas R non-statis yang tidak diwarisi dari superclass atau superinterface R. Tidak ada alternatif untuk tipe mentah sampai tipe generik diperkenalkan di Jawa . Ini mendukung pemrograman generik sejak versi 1.5, dan generik tidak diragukan lagi merupakan peningkatan yang signifikan. Namun, karena alasan kompatibilitas mundur, jebakan telah ditinggalkan yang berpotensi merusak sistem tipe. Mari kita lihat contoh berikut:

 List listOfNumbers = new ArrayList(); listOfNumbers.add(10); listOfNumbers.add("Twenty"); listOfNumbers.forEach(n -> System.out.println((int) n * 2));

Di sini kita memiliki daftar angka yang didefinisikan sebagai ArrayList mentah. Karena tipenya tidak ditentukan dengan parameter tipe, kita dapat menambahkan objek apa pun ke dalamnya. Tapi di baris terakhir kita melemparkan elemen ke int, menggandakannya, dan mencetak angka yang digandakan ke output standar. Kode ini akan dikompilasi tanpa kesalahan, tetapi setelah dijalankan akan memunculkan pengecualian runtime karena kami mencoba untuk melemparkan string ke bilangan bulat. Jelas, sistem tipe tidak dapat membantu kami menulis kode aman jika kami menyembunyikan informasi yang diperlukan darinya. Untuk memperbaiki masalah, kita perlu menentukan jenis objek yang akan kita simpan dalam koleksi:

 List<Integer> listOfNumbers = new ArrayList<>(); listOfNumbers.add(10); listOfNumbers.add("Twenty"); listOfNumbers.forEach(n -> System.out.println((int) n * 2));

Satu-satunya perbedaan dari aslinya adalah baris yang mendefinisikan koleksi:

 List<Integer> listOfNumbers = new ArrayList<>();

Kode tetap tidak dapat dikompilasi karena kami mencoba menambahkan string ke dalam koleksi yang diharapkan hanya menyimpan bilangan bulat. Kompiler akan menampilkan kesalahan dan menunjuk ke baris di mana kita mencoba menambahkan string "Dua puluh" ke daftar. Itu selalu merupakan ide yang baik untuk membuat parameter tipe generik. Dengan begitu, kompiler dapat melakukan semua pemeriksaan tipe yang mungkin, dan kemungkinan pengecualian runtime yang disebabkan oleh inkonsistensi sistem tipe diminimalkan.

Kesimpulan

Java sebagai platform menyederhanakan banyak hal dalam pengembangan perangkat lunak, mengandalkan JVM yang canggih dan bahasa itu sendiri. Namun, fitur-fiturnya, seperti menghapus manajemen memori manual atau alat OOP yang layak, tidak menghilangkan semua masalah dan masalah yang dihadapi pengembang Java biasa. Seperti biasa, pengetahuan, praktik, dan tutorial Java seperti ini adalah cara terbaik untuk menghindari dan mengatasi kesalahan aplikasi - jadi ketahui perpustakaan Anda, baca java, baca dokumentasi JVM, dan tulis program. Jangan lupa tentang penganalisis kode statis, karena mereka dapat menunjukkan bug yang sebenarnya dan menyoroti bug potensial.

Terkait: Tutorial Kelas Java Tingkat Lanjut: Panduan untuk Pemuatan Ulang Kelas