JavaRush /Java blogi /Random-UZ /Java-da solishtiruvchi
Viacheslav
Daraja

Java-da solishtiruvchi

Guruhda nashr etilgan
Java-da Comparator va taqqoslash haqida faqat dangasalar yozmagan. Men dangasa emasman - shuning uchun sizdan yana bir variantni sevishingizni va ma'qullashingizni so'rayman. Umid qilamanki, bu ortiqcha bo'lmaydi. Va ha, bu maqola savolga javob: "Siz xotiradan taqqoslash yoza olasizmi?" Umid qilamanki, ushbu maqolani o'qib chiqqandan so'ng, hamma xotiradan taqqoslash yozish imkoniyatiga ega bo'ladi.
Java-da solishtiruvchi - 1
Kirish Java tili ob'ektga yo'naltirilgan til ekanligi ma'lum. Natijada, Java-da ob'ektlar bilan ishlash odatiy holdir. Ammo ertami-kechmi ob'ektlarni qandaydir printsip bo'yicha taqqoslash vazifasi paydo bo'ladi. Shunday qilib, berilgan: Bizda Message sinfi tomonidan tasvirlangan ba'zi xabarlar mavjud:
public static class Message {
    private String message;
    private int id;

    public Message(String message) {
        this.message = message;
        this.id = new Random().nextInt(1000);
    }
    public String getMessage() {
        return message;
    }
    public Integer getId() {
        return id;
    }
    public String toString() {
        return "[" + id + "] " + message;
    }
}
Keling, ushbu sinfni Tutorialspoint java kompilyatoriga qo'shamiz . Importni qo'shishni ham eslaylik:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Asosiy usulda biz bir nechta xabarlarni yaratamiz:
public static void main(String[] args){
    List<Message> messages = new ArrayList();
    messages.add(new Message("Hello, World!"));
    messages.add(new Message("Hello, Sun!"));
    System.out.println(messages);
}
Keling, ularni solishtirmoqchi bo'lsak, nima qilishimiz kerakligini o'ylab ko'raylik? Misol uchun, biz id bo'yicha tartiblashni xohlaymiz. Va tartibni yaratish uchun qaysi ob'ekt oldingi (ya'ni kichikroq) va qaysi biri keyingi (ya'ni kattaroq) ekanligini tushunish uchun ob'ektlarni qandaydir tarzda solishtirish kerak. Keling, java.lang.Object kabi sinfdan boshlaylik . Ma'lumki, barcha sinflar ushbu Ob'ekt sinfidan bevosita meros qilib olinadi. Va bu mantiqiy, chunki Bu mohiyatan kontseptsiyani ifodalaydi: "Hamma narsa ob'ektdir" va barcha sinflar uchun umumiy xatti-harakatni ta'minlaydi. Va bu sinf har bir sinfning ikkita usuli borligini belgilaydi: → hashCode hashCode usuli sinfning namunasi sifatida ob'ektning ba'zi sonli (int) ko'rinishini qaytaradi. Bu nima degani? Bu shuni anglatadiki, agar siz sinfning ikki xil nusxasini yaratgan bo'lsangiz, unda misollar boshqacha bo'lgani uchun ularning hashCode ham boshqacha bo'lishi kerak. Usul tavsifida shunday deyilgan: "Ob'ekt sinfi tomonidan aniqlangan hashCode usuli qanchalik amaliy bo'lsa, har xil ob'ektlar uchun alohida butun sonlarni qaytaradi" Ya'ni, agar bu ikki xil misol bo'lsa, ular boshqacha bo'lishi kerak. hashCodes. Ya'ni, bu usul bizning taqqoslashimiz uchun mos emas. → tengdir Tenglar usuli "ob'ektlar tengmi" degan savolga javob beradi va mantiqiy qiymatni qaytaradi. Ushbu usul standart kodga ega:
public boolean equals(Object obj) {
    return (this == obj);
}
Ya'ni, ob'ektda ushbu usulni bekor qilmasdan, bu usul asosan ob'ektga havolalar mos keladimi yoki yo'qligini aytadi. Bu bizning xabarlarimiz uchun mos emas, chunki bizni ob'ektga havolalar qiziqtirmaydi, bizni xabar identifikatori qiziqtiradi. Va agar biz tenglik usulini bekor qilsak ham, biz oladigan maksimal miqdor: "Ular teng" yoki "Ular teng emas". Ammo tartibni aniqlash uchun bu etarli emas.

Java-da taqqoslash va solishtirish

Bizga nima mos keladi? Agar tarjimonda "qiyoslash" so'zini ingliz tiliga tarjima qilsak, biz "taqqoslash" tarjimasini olamiz. Ajoyib, keyin bizga solishtiradigan odam kerak. Agar siz bu taqqoslashni solishtirsangiz, unda solishtiruvchi solishtiruvchidir. Keling, Java Api-ni ochamiz va u erda Comparator ni topamiz . Va haqiqatan ham shunday interfeys mavjud - java.util.Comparator java.util.Comparator va java.lang.Comparable Ko'rib turganingizdek, shunday interfeys mavjud. Uni amalga oshiradigan sinf "Men ob'ektlarni taqqoslash funktsiyasini amalga oshiryapman" deydi. Haqiqatan ham eslash kerak bo'lgan yagona narsa bu taqqoslash shartnomasi bo'lib, u quyidagicha ifodalanadi:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
Endi solishtiruvchini yozamiz. Biz java.util.Comparator ni import qilishimiz kerak . Importdan so'ng, main ga usul qo'shing: Comparator<Message> comparator = new Comparator<Message>(); Tabiiyki, bu ishlamaydi, chunki Comparator - bu interfeys. Shuning uchun, qavslardan keyin biz jingalaklarni qo'shamiz { }. Ushbu qavslarga biz usulni yozamiz:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Buni yozishni eslash ham shart emas. Taqqoslovchi - qiyoslovchi, ya'ni solishtiruvchi. Taqqoslangan ob'ektlar qanday tartibda joylashganligi haqidagi savolga javob berish uchun biz int ni qaytaramiz. Hammasi shu, aslida. Oddiy va oson. Misoldan ko'rinib turibdiki, Comparator-dan tashqari yana bir interfeys mavjud - java.lang.Comparable , uni amalga oshirishda biz compareTo usulini aniqlashimiz kerak . Ushbu interfeysda aytilishicha, "Interfeysni amalga oshiradigan sinf sinf misollarini solishtirishga imkon beradi". Misol uchun, Integerning compareTo ilovasi quyidagicha ko'rinadi:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Bu barcha interfeyslarni qanday eslab qolish kerak? Nima sababdan? Hammasi ingliz tilidan keladi. Taqqoslash - solishtirish, solishtiruvchi solishtiruvchi (masalan, registrator sifatida. Ya'ni, ro'yxatdan o'tkazuvchi), "qiyoslangan" sifatdoshi esa Comparator. Xo'sh, "Solishtiring" so'zi nafaqat solishtirish, balki solishtirish deb ham tarjima qilingan. Hammasi oddiy. Java tilini ingliz tilida so'zlashuvchi odamlar yozgan va Java-da hamma narsani nomlashda ular oddiygina ingliz tilidan foydalanganlar va nomlashda qandaydir mantiq bor edi. Va compareTo usuli sinf misolini boshqa misollar bilan qanday solishtirish kerakligini tavsiflaydi. Masalan, satrlar leksigrafik jihatdan , raqamlar esa qiymat bo'yicha taqqoslanadi.
Java-da taqqoslash - 2
Java 8 ba'zi yaxshi o'zgarishlarni olib keldi. Agar biz Comparator interfeysiga diqqat bilan qarasak, uning ustida izoh borligini ko'ramiz @FunctionalInterface. Aslida, bu izoh ma'lumot uchun va bu interfeys funktsional ekanligini anglatadi. Bu shuni anglatadiki, ushbu interfeys faqatgina 1 ta mavhum usulga ega. Bu bizga nima beradi? Endi taqqoslash kodini quyidagicha yozishimiz mumkin:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Qavslar ichida o'zgaruvchilarni qanday nomlashimiz ko'rsatilgan. Javaning o'zi buni ko'radi, chunki ... Agar faqat bitta usul mavjud bo'lsa, unda qanday kirish parametrlari kerakligi, qancha va qanday turlari aniq bo'ladi. Keyinchalik, biz o'q bilan ularni kodning ushbu bo'limiga o'tkazmoqchi ekanligimizni aytamiz. Bundan tashqari, Java 8 tufayli interfeyslarda standart usullar paydo bo'ldi - bu biz interfeysni amalga oshirganimizda sukut bo'yicha (sukut bo'yicha) paydo bo'ladigan usullardir. Comparator interfeysida ulardan bir nechtasi mavjud. Masalan:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Kodingizni tozalaydigan yana bir usul mavjud. Keling, yuqoridagi misolni ko'rib chiqaylik, bu erda biz taqqoslagichimizni tasvirlab berdik. U nima qilyapti? Bu juda primitiv. U shunchaki ob'ektni oladi va undan solishtirish mumkin bo'lgan qiymatni chiqaradi. Misol uchun, Integer taqqoslanadigan ilovalarni amalga oshiradi, shuning uchun biz xabar identifikatori qiymatlari bo'yicha solishtirishni amalga oshira oldik. Bu oddiy taqqoslash funksiyasi ham shunday yozilishi mumkin:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Ya'ni, so'zma-so'z: "Bizda shunday taqqoslaydigan Comparator mavjud: u ob'ektlarni oladi, getId() usuli yordamida ulardan Comparable oladi, compareTo yordamida taqqoslaydi." Va boshqa dahshatli dizaynlar yo'q. Va nihoyat, yana bir xususiyatni ta'kidlamoqchiman. Taqqoslovchilarni bir-biriga bog'lash mumkin. Masalan:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Ilova

Taqqoslash deklaratsiyasi juda mantiqiy bo'lib chiqdi, shunday emasmi? Endi biz uni qanday va qaysi joylarda ishlatishni ko'rishimiz kerak. → Collections.sort (java.util.Collections) Albatta, biz kollektsiyalarni shu tarzda saralashimiz mumkin. Lekin hamma narsa emas, faqat ro'yxatlar. Va bu erda g'ayrioddiy narsa yo'q, chunki ... Bu indeks bo'yicha elementga kirishni talab qiladigan ro'yxat. Va bu ikkinchi raqamli elementni uchinchi element bilan almashtirish imkonini beradi. Shuning uchun, bu tarzda saralash faqat ro'yxatlar uchun mumkin:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) Massivlarni saralash ham qulay. Shunga qaramay, elementlarga indeks bo'yicha kirish uchun xuddi shu sababga ko'ra. → Java.util.SortedSet va java.util.SortedMap ning avlodlari Esda tutganimizdek, Set va Map yozuvlarni saqlash tartibini kafolatlamaydi. LEKIN bizda tartibni kafolatlaydigan maxsus dasturlar mavjud. Va agar to'plam elementlari java.lang.Comparable-ni amalga oshirmasa, biz Comparatorni bunday to'plamlarning konstruktoriga o'tkazishimiz mumkin:
Set<Message> msgSet = new TreeSet(comparator);
Stream API Java 8 da paydo bo'lgan Stream Api-da komparator oqim elementlari ustida ishlashni soddalashtirish imkonini beradi. Masalan, bizga 0 dan 999 gacha tasodifiy sonlar ketma-ketligi kerak:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Biz to'xtashimiz mumkin edi, ammo qiziqarliroq muammolar bor. Misol uchun, siz Xaritani tayyorlashingiz kerak, bu erda kalit xabar identifikatoridir. Shu bilan birga, biz bu kalitlarni shunday tartiblashni xohlaymizki, kalitlar eng kichikdan kattagacha tartibda bo'ladi. Keling, ushbu kod bilan boshlaylik:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Bu erda biz qaytib keladigan narsa aslida HashMap. Va biz bilganimizdek, u hech qanday buyurtmani kafolatlamaydi. Shu sababli, ID bo'yicha tartiblangan yozuvlarimiz shunchaki ishdan chiqdi. Yaxshi emas. Kollektorimizni biroz o'zgartirishimiz kerak:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
Kod biroz dahshatliroq ko'rinardi, ammo TreeMapning aniq amalga oshirilishi tufayli muammo endi to'g'ri hal qilindi. Turli guruhlar haqida ko'proq ma'lumotni bu erda o'qishingiz mumkin: Kollektorni o'zingiz yaratishingiz mumkin. Bu yerda ko'proq o'qishingiz mumkin: "Java 8 da maxsus kollektor yaratish" . Va bu erda muhokamani o'qish foydalidir: "Oqim bilan xaritalash uchun Java 8 ro'yxati" .
Java-da taqqoslash - 3
Comparator va Comparable rakes yaxshi. Ammo ular bilan bog'liq bo'lgan bitta nuance bor, uni eslash kerak. Sinf saralashni amalga oshirganda, u sizning sinfingizni Comparable ga o'tkazishi mumkinligini hisoblab chiqadi. Agar bunday bo'lmasa, siz bajarish vaqtida xatolik olasiz. Keling, bir misolni ko'rib chiqaylik:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Bu erda hech qanday yomon narsa yo'qdek tuyuladi. Lekin, aslida, bizning misolimizda, u xato bilan qulab tushadi: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable Va barchasi elementlarni saralashga harakat qilgani uchun (Axir bu SortedSet). Va men qila olmadim. SortedMap va SortedSet bilan ishlashda buni eslab qolishingiz kerak. Qo'shimcha ravishda ko'rish uchun tavsiya etiladi: Yuriy Tkach: HashSet va TreeSet - To'plamlar №1 - Kengaytirilgan Java
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION