JavaRush /Java блогу /Random-KY /Объекттерди салыштыруу: практика
articles
Деңгээл

Объекттерди салыштыруу: практика

Группада жарыяланган
Бул an objectтерди салыштырууга арналган макалалардын экинчиси. Алардын биринчисинде салыштыруунун теориялык негиздери – ал кантип жасалат, эмне үчүн жана кайда колдонулат деген маселени талкуулашты. Бул макалада биз түздөн-түз сандарды, an objectилерди, өзгөчө учурларды, кылдаттыктарды жана ачык эмес пункттарды салыштыруу жөнүндө сүйлөшөбүз. Тагыраак айтканда, бул жерде биз эмне жөнүндө сүйлөшөбүз:
Объекттерди салыштыруу: практика - 1
  • Саптарды салыштыруу: ' ==' жанаequals
  • МетодString.intern
  • Чыныгы примитивдерди салыштыруу
  • +0.0Жана-0.0
  • МаанисиNaN
  • Java 5.0. Методдорду түзүү жана ' ==' аркылуу салыштыруу
  • Java 5.0. Autoboxing/Unboxing: ' ==', ' >=' ' жана ' <=' an objectтин орогучтары үчүн.
  • Java 5.0. enum элементтерин салыштыруу (түрү enum)
Ошентип, баштайлы!

Саптарды салыштыруу: ' ==' жанаequals

Аа, бул саптар... Көптөгөн көйгөйлөрдү жаратуучу эң көп колдонулган түрлөрүнүн бири. Негизи, алар жөнүндө өзүнчө макала бар . Ал эми бул жерде мен салыштыруу маселелерине токтолоюн. Албетте, саптарды колдонуу менен салыштырууга болот equals. Мындан тышкары, аларды салыштыруу КЕРЕК equals. Бирок, билүүгө арзырлык кылдат жактары бар. Биринчиден, окшош саптар чындыгында бир an object болуп саналат. Муну төмөнкү codeду иштетүү менен оңой эле текшерсе болот:
String str1 = "string";
String str2 = "string";
System.out.println(str1==str2 ? "the same" : "not the same");
Натыйжа "ошол эле" болот . Бул сап шилтемелери бирдей дегенди билдирет. Бул компилятор деңгээлинде жасалат, албетте, эстутумду сактоо үчүн. Компилятор саптын БИР нускасын түзөт жана бул инстанцияга шилтеме str1дайындайт . str2Бирок, бул codeдо литералдар катары жарыяланган саптарга гана тиешелүү. Эгерде сиз сапты бөлүктөрдөн түзсөңүз, ага шилтеме башка болот. Ырастоо - бул мисал:
String str1 = "string";
String str2 = "str";
String str3 = "ing";
System.out.println(str1==(str2+str3) ? "the same" : "not the same");
Натыйжа "бирдей эмес" болот . Сиз ошондой эле көчүрүү конструкторун колдонуп жаңы an object түзө аласыз:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1==str2 ? "the same" : "not the same");
Натыйжа да "ошол эмес" болот . Ошентип, кээде саптарды шилтеме салыштыруу аркылуу салыштырууга болот. Бирок буга таянбай эле койгон оң. Мен саптын канондук чагылдырылышын алууга мүмкүндүк берген бир абдан кызыктуу ыкмага токтолгум келет - String.intern. Бул тууралуу кененирээк сүйлөшөлү.

String.intern ыкмасы

StringКласс сап пулун колдогонунан баштайлы . Класстарда аныкталган бардык сап литералдары, алар гана эмес, бул бассейнге кошулат. Ошентип, ыкма internбул бассейнден бар болгонго (метод деп аталган intern) көз карашынан алганда барабар сапты алууга мүмкүндүк берет equals. Эгерде бассейнде мындай сап жок болсо, анда ал жерде бар жайгаштырылган жана ага шилтеме кайтарылып берилет. Ошентип, эки бирдей сапка шилтемелер ар башка болсо да (жогоруда айтылган эки мисалдагыдай), анда бул саптарга чалуулар internбир эле an objectке шилтемени кайтарат:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1.intern()==str2.intern() ? "the same" : "not the same");
Бул codeдун аткарылышынын натыйжасы "ошол эле" болот . Эмне үчүн минтип жасалганын так айта албайм. Метод internжергorктүү жана чынын айтсам, мен C codeунун жапайы жерлерине киргим келбейт. Кыязы, бул эстутум керектөөсүн жана иштешин оптималдаштыруу үчүн жасалат. Кандай болбосун, бул ишке ашыруу өзгөчөлүгү жөнүндө билүү керек. Келгиле, кийинки бөлүккө өтөбүз.

Чыныгы примитивдерди салыштыруу

Баштоо үчүн мен суроо бергим келет. Абдан жөнөкөй. Төмөнкү сумма кандай – 0,3f + 0,4f? Неге? 0.7f? текшерип көрөлү:
float f1 = 0.7f;
float f2 = 0.3f + 0.4f;
System.out.println("f1==f2: "+(f1==f2));
Натыйжада? Like? Мага дагы. Бул үзүндүнү аягына чыгара албагандар үчүн айтаарым, жыйынтыгы...
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 бит бөлүп берет. Ошентип, колдонуу менен берorши мүмкүн болгон минималдуу абсолюттук сан float (даражаны эсепке албастан, анткени тактык жөнүндө сөз болуп жатат) 2-24≈6*10-8. Дал ушул кадам менен өкүлчүлүктөгү баалуулуктар иш жүзүндө кетет float. Жана кванттоо бар болгондуктан, ката да бар. Демек, тыянак: өкүлчүлүктөгү сандарды floatбелгилүү бир тактык менен гана салыштырууга болот. Мен аларды 6-ондук орунга (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 тест китепканасында реалдуу сандарды салыштыруу ыкмаларында тактык так көрсөтүлгөн. Ошол. салыштыруу ыкмасы үч параметрди камтыйт - сан, ал эмнеге барабар болушу керек жана салыштыруунун тактыгы. Баса, даражаны көрсөтүү менен сандарды orмий форматта жазууга байланыштуу кылдаттыктарды айткым келет. Суроо. 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 эки башка сан катары кароонун мааниси бар. Ал эми бизде эки an object болсо, алардын биринде талаа +0,0, экинчисинде -0,0 болсо, бул an objectтерди да бирдей эмес деп кароого болот. Суроо туулат - виртуалдык машина менен түз салыштыруу берсе, сандар бирдей эмес экенин кантип түшүнүүгө болот true? Жооп бул. Виртуалдык машина бул сандарды бирдей деп эсептесе да, алардын өкүлчүлүктөрү дагы эле ар башка. Ошондуктан, көз караштарды салыштыруу гана мүмкүн. Жана аны алуу үчүн формада жана тиешелүү түрдө бир аз өкүлчүлүктү кайтарган методдор бар int Float.floatToIntBits(float)жана (мурунку мисалдын уландысы): 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));
Натыйжа болот
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
Объекттерди салыштырганда бул кандай болушу мүмкүн? Эгерде an objectтин талаасы барабар болсо NaN, анда салыштыруу берет false, б.а. an objectилер тец эмес деп эсеп-телет. Бирок, логикалык жактан алганда, биз тескерисинче болушу мүмкүн. Методдун жардамы менен сиз каалаган натыйжага жете аласыз Float.isNaN(float). trueЭгерде аргумент болсо кайтарат NaN. Бул учурда, мен бит өкүлчүлүктөрүн салыштырууга таянbyte элем, анткени ал стандартташтырылган эмес. Балким, бул примитивдер жөнүндө жетиштүү. Келгиле, эми Java-да 5.0 versionсынан бери пайда болгон кылдаттыктарга өтөбүз. Ал эми мен токтолгум келген биринчи жагдай

Java 5.0. Методдорду түзүү жана ' ==' аркылуу салыштыруу

Дизайнда өндүрүш ыкмасы деп аталган үлгү бар. Кээде аны колдонуу конструкторду колдонууга караганда алда канча пайдалуу. Мен бир мисал келтирейин. Мен an objectтин кабыгын жакшы билем деп ойлойм Boolean. Бул класс өзгөрүлгүс жана эки гана маанини камтышы мүмкүн. Башкача айтканда, ар кандай муктаждыктар үчүн, эки гана нускасы жетиштүү. Ал эми аларды алдын ала түзүп, анан жөн эле кайтарып берсеңиз, бул конструкторду колдонууга караганда алда канча тезирээк болот. Мындай ыкма бар Boolean: valueOf(boolean). Ал 1.4 versionсында пайда болгон. Окшош өндүрүш ыкмалары 5.0 versionсында Byte, Character, Short, Integerжана класстарда киргизилген Long. Бул класстар жүктөлгөндө, алардын инстанцияларынын массивдери примитивдик маанилердин белгилүү диапазондоруна ылайыктуу түзүлөт. Бул диапазондор төмөнкүдөй:
Объекттерди салыштыруу: практика - 2
Бул ыкманы колдонууда, valueOf(...)аргумент көрсөтүлгөн диапазонго туура келсе, ошол эле an object дайыма кайтарылат дегенди билдирет. Балким, бул ылдамдыкты бир аз жогорулатат. Бирок, ошол эле учурда, анын түбүнө жетүү үчүн абдан кыйын болушу мүмкүн, ошондой мүнөздөгү көйгөйлөр пайда болот. Бул тууралуу көбүрөөк оку. Теорияда өндүрүш ыкмасы класстарга да valueOfкошулган . Алардын сыпаттамасы сизге жаңы көчүрмөнүн кереги жок болсо, анда бул ыкманы колдонуу жакшы болот, анткени ал ылдамдыктын жогорулашын бере алат ж.б. жана башка. Бирок, учурдагы (Java 5.0) ишке ашырууда бул ыкмада жаңы инстанция түзүлөт, б.а. Аны колдонуу ылдамдыгын жогорулатууга кепилдик жок. Анын үстүнө, мен үчүн бул ыкманы кантип тездетүүнү элестетүү кыйын, анткени баалуулуктардын үзгүлтүксүздүгүнө байланыштуу ал жерде кэш уюштуруу мүмкүн эмес. Бүтүн сандардан башкасы. Дегеним, бөлчөк бөлүгү жок.FloatDouble

Java 5.0. Autoboxing/Unboxing: ' ==', ' >=' ' жана ' <=' an objectтин орогучтары үчүн.

Операцияларды оптималдаштыруу үчүн бүтүн примитивдер үчүн орогучтарга өндүрүш ыкмалары жана инстанциялардын кэши кошулган деп ойлойм autoboxing/unboxing. Бул эмне экенин эскертип коёюн. Эгерде an object операцияга тартылышы керек болсо, бирок примитив тартылса, анда бул примитив автоматтык түрдө an objectтин оромосуна оролот. Бул autoboxing. Жана тескерисинче - эгерде операцияга примитив тартылышы керек болсо, анда сиз ал жерде an objectтин кабыгын алмаштырсаңыз болот жана маани андан автоматтык түрдө кеңейтилет. Бул unboxing. Албетте, мындай ыңгайлуулук үчүн акы төлөшүңүз керек. Автоматтык түрдө конversionлоо операциялары колдонмону бир аз жайлатат. Бирок, бул учурдагы темага тиешеси жок, ошондуктан бул суроону калтыралы. Примитивдерге же снаряддарга так байланышкан операциялар менен алектенсек, баары жакшы. '' операциясы эмне болот ==? IntegerИчинде бирдей маанидеги эки an object бар дейли . Алар кантип салыштырышат?
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 ошол эле an objectтер кайтарылса! Тузак ушул жерде жатат. Ошол эле an objectтер кайтарылганын байкаганыбыздан кийин, биз бул дайыма эле ушундай болобу же жокпу, эксперимент жасай баштайбыз. Жана канча баалуулуктарды текшеребиз? Бир? Он? Бир жүз? Кыязы, биз нөлдүн тегерегиндеги ар бир багытта жүзгө чейин чектейбиз. Ал эми биз бардык жерде теңдикти алабыз. Баары жакшы болуп жаткандай сезилет. Бирок, бир аз артка карагыла, бул жерде . Сиз кармаган нерсе эмне экенин билдиңизби?.. Ооба, автобокс учурунда an objectтин кабыкчаларынын инстанциялары өндүрүш ыкмаларын колдонуу менен түзүлөт. Бул төмөнкү сыноо менен жакшы сүрөттөлгөн:
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
Кэштөө диапазонуна түшкөн маанилер үчүн бирдей an objectтер кайтарылат, анын сыртындагылар үчүн ар кандай an objectтер кайтарылат. Демек, эгерде примитивдердин ордуна тиркеменин бир жеринде кабыкчалар салыштырылса, эң коркунучтуу катаны алуу мүмкүнчүлүгү бар: калкыма. Анткени code, балким, бул ката көрүнбөй турган чектелүү диапазондо да текшерилет. Ал эми реалдуу иште кандайдыр бир эсептөөлөрдүн жыйынтыгына жараша пайда болот же жок болот. Мындай катаны тапкандан көрө жинди болуу оңой. Ошондуктан, мүмкүн болушунча автобоксингден алыс болууга кеңеш берет элем. А бул эмес. Математиканы эстейли, 5-класстан ашпай. Теңсиздиктер болсун 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, анда бул мүмкүнчүлүктү толугу менен пайдалануу керек. Мен токтолгум келген акыркы тема... Java 5.0. санап чыгуу элементтерин салыштыруу (энум түрү) Белгилүү болгондой, Java 5.0 versionсынан бери enum - enumeration сыяктуу түрү киргизилген. Анын инстанциялары демейки боюнча класстагы инстанция декларациясынын аталышын жана катар номерин камтыйт. Демек, кулактандыруу тартиби өзгөргөндө, сандар да өзгөрөт. Бирок, мен макалада айткандай , "Кандай болсо да, сериялаштыруу" , бул көйгөй жаратпайт. Бардык эсепке алуу элементтери бир нускада бар, бул виртуалдык машина деңгээлинде көзөмөлдөнөт. Ошондуктан, алар шилтемелерди колдонуу менен, түздөн-түз салыштырууга болот. * * * Объектилерди салыштырууну ишке ашыруунун практикалык жагы жөнүндө бүгүнкү күндө бардыгы ушул. Балким, мен бир нерсени сагындым. Адаттагыдай эле, мен сиздин комментарийлериңизди күтөм! Азырынча өргүүгө уруксат бериңиз. Көңүл бурганыңыздар үчүн рахмат! Булакка шилтеме: Объекттерди салыштыруу: практика
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION