97. האם חלים כללים כלשהם בעת עקיפת equals()?
בעת עקיפת המתודה equals() עליך לציית לכללים הבאים:-
רפלקסיביות - עבור כל ערך x , x.equals(x) חייב תמיד להחזיר true (כאשר x != null ).
-
סימטריה - עבור כל הערכים x ו- y , x.equals(y) חייב להחזיר true רק אם y.equals(x) מחזיר true .
-
טרנזיטיביות - עבור כל הערכים x , y ו- z , אם x.equals(y) מחזירה true ו- y.equals(z) גם מחזירה true , אז x.equals(z) חייב להחזיר true .
-
עקביות - עבור כל הערכים x ו- y , קריאה חוזרת ונשנית של x.equals(y) תמיד תחזיר את אותו הערך כל עוד השדות המשמשים להשוואה בין שני האובייקטים לא השתנו בין כל קריאה.
-
השוואת null - עבור כל ערך x , הקריאה ל-x.equals(null) חייבת להחזיר false .
98. מה קורה אם לא תעקוף equals() ו-hashCode()?
במקרה זה, hashCode() יחזיר מספר שנוצר על סמך הכתובת של תא הזיכרון שבו האובייקט מאוחסן. במילים אחרות, כאשר שיטת hashCode() המקורית נקראת בשני אובייקטים בעלי אותם שדות בדיוק, התוצאה תהיה שונה (מכיוון שהם מאוחסנים במיקומי זיכרון שונים). השיטה המקורית equals() משווה הפניות, כלומר היא מציינת אם ההפניות מצביעות על אותו אובייקט. במילים אחרות, ההשוואה משתמשת באופרטור == , והיא תמיד תחזיר false עבור אובייקטים שונים, גם כאשר השדות שלהם זהים. true מוחזר רק כאשר משווים הפניות לאותו אובייקט. לפעמים זה הגיוני לא לעקוף את השיטות הללו. לדוגמה, אתה רוצה שכל האובייקטים של מחלקה מסוימת יהיו ייחודיים - החלפת שיטות אלה עלולה רק לקלקל את הערבות הקיימת של קודי hash ייחודיים. הדבר החשוב הוא להבין את הניואנסים של שיטות אלו, בין אם נדחקות או לא, ולהשתמש בכל גישה שהמצב דורש.99. מדוע דרישת הסימטריה מתקיימת רק אם x.equals(y) מחזירה אמת?
השאלה הזו קצת מוזרה. אם אובייקט A שווה לאובייקט B, אז אובייקט B שווה לאובייקט A. אם B אינו שווה לאובייקט A, אז איך יכול להיות שההפך הוא אפשרי? זה השכל הישר.100. מהי התנגשות HashCode? איך מתמודדים עם זה?
התנגשות HashCode מתרחשת כאשר לשני אובייקטים שונים יש את אותו HashCode . כיצד זה אפשרי? ובכן, קוד ה-hash ממופה למספר שלם, בעל טווח שבין -2147483648 ל-2147483647. כלומר, הוא יכול להיות אחד מתוך כ-4 מיליארד מספרים שלמים שונים. הטווח הזה הוא עצום אבל לא אינסופי. זה אומר שיש מצבים שבהם שני אובייקטים שונים לחלוטין עשויים להיות בעלי אותו קוד hash. זה מאוד לא סביר, אבל זה אפשרי. פונקציית Hash מיושמת בצורה גרועה יכולה להפוך קודי Hash זהים לתכופים יותר על ידי החזרת מספרים בטווח קטן, ובכך להגדיל את הסיכוי להתנגשויות. כדי להפחית התנגשויות, אתה צריך יישום טוב של שיטת HashCode המפזרת את הערכים באופן אחיד ומקטינה את הסיכוי לערכים חוזרים.101. מה קורה אם הערך של רכיב המשתתף בחוזה hashCode משתנה?
אם אלמנט המעורב בחישוב של קוד גיבוב משתנה, אזי קוד ה-hash של האובייקט אמור להשתנות (אם פונקציית ה-hash טובה). זו הסיבה שאתה צריך להשתמש באובייקטים בלתי ניתנים לשינוי כמפתחות ב- HashMap , מכיוון שלא ניתן לשנות את המצב הפנימי שלהם (שדות) לאחר היצירה. ומכאן נובע שקוד ה-hash שלהם אכן משתנה לאחר היצירה. אם אתה משתמש באובייקט שניתן לשנות כמפתח, אז כאשר השדות של האובייקט ישתנו, קוד ה-hash שלו ישתנה, ואתה עלול לאבד את צמד המפתח-ערך המתאים ב- HashMap . אחרי הכל, הוא יישמר ב-bucket המשויך לקוד ה-hash המקורי, אבל לאחר שהאובייקט ישתנה, תחפשו אותו ב-bucket אחר.102. כתוב שיטות equals() ו-hashCode() עבור כיתת Student שיש לה שדות של שם מחרוזת ושדות int age.
public class Student {
int age;
String name;
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final Student student = (Student) o;
if (this.age != student.age) {
return false;
}
return this.name != null ? this.name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = this.age;
result = 31 * result + (this.name != null ? this.name.hashCode() : 0);
return result;
}
}
שווים():
-
ראשית, אנו משווים את ההפניות ישירות, כי אם הפניות מצביעות על אותו אובייקט, מה הטעם להמשיך ולבדוק את השוויון? אנחנו כבר יודעים שהתוצאה תהיה נכונה .
-
אנחנו בודקים את null והאם סוגי המחלקות זהים כי אם הפרמטר הוא null או מסוג אחר, אז האובייקטים לא יכולים להיות שווים, והתוצאה חייבת להיות false .
-
נטיל את הפרמטר לאותו סוג (הרי מה אם זה אובייקט מסוג האב).
-
נשווה בין השדות הפרימיטיביים (השוואה באמצעות =! תספיק). אם הם לא שווים, נחזיר false .
-
אנו בודקים את השדה הלא פרימיטיבי כדי לראות אם הוא null ובאמצעות מתודה equals ( המחלקה String עוקפת את השיטה, כך שהיא תבצע את ההשוואה בצורה נכונה). אם שני השדות הם null, או שווים מחזירה true , נפסיק לבדוק, והשיטה מחזירה true .
-
אנו מגדירים את הערך ההתחלתי של קוד ה-hash שווה לערך של שדה הגיל של האובייקט .
-
נכפיל את קוד הגיבוב הנוכחי ב-31 (לפיזור גדול יותר בערכים) ולאחר מכן נוסיף את קוד הגיבוב של שדה המחרוזת הלא פרימיטיבי (אם הוא לא null).
-
אנחנו מחזירים את התוצאה.
-
דריסה של השיטה בדרך זו פירושה שאובייקטים בעלי אותו שם וערכי int תמיד יחזירו את אותו קוד hash.
103. מה ההבדל בין שימוש ב-"if (instanceof Student)" לבין "if (getClass() == obj.getClass())"?
בואו נסתכל מה עושה כל ביטוי:-
instanceof בודק האם הפניה לאובייקט בצד שמאל היא מופע מהסוג בצד ימין או אחד מתתי הסוגים שלו.
-
"getClass() == ..." בודק אם הסוגים זהים.
104. תן תיאור קצר של שיטת clone() .
שיטת clone() שייכת למחלקה Object . מטרתו ליצור ולהחזיר שיבוט (עותק) של האובייקט הנוכחי. כדי להשתמש בשיטה זו, עליך ליישם את ממשק הסמן שניתן לשבוט :Student implements Cloneable
ועקוף את שיטת clone() עצמה:
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
אחרי הכל, הוא מוגן במחלקת Object , כלומר הוא יהיה גלוי רק בתוך הכיתה Student ולא גלוי לכיתות חיצוניות.
105. אילו שיקולים מיוחדים אתה צריך לזכור לגבי שיטת clone() ומשתני התייחסות באובייקט?
כאשר אובייקטים משובטים, רק ערכים פרימיטיביים והערך של הפניות לאובייקט מועתקים. משמעות הדבר היא שאם לאובייקט יש שדה שמפנה לאובייקט אחר, אז רק ההפניה תשובט - אובייקט אחר שהפניה אליו לא ישובט. זה מה שנקרא עותק רדוד. אז מה אם אתה צריך עותק מלא, שבו כל אובייקט מקונן משובט? איך אתה מוודא שלא מדובר רק בעותקים של הפניות, אלא בעותקים מלאים של אובייקטים נפרדים התופסים כתובות זיכרון שונות בערימה? למעשה, הכל די פשוט - עבור כל מחלקה שמתייחסת אליה באופן פנימי, אתה צריך לעקוף את שיטת clone() ולהוסיף את ממשק Cloneable marker. ברגע שתעשה זאת, פעולת השיבוט לא תעתיק הפניות לאובייקטים קיימים, אלא תעתיק את האובייקטים המוזכרים, שכן כעת יש להם את היכולת להעתיק את עצמם.חריגים
106. מה ההבדל בין טעות לחריגה?
חריגים, כמו גם שגיאות, הם תת-מחלקות של Throwable . עם זאת, יש להם את ההבדלים שלהם. השגיאה מצביעה על בעיה המתרחשת בעיקר עקב מחסור במשאבי מערכת. והאפליקציה שלנו לא אמורה לראות בעיות מסוג זה. דוגמאות לשגיאות אלו כוללות קריסת מערכת ושגיאה מחוץ לזיכרון. שגיאות מתרחשות בעיקר בזמן ריצה, מכיוון שהן לא מסומנות. חריגים הם בעיות שעלולות להתרחש בזמן ריצה ובזמן קומפילציה. בעיות אלו מתעוררות בדרך כלל בקוד שאנו כותבים כמפתחים. זה אומר שהחריגים האלה צפויים יותר ותלויים בנו יותר. לעומת זאת, שגיאות הן אקראיות יותר ויותר בלתי תלויות בנו. במקום זאת, הם תלויים בבעיות במערכת שבה האפליקציה שלנו פועלת.107. מה ההבדל בין מסומן, לא מסומן, חריג, זריקה וזריקה?
כפי שאמרתי קודם, חריג הוא שגיאת זמן ריצה או קומפילציה שמתרחשת בקוד שנכתב על ידי המפתח (עקב מצב לא נורמלי כלשהו). מסומן הוא מה שאנו מכנים חריגים ששיטה חייבת לטפל בהם תמיד באמצעות מנגנון ה- try-catch או העברה מחדש לשיטת הקריאה. מילת המפתח throws משמשת בכותרת של מתודה כדי לציין את החריגים שהשיטה עשויה לזרוק. במילים אחרות, הוא מספק לנו מנגנון לזריקת חריגים לשיטת הקריאה. אין צורך לטפל בחריגים שלא מסומנים . הם נוטים להיות פחות צפויים וסבירים פחות. עם זאת, אתה יכול להתמודד איתם אם אתה רוצה. אנו משתמשים בזריקה בעת זריקה ידנית של חריגה, לדוגמה:throw new Exception();
108. מהי היררכיית החריגים?
היררכיית החריגים היא נרחבת מאוד. יש יותר מדי מכדי לתאר כאן בצורה מספקת. לכן, במקום זאת, נשקול רק את ענפי המפתח שלה: כאן, בחלק העליון של ההיררכיה, אנו רואים את המחלקה Throwable , שהיא האב הקדמון הכללי של היררכיית החריג ומתחלקת בתורה ל:- שגיאות - בעיות קריטיות ולא מסומנות.
- חריגים - חריגים שניתן לבדוק.
109. מה זה הם חריגים מסומנים ולא מסומנים?
כמו שאמרתי קודם:-
חריגים מסומנים הם חריגים שאתה חייב איכשהו לטפל בהם. כלומר, אתה חייב לטפל בהם בגוש נסיון או לזרוק אותם לשיטה שלמעלה. לשם כך, לאחר רישום ארגומנטי המתודה בחתימת השיטה, השתמש ב-throws <exception type> כדי לציין שהמתודה יכולה לזרוק את החריג הזה. זה דומה קצת לאזהרה, תוך מתן הודעה לשיטת ההתקשרות שהיא חייבת לקחת אחריות על הטיפול בחריג זה.
-
אין צורך לטפל בחריגים שאינם מסומנים , מכיוון שהם אינם נבדקים בזמן ההידור ובדרך כלל הם בלתי צפויים יותר. ההבדל העיקרי שלהם עם חריגים מסומנים הוא שהטיפול בהם על ידי שימוש בגוש נסיון או על ידי השלכה מחדש הוא אופציונלי ולא חובה.
101. כתוב דוגמה שבה אתה משתמש בלוק של try-catch כדי לתפוס ולטפל בחריג.
try{ // Start of the try-catch block
throw new Exception(); // Manually throw an exception
} catch (Exception e) { // Exceptions of this type and its subtypes will be caught
System.out.println("Oops! Something went wrong =("); // Display the exception
}
102. כתוב דוגמה שבה אתה תופס ולטפל בחריגים המותאמים אישית שלך.
ראשית, בואו נכתוב מחלקה חריגה משלנו שיורשת את Exception ונעקוף את הבנאי שלו שלוקח הודעת שגיאה כארגומנט:public class CustomException extends Exception {
public CustomException(final String message) {
super(message);
}
}
בשלב הבא נזרוק אחד ידנית ונתפוס אותו בדיוק כפי שעשינו בדוגמה של השאלה הקודמת:
try{
throw new CustomException("Oops! Something went wrong =(");
} catch (CustomException e) {
System.out.println(e.getMessage());
}
שוב, כאשר אנו מריצים את הקוד שלנו, אנו מקבלים את הפלט הבא:
GO TO FULL VERSION