JavaRush /Blog Java /Random-MS /Perbandingan objek: latihan
articles
Tahap

Perbandingan objek: latihan

Diterbitkan dalam kumpulan
Ini adalah artikel kedua yang dikhaskan untuk membandingkan objek. Yang pertama daripada mereka membincangkan asas teori perbandingan - bagaimana ia dilakukan, mengapa dan di mana ia digunakan. Dalam artikel ini kita akan bercakap secara langsung tentang membandingkan nombor, objek, kes khas, kehalusan dan perkara yang tidak jelas. Lebih tepat lagi, inilah yang akan kita bincangkan:
Perbandingan objek: latihan - 1
  • Perbandingan rentetan: ' ==' danequals
  • KaedahString.intern
  • Perbandingan primitif sebenar
  • +0.0Dan-0.0
  • MaknanyaNaN
  • Java 5.0. Menjana kaedah dan perbandingan melalui ' =='
  • Java 5.0. Autoboxing/Unboxing: ' ==', ' >=' dan ' <=' untuk pembalut objek.
  • Java 5.0. perbandingan unsur enum (jenis enum)
Jadi mari kita mulakan!

Perbandingan rentetan: ' ==' danequals

Ah, baris ini... Salah satu jenis yang paling biasa digunakan, yang menyebabkan banyak masalah. Pada dasarnya, terdapat artikel berasingan tentang mereka . Dan di sini saya akan menyentuh isu perbandingan. Sudah tentu, rentetan boleh dibandingkan menggunakan equals. Selain itu, mereka MESTI dibandingkan melalui equals. Walau bagaimanapun, terdapat kehalusan yang patut diketahui. Pertama sekali, rentetan yang sama sebenarnya adalah satu objek. Ini boleh disahkan dengan mudah dengan menjalankan kod berikut:
String str1 = "string";
String str2 = "string";
System.out.println(str1==str2 ? "the same" : "not the same");
Hasilnya akan menjadi "sama" . Ini bermakna rujukan rentetan adalah sama. Ini dilakukan pada peringkat pengkompil, jelas untuk menjimatkan memori. Pengkompil mencipta SATU contoh rentetan, dan memberikan str1rujukan str2kepada contoh ini. Walau bagaimanapun, ini hanya terpakai kepada rentetan yang diisytiharkan sebagai literal dalam kod. Jika anda mengarang rentetan daripada kepingan, pautan kepadanya akan berbeza. Pengesahan - contoh ini:
String str1 = "string";
String str2 = "str";
String str3 = "ing";
System.out.println(str1==(str2+str3) ? "the same" : "not the same");
Hasilnya akan menjadi "tidak sama" . Anda juga boleh membuat objek baharu menggunakan pembina salinan:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1==str2 ? "the same" : "not the same");
Hasilnya juga akan menjadi "tidak sama" . Oleh itu, kadangkala rentetan boleh dibandingkan melalui perbandingan rujukan. Tetapi lebih baik jangan bergantung pada ini. Saya ingin menyentuh satu kaedah yang sangat menarik yang membolehkan anda mendapatkan apa yang dipanggil perwakilan kanonik rentetan - String.intern. Mari kita bincangkan dengan lebih terperinci.

Kaedah String.intern

Mari kita mulakan dengan fakta bahawa kelas Stringmenyokong kumpulan rentetan. Semua literal rentetan yang ditakrifkan dalam kelas, dan bukan sahaja mereka, ditambahkan pada kolam ini. Jadi, kaedah ini internmembolehkan anda mendapatkan rentetan daripada kolam ini yang sama dengan yang sedia ada (yang kaedah dipanggil intern) dari sudut pandangan equals. Jika baris sedemikian tidak wujud dalam kolam, maka baris yang sedia ada diletakkan di sana dan pautan kepadanya dikembalikan. Oleh itu, walaupun rujukan kepada dua rentetan yang sama adalah berbeza (seperti dalam dua contoh di atas), maka panggilan ke rentetan ini internakan mengembalikan rujukan kepada objek yang sama:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1.intern()==str2.intern() ? "the same" : "not the same");
Hasil daripada melaksanakan sekeping kod ini akan menjadi "sama" . Saya tidak dapat menyatakan dengan tepat mengapa ia dilakukan dengan cara ini. Kaedah ini internasli, dan sejujurnya, saya tidak mahu masuk ke dalam kod C liar. Kemungkinan besar ini dilakukan untuk mengoptimumkan penggunaan memori dan prestasi. Walau apa pun, adalah wajar mengetahui tentang ciri pelaksanaan ini. Mari kita beralih ke bahagian seterusnya.

Perbandingan primitif sebenar

Sebagai permulaan, saya ingin bertanya satu soalan. Sangat ringkas. Apakah jumlah berikut – 0.3f + 0.4f? kenapa? 0.7f? Mari semak:
float f1 = 0.7f;
float f2 = 0.3f + 0.4f;
System.out.println("f1==f2: "+(f1==f2));
Akibatnya? Suka? Saya juga. Bagi mereka yang tidak melengkapkan serpihan ini, saya akan mengatakan bahawa hasilnya akan ...
f1==f2: false
Mengapa ini berlaku?.. Mari kita lakukan ujian lain:
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 penukaran kepada double. Ini dilakukan untuk menghasilkan lebih banyak tempat perpuluhan. Keputusan:
f1=0.30000001192092896
f2=0.4000000059604645
f3=0.7000000476837158
f4=0.699999988079071
Tegasnya, hasilnya boleh diramalkan. Perwakilan bahagian pecahan dijalankan menggunakan siri terhingga 2-n, dan oleh itu tidak perlu bercakap tentang perwakilan tepat nombor yang dipilih secara sewenang-wenangnya. Seperti yang dapat dilihat daripada contoh, ketepatan perwakilan floatialah 7 tempat perpuluhan. Tegasnya, perwakilan float memperuntukkan 24 bit kepada mantissa. Oleh itu, nombor mutlak minimum yang boleh diwakili menggunakan float (tanpa mengambil kira darjah, kerana kita bercakap tentang ketepatan) ialah 2-24≈6*10-8. Dengan langkah inilah nilai-nilai dalam perwakilan sebenarnya pergi float. Dan kerana terdapat kuantisasi, terdapat juga ralat. Oleh itu kesimpulannya: nombor dalam perwakilan floathanya boleh dibandingkan dengan ketepatan tertentu. Saya akan mengesyorkan membundarkannya ke tempat perpuluhan ke-6 (10-6), atau, sebaik-baiknya, semak nilai mutlak perbezaan antara mereka:
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 kes ini, hasilnya menggalakkan:
|f3-f4|<1e-6: true
Sudah tentu, gambar itu betul-betul sama dengan jenis double. Satu-satunya perbezaan ialah 53 bit diperuntukkan untuk mantissa, oleh itu, ketepatan perwakilan ialah 2-53≈10-16. Ya, nilai pengkuantitian jauh lebih kecil, tetapi ia ada. Dan ia boleh memainkan jenaka yang kejam. Ngomong-ngomong, dalam pustaka ujian JUnit , dalam kaedah untuk membandingkan nombor nyata, ketepatan dinyatakan secara eksplisit. Itu. kaedah perbandingan mengandungi tiga parameter - nombor, perkara yang sepatutnya sama dengannya, dan ketepatan perbandingan. Ngomong-ngomong, saya ingin menyebut kehalusan yang berkaitan dengan menulis nombor dalam format saintifik, yang menunjukkan ijazah. soalan. Bagaimana untuk menulis 10-6? Amalan menunjukkan bahawa lebih daripada 80% jawapan – 10e-6. Manakala, jawapan yang betul ialah 1e-6! Dan 10e-6 ialah 10-5! Kami memijak rake ini dalam salah satu projek, secara tidak dijangka. Mereka mencari kesilapan itu untuk masa yang sangat lama, melihat pemalar 20 kali. Dan tiada siapa yang mempunyai bayangan keraguan tentang ketepatannya, sehingga suatu hari, sebahagian besarnya secara tidak sengaja, pemalar 10e-3 dicetak dan mereka mendapati dua digit selepas titik perpuluhan dan bukannya tiga yang dijangkakan. Oleh itu, berhati-hatilah! Jom teruskan.

+0.0 dan -0.0

Dalam perwakilan nombor nyata, bit yang paling ketara ditandatangani. Apakah yang berlaku jika semua bit lain adalah 0? Tidak seperti integer, di mana dalam keadaan sedemikian hasilnya adalah nombor negatif yang terletak pada had bawah julat perwakilan, nombor nyata dengan hanya bit paling ketara ditetapkan kepada 1 juga bermakna 0, hanya dengan tanda tolak. Oleh itu, kita mempunyai dua sifar - +0.0 dan -0.0. Persoalan logik timbul: adakah nombor ini dianggap sama? Mesin maya berfikir dengan cara ini. Walau bagaimanapun, ini adalah dua nombor yang berbeza , kerana hasil daripada operasi dengan mereka, nilai yang berbeza diperoleh:
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 kes, masuk akal untuk menganggap +0.0 dan -0.0 sebagai dua nombor berbeza. Dan jika kita mempunyai dua objek, dalam satu daripadanya medannya ialah +0.0, dan dalam satu lagi -0.0, objek ini juga boleh dianggap sebagai tidak sama rata. Persoalannya timbul - bagaimana anda boleh memahami bahawa nombor adalah tidak sama jika perbandingan langsung mereka dengan mesin maya memberikan true? Jawapannya begini. Walaupun mesin maya menganggap nombor ini sama, perwakilannya masih berbeza. Oleh itu, satu-satunya perkara yang boleh dilakukan ialah membandingkan pandangan. Dan untuk mendapatkannya, terdapat kaedah int Float.floatToIntBits(float)dan long Double.doubleToLongBits(double), yang mengembalikan sedikit perwakilan dalam bentuk intdan longmasing-masing (sambungan 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 akan menjadi
i1 (+0.0):0
i2 (-0.0):10000000000000000000000000000000
i1==i2: false
Oleh itu, jika anda mempunyai +0.0 dan -0.0 adalah nombor yang berbeza, maka anda harus membandingkan pembolehubah sebenar melalui perwakilan bitnya. Kami nampaknya telah menyelesaikan +0.0 dan -0.0. -0.0, bagaimanapun, bukan satu-satunya kejutan. Terdapat juga perkara seperti...

nilai NaN

NaNbermaksud Not-a-Number. Nilai ini muncul sebagai hasil daripada operasi matematik yang salah, katakan, membahagikan 0.0 dengan 0.0, infiniti dengan infiniti, dsb. Keanehan nilai ini ialah ia tidak sama dengan dirinya sendiri. Mereka.:
float x = 0.0f/0.0f;
System.out.println("x="+x);
System.out.println("x==x: "+(x==x));
...akan terhasil...
x=NaN
x==x: false
Bagaimanakah ini boleh berlaku apabila membandingkan objek? Jika medan objek adalah sama dengan NaN, maka perbandingan akan memberi false, i.e. objek dijamin dianggap tidak sama rata. Walaupun, secara logiknya, kita mungkin mahu sebaliknya. Anda boleh mencapai hasil yang diingini menggunakan kaedah Float.isNaN(float). Ia kembali truejika hujahnya adalah NaN. Dalam kes ini, saya tidak akan bergantung pada membandingkan perwakilan bit, kerana ia tidak diseragamkan. Mungkin itu sudah cukup tentang primitif. Sekarang mari kita beralih kepada kehalusan yang telah muncul di Java sejak versi 5.0. Dan perkara pertama yang ingin saya sentuh ialah

Java 5.0. Menjana kaedah dan perbandingan melalui ' =='

Terdapat corak dalam reka bentuk yang dipanggil kaedah menghasilkan. Kadang-kadang penggunaannya jauh lebih menguntungkan daripada menggunakan pembina. Biar saya berikan satu contoh. Saya rasa saya tahu shell objek dengan baik Boolean. Kelas ini tidak boleh diubah dan boleh mengandungi hanya dua nilai. Iaitu, sebenarnya, untuk sebarang keperluan, hanya dua salinan sahaja yang mencukupi. Dan jika anda menciptanya terlebih dahulu dan kemudian hanya mengembalikannya, ia akan menjadi lebih pantas daripada menggunakan pembina. Ada kaedah sebegitu Boolean: valueOf(boolean). Ia muncul dalam versi 1.4. Kaedah penghasilan yang serupa telah diperkenalkan dalam versi 5.0 dalam kelas Byte, Character, Short, Integerdan Long. Apabila kelas ini dimuatkan, tatasusunan kejadian mereka dibuat sepadan dengan julat nilai primitif tertentu. Julat ini adalah seperti berikut:
Perbandingan objek: latihan - 2
Ini bermakna apabila menggunakan kaedah, valueOf(...)jika hujah berada dalam julat yang ditentukan, objek yang sama akan sentiasa dikembalikan. Mungkin ini memberikan sedikit peningkatan dalam kelajuan. Tetapi pada masa yang sama, masalah timbul sedemikian rupa sehingga agak sukar untuk menyelesaikannya. Baca lebih lanjut mengenainya. Secara teorinya, kaedah penghasilan valueOftelah ditambahkan pada kedua-dua Floatdan kelas Double. Penerangan mereka mengatakan bahawa jika anda tidak memerlukan salinan baru, maka lebih baik menggunakan kaedah ini, kerana ia boleh meningkatkan kelajuan, dsb. dan sebagainya. Walau bagaimanapun, dalam pelaksanaan semasa (Java 5.0), contoh baharu dicipta dalam kaedah ini, i.e. Penggunaannya tidak dijamin memberikan peningkatan kelajuan. Lebih-lebih lagi, sukar bagi saya untuk membayangkan bagaimana kaedah ini boleh dipercepatkan, kerana disebabkan kesinambungan nilai, cache tidak dapat diatur di sana. Kecuali integer. Maksud saya, tanpa bahagian pecahan.

Java 5.0. Autoboxing/Unboxing: ' ==', ' >=' dan ' <=' untuk pembalut objek.

Saya mengesyaki bahawa kaedah pengeluaran dan cache contoh telah ditambahkan pada pembungkus untuk primitif integer untuk mengoptimumkan operasi autoboxing/unboxing. Biar saya ingatkan anda apa itu. Jika objek mesti terlibat dalam operasi, tetapi primitif terlibat, maka primitif ini secara automatik dibalut dalam pembalut objek. ini autoboxing. Dan sebaliknya - jika primitif mesti terlibat dalam operasi, maka anda boleh menggantikan cangkang objek di sana, dan nilainya akan dikembangkan secara automatik daripadanya. ini unboxing. Sememangnya, anda perlu membayar untuk kemudahan sedemikian. Operasi penukaran automatik agak memperlahankan prestasi aplikasi. Walau bagaimanapun, ini tidak berkaitan dengan topik semasa, jadi mari tinggalkan soalan ini. Semuanya baik-baik saja selagi kita berurusan dengan operasi yang jelas berkaitan dengan primitif atau cengkerang. Apakah yang akan berlaku kepada ==operasi ''? Katakan kita mempunyai dua objek Integerdengan nilai yang sama di dalamnya. Bagaimana mereka akan membandingkan?
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1==i2: "+(i1==i2));
Keputusan:
i1==i2: false

Кто бы сомневался... Сравниваются они How an objectы. А если так:Integer i1 = 1;
Integer i2 = 1;
System.out.println("i1==i2: "+(i1==i2));
Keputusan:
i1==i2: true
Sekarang ini lebih menarik! Jika autoboxing-e objek yang sama dikembalikan! Di sinilah letaknya perangkap. Sebaik sahaja kami mendapati bahawa objek yang sama dikembalikan, kami akan mula mencuba untuk melihat sama ada ini selalu berlaku. Dan berapa banyak nilai yang akan kita periksa? satu? Sepuluh? Seratus? Kemungkinan besar kita akan mengehadkan diri kepada seratus dalam setiap arah sekitar sifar. Dan kita mendapat kesaksamaan di mana-mana. Nampaknya semuanya baik-baik saja. Walau bagaimanapun, lihat sedikit ke belakang, di sini . Pernahkah anda meneka apa tangkapan itu?.. Ya, contoh cengkerang objek semasa autoboxing dicipta menggunakan kaedah penghasilan. Ini digambarkan dengan baik oleh ujian 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 menjadi seperti ini:
number=-129: false
number=-128: true
number=127: true
number=128: false
Untuk nilai yang berada dalam julat caching , objek yang sama dikembalikan, bagi yang di luarnya, objek berbeza dikembalikan. Oleh itu, jika di suatu tempat dalam cangkerang aplikasi dibandingkan dan bukannya primitif, terdapat peluang untuk mendapatkan ralat yang paling dahsyat: yang terapung. Kerana kod kemungkinan besar juga akan diuji pada julat nilai yang terhad di mana ralat ini tidak akan muncul. Tetapi dalam kerja sebenar, ia sama ada akan muncul atau hilang, bergantung pada hasil beberapa pengiraan. Lebih mudah menjadi gila daripada mencari kesilapan seperti itu. Oleh itu, saya akan menasihati anda untuk mengelakkan autoboxing di mana mungkin. Dan bukan itu sahaja. Mari kita ingat matematik, tidak lebih daripada darjah 5. Biarkan ketidaksamaan A>=Bdan А<=B. Apa yang boleh dikatakan tentang hubungan Adan B? Hanya ada satu perkara - mereka sama. Adakah anda bersetuju? Saya rasa betul. Mari jalankan ujian:
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));
Keputusan:
i1>=i2: true
i1<=i2: true
i1==i2: false
Dan ini adalah perkara pelik terbesar bagi saya. Saya tidak faham sama sekali mengapa ciri ini diperkenalkan ke dalam bahasa jika ia memperkenalkan percanggahan sedemikian. Secara umum, saya akan mengulangi sekali lagi - jika boleh dilakukan tanpa autoboxing/unboxing, maka ia patut menggunakan peluang ini sepenuhnya. Topik terakhir yang saya ingin sentuh ialah... Java 5.0. perbandingan elemen enumerasi (jenis enum) Seperti yang anda ketahui, sejak versi 5.0 Java telah memperkenalkan jenis seperti enum - enumeration. Contohnya secara lalai mengandungi nama dan nombor urutan dalam perisytiharan contoh dalam kelas. Sehubungan itu, apabila pesanan pengumuman berubah, nombor berubah. Walau bagaimanapun, seperti yang saya katakan dalam artikel 'Serialization as it is' , ini tidak menimbulkan masalah. Semua elemen penghitungan wujud dalam satu salinan, ini dikawal pada peringkat mesin maya. Oleh itu, mereka boleh dibandingkan secara langsung, menggunakan pautan. * * * Mungkin itu sahaja untuk hari ini tentang sisi praktikal pelaksanaan perbandingan objek. Mungkin saya kehilangan sesuatu. Seperti biasa, saya menantikan komen anda! Buat masa ini, izinkan saya bercuti. Terima kasih semua atas perhatian anda! Pautan ke sumber: Membandingkan objek: latihan
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION