JavaRush /Java блогу /Random-KY /Java тилиндеги компаратор
Viacheslav
Деңгээл

Java тилиндеги компаратор

Группада жарыяланган
Жалкоо адамдар гана Java тorнде Comparator жана салыштыруу жөнүндө жазган эмес. Мен жалкоо эмесмин, ошондуктан мен сизден дагы бир вариацияны сүйүп, жактырууну суранам. Бул ашыкча болбойт деп үмүттөнөм. Ооба, бул макала: "Сиз эс тутумдан салыштыруучу жаза аласызбы?" Бул макаланы окугандан кийин ар бир адам эсинден салыштыруучу жаза алат деп ишенем.
Java тorндеги компаратор - 1
Киришүү Java тor an objectиге багытталган тил экендиги белгилүү. Натыйжада, Javaда an objectтер менен иштөө кеңири таралган. Бирок эртеби-кечпи кандайдыр бир принцип боюнча an objectилерди салыштыруу милдети келип чыгат. Ошентип, берилген: Бизде Message классы сүрөттөгөн кээ бир билдирүүлөр бар:
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;
    }
}
Келгиле, бул классты Tutorialspoint java компиляторуна кошолу . Ошондой эле импортту кошууну унутпайлы:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Негизги ыкмада биз бир нече билдирүүлөрдү түзөбүз:
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);
}
Келгиле, ойлонуп көрөлү, эгерде биз аларды салыштыргыбыз келсе, эмне кылышыбыз керек? Мисалы, биз id боюнча сорттоону каалайбыз. Ал эми тартипти түзүү үчүн, кайсы an object мурунку (башкача айтканда, кичине) жана кийинки (башкача айтканда, чоңураак) экенин түшүнүү үчүн an objectтерди кандайдыр бир жол менен салыштыруу керек. Java.lang.Object сыяктуу класстан баштайлы . Белгилүү болгондой, бардык класстар бул Object классынан кыйыр түрдө мураска алышат. Жана бул логикалуу, анткени Бул «бардыгы an object» деген түшүнүктү билдирет жана бардык класстар үчүн жалпы жүрүм-турумду камсыз кылат. Жана бул класс ар бир класстын эки ыкмасы бар экенин аныктайт: → hashCode hashCode ыкмасы класстын мисалы катары an objectтин кээ бир сандык (int) өкүлчүлүгүн кайтарат. Бул эмнени билдирет? Бул класстын эки башка инстанциясын түзсөңүз, инстанциялар башка болгондуктан, алардын hashCode башка болушу керек дегенди билдирет. Бул ыкманын сүрөттөмөсүндө мындай деп айтылат: "Объект классы тарабынан аныкталган hashCode ыкмасы канчалык практикалык болсо да, ар башка an objectтер үчүн ар башка бүтүн сандарды кайтарат" Башкача айтканда, эгерде булар эки башка инстанция болсо, анда алар ар кандай болушу керек. hashCodes. Башкача айтканда, бул ыкма биздин салыштыруу үчүн ылайыктуу эмес. → барабар Теңдөө ыкмасы “an objectтер бирдейби” деген суроого жооп берет жана логикалык маанини берет. Бул ыкманын демейки codeу бар:
public boolean equals(Object obj) {
    return (this == obj);
}
Башкача айтканда, an objectте бул ыкманы жокко чыгарбастан, бул ыкма an objectиге шилтемелер дал келээрин же дал келбей турганын айтат. Бул биздин билдирүүлөрүбүз үчүн ылайыктуу эмес, анткени биз an objectке шилтемелерге кызыкпайбыз, биз билдирүү идентификаторуна кызыгабыз. Эгерде биз барабар ыкмасын жокко чыгарсак дагы, биз ала турган максимум: "Алар бирдей" же "Алар бирдей эмес." Бирок бул бизге тартипти аныктоо үчүн аздык кылат.

Java тorнде салыштыруучу жана салыштырылуучу

Бизге эмне жагат? Котормочуда “салыштыруу” деген сөздү англис тorне которсок, “салыштыруу” котормосун алабыз. Жакшы, анда бизге салыштыра турган адам керек. Эгер сиз бул салыштырууну салыштырсаңыз, анда салыштыруучу Салыштыруучу болот. Келгиле, Java Api ачып, ал жерден Comparator табалы . Чынында эле, мындай интерфейс бар - java.util.Comparator java.util.Comparator жана java.lang.Comparable Көрүнүп тургандай, мындай интерфейс бар. Аны ишке ашырган класс "Мен an objectтерди салыштыруу функциясын ишке ашырып жатам" дейт. Чынында эстен чыгарбоо керек болгон бир гана нерсе - бул салыштыруу келишими, ал төмөнкүчө чагылдырылган:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
Эми салыштыруучуну жазалы. Биз java.util.Comparator импорттообуз керек болот . Импорттоодон кийин, main ыкмасына бир ыкманы кошуңуз: Comparator<Message> comparator = new Comparator<Message>(); Албетте, бул иштебейт, анткени Comparator - бул интерфейс. Ошондуктан, кашаадан кийин тармалдарды кошобуз { }. Бул кашааларга биз ыкманы жазабыз:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Муну жазууну унутпашыңыз керек. Салыштыргыч - салыштыруу жүргүзгөн, б.а. салыштыруучу. Салыштырылган an objectтер кандай тартипте турат деген суроого жооп берүү үчүн, биз int кайтарабыз. Чындыгында, баары ушул. Жөнөкөй жана оңой. Мисалдан көрүнүп тургандай, Comparatorдон тышкары дагы бир интерфейс бар - java.lang.Comparable , аны ишке ашырууда биз compareTo ыкмасын аныкташыбыз керек . Бул интерфейсте "Интерфейсти ишке ашырган класс класстын мисалдарын салыштырууга мүмкүндүк берет" деп айтылат. Мисалы, Integerдин compareTo ишке ашыруусу төмөнкүдөй көрүнөт:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Бул интерфейстердин баарын кантип эстеп калуу керек? Эмне үчүн? Баары англис тorнен келет. Салыштыруу - салыштыруу, салыштыруучу - Салыштырма (мисалы, каттоочу катары. Башкача айтканда, каттоочу), ал эми "салыштыруу" сын атооч - Салыштырмалуу. Ооба, "Салыштыруу менен" салыштыруу менен гана эмес, салыштыруу менен да которулат. Баары оңой. Java тorн англис тилдүү адамдар жазган жана Java тorнде бардык нерсени атоодо алар жөн гана англис тorн жетектеген жана ат коюуда кандайдыр бир логика болгон. Жана compareTo методу класстын инстанциясын башка инстанциялар менен кантип салыштыруу керектигин сүрөттөйт. Мисалы, саптар лексиграфиялык жактан , ал эми сандар мааниси боюнча салыштырылат.
Java тorндеги компаратор - 2
Java 8 жакшы өзгөрүүлөрдү алып келди. Comparator интерфейсине кылдаттык менен карасак, анын үстүндө annotation бар экенин көрөбүз @FunctionalInterface. Чынында, бул annotation маалымат үчүн жана бул интерфейстин иштешин билдирет. Бул бул интерфейстин 1 гана абстракттуу ыкмасы бар экенин билдирет. Бул бизге эмне берет? Эми салыштыруучу codeду төмөнкүдөй жаза алабыз:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Кашанын ичинде өзгөрмөлөрдү кантип атайбыз. Java өзү муну көрөт, анткени ... Эгерде бир гана ыкма болсо, анда кандай киргизүү параметрлери, канча жана кандай түрлөрү керек экендиги түшүнүктүү. Андан кийин, биз жебе менен аларды codeдун бул бөлүмүнө өткөргүбүз келет деп айтабыз. Кошумчалай кетсек, Java 8дин аркасында интерфейстерде демейки ыкмалар пайда болгон - бул интерфейсти ишке ашырууда демейки боюнча (демейки боюнча) пайда болгон ыкмалар. Comparator интерфейсинде булардын бир нечеси бар. Мисалы:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Кодуңузду таза кыла турган дагы бир ыкма бар. Келгиле, жогорудагы мисалды карап көрөлү, анда биз салыштыруучубузду сүрөттөгөнбүз. Ал эмне кылып жатат? Бул абдан примитивдүү. Ал жөн гана an objectти алат жана андан салыштырууга боло турган кандайдыр бир маанини чыгарат. Мисалы, Integer салыштырылуучуну ишке ашырат, ошондуктан биз билдирүү id маанилери боюнча салыштыруу жүргүзө алдык. Бул жөнөкөй компаратор функциясын төмөнкүчө жазса болот:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Башкача айтканда, түзмө-түз: "Бизде мындай салыштыруучу Салыштыруучу бар: ал an objectтерди алат, алардан getId() ыкмасын колдонуу менен Салыштырууну алат, compareTo аркылуу салыштырат." Мындан ары коркунучтуу дизайн жок. Акырында дагы бир өзгөчөлүгүн белгилегим келет. Салыштыргычтарды чынжырча менен бириктирсе болот. Мисалы:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Колдонмо

Салыштыруучу декларация абдан логикалык болуп чыкты, туурабы? Эми аны кантип, кайсы жерлерде колдонуш керек экенин карап чыгышыбыз керек. → Collections.sort (java.util.Collections) Албетте, биз коллекцияларды ушинтип сорттосок болот. Бирок баары эмес, жөн гана тизмелер. Жана бул жерде адаттан тыш эч нерсе жок, анткени... Бул индекс боюнча элементке кирүүнү талап кылган тизме. Бул экинчи элементти үчүнчү элемент менен алмаштырууга мүмкүндүк берет. Демек, мындай иреттөө тизмелер үчүн гана мүмкүн:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) Массивдерди сорттоого да ыңгайлуу. Дагы эле, индекс боюнча элементтерге жетүү үчүн ошол эле себептен. → java.util.SortedSet жана java.util.SortedMap урпактары эсибизде тургандай, Set жана Map жазууларды сактоо тартибине кепилдик бербейт. БИРОК бизде тартипти кепилдеген атайын ишке ашыруулар бар. Ал эми коллекциянын элементтери java.lang.Comparable ишке ашырбаса, анда биз Comparatorду мындай коллекциялардын конструкторуна өткөрүп алабыз:
Set<Message> msgSet = new TreeSet(comparator);
Stream API Java 8де пайда болгон Stream Apiде компаратор агым элементтериндеги ишти жөнөкөйлөтүүгө мүмкүндүк берет. Мисалы, бизге 0дөн 999га чейинки кокустук сандардын ырааттуулугу керек:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Биз токтотсок болмок, бирок андан да кызыктуу көйгөйлөр бар. Мисалы, сиз Картаны даярдашыңыз керек, анда ачкыч билдирүү идентификатору болуп саналат. Ошол эле учурда, биз бул ачкычтарды эң кичинеден чоңго чейин иретте тургандай кылып иргегибиз келет. Бул code менен баштайлы:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Бул жерде биз кайра ала турган нерсе чындыгында HashMap. Жана биз билгендей, эч кандай тартипке кепилдик бербейт. Ошондуктан, ID боюнча сорттолгон жазууларыбыз жөн эле иштен чыкты. Жакшы эмес. Коллекторубузду бир аз өзгөртүүгө туура келет:
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));
Код бир аз сойлоп көрүндү, бирок TreeMapтын ачык-айкын ишке ашырылышынын аркасында көйгөй азыр туура чечилди. Ар кандай топтор тууралуу кененирээк бул жерден окуй аласыз: Коллекторду өзүңүз түзө аласыз. Сиз бул жерден көбүрөөк окуй аласыз: "Java 8де ыңгайлаштырылган коллекторду түзүү" . Жана бул жерде талкууну окуу пайдалуу: "Java 8 тизмеси агым менен картага түзүлөт" .
Java тorндеги компаратор - 3
Comparator жана Comparable тырмоо жакшы. Бирок алар менен байланышкан бир нюанс бар, аны эстен чыгарбоо керек. Класс сорттоо жүргүзгөндө, ал классыңызды Салыштырууга чыгара аларын эсептейт. Эгер андай болбосо, сиз аткаруу учурунда ката аласыз. Келгиле, бир мисал карап көрөлү:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Бул жерде эч кандай ката жок окшойт. Бирок, чындыгында, биздин мисалда, ал ката менен кыйроого учурайт: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable Жана баары элементтерди сорттоого аракет кылгандыктан (Бул SortedSet, баары бир). А мен кыла албадым. SortedMap жана SortedSet менен иштөөдө муну эстен чыгарбашыңыз керек. Кошумча көрүү үчүн сунушталган: Юрий Ткач: HashSet жана TreeSet - Коллекциялар №1 - Өркүндөтүлгөн Java
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION