JavaRush /Java Blogu /Random-AZ /Obyektlərin müqayisəsi: təcrübə
articles
Səviyyə

Obyektlərin müqayisəsi: təcrübə

Qrupda dərc edilmişdir
Bu, obyektlərin müqayisəsinə həsr olunmuş məqalələrin ikincisidir. Onlardan birincisi müqayisənin nəzəri əsaslarını - bunun necə edildiyini, nə üçün və harada istifadə edildiyini müzakirə etdi. Bu yazıda rəqəmlərin, obyektlərin, xüsusi halların, incəliklərin və qeyri-aşkar məqamların müqayisəsi haqqında birbaşa danışacağıq. Daha dəqiq desək, bu barədə danışacağıq:
Obyektlərin müqayisəsi: təcrübə - 1
  • Simli müqayisə: ' ==' vəequals
  • MetodString.intern
  • Həqiqi primitivlərin müqayisəsi
  • +0.0-0.0
  • MənaNaN
  • Java 5.0. ==' ' vasitəsilə üsulların yaradılması və müqayisəsi
  • Java 5.0. Autoboxing/Unboxing: ' ==', ' >=' və ' <=' obyekt sarğıları üçün.
  • Java 5.0. enum elementlərinin müqayisəsi (növ enum)
Beləliklə, başlayaq!

Simli müqayisə: ' ==' vəequals

Ah, bu sətirlər... Ən çox istifadə olunan, çox problem yaradan növlərdən biridir. Prinsipcə, onlar haqqında ayrıca məqalə var . Burada isə müqayisə məsələlərinə toxunacağam. Əlbəttə ki, simləri istifadə edərək müqayisə etmək olar equals. Üstəlik, onlar vasitəsilə müqayisə edilməlidir equals. Bununla belə, bilməyə dəyər olan incəliklər var. Əvvəla, eyni sətirlər əslində tək bir obyektdir. Bunu aşağıdakı kodu işlətməklə asanlıqla yoxlamaq olar:
String str1 = "string";
String str2 = "string";
System.out.println(str1==str2 ? "the same" : "not the same");
Nəticə "eyni" olacaq . Bu o deməkdir ki, simli istinadlar bərabərdir. Bu, aydın şəkildə yaddaşa qənaət etmək üçün tərtibçi səviyyəsində edilir. Kompilyator sətrin BİR nüsxəsini yaradır və bu instansiyaya istinad str1təyin edir. str2Bununla belə, bu, yalnız kodda literal kimi elan edilmiş sətirlərə aiddir. Parçalardan bir sim tərtib etsəniz, ona keçid fərqli olacaq. Təsdiq - bu nümunə:
String str1 = "string";
String str2 = "str";
String str3 = "ing";
System.out.println(str1==(str2+str3) ? "the same" : "not the same");
Nəticə "eyni olmayacaq" . Kopya konstruktorundan istifadə edərək yeni obyekt də yarada bilərsiniz:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1==str2 ? "the same" : "not the same");
Nəticə də "eyni olmayacaq" . Beləliklə, bəzən sətirləri istinad müqayisəsi ilə müqayisə etmək olar. Ancaq buna güvənməmək daha yaxşıdır. Mən sətirin kanonik təsvirini əldə etməyə imkan verən çox maraqlı bir üsula toxunmaq istərdim - String.intern. Bu barədə daha ətraflı danışaq.

String.intern metodu

Sinfin simli hovuzu dəstəklədiyindən başlayaq String. Yalnız onlar deyil, siniflərdə müəyyən edilmiş bütün sətir literalları bu hovuza əlavə edilir. Beləliklə, metod internbu hovuzdan mövcud olana (metodun çağırıldığı intern) nöqteyi-nəzərindən bərabər olan sətir əldə etməyə imkan verir equals. Hovuzda belə bir sıra yoxdursa, mövcud olan orada yerləşdirilir və ona bir keçid qaytarılır. Beləliklə, iki bərabər sətirə istinadlar fərqli olsa belə (yuxarıdakı iki nümunədə olduğu kimi), bu sətirlərə edilən zənglər interneyni obyektə istinadı qaytaracaq:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1.intern()==str2.intern() ? "the same" : "not the same");
Bu kod parçasının icrasının nəticəsi "eyni" olacaq . Bunun niyə belə edildiyini dəqiq deyə bilmərəm. Metod internyerlidir və düzünü desəm, C kodunun vəhşiliyinə girmək istəmirəm. Çox güman ki, bu, yaddaş istehlakını və performansını optimallaşdırmaq üçün edilir. Hər halda, bu həyata keçirmə xüsusiyyəti haqqında bilməyə dəyər. Gəlin növbəti hissəyə keçək.

Həqiqi primitivlərin müqayisəsi

Başlamaq üçün bir sual vermək istəyirəm. Çox sadə. Aşağıdakı cəmi nədir - 0.3f + 0.4f? Niyə? 0.7f? yoxlayaq:
float f1 = 0.7f;
float f2 = 0.3f + 0.4f;
System.out.println("f1==f2: "+(f1==f2));
Nəticə olaraq? Kimi? Mən də həmçinin. Bu fraqmenti tamamlamayanlar üçün deyim ki, nəticə belə olacaq...
f1==f2: false
Bu niyə baş verir?.. Gəlin başqa bir sınaq keçirək:
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);
-a çevrilməsini qeyd edin double. Bu, daha çox onluq yer çıxarmaq üçün edilir. Nəticə:
f1=0.30000001192092896
f2=0.4000000059604645
f3=0.7000000476837158
f4=0.699999988079071
Düzünü desək, nəticə proqnozlaşdırıla bilər. Kəsr hissənin təsviri sonlu 2-n seriyasından istifadə etməklə həyata keçirilir və buna görə də ixtiyari seçilmiş ədədin dəqiq təsviri haqqında danışmağa ehtiyac yoxdur. Nümunədən göründüyü kimi, təmsil dəqiqliyi float7 onluq yerdir. Düzünü desək, nümayəndəlik float mantisaya 24 bit ayırır. float Beləliklə, istifadə etməklə (dərəcə nəzərə alınmadan, dəqiqlikdən bəhs etdiyimiz üçün) təmsil oluna bilən minimum mütləq ədəd 2-24≈6*10-8-dir. Məhz bu addımla təmsildəki dəyərlər əslində gedir float. Kvantlaşdırma olduğu üçün səhv də var. Nəticə budur: təsvirdəki rəqəmlər floatyalnız müəyyən bir dəqiqliklə müqayisə edilə bilər. Onları 6-cı onluq yerə (10-6) yuvarlaqlaşdırmağı və ya tercihen aralarındakı fərqin mütləq dəyərini yoxlamağı tövsiyə edərdim:
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 ));
Bu vəziyyətdə nəticə ümidvericidir:
|f3-f4|<1e-6: true
Əlbəttə ki, şəkil növü ilə tamamilə eynidir double. Yeganə fərq, mantis üçün 53 bitin ayrılmasıdır, buna görə də təmsil dəqiqliyi 2-53≈10-16-dır. Bəli, kvantlaşdırma dəyəri daha kiçikdir, lakin oradadır. Və qəddar bir zarafat oynaya bilər. Yeri gəlmişkən, JUnit test kitabxanasında həqiqi ədədlərin müqayisəsi üsullarında dəqiqlik açıq şəkildə göstərilmişdir. Bunlar. müqayisə üsulu üç parametrdən ibarətdir - rəqəm, nəyə bərabər olmalıdır və müqayisənin dəqiqliyi. Yeri gəlmişkən, dərəcəni göstərən rəqəmlərin elmi formatda yazılması ilə bağlı incəlikləri qeyd etmək istərdim. Sual. 10-6 necə yazılır? Təcrübə göstərir ki, 80%-dən çoxu cavab verir – 10e-6. Bu arada, düzgün cavab 1e-6-dır! Və 10e-6 10-5-dir! Layihələrdən birində gözlənilmədən bu dırmıqla addımladıq. Çox uzun müddət xətanı axtardılar, sabitlərə 20 dəfə baxdılar.Və heç kəsin onların düzgünlüyünə şübhəsi yox idi, bir gün, əsasən təsadüfən, sabit 10e-3 çap olunana qədər və ikisini tapdılar. gözlənilən üç əvəzinə onluq nöqtədən sonrakı rəqəmlər. Buna görə də diqqətli olun! Gəlin davam edək.

+0,0 və -0,0

Həqiqi ədədlərin təsvirində ən əhəmiyyətli bit işarələnir. Bütün digər bitlər 0 olarsa nə olar? Tam ədədlərdən fərqli olaraq, belə bir vəziyyətdə nəticə təmsil diapazonunun aşağı sərhəddində yerləşən mənfi ədəddir, yalnız ən əhəmiyyətli biti 1-ə təyin edilmiş həqiqi ədəd, yalnız mənfi işarəsi olan 0 deməkdir. Beləliklə, iki sıfırımız var - +0.0 və -0.0. Məntiqi sual yaranır: bu rəqəmləri bərabər hesab etmək lazımdırmı? Virtual maşın məhz belə düşünür. Ancaq bunlar iki fərqli rəqəmdir, çünki onlarla əməliyyatlar nəticəsində fərqli dəyərlər əldə edilir:
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);
... və nəticə:
f1=0.0
f2=-0.0
f1==f2: true
f3=Infinity
f4=-Infinity
Beləliklə, bəzi hallarda +0.0 və -0.0-a iki fərqli rəqəm kimi yanaşmağın mənası var. Birində sahə +0,0, digərində isə -0,0 olan iki obyektimiz varsa, bu obyektləri də qeyri-bərabər hesab etmək olar. Sual yaranır - virtual maşınla birbaşa müqayisə edildikdə rəqəmlərin qeyri-bərabər olduğunu necə başa düşmək olar true? Cavab budur. Virtual maşın bu rəqəmləri bərabər hesab etsə də, onların təmsilləri hələ də fərqlidir. Buna görə də edilə biləcək yeganə şey baxışları müqayisə etməkdir. Və onu əldə etmək üçün formada və müvafiq olaraq bir qədər təmsili qaytaran üsullar int Float.floatToIntBits(float)var (əvvəlki nümunənin davamı): long Double.doubleToLongBits(double)intlong
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));
Nəticə olacaq
i1 (+0.0):0
i2 (-0.0):10000000000000000000000000000000
i1==i2: false
Beləliklə, əgər sizdə +0.0 və -0.0 fərqli rəqəmlərdirsə, onda siz real dəyişənləri bit təsvirləri vasitəsilə müqayisə etməlisiniz. Deyəsən, +0.0 və -0.0 sıraladıq. -0,0 isə yeganə sürpriz deyil. Belə bir şey də var...

NaN dəyəri

NaNüçün dayanır Not-a-Number. Bu dəyər səhv riyazi əməliyyatlar nəticəsində yaranır, məsələn, 0,0-ın 0,0-a, sonsuzluğun sonsuza bölünməsi və s. Bu dəyərin özəlliyi ondadır ki, özünə bərabər deyil. Bunlar.:
float x = 0.0f/0.0f;
System.out.println("x="+x);
System.out.println("x==x: "+(x==x));
...nəticə verəcək...
x=NaN
x==x: false
Obyektləri müqayisə edərkən bu necə ola bilər? Obyektin sahəsi bərabərdirsə NaN, onda müqayisə verəcəkdir false, yəni. obyektlərin qeyri-bərabər hesab edilməsinə zəmanət verilir. Baxmayaraq ki, məntiqlə biz bunun əksini istəyə bilərik. Metoddan istifadə edərək istədiyiniz nəticəni əldə edə bilərsiniz Float.isNaN(float). trueArqument olarsa qaytarır NaN. Bu halda, mən bit təmsilçilərinin müqayisəsinə etibar etməzdim, çünki standartlaşdırılmayıb. Primitivlər haqqında bəlkə də bu kifayətdir. İndi keçək Java-da 5.0 versiyasından bəri ortaya çıxan incəliklərə. Və toxunmaq istədiyim ilk məqam budur

Java 5.0. ==' ' vasitəsilə üsulların yaradılması və müqayisəsi

Dizaynda istehsal üsulu deyilən bir nümunə var. Bəzən onun istifadəsi konstruktordan istifadə etməkdən daha sərfəlidir. Sizə bir misal deyim. Düşünürəm ki, obyekt qabığını yaxşı bilirəm Boolean. Bu sinif dəyişməzdir və yalnız iki dəyərdən ibarət ola bilər. Yəni, əslində, hər hansı bir ehtiyac üçün yalnız iki nüsxə kifayətdir. Əgər siz onları əvvəlcədən yaratsanız və sonra sadəcə geri qaytarsanız, bu, konstruktordan istifadə etməkdən daha sürətli olacaq. Belə bir üsul var Boolean: valueOf(boolean). 1.4 versiyasında ortaya çıxdı. ByteOxşar istehsal üsulları , Character, Short, Integervə siniflərində 5.0 versiyasında təqdim edilmişdir Long. Bu siniflər yükləndikdə, onların nümunələrinin ibtidai dəyərlərin müəyyən diapazonlarına uyğun massivləri yaradılır. Bu diapazonlar aşağıdakılardır:
Obyektlərin müqayisəsi: təcrübə - 2
Bu o deməkdir ki, metoddan istifadə edərkən, valueOf(...)arqument göstərilən diapazona düşərsə, həmişə eyni obyekt qaytarılacaqdır. Bəlkə də bu, sürətin bir qədər artmasına səbəb olur. Ancaq eyni zamanda, elə bir xarakterli problemlər yaranır ki, onun dibinə varmaq kifayət qədər çətin ola bilər. Bu barədə ətraflı oxuyun. Nəzəriyyə olaraq, istehsal üsulu həm siniflərə , həm də siniflərə valueOfəlavə edilmişdir . Onların təsvirində deyilir ki, yeni bir nüsxəyə ehtiyacınız yoxdursa, bu üsuldan istifadə etmək daha yaxşıdır, çünki sürət artımını verə bilər və s. və s. Bununla belə, cari (Java 5.0) tətbiqində bu metodda yeni instansiya yaradılır, yəni. Onun istifadəsi sürətin artmasına zəmanət verilmir. Üstəlik, bu metodun necə sürətləndirilə biləcəyini təsəvvür etmək mənim üçün çətindir, çünki dəyərlərin davamlılığı səbəbindən orada bir önbellek təşkil edilə bilməz. Tam ədədlər istisna olmaqla. Demək istəyirəm ki, kəsr hissəsi olmadan.FloatDouble

Java 5.0. Autoboxing/Unboxing: ' ==', ' >=' və ' <=' obyekt sarğıları üçün.

Mən əməliyyatları optimallaşdırmaq üçün istehsal üsulları və nümunə keşinin tam ədəd primitivləri üçün sarğılara əlavə olunduğundan şübhələnirəm autoboxing/unboxing. Bunun nə olduğunu xatırlatdım. Əgər obyekt əməliyyatda iştirak etməlidirsə, lakin primitiv iştirak edirsə, bu primitiv avtomatik olaraq obyekt sarğısına bükülür. Bu autoboxing. Və əksinə - əgər əməliyyatda primitiv iştirak etməlidirsə, onda siz orada bir obyekt qabığını əvəz edə bilərsiniz və dəyər avtomatik olaraq ondan genişlənəcəkdir. Bu unboxing. Təbii ki, belə rahatlıq üçün pul ödəməlisiniz. Avtomatik çevirmə əməliyyatları tətbiqi bir qədər ləngidir. Ancaq bu, cari mövzuya aid deyil, ona görə də bu sualı buraxaq. Primitivlər və ya qabıqlarla açıq şəkildə əlaqəli əməliyyatlarla məşğul olduğumuz müddətcə hər şey yaxşıdır. '' əməliyyatı nə olacaq ==? Tutaq ki, Integeriçərisində eyni dəyərə malik iki obyektimiz var. Necə müqayisə edəcəklər?
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1==i2: "+(i1==i2));
Nəticə:
i1==i2: false

Кто бы сомневался... Сравниваются они How an objectы. А если так:Integer i1 = 1;
Integer i2 = 1;
System.out.println("i1==i2: "+(i1==i2));
Nəticə:
i1==i2: true
İndi bu daha maraqlıdır! Əgər autoboxing-e eyni obyektlər qaytarılır! Tələ buradadır. Eyni obyektlərin geri qaytarıldığını aşkar etdikdən sonra bunun həmişə belə olub olmadığını yoxlamaq üçün sınaqlara başlayacağıq. Və neçə dəyəri yoxlayacağıq? Bir? On? Yüz? Çox güman ki, biz özümüzü sıfır ətrafında hər istiqamətdə yüzlərlə məhdudlaşdıracağıq. Və biz hər yerdə bərabərlik əldə edirik. Deyəsən hər şey qaydasındadır. Bununla belə, burada bir az geriyə baxın . Tutmağın nə olduğunu təxmin etdinizmi?.. Bəli, avtoboks zamanı obyekt qabıqlarının nümunələri istehsal metodlarından istifadə etməklə yaradılır. Bu, aşağıdakı testlə yaxşı təsvir edilmişdir:
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));
        }
    }
}
Nəticə belə olacaq:
number=-129: false
number=-128: true
number=127: true
number=128: false
Keshləmə diapazonuna düşən dəyərlər üçün eyni obyektlər, ondan kənarda olanlar üçün fərqli obyektlər qaytarılır. Və buna görə də, əgər tətbiqin bir yerində primitivlər əvəzinə qabıqlar müqayisə edilərsə, ən dəhşətli səhvi əldə etmək şansı var: üzən. Çünki kod çox güman ki, bu xətanın görünməyəcəyi məhdud dəyərlər aralığında da sınaqdan keçiriləcək. Amma real işdə bəzi hesablamaların nəticələrindən asılı olaraq ya görünəcək, ya da yox olacaq. Dəli olmaq belə bir səhv tapmaqdan daha asandır. Ona görə də sizə məsləhət görərdim ki, mümkün olan yerdə avtoboksdan qaçın. Və bu deyil. Riyaziyyatı xatırlayaq, 5-ci sinifdən artıq deyil. Qoy bərabərsizliklər A>=BА<=B. Münasibətlər haqqında nə demək olar AB? Yalnız bir şey var - onlar bərabərdirlər. Razısan? Məncə bəli. Testi keçirək:
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));
Nəticə:
i1>=i2: true
i1<=i2: true
i1==i2: false
Və bu mənim üçün ən böyük qəribəlikdir. Əgər belə ziddiyyətlər təqdim edirsə, bu xüsusiyyətin dilə niyə daxil edildiyini heç anlamıram. Ümumiyyətlə, bir daha təkrar edəcəyəm - onsuz etmək mümkündürsə autoboxing/unboxing, bu fürsətdən maksimum istifadə etməyə dəyər. Ən son toxunmaq istədiyim mövzu... Java 5.0. sadalanma elementlərinin müqayisəsi (enum növü) Bildiyiniz kimi, Java 5.0 versiyasından bəri enum - enumeration kimi bir növü təqdim etmişdir. Onun nümunələri standart olaraq sinifdəki misal bəyannaməsində ad və sıra nömrəsini ehtiva edir. Müvafiq olaraq, elan sırası dəyişdikdə nömrələr də dəyişir. Bununla belə, 'Serializasiya olduğu kimi' məqaləsində dediyim kimi , bu problem yaratmır. Bütün siyahı elementləri bir nüsxədə mövcuddur, bu virtual maşın səviyyəsində idarə olunur. Buna görə də, keçidlərdən istifadə edərək, onları birbaşa müqayisə etmək olar. * * * Obyekt müqayisəsini həyata keçirməyin praktik tərəfi haqqında bəlkə də bu gün üçün bunlardır. Bəlkə nəyisə qaçırdım. Həmişə olduğu kimi, şərhlərinizi gözləyirəm! Hələlik icazə verin məzuniyyətimi götürüm. Diqqətiniz üçün hamınıza təşəkkür edirik! Mənbəyə keçid: Obyektlərin müqayisəsi: təcrübə
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION