JavaRush /Java Blog /Random-ID /Rehat kopi #146. 5 kesalahan yang dilakukan 99% pengemban...

Rehat kopi #146. 5 kesalahan yang dilakukan 99% pengembang Java. String di Java - tampilan dalam

Dipublikasikan di grup Random-ID

5 kesalahan yang dilakukan 99% pengembang Java

Sumber: Medium Dalam postingan ini, Anda akan mempelajari kesalahan paling umum yang dilakukan banyak pengembang Java. Rehat kopi #146.  5 kesalahan yang dilakukan 99% pengembang Java.  String di Java - tampilan dalam - 1Sebagai seorang programmer Java, saya tahu betapa buruknya menghabiskan banyak waktu untuk memperbaiki bug di kode Anda. Terkadang ini memakan waktu beberapa jam. Namun, banyak kesalahan muncul karena pengembang mengabaikan aturan dasar - yaitu, ini adalah kesalahan tingkat sangat rendah. Hari ini kita akan melihat beberapa kesalahan pengkodean yang umum dan kemudian menjelaskan cara memperbaikinya. Saya harap ini membantu Anda menghindari masalah dalam pekerjaan sehari-hari Anda.

Membandingkan objek menggunakan Objects.equals

Saya berasumsi Anda sudah familiar dengan metode ini. Banyak pengembang yang sering menggunakannya. Teknik ini, yang diperkenalkan di JDK 7, membantu Anda membandingkan objek dengan cepat dan secara efektif menghindari pemeriksaan penunjuk nol yang mengganggu. Namun cara ini terkadang digunakan secara tidak benar. Inilah yang saya maksud:
Long longValue = 123L;
System.out.println(longValue==123); //true
System.out.println(Objects.equals(longValue,123)); //false
Mengapa mengganti == dengan Objects.equals() menghasilkan hasil yang salah? Hal ini karena kompiler == akan mendapatkan tipe data dasar yang sesuai dengan tipe kemasan longValue dan kemudian membandingkannya dengan tipe data dasar tersebut. Ini setara dengan kompiler yang secara otomatis mengonversi konstanta ke tipe data perbandingan yang mendasarinya. Setelah menggunakan metode Objects.equals() , tipe data dasar default dari konstanta kompiler adalah int . Di bawah ini adalah kode sumber untuk Objects.equals() di mana a.equals(b) menggunakan Long.equals() dan menentukan tipe objek. Hal ini terjadi karena compiler berasumsi bahwa konstanta tersebut bertipe int , sehingga hasil perbandingannya pasti salah.
public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }

  public boolean equals(Object obj) {
        if (obj instanceof Long) {
            return value == ((Long)obj).longValue();
        }
        return false;
    }
Mengetahui alasannya, memperbaiki kesalahan tersebut sangat sederhana. Cukup deklarasikan tipe data konstanta, seperti Objects.equals(longValue,123L) . Masalah di atas tidak akan muncul jika logikanya ketat. Yang perlu kita lakukan adalah mengikuti aturan pemrograman yang jelas.

Format tanggal salah

Dalam perkembangan sehari-hari, seringkali Anda perlu mengubah tanggal, namun banyak orang yang menggunakan format yang salah sehingga berujung pada hal-hal yang tidak terduga. Berikut ini contohnya:
Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant));//2022-12-31 08:00:00
Ini menggunakan format YYYY-MM-dd untuk mengubah tanggal dari 2021 ke 2022. Anda tidak harus melakukan itu. Mengapa? Hal ini karena pola Java DateTimeFormatter “YYYY” didasarkan pada standar ISO-8601, yang mendefinisikan tahun sebagai hari Kamis setiap minggunya. Namun tanggal 31 Desember 2021 jatuh pada hari Jumat, sehingga program salah mencantumkan tahun 2022. Untuk menghindarinya, Anda harus menggunakan format yyyy-MM-dd untuk memformat tanggal . Kesalahan ini jarang terjadi, hanya menjelang datangnya tahun baru. Namun di perusahaan saya hal itu menyebabkan kegagalan produksi.

Menggunakan ThreadLocal di ThreadPool

Jika Anda membuat variabel ThreadLocal , maka thread yang mengakses variabel tersebut akan membuat variabel lokal thread. Dengan cara ini Anda dapat menghindari masalah keamanan thread. Namun, jika Anda menggunakan ThreadLocal pada kumpulan thread , Anda harus berhati-hati. Kode Anda mungkin memberikan hasil yang tidak diharapkan. Sebagai contoh sederhana, katakanlah kita memiliki platform e-niaga dan pengguna perlu mengirim email untuk mengonfirmasi pembelian produk yang telah selesai.
private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);

    private ExecutorService executorService = Executors.newFixedThreadPool(4);

    public void executor() {
        executorService.submit(()->{
            User user = currentUser.get();
            Integer userId = user.getId();
            sendEmail(userId);
        });
    }
Jika kita menggunakan ThreadLocal untuk menyimpan informasi pengguna, kesalahan tersembunyi akan muncul. Karena kumpulan thread digunakan, dan thread dapat digunakan kembali, saat menggunakan ThreadLocal untuk mendapatkan informasi pengguna, informasi orang lain mungkin salah ditampilkan. Untuk mengatasi masalah ini, Anda harus menggunakan sesi.

Gunakan HashSet untuk menghapus data duplikat

Saat coding, kita sering kali membutuhkan deduplikasi. Saat memikirkan deduplikasi, hal pertama yang dipikirkan banyak orang adalah menggunakan HashSet . Namun, penggunaan HashSet yang ceroboh dapat menyebabkan kegagalan deduplikasi.
User user1 = new User();
user1.setUsername("test");

User user2 = new User();
user2.setUsername("test");

List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size());// the size is 2
Beberapa pembaca yang penuh perhatian seharusnya bisa menebak alasan kegagalan tersebut. HashSet menggunakan kode hash untuk mengakses tabel hash dan menggunakan metode sama dengan untuk menentukan apakah objek sama. Jika objek yang ditentukan pengguna tidak mengesampingkan metode kode hash dan metode sama dengan , maka metode kode hash dan metode sama dengan objek induk akan digunakan secara default. Hal ini akan menyebabkan HashSet berasumsi bahwa keduanya adalah dua objek berbeda, menyebabkan deduplikasi gagal.

Menghilangkan benang renang yang "dimakan".

ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.submit(()->{
            //do something
            double result = 10/0;
        });
Kode di atas mensimulasikan skenario di mana pengecualian dilemparkan ke dalam kumpulan thread. Kode bisnis harus mengasumsikan berbagai situasi, sehingga kemungkinan besar akan memunculkan RuntimeException karena alasan tertentu . Namun jika tidak ada penanganan khusus di sini, maka pengecualian ini akan “dimakan” oleh thread pool. Dan Anda bahkan tidak memiliki cara untuk memeriksa penyebab pengecualian tersebut. Oleh karena itu, yang terbaik adalah menangkap pengecualian di kumpulan proses.

String di Java - tampilan dalam

Sumber: Medium Penulis artikel ini memutuskan untuk melihat secara mendetail pembuatan, fungsionalitas, dan fitur string di Java. Rehat kopi #146.  5 kesalahan yang dilakukan 99% pengembang Java.  String di Java - tampilan dalam - 2

Penciptaan

Sebuah string di Java dapat dibuat dengan dua cara berbeda: secara implisit, sebagai string literal, dan secara eksplisit, menggunakan kata kunci new . Literal string adalah karakter yang diapit tanda kutip ganda.
String literal   = "Michael Jordan";
String object    = new String("Michael Jordan");
Meskipun kedua deklarasi membuat objek string, ada perbedaan dalam cara kedua objek tersebut ditempatkan di memori heap.

Representasi internal

Sebelumnya, string disimpan dalam bentuk char[] , artinya setiap karakter merupakan elemen terpisah dalam array karakter. Karena karakter tersebut direpresentasikan dalam format pengkodean karakter UTF-16 , ini berarti setiap karakter menggunakan dua byte memori. Ini tidak sepenuhnya benar, karena statistik penggunaan menunjukkan bahwa sebagian besar objek string hanya terdiri dari karakter Latin-1 saja . Karakter Latin-1 dapat direpresentasikan menggunakan satu byte memori, yang dapat mengurangi penggunaan memori secara signifikan—sebanyak 50%. Fitur string internal baru diimplementasikan sebagai bagian dari rilis JDK 9 berdasarkan JEP 254 yang disebut Compact Strings. Dalam rilis ini, char[] diubah menjadi byte[] dan bidang flag encoder ditambahkan untuk mewakili pengkodean yang digunakan (Latin-1 atau UTF-16). Setelah ini, pengkodean terjadi berdasarkan isi string. Jika nilai hanya berisi karakter Latin-1, maka pengkodean Latin-1 digunakan ( kelas StringLatin1 ) atau pengkodean UTF-16 digunakan ( kelas StringUTF16 ).

Alokasi memori

Seperti disebutkan sebelumnya, ada perbedaan dalam cara memori dialokasikan untuk objek-objek ini di heap. Menggunakan kata kunci eksplisit new cukup mudah karena JVM membuat dan mengalokasikan memori untuk variabel di heap. Oleh karena itu, menggunakan string literal mengikuti proses yang disebut magang. Interning string adalah proses memasukkan string ke dalam kumpulan. Ia menggunakan metode untuk menyimpan hanya satu salinan dari setiap nilai string individual, yang tidak boleh diubah. Nilai individual disimpan di kumpulan String Intern. Kumpulan ini adalah penyimpanan Hashtable yang menyimpan referensi ke setiap objek string yang dibuat menggunakan literal dan hashnya. Meskipun nilai string ada di heap, referensinya dapat ditemukan di kumpulan internal. Hal ini dapat dengan mudah diverifikasi menggunakan percobaan di bawah ini. Di sini kita memiliki dua variabel dengan nilai yang sama:
String firstName1   = "Michael";
String firstName2   = "Michael";
System.out.println(firstName1 == firstName2);             //true
Selama eksekusi kode, ketika JVM bertemu firstName1 , JVM mencari nilai string di kumpulan string internal Michael . Jika tidak dapat menemukannya, maka entri baru dibuat untuk objek di kumpulan internal. Ketika eksekusi mencapai firstName2 , proses diulangi lagi dan kali ini nilainya dapat ditemukan di kumpulan berdasarkan variabel firstName1 . Dengan cara ini, alih-alih menduplikasi dan membuat entri baru, tautan yang sama akan dikembalikan. Oleh karena itu, kondisi kesetaraan terpenuhi. Di sisi lain, jika variabel dengan nilai Michael dibuat menggunakan kata kunci baru, tidak ada magang yang terjadi dan kondisi kesetaraan tidak terpenuhi.
String firstName3 = new String("Michael");
System.out.println(firstName3 == firstName2);           //false
Magang dapat digunakan dengan metode firstName3 intern() , meskipun hal ini biasanya tidak disukai.
firstName3 = firstName3.intern();                      //Interning
System.out.println(firstName3 == firstName2);          //true
Magang juga dapat terjadi ketika menggabungkan dua string literal menggunakan operator + .
String fullName = "Michael Jordan";
System.out.println(fullName == "Michael " + "Jordan");     //true
Di sini kita melihat bahwa pada waktu kompilasi, kompiler menambahkan literal dan menghapus operator + dari ekspresi untuk membentuk string tunggal seperti yang ditunjukkan di bawah ini. Saat runtime, fullName dan “literal yang ditambahkan” diinternir dan kondisi kesetaraan terpenuhi.
//After Compilation
System.out.println(fullName == "Michael Jordan");

Persamaan

Dari percobaan di atas, Anda dapat melihat bahwa hanya string literal yang diinternir secara default. Namun, aplikasi Java tentu tidak hanya memiliki literal string, karena aplikasi tersebut mungkin menerima string dari sumber yang berbeda. Oleh karena itu, penggunaan operator kesetaraan tidak disarankan dan dapat menimbulkan hasil yang tidak diinginkan. Pengujian kesetaraan hanya boleh dilakukan dengan metode yang sama . Ia melakukan kesetaraan berdasarkan nilai string, bukan alamat memori tempat penyimpanannya.
System.out.println(firstName1.equals(firstName2));       //true
System.out.println(firstName3.equals(firstName2));       //true
Ada juga versi yang sedikit dimodifikasi dari metode sama dengan yang disebut sama denganIgnoreCase . Ini mungkin berguna untuk tujuan yang tidak peka huruf besar-kecil.
String firstName4 = "miCHAEL";
System.out.println(firstName4.equalsIgnoreCase(firstName1));  //true

Kekekalan

String tidak dapat diubah, artinya keadaan internalnya tidak dapat diubah setelah dibuat. Anda dapat mengubah nilai suatu variabel, tetapi tidak dapat mengubah nilai string itu sendiri. Setiap metode kelas String yang menangani manipulasi objek (misalnya, concat , substring ) mengembalikan salinan nilai baru daripada memperbarui nilai yang sudah ada.
String firstName  = "Michael";
String lastName   = "Jordan";
firstName.concat(lastName);

System.out.println(firstName);                       //Michael
System.out.println(lastName);                        //Jordan
Seperti yang Anda lihat, tidak ada perubahan yang terjadi pada variabel apa pun: baik firstName maupun lastName . Metode kelas string tidak mengubah keadaan internal, mereka membuat salinan baru dari hasilnya dan mengembalikan hasilnya seperti yang ditunjukkan di bawah ini.
firstName = firstName.concat(lastName);

System.out.println(firstName);                      //MichaelJordan
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION