JavaRush /Java Blog /Random-ID /Perbandingan objek: latihan
articles
Level 15

Perbandingan objek: latihan

Dipublikasikan di grup Random-ID
Ini adalah artikel kedua yang ditujukan untuk membandingkan objek. Yang pertama membahas landasan teori perbandingan - bagaimana hal itu dilakukan, mengapa dan di mana digunakan. Pada artikel ini kita akan berbicara langsung tentang membandingkan angka, objek, kasus khusus, kehalusan dan poin yang tidak jelas. Lebih tepatnya, inilah yang akan kita bicarakan:
Perbandingan objek: latihan - 1
  • Perbandingan string: ' ==' danequals
  • metodeString.intern
  • Perbandingan primitif nyata
  • +0.0Dan-0.0
  • ArtiNaN
  • Jawa 5.0. Menghasilkan metode dan perbandingan melalui ' =='
  • Jawa 5.0. Autoboxing/Unboxing: ' ==', ' >=' dan ' <=' untuk pembungkus objek.
  • Jawa 5.0. perbandingan elemen enum (tipe enum)
Jadi mari kita mulai!

Perbandingan string: ' ==' danequals

Ah, baris-baris ini... Salah satu tipe yang paling umum digunakan, yang menyebabkan banyak masalah. Pada prinsipnya, ada artikel terpisah tentang mereka . Dan di sini saya akan menyentuh masalah perbandingan. Tentu saja, string dapat dibandingkan menggunakan equals. Selain itu, mereka HARUS dibandingkan melalui equals. Namun, ada seluk-beluknya yang perlu diketahui. Pertama-tama, string identik sebenarnya adalah satu objek. Ini dapat diverifikasi dengan mudah dengan menjalankan kode berikut:
String str1 = "string";
String str2 = "string";
System.out.println(str1==str2 ? "the same" : "not the same");
Hasilnya akan “sama” . Artinya referensi stringnya sama. Ini dilakukan pada tingkat kompiler, jelas untuk menghemat memori. Kompiler membuat SATU contoh string, dan memberikan str1referensi str2ke contoh ini. Namun, ini hanya berlaku untuk string yang dideklarasikan sebagai literal dalam kode. Jika Anda membuat string dari potongan-potongan, tautan ke sana akan berbeda. Konfirmasi - contoh ini:
String str1 = "string";
String str2 = "str";
String str3 = "ing";
System.out.println(str1==(str2+str3) ? "the same" : "not the same");
Hasilnya akan “tidak sama” . Anda juga dapat membuat objek baru menggunakan copy konstruktor:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1==str2 ? "the same" : "not the same");
Hasilnya juga akan “tidak sama” . Jadi, terkadang string dapat dibandingkan melalui perbandingan referensi. Tapi lebih baik tidak mengandalkan ini. Saya ingin menyentuh satu metode yang sangat menarik yang memungkinkan Anda mendapatkan apa yang disebut representasi kanonik dari sebuah string - String.intern. Mari kita bicarakan ini lebih detail.

Metode string.magang

Mari kita mulai dengan fakta bahwa kelas Stringmendukung kumpulan string. Semua literal string yang ditentukan dalam kelas, dan bukan hanya kelas, ditambahkan ke kumpulan ini. Jadi, metode ini internmemungkinkan Anda untuk mendapatkan string dari kumpulan ini yang sama dengan string yang sudah ada (yang menjadi tempat pemanggilan metode tersebut intern) dari sudut pandang equals. Jika baris seperti itu tidak ada di kumpulan, maka baris yang sudah ada ditempatkan di sana dan tautan ke baris tersebut dikembalikan. Jadi, meskipun referensi ke dua string yang sama berbeda (seperti pada dua contoh di atas), pemanggilan ke string ini internakan mengembalikan referensi ke objek yang sama:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1.intern()==str2.intern() ? "the same" : "not the same");
Hasil dari mengeksekusi potongan kode ini akan menjadi "sama" . Saya tidak bisa mengatakan secara pasti mengapa hal itu dilakukan dengan cara ini. Metodenya internasli, dan sejujurnya, saya tidak ingin terlibat dalam belantara kode C. Kemungkinan besar hal ini dilakukan untuk mengoptimalkan konsumsi memori dan kinerja. Bagaimanapun, ada baiknya mengetahui tentang fitur implementasi ini. Mari kita lanjutkan ke bagian selanjutnya.

Perbandingan primitif nyata

Pertama-tama, saya ingin mengajukan pertanyaan. Sangat sederhana. Berapa jumlah berikut – 0.3f + 0.4f? Mengapa? 0,7f? Mari kita periksa:
float f1 = 0.7f;
float f2 = 0.3f + 0.4f;
System.out.println("f1==f2: "+(f1==f2));
Sebagai akibat? Menyukai? Saya juga. Bagi yang belum menyelesaikan penggalan ini, saya katakan hasilnya adalah...
f1==f2: false
Mengapa ini terjadi?.. Mari kita lakukan tes lainnya:
float f1 = 0.3f;
float f2 = 0.4f;
float f3 = f1 + f2;
float f4 = 0.7f;
System.out.println("f1="+(double)f1);
System.out.println("f2="+(double)f2);
System.out.println("f3="+(double)f3);
System.out.println("f4="+(double)f4);
Perhatikan konversinya menjadi double. Hal ini dilakukan untuk menghasilkan lebih banyak tempat desimal. Hasil:
f1=0.30000001192092896
f2=0.4000000059604645
f3=0.7000000476837158
f4=0.699999988079071
Sebenarnya, hasilnya bisa diprediksi. Representasi bagian pecahan dilakukan dengan menggunakan deret berhingga 2-n, oleh karena itu tidak perlu membicarakan representasi eksak dari bilangan yang dipilih secara sewenang-wenang. Seperti terlihat dari contoh, akurasi representasi floatadalah 7 tempat desimal. Sebenarnya, representasi tersebut float mengalokasikan 24 bit ke mantissa. Jadi, bilangan absolut minimum yang dapat direpresentasikan menggunakan float (tanpa memperhitungkan derajat, karena kita berbicara tentang akurasi) adalah 2-24≈6*10-8. Dengan langkah inilah nilai-nilai dalam representasi sebenarnya berjalan float. Dan karena ada kuantisasi, maka ada juga kesalahan. Oleh karena itu kesimpulannya: angka-angka dalam suatu representasi floathanya dapat dibandingkan dengan akurasi tertentu. Saya akan merekomendasikan membulatkannya ke angka desimal ke-6 (10-6), atau, sebaiknya, memeriksa nilai absolut selisih di antara keduanya:
float f1 = 0.3f;
float f2 = 0.4f;
float f3 = f1 + f2;
float f4 = 0.7f;
System.out.println("|f3-f4|<1e-6: "+( Math.abs(f3-f4) < 1e-6 ));
Dalam hal ini, hasilnya menggembirakan:
|f3-f4|<1e-6: true
Tentu saja gambarnya sama persis dengan tipenya double. Satu-satunya perbedaan adalah 53 bit dialokasikan untuk mantissa, oleh karena itu akurasi representasinya adalah 2-53≈10-16. Ya, nilai kuantisasinya jauh lebih kecil, tetapi memang ada. Dan itu bisa menjadi lelucon yang kejam. Omong-omong, di perpustakaan pengujian JUnit , dalam metode untuk membandingkan bilangan real, presisi ditentukan secara eksplisit. Itu. metode perbandingan berisi tiga parameter - angka, apa yang harus sama, dan keakuratan perbandingan. Ngomong-ngomong, saya ingin menyebutkan seluk-beluk yang terkait dengan penulisan angka dalam format ilmiah, yang menunjukkan derajatnya. Pertanyaan. Bagaimana cara menulis 10-6? Latihan menunjukkan bahwa lebih dari 80% menjawab – 10e-6. Sedangkan jawaban yang benar adalah 1e-6! Dan 10e-6 adalah 10-5! Kami menginjak salah satu proyek ini, secara tidak terduga. Mereka mencari kesalahan untuk waktu yang sangat lama, melihat konstanta sebanyak 20 kali. Dan tidak ada seorang pun yang meragukan kebenarannya, sampai suatu hari, sebagian besar secara tidak sengaja, konstanta 10e-3 dicetak dan mereka menemukan dua digit setelah koma desimal, bukan tiga yang diharapkan. Oleh karena itu, berhati-hatilah! Mari kita lanjutkan.

+0,0 dan -0,0

Dalam representasi bilangan real, bit paling signifikan ditandatangani. Apa yang terjadi jika semua bit lainnya 0? Berbeda dengan bilangan bulat, yang dalam situasi seperti ini hasilnya adalah bilangan negatif yang terletak di batas bawah rentang representasi, bilangan real dengan hanya bit paling signifikan yang disetel ke 1 juga berarti 0, hanya dengan tanda minus. Jadi, kita memiliki dua angka nol - +0,0 dan -0,0. Sebuah pertanyaan logis muncul: haruskah angka-angka ini dianggap sama? Mesin virtual berpikir persis seperti ini. Namun, ini adalah dua angka yang berbeda , karena sebagai hasil operasi dengannya, diperoleh nilai yang berbeda:
float f1 = 0.0f/1.0f;
float f2 = 0.0f/-1.0f;
System.out.println("f1="+f1);
System.out.println("f2="+f2);
System.out.println("f1==f2: "+(f1==f2));
float f3 = 1.0f / f1;
float f4 = 1.0f / f2;
System.out.println("f3="+f3);
System.out.println("f4="+f4);
...dan hasilnya:
f1=0.0
f2=-0.0
f1==f2: true
f3=Infinity
f4=-Infinity
Jadi dalam beberapa kasus masuk akal untuk memperlakukan +0,0 dan -0,0 sebagai dua angka berbeda. Dan jika kita memiliki dua objek, yang salah satunya memiliki bidang +0,0 dan yang lainnya -0,0, maka objek tersebut juga dapat dianggap tidak setara. Timbul pertanyaan - bagaimana Anda dapat memahami bahwa angka-angka tersebut tidak sama jika dibandingkan langsung dengan mesin virtual true? Jawabannya adalah ini. Meskipun mesin virtual menganggap angka-angka ini sama, representasinya tetap berbeda. Oleh karena itu, satu-satunya hal yang dapat dilakukan adalah membandingkan pandangan. Dan untuk mendapatkannya, ada metode int Float.floatToIntBits(float)dan long Double.doubleToLongBits(double), yang mengembalikan representasi bit dalam bentuk intdan longmasing-masing (lanjutan dari contoh sebelumnya):
int i1 = Float.floatToIntBits(f1);
int i2 = Float.floatToIntBits(f2);
System.out.println("i1 (+0.0):"+ Integer.toBinaryString(i1));
System.out.println("i2 (-0.0):"+ Integer.toBinaryString(i2));
System.out.println("i1==i2: "+(i1 == i2));
Hasilnya adalah
i1 (+0.0):0
i2 (-0.0):10000000000000000000000000000000
i1==i2: false
Jadi, jika Anda memiliki +0,0 dan -0,0 angka yang berbeda, maka Anda harus membandingkan variabel nyata melalui representasi bitnya. Tampaknya kita telah menyelesaikan +0,0 dan -0,0. -0,0, bagaimanapun, bukanlah satu-satunya kejutan. Ada juga yang namanya...

nilai NaN

NaNberdiri untuk Not-a-Number. Nilai ini muncul sebagai akibat dari operasi matematika yang salah, misalnya membagi 0,0 dengan 0,0, tak terhingga dengan tak terhingga, dll. Keunikan nilai ini adalah tidak setara dengan dirinya sendiri. Itu.:
float x = 0.0f/0.0f;
System.out.println("x="+x);
System.out.println("x==x: "+(x==x));
...akan menghasilkan...
x=NaN
x==x: false
Bagaimana hal ini bisa terjadi ketika membandingkan objek? Jika bidang benda sama dengan NaN, maka perbandingannya akan memberikan false, yaitu. objek dijamin dianggap tidak setara. Meskipun, secara logika, kita mungkin menginginkan hal sebaliknya. Anda dapat mencapai hasil yang diinginkan dengan menggunakan metode ini Float.isNaN(float). Ia kembali truejika argumennya adalah NaN. Dalam hal ini, saya tidak akan mengandalkan perbandingan representasi bit, karena itu tidak terstandarisasi. Mungkin cukup tentang primitif. Sekarang mari kita beralih ke seluk-beluk yang muncul di Java sejak versi 5.0. Dan poin pertama yang ingin saya sentuh adalah

Jawa 5.0. Menghasilkan metode dan perbandingan melalui ' =='

Ada pola dalam desain yang disebut metode produksi. Terkadang penggunaannya jauh lebih menguntungkan daripada menggunakan konstruktor. Izinkan saya memberi Anda sebuah contoh. Saya rasa saya mengenal objek shell dengan baik Boolean. Kelas ini tidak dapat diubah dan hanya dapat berisi dua nilai. Artinya, untuk kebutuhan apa pun, cukup dua eksemplar saja. Dan jika Anda membuatnya terlebih dahulu dan kemudian mengembalikannya, itu akan jauh lebih cepat daripada menggunakan konstruktor. Ada metode seperti itu Boolean: valueOf(boolean). Itu muncul di versi 1.4. Metode produksi serupa diperkenalkan pada versi 5.0 di kelas Byte, Character, Short, Integerdan Long. Ketika kelas-kelas ini dimuat, array dari instance-nya dibuat sesuai dengan rentang nilai primitif tertentu. Kisaran tersebut adalah sebagai berikut:
Perbandingan objek: latihan - 2
Ini berarti bahwa ketika menggunakan metode ini, valueOf(...)jika argumen berada dalam rentang yang ditentukan, objek yang sama akan selalu dikembalikan. Mungkin ini memberikan peningkatan kecepatan. Tetapi pada saat yang sama, masalah-masalah muncul sedemikian rupa sehingga cukup sulit untuk menyelesaikannya. Baca lebih lanjut tentang itu. Secara teori, metode produksi valueOftelah ditambahkan ke kelas Floatdan Double. Deskripsi mereka mengatakan bahwa jika Anda tidak memerlukan salinan baru, lebih baik menggunakan metode ini, karena itu dapat memberikan peningkatan kecepatan, dll. dan seterusnya. Namun, dalam implementasi saat ini (Java 5.0), sebuah instance baru dibuat dalam metode ini, yaitu. Penggunaannya tidak dijamin memberikan peningkatan kecepatan. Selain itu, sulit bagi saya untuk membayangkan bagaimana metode ini dapat dipercepat, karena karena kontinuitas nilai, cache tidak dapat diatur di sana. Kecuali bilangan bulat. Maksudku, tanpa bagian pecahan.

Jawa 5.0. Autoboxing/Unboxing: ' ==', ' >=' dan ' <=' untuk pembungkus objek.

Saya menduga bahwa metode produksi dan cache contoh ditambahkan ke pembungkus untuk bilangan bulat primitif untuk mengoptimalkan operasi autoboxing/unboxing. Izinkan saya mengingatkan Anda apa itu. Jika suatu objek harus terlibat dalam suatu operasi, tetapi primitif terlibat, maka primitif ini secara otomatis dibungkus dalam pembungkus objek. Ini autoboxing. Dan sebaliknya - jika primitif harus dilibatkan dalam suatu operasi, maka Anda dapat mengganti shell objek di sana, dan nilainya akan secara otomatis diperluas darinya. Ini unboxing. Tentu saja, Anda harus membayar untuk kenyamanan tersebut. Operasi konversi otomatis agak memperlambat kinerja aplikasi. Namun, ini tidak relevan dengan topik saat ini, jadi tinggalkan pertanyaan ini. Semuanya baik-baik saja selama kita berurusan dengan operasi yang jelas-jelas terkait dengan primitif atau shell. Apa yang akan terjadi pada ==operasi ' '? Katakanlah kita memiliki dua objek Integerdengan nilai yang sama di dalamnya. Bagaimana perbandingannya?
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1==i2: "+(i1==i2));
Hasil:
i1==i2: false

Кто бы сомневался... Сравниваются они How an objectы. А если так:Integer i1 = 1;
Integer i2 = 1;
System.out.println("i1==i2: "+(i1==i2));
Hasil:
i1==i2: true
Sekarang ini lebih menarik! Jika autoboxing-e objek yang sama dikembalikan! Di sinilah letak jebakannya. Setelah kami menemukan bahwa objek yang sama dikembalikan, kami akan mulai bereksperimen untuk melihat apakah hal ini selalu terjadi. Dan berapa banyak nilai yang akan kita periksa? Satu? Sepuluh? Seratus? Kemungkinan besar kita akan membatasi diri kita pada seratus di setiap arah sekitar nol. Dan kita mendapatkan kesetaraan di mana pun. Tampaknya semuanya baik-baik saja. Namun, lihat sedikit ke belakang, di sini . Sudahkah Anda menebak apa tangkapannya?.. Ya, instance shell objek selama autoboxing dibuat menggunakan metode produksi. Hal ini diilustrasikan dengan baik oleh tes berikut:
public class AutoboxingTest {

    private static final int numbers[] = new int[]{-129,-128,127,128};

    public static void main(String[] args) {
        for (int number : numbers) {
            Integer i1 = number;
            Integer i2 = number;
            System.out.println("number=" + number + ": " + (i1 == i2));
        }
    }
}
Hasilnya akan seperti ini:
number=-129: false
number=-128: true
number=127: true
number=128: false
Untuk nilai yang termasuk dalam rentang caching , objek identik dikembalikan, untuk nilai di luarnya, objek berbeda dikembalikan. Oleh karena itu, jika di suatu tempat di shell aplikasi dibandingkan dengan yang primitif, ada kemungkinan mendapatkan kesalahan yang paling mengerikan: kesalahan mengambang. Karena kode tersebut kemungkinan besar juga akan diuji pada rentang nilai terbatas di mana kesalahan ini tidak akan muncul. Namun dalam pekerjaan nyata, itu akan muncul atau hilang, tergantung pada hasil beberapa perhitungan. Lebih mudah menjadi gila daripada menemukan kesalahan seperti itu. Oleh karena itu, saya menyarankan Anda untuk menghindari autoboxing sedapat mungkin. Dan bukan itu saja. Mari kita ingat matematika, tidak lebih dari kelas 5 SD. Biarkan ketidaksetaraan A>=Bdan А<=B. Apa yang bisa dikatakan tentang hubungan Adan B? Hanya ada satu hal - mereka setara. Apa kamu setuju? Saya pikir ya. Mari kita jalankan tesnya:
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1>=i2: "+(i1>=i2));
System.out.println("i1<=i2: "+(i1<=i2));
System.out.println("i1==i2: "+(i1==i2));
Hasil:
i1>=i2: true
i1<=i2: true
i1==i2: false
Dan ini adalah hal aneh terbesar bagi saya. Saya sama sekali tidak mengerti mengapa fitur ini diperkenalkan ke dalam bahasa jika menimbulkan kontradiksi seperti itu. Secara umum, saya akan ulangi sekali lagi - jika memungkinkan untuk dilakukan tanpa autoboxing/unboxing, maka ada baiknya menggunakan kesempatan ini sepenuhnya. Topik terakhir yang ingin saya bahas adalah... Java 5.0. perbandingan elemen enumerasi (tipe enum) Seperti yang Anda ketahui, sejak versi 5.0 Java telah memperkenalkan tipe seperti enum - enumerasi. Instance-nya secara default berisi nama dan nomor urut dalam deklarasi instance di kelas. Oleh karena itu, ketika urutan pengumuman berubah, jumlahnya pun berubah. Namun, seperti yang saya katakan di artikel 'Serialisasi apa adanya' , hal ini tidak menimbulkan masalah. Semua elemen enumerasi ada dalam satu salinan, ini dikontrol di tingkat mesin virtual. Oleh karena itu, keduanya dapat dibandingkan secara langsung menggunakan tautan. * * * Mungkin itu saja untuk hari ini tentang sisi praktis penerapan perbandingan objek. Mungkin saya melewatkan sesuatu. Seperti biasa, saya menantikan komentar Anda! Untuk saat ini, izinkan saya pergi. Terima kasih atas perhatian Anda! Tautan ke sumber: Membandingkan objek: latihan
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION