CodeGym /בלוג Java /Random-HE /ממשק Comparator של Java
John Squirrels
רָמָה
San Francisco

ממשק Comparator של Java

פורסם בקבוצה
העצלנים הם לא היחידים שכותבים על Comparators והשוואות בג'אווה. אני לא עצלן, אז בבקשה תאהבו ותתערבו על עוד הסבר. אני מקווה שזה לא יהיה מיותר. וכן, מאמר זה הוא התשובה לשאלה: " האם אתה יכול לכתוב משווה מהזיכרון? " אני מקווה שכולם יוכלו לכתוב משווה מהזיכרון לאחר קריאת המאמר הזה. ממשק Javas Comparator - 1

מבוא

כפי שאתה יודע, ג'אווה היא שפה מונחה עצמים. כתוצאה מכך, נהוג לתפעל אובייקטים ב-Java. אבל במוקדם או במאוחר, אתה עומד בפני המשימה של השוואת אובייקטים על סמך מאפיין כלשהו. לדוגמה : נניח שיש לנו הודעה שתוארה על ידי 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;
    }
}
שים את המחלקה הזו במהדר Java של Tutorialspoint . אל תשכח להוסיף גם את הצהרות הייבוא:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
בשיטה main, צור מספר הודעות:
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. וכדי ליצור סדר, צריך איכשהו להשוות בין האובייקטים כדי להבין איזה אובייקט צריך לבוא קודם (כלומר הקטן) ואיזה צריך לעקוב אחריו (כלומר הגדול יותר). נתחיל עם מחלקה כמו java.lang.Object . אנו יודעים שכל המעמדות יורשים את Objectהמעמד באופן מרומז. וזה הגיוני כי זה משקף את התפיסה ש"הכל הוא אובייקט" ומספק התנהגות משותפת לכל הכיתות. מחלקה זו קובעת שלכל מחלקה יש שתי שיטות: → hashCode השיטה hashCodeמחזירה intייצוג מספרי ( ) כלשהו של האובייקט. מה זה אומר? זה אומר שאם אתה יוצר שני מופעים שונים של מחלקה, אז הם צריכים להיות hashCodes שונים. תיאור השיטה אומר לא פחות: "ככל שהיא מעשית באופן סביר, שיטת ה-hashCode המוגדרת על ידי Class Object מחזירה מספרים שלמים נפרדים עבור אובייקטים נפרדים". במילים אחרות, עבור שתי instances שונות, צריכות להיות hashCodes שונות. כלומר, שיטה זו אינה מתאימה להשוואה שלנו. → equals. השיטה equalsעונה על השאלה "האם החפצים הללו שווים?" ומחזירה boolean." כברירת מחדל, לשיטה זו יש את הקוד הבא:
public boolean equals(Object obj) {
    return (this == obj);
}
כלומר, אם שיטה זו אינה מבוטלת, היא בעצם אומרת אם הפניות לאובייקט תואמות או לא. זה לא מה שאנחנו רוצים עבור ההודעות שלנו, כי אנחנו מעוניינים במזהי הודעות, לא בהפניות לאובייקטים. וגם אם נתגבר על equalsהשיטה, הכי הרבה שאנחנו יכולים לקוות הוא ללמוד אם הם שווים. וזה לא מספיק לנו כדי לקבוע את הסדר. אז מה אנחנו צריכים אז? אנחנו צריכים משהו שישווה. מי שמשווה הוא א Comparator. פתח את Java API ומצא Comparator . אכן, יש java.util.Comparatorממשק java.util.Comparator and java.util.Comparable כפי שאתה יכול לראות, ממשק כזה קיים. מחלקה שמיישמת את זה אומרת: "אני מיישם שיטה שמשווה אובייקטים." הדבר היחיד שאתה באמת צריך לזכור הוא חוזה ההשוואה, שמתבטא באופן הבא:

Comparator returns an int according to the following rules: 
  • It returns a negative int if the first object is smaller
  • It returns a positive int if the first object is larger
  • It returns zero if the objects are equal
עכשיו בואו נכתוב משווה. נצטרך לייבא java.util.Comparator. לאחר הצהרת הייבוא, הוסף את הדברים הבאים לשיטה main: Comparator<Message> comparator = new Comparator<Message>(); כמובן, זה לא יעבוד, כי Comparatorזה ממשק. אז אנחנו מוסיפים פלטה מתולתלת {}אחרי הסוגריים. כתוב את השיטה הבאה בתוך הפלטה:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
אתה אפילו לא צריך לזכור את האיות. משווה הוא מי שמבצע השוואה, כלומר משווה. כדי לציין את הסדר היחסי של האובייקטים, נחזיר int. זה בעצם זה. נחמד וקל. כפי שניתן לראות מהדוגמה, בנוסף ל-Comparator, יש ממשק נוסף — java.lang.Comparable, שמחייב אותנו ליישם את compareToהשיטה. ממשק זה אומר, "מחלקה המיישמת אותי מאפשרת להשוות מופעים של המחלקה." לדוגמה, Integerהיישום של compareTo הוא כדלקמן:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 הציגה כמה שינויים נחמדים. אם תסתכל מקרוב על Comparatorהממשק, תראה את @FunctionalInterfaceההערה מעליו. הערה זו מיועדת למטרות מידע ומספרת לנו שהממשק הזה פונקציונלי. המשמעות היא שלממשק הזה יש רק שיטה אבסטרקטית אחת, שהיא שיטה ללא מימוש. מה זה נותן לנו? כעת נוכל לכתוב את הקוד של המשווה כך:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
אנו שמות את המשתנים בסוגריים. Java תראה שמכיוון שיש רק שיטה אחת, אז המספר הנדרש וסוגי פרמטרי הקלט ברורים. לאחר מכן אנו משתמשים באופרטור החץ כדי להעביר אותם לחלק זה של הקוד. יתרה מכך, הודות ל-Java 8, יש לנו כעת שיטות ברירת מחדל בממשקים. שיטות אלו מופיעות כברירת מחדל כאשר אנו מיישמים ממשק. לממשק Comparatorיש כמה. לדוגמה:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
יש שיטה אחרת שתהפוך את הקוד שלך לנקי יותר. תסתכל על הדוגמה למעלה, שבה הגדרנו את המשווה שלנו. מה זה עושה? זה די פרימיטיבי. זה פשוט לוקח אובייקט ומחלץ איזה ערך שהוא "שווה". לדוגמה, Integerמיישם comparable, כך שנוכל לבצע פעולת compareTo על הערכים של שדות מזהה הודעה. ניתן לכתוב את פונקציית ההשוואה הפשוטה הזו כך:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
במילים אחרות, יש לנו a Comparatorשמשווה כך: הוא לוקח אובייקטים, משתמש getId()בשיטה כדי לקבל Comparableמהם a, ואז משתמש 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) קל גם למיין מערכים. שוב, מאותה סיבה - הרכיבים שלהם נגישים באמצעות אינדקס. ← Descendants of java.util.SortedSet and java.util.SortedMap אתה תזכור את זה Setולא Mapתבטיח את הסדר שבו רכיבים מאוחסנים. אבל, יש לנו יישומים מיוחדים שאכן מבטיחים את ההזמנה. ואם האלמנטים של אוסף אינם מיישמים את java.util.Comparable, אז נוכל להעביר את Comparatorהבנאי שלו:
Set<Message> msgSet = new TreeSet(comparator);
Stream API ב-Stream API, שהופיע ב-Java 8, השוואות מאפשרות לך לפשט את העבודה עם רכיבי זרימה. לדוגמה, נניח שאנחנו צריכים רצף של מספרים אקראיים מ-0 עד 999, כולל:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
אנחנו יכולים לעצור כאן, אבל יש בעיות מעניינות עוד יותר. לדוגמה, נניח שאתה צריך להכין Map, כאשר המפתח הוא מזהה הודעה. בנוסף, אנחנו רוצים למיין את המפתחות האלה, אז נתחיל עם הקוד הבא:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
אנחנו למעשה מקבלים HashMapכאן. וכידוע, זה לא מבטיח שום סדר. כתוצאה מכך, האלמנטים שלנו, שמוינו לפי זיהוי, פשוט מאבדים את ההזמנה שלהם. לא טוב. נצטרך לשנות קצת את האספן שלנו:
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));
הקוד התחיל להיראות קצת יותר מפחיד, אבל עכשיו הבעיה נפתרה כראוי. קרא עוד על הקבוצות השונות כאן: אתה יכול ליצור אספן משלך. קרא עוד כאן: "יצירת אספן מותאם אישית ב-Java 8" . ותוכלו להפיק תועלת מקריאת הדיון כאן: "רשימת Java 8 למיפוי עם זרם" .

מלכודת נפילה

Comparatorוהם Comparableטובים. אבל יש ניואנס אחד שכדאי לזכור. כאשר כיתה מבצעת מיון, היא מצפה שניתן להמיר את הכיתה שלך ל- 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.

קריאה נוספת:

הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION