JavaRush /وبلاگ جاوا /Random-FA /مقایسه اشیاء: تمرین
articles
مرحله

مقایسه اشیاء: تمرین

در گروه منتشر شد
این دومین مقاله ای است که به مقایسه اشیاء اختصاص دارد. اولین آنها در مورد مبنای نظری مقایسه بحث کردند - چگونه انجام می شود، چرا و کجا استفاده می شود. در این مقاله به طور مستقیم در مورد مقایسه اعداد، اشیا، موارد خاص، ظرافت ها و نکات غیر مشهود صحبت خواهیم کرد. به طور دقیق تر، در اینجا چیزی است که ما در مورد آن صحبت خواهیم کرد:
مقایسه اشیاء: تمرین - 1
  • مقایسه رشته: ' ==' وequals
  • روشString.intern
  • مقایسه اولیه های واقعی
  • +0.0و-0.0
  • معنیNaN
  • جاوا 5.0. روش های تولید و مقایسه از طریق " =="
  • جاوا 5.0. جعبه‌گشایی/جعبه‌گشایی خودکار: « ==»، « >=» و « <=» برای بسته‌بندی‌های شی.
  • جاوا 5.0. مقایسه عناصر enum (نوع enum)
پس بیایید شروع کنیم!

مقایسه رشته: ' ==' وequals

آخه این خطوط ... یکی از پرکاربردترین انواعی که مشکلات زیادی ایجاد میکنه. در اصل، یک مقاله جداگانه در مورد آنها وجود دارد . و در اینجا به مسائل مقایسه می پردازم. البته می توان رشته ها را با استفاده از equals. علاوه بر این، آنها باید از طریق مقایسه شوند equals. با این حال، نکات ظریفی وجود دارد که ارزش دانستن دارد. اول از همه، رشته های یکسان در واقع یک شی واحد هستند. این را می توان به راحتی با اجرای کد زیر تأیید کرد:
String str1 = "string";
String str2 = "string";
System.out.println(str1==str2 ? "the same" : "not the same");
نتیجه "یکسان" خواهد بود . یعنی ارجاعات رشته برابر است. این کار در سطح کامپایلر انجام می شود، بدیهی است برای ذخیره حافظه. کامپایلر یک نمونه از رشته را ایجاد می کند و str1یک str2مرجع به این نمونه اختصاص می دهد. با این حال، این فقط در مورد رشته‌هایی که در کد به صورت لفظی اعلام شده‌اند صدق می‌کند. اگر رشته ای را از قطعات بسازید، پیوند به آن متفاوت خواهد بود. تایید - این مثال:
String str1 = "string";
String str2 = "str";
String str3 = "ing";
System.out.println(str1==(str2+str3) ? "the same" : "not the same");
نتیجه "یکسان نیست" خواهد بود . همچنین می توانید با استفاده از سازنده کپی یک شی جدید ایجاد کنید:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1==str2 ? "the same" : "not the same");
نتیجه نیز "یکسان نیست" خواهد بود . بنابراین، گاهی اوقات می توان رشته ها را از طریق مقایسه مرجع مقایسه کرد. اما بهتر است به این موضوع اعتماد نکنید. من می خواهم یک روش بسیار جالب را لمس کنم که به شما امکان می دهد به اصطلاح نمایش متعارف یک رشته - String.intern. بیایید در مورد آن با جزئیات بیشتر صحبت کنیم.

روش String.Intern

بیایید با این واقعیت شروع کنیم که کلاس از Stringاستخر رشته ای پشتیبانی می کند. تمام حروف رشته ای تعریف شده در کلاس ها، و نه تنها آنها، به این مجموعه اضافه می شوند. بنابراین، این متد internبه شما امکان می‌دهد از این Pool رشته‌ای دریافت کنید که internاز دیدگاه equals. اگر چنین ردیفی در استخر وجود نداشته باشد، ردیف موجود در آنجا قرار می گیرد و پیوندی به آن برگردانده می شود. بنابراین، حتی اگر ارجاعات به دو رشته مساوی متفاوت باشد (مانند دو مثال بالا)، در این صورت فراخوانی به این رشته ها internیک مرجع را به همان شی برمی گرداند:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1.intern()==str2.intern() ? "the same" : "not the same");
نتیجه اجرای این قطعه کد "یکسان" خواهد بود . نمی توانم دقیقاً بگویم چرا این کار را انجام داد. روش internبومی است، و صادقانه بگویم، نمی‌خواهم وارد طبیعت کد C شوم. به احتمال زیاد این کار برای بهینه سازی مصرف و عملکرد حافظه انجام می شود. در هر صورت، ارزش دانستن این ویژگی پیاده سازی را دارد. بریم سراغ قسمت بعدی.

مقایسه اولیه های واقعی

برای شروع، می خواهم یک سوال بپرسم. بسیار ساده. مجموع زیر چند است - 0.3f + 0.4f؟ چرا؟ 0.7f بیایید بررسی کنیم:
float f1 = 0.7f;
float f2 = 0.3f + 0.4f;
System.out.println("f1==f2: "+(f1==f2));
در نتیجه؟ پسندیدن؟ من هم همینطور. برای کسانی که این قطعه را کامل نکرده اند، می گویم که نتیجه این خواهد بود ...
f1==f2: false
چرا این اتفاق می افتد؟.. بیایید آزمایش دیگری انجام دهیم:
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);
به تبدیل به double. این کار به منظور خروجی ارقام اعشاری بیشتر انجام می شود. نتیجه:
f1=0.30000001192092896
f2=0.4000000059604645
f3=0.7000000476837158
f4=0.699999988079071
به طور دقیق، نتیجه قابل پیش بینی است. نمایش بخش کسری با استفاده از یک سری محدود 2-n انجام می شود و بنابراین نیازی به صحبت در مورد نمایش دقیق یک عدد دلخواه انتخاب شده نیست. همانطور که از مثال مشخص است، دقت نمایش float7 رقم اعشار است. به طور دقیق، نمایش float 24 بیت را به مانتیس اختصاص می دهد. بنابراین، حداقل عدد مطلقی که می توان با استفاده از آن نشان داد float (بدون در نظر گرفتن درجه، زیرا ما در مورد دقت صحبت می کنیم) 2-24≈6*10-8 است. با این مرحله است که مقادیر موجود در نمایش در واقع از بین می روند float. و چون کوانتیزاسیون وجود دارد، خطا نیز وجود دارد. از این رو نتیجه گیری: اعداد در یک نمایش را floatفقط با دقت خاصی می توان مقایسه کرد. من توصیه می کنم آنها را به رقم ششم اعشار گرد کنید (10-6)، یا ترجیحاً قدر مطلق تفاوت بین آنها را بررسی کنید:
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 ));
در این مورد، نتیجه دلگرم کننده است:
|f3-f4|<1e-6: true
البته تصویر دقیقاً مشابه نوع است double. تنها تفاوت این است که 53 بیت برای آخوندک تخصیص داده شده است، بنابراین، دقت نمایش 2-53≈10-16 است. بله، مقدار کوانتیزاسیون بسیار کوچکتر است، اما وجود دارد. و می تواند یک شوخی بی رحمانه بازی کند. به هر حال، در کتابخانه آزمون JUnit ، در روش های مقایسه اعداد واقعی، دقت به صراحت مشخص شده است. آن ها روش مقایسه شامل سه پارامتر است - تعداد، چه چیزی باید برابر باشد و دقت مقایسه. ضمناً می خواهم به ظرافت های مربوط به نوشتن اعداد در قالب علمی اشاره کنم که نشان دهنده مدرک است. سوال چگونه 10-6 بنویسیم؟ تمرین نشان می دهد که بیش از 80٪ پاسخ - 10e-6. در ضمن پاسخ صحیح 1e-6 است! و 10e-6 10-5 است! ما در یکی از پروژه ها به طور کاملا غیر منتظره روی این چنگک زدیم. آنها برای مدت طولانی به دنبال خطا بودند، 20 بار به ثابت ها نگاه کردند و هیچ کس در صحت آنها تردیدی نداشت، تا اینکه یک روز، عمدتاً به طور تصادفی، ثابت 10e-3 چاپ شد و دو مورد را پیدا کردند. ارقام بعد از نقطه اعشار به جای سه مورد انتظار. بنابراین، مراقب باشید! بیایید ادامه دهیم.

+0.0 و -0.0

در نمایش اعداد واقعی، مهم ترین بیت علامت گذاری شده است. اگر همه بیت های دیگر 0 باشند چه اتفاقی می افتد؟ بر خلاف اعداد صحیح، که در چنین شرایطی، نتیجه یک عدد منفی است که در حد پایین محدوده نمایش قرار دارد، یک عدد واقعی که فقط مهم‌ترین بیت آن روی 1 تنظیم شده است نیز به معنای 0 است، فقط با علامت منفی. بنابراین، ما دو صفر داریم - 0.0 + و -0.0. یک سوال منطقی مطرح می شود: آیا این اعداد باید برابر در نظر گرفته شوند؟ ماشین مجازی دقیقاً اینگونه فکر می کند. با این حال، این دو عدد متفاوت هستند ، زیرا در نتیجه عملیات با آنها، مقادیر متفاوتی به دست می آید:
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);
... و نتیجه:
f1=0.0
f2=-0.0
f1==f2: true
f3=Infinity
f4=-Infinity
بنابراین در برخی موارد منطقی است که +0.0 و -0.0 را به عنوان دو عدد متفاوت در نظر بگیریم. و اگر دو شی داشته باشیم که در یکی از آنها فیلد +0.0 و در دیگری -0.0 است، این اشیا نیز می توانند نابرابر در نظر گرفته شوند. این سوال مطرح می شود - اگر مقایسه مستقیم آنها با یک ماشین مجازی نشان دهد چگونه می توانید بفهمید که اعداد نابرابر هستند true؟ جواب این است. حتی اگر ماشین مجازی این اعداد را مساوی در نظر بگیرد، بازنمودهای آنها هنوز متفاوت است. بنابراین، تنها کاری که می توان انجام داد، مقایسه دیدگاه ها است. و برای به دست آوردن آن، متدهای int Float.floatToIntBits(float)و وجود دارد long Double.doubleToLongBits(double)که به ترتیب یک نمایش بیت را به شکل intو longبه ترتیب برمی گرداند (ادامه مثال قبل):
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));
نتیجه خواهد شد
i1 (+0.0):0
i2 (-0.0):10000000000000000000000000000000
i1==i2: false
بنابراین، اگر +0.0 دارید و -0.0 اعداد متفاوتی هستند، باید متغیرهای واقعی را از طریق نمایش بیت آنها مقایسه کنید. به نظر می رسد که ما 0.0 و -0.0 را مرتب کرده ایم. با این حال -0.0 تنها شگفتی نیست. همچنین چیزی وجود دارد که ...

مقدار NaN

NaNمخفف Not-a-Number. این مقدار در نتیجه عملیات ریاضی نادرست ظاهر می شود، مثلاً تقسیم 0.0 بر 0.0، بی نهایت بر بی نهایت و غیره. ویژگی این ارزش این است که با خودش برابر نیست. آن ها.:
float x = 0.0f/0.0f;
System.out.println("x="+x);
System.out.println("x==x: "+(x==x));
... نتیجه خواهد داد ...
x=NaN
x==x: false
چگونه می توان این را در هنگام مقایسه اشیاء نشان داد؟ اگر میدان شی برابر باشد NaN، مقایسه به دست می‌دهد false، یعنی. تضمین می شود که اشیا نابرابر در نظر گرفته شوند. اگرچه، منطقاً، ممکن است دقیقاً برعکس آن را بخواهیم. با استفاده از روش می توانید به نتیجه دلخواه برسید Float.isNaN(float). trueاگر آرگومان باشد برمی گردد NaN. در این مورد، من به مقایسه نمایش های بیت تکیه نمی کنم، زیرا استاندارد نیست شاید در مورد بدوی ها همین کافی باشد. بیایید اکنون به نکات ظریفی برویم که از نسخه 5.0 در جاوا ظاهر شده است. و اولین نکته ای که می خواهم به آن اشاره کنم این است

جاوا 5.0. روش های تولید و مقایسه از طریق " =="

الگویی در طراحی به نام روش تولید وجود دارد. گاهی اوقات استفاده از آن بسیار سودمندتر از استفاده از سازنده است. بگذارید برای شما مثالی بزنم. فکر می کنم پوسته شی را خوب می شناسم Boolean. این کلاس تغییرناپذیر است و فقط دو مقدار می تواند داشته باشد. یعنی در واقع برای هر نیازی فقط دو نسخه کافی است. و اگر آنها را از قبل ایجاد کنید و سپس به سادگی آنها را برگردانید، بسیار سریعتر از استفاده از سازنده خواهد بود. چنین روشی وجود دارد Boolean: valueOf(boolean). در نسخه 1.4 ظاهر شد. Byteروش های تولید مشابه در نسخه 5.0 در کلاس های , Character, Short, Integerو معرفی شد Long. هنگامی که این کلاس ها بارگذاری می شوند، آرایه هایی از نمونه های آنها مطابق با محدوده خاصی از مقادیر اولیه ایجاد می شود. این محدوده ها به شرح زیر است:
مقایسه اشیاء: تمرین - 2
به این معنی که هنگام استفاده از متد، valueOf(...)اگر آرگومان در محدوده مشخص شده قرار گیرد، همیشه همان شیء برگردانده می شود. شاید این باعث افزایش سرعت شود. اما در عین حال، مشکلاتی به گونه‌ای به وجود می‌آیند که رسیدن به ته آن بسیار دشوار است. در مورد آن بیشتر بخوانید. در تئوری، روش تولید به هر دو کلاس و valueOfاضافه شده است . توضیحات آنها می گوید که اگر به نسخه جدیدی نیاز ندارید، بهتر است از این روش استفاده کنید، زیرا می تواند باعث افزایش سرعت و غیره شود. و غیره با این حال، در اجرای فعلی (جاوا 5.0)، یک نمونه جدید در این روش ایجاد می شود، یعنی. استفاده از آن تضمینی برای افزایش سرعت نیست. علاوه بر این، تصور اینکه چگونه می توان این روش را تسریع کرد برای من دشوار است، زیرا به دلیل تداوم مقادیر، یک کش نمی تواند در آنجا سازماندهی شود. به جز اعداد صحیح منظورم بدون قسمت کسری است.FloatDouble

جاوا 5.0. جعبه‌گشایی/جعبه‌گشایی خودکار: « ==»، « >=» و « <=» برای بسته‌بندی‌های شی.

من گمان می‌کنم که روش‌های تولید و کش نمونه به wrapper‌های اعداد اولیه برای بهینه‌سازی عملیات اضافه شده است autoboxing/unboxing. بگذارید به شما یادآوری کنم که چیست. اگر یک شی باید در یک عملیات درگیر باشد، اما یک چیز ابتدایی درگیر باشد، آنگاه این اولیه به طور خودکار در یک شیء بسته بندی می شود. این autoboxing. و برعکس - اگر یک اولیه باید در این عملیات دخالت داشته باشد، می توانید یک پوسته شی را در آنجا جایگزین کنید و مقدار به طور خودکار از آن گسترش می یابد. این unboxing. طبیعتاً برای چنین راحتی باید هزینه پرداخت کنید. عملیات تبدیل خودکار برنامه را تا حدودی کند می کند. با این حال، این به موضوع فعلی مربوط نیست، بنابراین اجازه دهید این سوال را ترک کنیم. همه چیز خوب است تا زمانی که ما با عملیات هایی سروکار داریم که به وضوح به مواد اولیه یا پوسته مربوط می شوند. چه اتفاقی برای ==عملیات "" خواهد افتاد؟ Integerفرض کنید دو شی با مقدار یکسان در داخل داریم . چگونه مقایسه خواهند کرد؟
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1==i2: "+(i1==i2));
نتیجه:
i1==i2: false

Кто бы сомневался... Сравниваются они How an objectы. А если так:Integer i1 = 1;
Integer i2 = 1;
System.out.println("i1==i2: "+(i1==i2));
نتیجه:
i1==i2: true
حالا این جالب تر است! اگر autoboxing-e همان اشیاء برگردانده شوند! تله اینجاست. هنگامی که متوجه شدیم که همان اشیاء برگردانده شده اند، آزمایش را شروع می کنیم تا ببینیم آیا همیشه اینطور است یا خیر. و چند مقدار را بررسی خواهیم کرد؟ یکی؟ ده؟ یکصد؟ به احتمال زیاد ما خود را به صد در هر جهت در حدود صفر محدود خواهیم کرد. و ما در همه جا برابر هستیم. به نظر می رسد که همه چیز خوب است. با این حال، کمی به عقب نگاه کنید، اینجا . آیا حدس زده اید که گیر چیست؟.. بله، نمونه هایی از پوسته های جسم در حین اتوباکسینگ با استفاده از روش های تولید ایجاد می شوند. این به خوبی با آزمون زیر نشان داده شده است:
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));
        }
    }
}
نتیجه به این صورت خواهد بود:
number=-129: false
number=-128: true
number=127: true
number=128: false
برای مقادیری که در محدوده ذخیره سازی قرار می گیرند ، اشیاء یکسان برگردانده می شوند، برای مقادیر خارج از آن، اشیاء مختلف برگردانده می شوند. و بنابراین، اگر در جایی در برنامه به جای موارد اولیه، پوسته ها مقایسه شوند، احتمال دریافت وحشتناک ترین خطا وجود دارد: یک خطای شناور. زیرا به احتمال زیاد کد روی محدوده محدودی از مقادیر نیز آزمایش می شود که در آنها این خطا ظاهر نمی شود. اما در کار واقعی، بسته به نتایج برخی از محاسبات، یا ظاهر می شود یا ناپدید می شود. دیوانه شدن آسانتر از یافتن چنین اشتباهی است. بنابراین، من به شما توصیه می کنم تا حد امکان از اتوباکس پرهیز کنید. و این نیست. بیایید ریاضیات را به یاد بیاوریم، نه بیشتر از کلاس پنجم. اجازه دهید نابرابری ها A>=Bو А<=B. Aدر مورد رابطه چه می توان گفت B؟ تنها یک چیز وجود دارد - آنها برابر هستند. موافقید؟ فکر می کنم بله. بیایید تست را اجرا کنیم:
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));
نتیجه:
i1>=i2: true
i1<=i2: true
i1==i2: false
و این بزرگترین چیز عجیب برای من است. من اصلاً نمی‌فهمم که چرا این ویژگی وارد زبان شده است، اگر چنین تناقضاتی را ایجاد کند. به طور کلی، یک بار دیگر تکرار می کنم - اگر می توان بدون آن انجام داد autoboxing/unboxing، پس ارزش آن را دارد که از این فرصت نهایت استفاده را بکنید. آخرین موضوعی که می خواهم به آن بپردازم ... جاوا 5.0 است. مقایسه عناصر enumeration (نوع enum) همانطور که می دانید از نسخه 5.0 جاوا نوعی را به نام enum - enumeration معرفی کرده است. نمونه‌های آن به‌طور پیش‌فرض حاوی نام و شماره ترتیب در اعلان نمونه در کلاس هستند. بر این اساس، زمانی که دستور آگهی تغییر می کند، اعداد نیز تغییر می کنند. با این حال، همانطور که در مقاله "سریال سازی همانطور که هست" گفتم ، این مشکلی ایجاد نمی کند. همه عناصر شمارش در یک نسخه وجود دارند، این در سطح ماشین مجازی کنترل می شود. بنابراین، می توان آنها را به طور مستقیم با استفاده از پیوندها مقایسه کرد. * * * شاید همه اینها برای امروز در مورد جنبه عملی اجرای مقایسه شیء باشد. شاید من چیزی را از دست داده ام. مثل همیشه منتظر نظرات شما هستم! فعلا بذار مرخصی بگیرم با تشکر از همه شما برای توجه شما! پیوند به منبع: مقایسه اشیاء: تمرین
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION