equals() ו-hashCode()
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Java של Coddy. שיעור 42 מתוך 87.
בעת השוואה בין אובייקטים ב-Java, האופרטור == בודק אם שתי הפניות מצביעות לאותו מיקום בזיכרון. כדי להשוות את התוכן בפועל של אובייקטים, צריך לדרוס את המתודה equals().
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
}השיטה equals() פועלת לפי תבנית סטנדרטית: קודם בודקים אם מדובר באותו אובייקט, אחר כך מאמתים את הסוג, ולבסוף משווים בין השדות הרלוונטיים.
בכל פעם שדורסים את equals(), חייבים לדרוס גם את hashCode(). זהו חוזה ב-Java: לאובייקטים שווים חייב להיות אותו קוד גיבוב. אוספים מבוססי גיבוב כמו HashMap ו-HashSet מסתמכים על הכלל הזה כדי לפעול כראוי.
@Override
public int hashCode() {
return Objects.hash(name, age);
}מתודת השירות Objects.hash() יוצרת קוד גיבוב מאותם השדות שבהם נעשה שימוש ב־equals(). אם לשני אובייקטים מסוג Person יש אותו שם ואותו גיל, הם ייחשבו שווים ויפיקו קודי גיבוב זהים, וכך תובטח התנהגות עקבית באוספים.
אתגר
קלבואו נבנה מערכת לניהול רשומות של תלמידים שמממשת נכון שוויון בין אובייקטים. תיצרו מחלקה Student שבה שני תלמידים נחשבים שווים אם יש להם אותו מזהה תלמיד ואותו שם, בלי קשר לשאלה אם הם אותו אובייקט בזיכרון.
תארגנו את הקוד בשני קבצים:
Student.java: צרו מחלקה שמייצגת תלמיד ובה שלושה שדות פרטיים:studentId(String),name(String), ו-gpa(double). כללו בנאי לאתחול כל השדות ושיטות getter לכל שדה.דרסו את השיטה
equals()כך ששני אובייקטים מסוג Student ייחשבו שווים כאשר יש להם אותוstudentIdוגם אותוname. פעלו לפי התבנית המקובלת: בדקו אם מדובר באותה הפניה, בדקו אם הערך הוא null ואם הטיפוס תואם, ואז השוו בין השדות הרלוונטיים.דרסו את השיטה
hashCode()באמצעותObjects.hash()עם אותם שדות שבהם השתמשתם ב-equals(). זכרו לייבא אתjava.util.Objectsבראש הקובץ.Main.java: הפיחו חיים במחלקה Student שלכם באמצעות השוואת אובייקטים של תלמידים. תקבלו שש קלטים המייצגים שני תלמידים: תחילה המזהה, השם והממוצע של התלמיד הראשון, ולאחר מכן המזהה, השם והממוצע של התלמיד השני.צרו את שני אובייקטי Student, ואז בצעו שלוש השוואות והדפיסו את התוצאות:
- הדפיסו
Same reference:ולאחריו האם התלמיד הראשון שווה לעצמו (התשובה תמיד צריכה להיות true) - הדפיסו
Content equality:ולאחריו האם התלמיד הראשון שווה לתלמיד השני (בהתאם למימוש שלכם של equals) - הדפיסו
Hash codes match:ולאחריו האם לשני התלמידים יש אותו קוד גיבוב
- הדפיסו
תקבלו שש קלטים: המזהה של התלמיד הראשון (String), השם של התלמיד הראשון (String), הממוצע של התלמיד הראשון (double), המזהה של התלמיד השני (String), השם של התלמיד השני (String), והממוצע של התלמיד השני (double).
שימו לב שכאשר לשני תלמידים יש מזהים ושמות זהים, גם equals() מחזירה true וגם קודי הגיבוב שלהם תואמים. זהו החוזה שעליכם לקיים!
נסו בעצמכם
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// קרא את נתוני התלמיד הראשון
String id1 = scanner.nextLine();
String name1 = scanner.nextLine();
double gpa1 = scanner.nextDouble();
scanner.nextLine(); // צרוך את התו של סוף השורה
// קרא את נתוני התלמיד השני
String id2 = scanner.nextLine();
String name2 = scanner.nextLine();
double gpa2 = scanner.nextDouble();
// TODO: צור שני אובייקטים מסוג Student באמצעות נתוני הקלט
// TODO: הדפס "Same reference: " ולאחר מכן ציין אם student1 שווה לעצמו
// TODO: הדפס "Content equality: " ולאחר מכן ציין אם student1 שווה ל-student2
// TODO: הדפס "Hash codes match: " ולאחר מכן ציין אם לשני התלמידים יש אותו קוד גיבוב
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמבוא לתכנות מונחה עצמיםמחלקות לעומת אובייקטיםמילת המפתח thisמתודותשדות (מאפיינים)מתודת הבנאיהעמסת בנאיםחזרה – מחשבון פשוט4ירושה
ירושה בסיסית (extends)מילת המפתח superדריסת מתודות (@Override)שרשור בנאיםהמחלקה Objectירושה יחידה ורב־רמתיתלמה אין ירושה מרובה של מחלקותסיכום – היררכיית עובדים7מתודות מיוחדות ומחלקת Object
מתודת toString()equals() ו-hashCode()מתודת clone()compareTo() ו-Comparableהממשק Comparatorסיכום – מיון מותאם אישית2מצייני גישה ואנקפסולציה
סקירה של רמות הגישהמתודות Getter ו-Setterהסתרת מידעמילת המפתח finalסיכום – מנהל חשבון בנק5פולימורפיזם
יסודות העמסת מתודותדריסת מתודות (בזמן ריצה)המרה כלפי מעלה וכלפי מטההאופרטור instanceofמחלקות ומתודות מופשטותסיכום – מחשבון צורות8מושגי OOP מתקדמים
הרכבה מול ירושהאגרגציה מול הרכבהמחלקות פנימיות, מקוננות ואנונימיותEnums ומתודות של EnumRecords (Java 16+)מחלקות חתומות (Java 17+)3מאפייני מחלקה וחברים סטטיים
משתני מופע לעומת משתנים סטטייםמתודות סטטיותבלוקים סטטייםקבועים (static final)סיכום — מונה ומחלקת עזר6ממשקים ומחלקות מופשטות
מבוא לממשקיםמימוש ממשקיםמימוש של כמה ממשקיםמתודות ברירת מחדל וסטטיות בממשקמחלקות מופשטות לעומת ממשקיםממשקים פונקציונלייםסיכום – מערכת תשלומיםתרגלו בעצמכם: קומפיילר Java אונליין