העצלנים הם לא היחידים שכותבים על Comparators והשוואות בג'אווה. אני לא עצלן, אז בבקשה תאהבו ותתערבו על עוד הסבר. אני מקווה שזה לא יהיה מיותר. וכן, מאמר זה הוא התשובה לשאלה: " האם אתה יכול לכתוב משווה מהזיכרון? " אני מקווה שכולם יוכלו לכתוב משווה מהזיכרון לאחר קריאת המאמר הזה.
מבוא
כפי שאתה יודע, ג'אווה היא שפה מונחה עצמים. כתוצאה מכך, נהוג לתפעל אובייקטים ב-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
ייצוג מספרי ( ) כלשהו של האובייקט. מה זה אומר? זה אומר שאם אתה יוצר שני מופעים שונים של מחלקה, אז הם צריכים להיות hashCode
s שונים. תיאור השיטה אומר לא פחות: "ככל שהיא מעשית באופן סביר, שיטת ה-hashCode המוגדרת על ידי Class Object מחזירה מספרים שלמים נפרדים עבור אובייקטים נפרדים". במילים אחרות, עבור שתי instance
s שונות, צריכות להיות hashCode
s שונות. כלומר, שיטה זו אינה מתאימה להשוואה שלנו. → 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
היישום של compare
To הוא כדלקמן:
(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
.
קריאה נוספת: |
---|
GO TO FULL VERSION