JavaRush /בלוג Java /Random-HE /שווה ב-Java ו-String compare - השוואת מחרוזות

שווה ב-Java ו-String compare - השוואת מחרוזות

פורסם בקבוצה
שלום! היום נדבר על נושא מאוד חשוב ומעניין, כלומר השוואת אובייקטים זה לזה equals() בג'אווה. ואכן, באילו מקרים בג'אווה אובייקט A יהיה שווה לאובייקט B ? שווה והשוואת מחרוזות - 1בואו ננסה לכתוב דוגמה:
public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car car1 = new Car();
       car1.model = "Ferrari";
       car1.maxSpeed = 300;

       Car car2 = new Car();
       car2.model = "Ferrari";
       car2.maxSpeed = 300;

       System.out.println(car1 == car2);
   }
}
פלט מסוף:

false
בסדר, תפסיק. מדוע, בעצם, שתי המכוניות הללו אינן שוות? נתנו להם את אותם מאפיינים, אבל התוצאה של ההשוואה שקרית. התשובה פשוטה. האופרטור ==משווה לא מאפיינים של אובייקטים, אלא קישורים. גם אם לשני אובייקטים יש 500 מאפיינים זהים, תוצאת ההשוואה עדיין תהיה שקרית. אחרי הכל, קישורים car1מצביעים car2 על שני אובייקטים שונים , לשתי כתובות שונות. תארו לעצמכם מצב עם השוואה בין אנשים. כנראה יש אדם בעולם שיש לו אותו שם, צבע עיניים, גיל, גובה, צבע שיער וכו' כמוך. כלומר, אתם דומים בהרבה מובנים, אבל עדיין אתם לא תאומים, ובעיקר לא אותו אדם. שווה והשוואת מחרוזות - 2האופרטור מחיל בערך את אותו היגיון ==כאשר אנו משתמשים בו כדי להשוות בין שני אובייקטים. אבל מה אם אתה צריך היגיון שונה בתוכנית שלך? לדוגמה, אם התוכנית שלך מדמה ניתוח DNA. עליה להשוות את קוד ה-DNA של שני אנשים ולקבוע שהם תאומים.
public class Man {

   int dnaCode;

   public static void main(String[] args) {

       Man man1 = new Man();
       man1.dnaCode = 1111222233;

       Man man2 = new Man();
       man2.dnaCode = 1111222233;

       System.out.println(man1 == man2);
   }
}
פלט מסוף:

false
זה הגיוני שהתוצאה הייתה זהה (אחרי הכל, לא שינינו כלום), אבל עכשיו אנחנו לא מרוצים מזה! ואכן, בחיים האמיתיים, ניתוח DNA הוא ערובה של מאה אחוז לכך שאנו עומדים בפני תאומים. אבל התוכנית והמפעיל שלנו ==אומרים לנו אחרת. כיצד נוכל לשנות התנהגות זו ולוודא שאם בדיקות ה-DNA תואמות, התוכנית תפיק את התוצאה הנכונה? לשם כך נוצרה שיטה מיוחדת ב-Java - equals() .

שיטת Equals() ב-Java

כמו השיטה toString()שדיברנו עליה קודם, equals() שייכת למחלקה, Objectהמחלקה החשובה ביותר בג'אווה, ממנה נגזרות כל המחלקות האחרות. עם זאת, equals() עצמו לא ישנה את ההתנהגות של התוכנית שלנו בשום אופן:
public class Man {

   String dnaCode;

   public static void main(String[] args) {

       Man man1 = new Man();
       man1.dnaCode = "111122223333";

       Man man2 = new Man();
       man2.dnaCode = "111122223333";

       System.out.println(man1.equals(man2));
   }
}
פלט מסוף:

false
בדיוק אותה תוצאה, אז למה יש צורך בשיטה הזו? :/ זה פשוט. העובדה היא שכעת השתמשנו בשיטה זו כפי שהיא מיושמת במחלקה עצמה Object. ואם ניכנס לקוד המחלקה Objectונראה איך השיטה הזו מיושמת בו ומה היא עושה, נראה:
public boolean equals(Object obj) {
   return (this == obj);
}
זו הסיבה לכך שהתנהגות התוכנית שלנו לא השתנתה! בתוך שיטת equals() של המחלקה Objectנמצאת אותה השוואת הפניות, ==. אבל החוכמה של השיטה הזו היא שאנחנו יכולים לעקוף אותה. דריסה פירושה כתיבת שיטת equals() משלך בכיתה שלנו Manולגרום לה להתנהג כמו שאנחנו רוצים! כעת איננו משוכנעים שהמחאה man1.equals(man2)בעצם עושה את אותו הדבר כמו man1 == man2. הנה מה שנעשה במצב זה:
public class Man {

   int dnaCode;

   public boolean equals(Man man) {
       return this.dnaCode ==  man.dnaCode;
   }

   public static void main(String[] args) {

       Man man1 = new Man();
       man1.dnaCode = 1111222233;

       Man man2 = new Man();
       man2.dnaCode = 1111222233;

       System.out.println(man1.equals(man2));

   }
}
פלט מסוף:

true
תוצאה שונה לחלוטין! על ידי כתיבת שיטת equals() משלנו במקום השיטה הסטנדרטית, השגנו את ההתנהגות הנכונה: עכשיו אם לשני אנשים יש את אותו קוד DNA, התוכנית אומרת לנו: "ניתוח DNA הראה שהם תאומים" ומחזירה אמת! על ידי דריסת המתודה equals() במחלקות שלך, תוכל ליצור בקלות את הלוגיקה הדרושה להשוואת אובייקטים. נגענו בהשוואת חפצים רק במונחים כלליים. עדיין תהיה לנו הרצאה גדולה נפרדת בנושא זה (תוכל לקרוא אותה במהירות עכשיו, אם אתה מעוניין).

String compare ב-Java - String comparison

מדוע אנו מתייחסים להשוואות מחרוזות בנפרד מכל השאר? ובכן, למעשה, שורות בתכנות הן סיפור אחר לגמרי. ראשית, אם לוקחים את כל תוכניות הג'אווה שנכתבו על ידי האנושות, כ-25% מהאובייקטים בהן מורכבים מהן. לכן, נושא זה חשוב מאוד. שנית, תהליך השוואת מחרוזות הוא באמת שונה מאובייקטים אחרים. בואו נסתכל על דוגמה פשוטה:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush is the best site to learn Java!";
       String s2 = new String("JavaRush is the best site to learn Java!");
       System.out.println(s1 == s2);
   }
}
פלט מסוף:

false
אבל למה שקר? השורות זהות לחלוטין, מילה במילה :/ אפשר להניח: זה בגלל שהאופרטור == משווה הפניות! אחרי הכל, יש s1להם s2כתובות שונות בזיכרון. אם המחשבה הזו עלתה בדעתך, אז בואו נחזור על הדוגמה שלנו:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush is the best site to learn Java!";
       String s2 = "JavaRush is the best site to learn Java!";
       System.out.println(s1 == s2);
   }
}
עכשיו יש לנו גם שני קישורים, אבל התוצאה השתנתה להיפך: פלט מסוף:

true
מבולבל לגמרי? :) בוא נבין את זה. המפעיל ==למעשה משווה כתובות בזיכרון. כלל זה תמיד עובד ואין צורך להטיל בו ספק. המשמעות היא שאם s1 == s2הוא מחזיר אמת, לשתי המחרוזות הללו יש אותה כתובת בזיכרון. ואכן כך! זה הזמן להכיר אזור זיכרון מיוחד לאחסון מיתרים - מאגר המיתרים ( String pool) שווה והשוואת מחרוזות - 3מאגר המיתרים הוא אזור לאחסון כל ערכי המיתרים שאתה יוצר בתוכנית שלך. בשביל מה הוא נוצר? כפי שהוזכר קודם לכן, מיתרים תופסים חלק עצום מכל האובייקטים. בכל תוכנית גדולה נוצרות הרבה שורות. על מנת לחסוך בזיכרון זה מה שצריך String Pool- שם מונחת שורה עם הטקסט הדרוש, ובעתיד קישורים חדשים שנוצרו מתייחסים לאותו אזור זיכרון, אין צורך להקצות זיכרון נוסף בכל פעם. בכל פעם שאתה כותב String = “........”, התוכנה בודקת אם יש שורה עם טקסט כזה במאגר המחרוזות. אם יש, לא ייווצר חדש. והקישור החדש יצביע על אותה כתובת במאגר המחרוזות שבו המחרוזת הזו מאוחסנת. לכן, כשכתבנו בתוכנית
String s1 = "JavaRush is the best site to learn Java!";
String s2 = "JavaRush is the best site to learn Java!";
הקישור s2מצביע בדיוק לאותו מקום כמו s1. הפקודה הראשונה יצרה שורה חדשה במאגר המחרוזות עם הטקסט שהיינו צריכים, וכאשר הגיעה לשנייה, היא פשוט התייחסה לאותו אזור זיכרון כמו s1. אתה יכול ליצור לפחות 500 שורות נוספות עם אותו טקסט, התוצאה לא תשתנה. תפסיק. אבל מדוע אם כן הדוגמה הזו לא עבדה עבורנו קודם לכן?
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush is the best site to learn Java!";
       String s2 = new String("JavaRush is the best site to learn Java!");
       System.out.println(s1 == s2);
   }
}
אני חושב, אינטואיטיבית, אתה כבר מנחש מה הסיבה :) נסה לנחש לפני שתקרא עוד. אתה יכול לראות ששני הקווים הללו נוצרו בצורה שונה. האחד הוא בעזרת המפעיל new, והשני בלעדיו. זו בדיוק הסיבה. האופרטור החדש, בעת יצירת אובייקט, מקצה עבורו אזור חדש בזיכרון בכוח . והשורה שנוצרה עם new, אינה מסתיימת ב String Pool: היא הופכת לאובייקט נפרד, גם אם הטקסט שלו זהה לחלוטין לאותה שורה מ'א String Pool. כלומר, אם נכתוב את הקוד הבא:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush is the best site to learn Java!";
       String s2 = "JavaRush is the best site to learn Java!";
       String s3 = new String("JavaRush is the best site to learn Java!");
   }
}
בזיכרון זה ייראה כך: שווים והשוואת מחרוזות - 4ובכל פעם שנוצר אובייקט חדש, newיוקצה אזור חדש בזיכרון, גם אם הטקסט בתוך השורות החדשות זהה! נראה שסידרנו את האופרטור ==, אבל מה לגבי החבר החדש שלנו - שיטת equals()?
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush is the best site to learn Java!";
       String s2 = new String("JavaRush is the best site to learn Java!");
       System.out.println(s1.equals(s2));
   }
}
פלט מסוף:

true
מעניין. אנחנו יודעים בדיוק מה s1ומצביעים s2על אזורים שונים בזיכרון. אבל, בכל זאת, שיטת equals() אומרת שהם שווים. למה? זכור, למעלה אמרנו שניתן לעקוף את המתודה equals() במחלקה שלך כך שהיא משווה אובייקטים כמו שאתה צריך? זה מה שהם עשו עם הכיתה String. יש לו שיטת equals() נדחקת. וזה לא משווה בין קישורים, אלא את רצף התווים במחרוזות. ואם הטקסט במחרוזות זהה, זה לא משנה איך הם נוצרו והיכן הם מאוחסנים: במאגר המיתרים, או באזור זיכרון נפרד. תוצאת ההשוואה תהיה נכונה. אגב, Java מאפשרת לך להשוות נכון מחרוזות בצורה לא תלוית רישיות. במצב רגיל, אם תכתוב אחת מהשורות, למשל, ב-caps, תוצאת ההשוואה תהיה שקר:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush is the best site to learn Java!";
       String s2 = new String("JAVARUSH - ЛУЧШИЙ САЙТ ДЛЯ ИЗУЧЕНИЯ JAVA!");
       System.out.println(s1.equals(s2));
   }
}
פלט מסוף:

false
במקרה זה, למחלקה Stringיש שיטה equalsIgnoreCase(). אם הדבר העיקרי בהשוואה שלך הוא רצף הדמויות הספציפיות, ולא המקרה שלהם, אתה יכול להשתמש בו. לדוגמה, זה יהיה שימושי בעת השוואת שתי כתובות דוא"ל:
public class Main {

   public static void main(String[] args) {

       String address1 = "Moscow, Academician Korolev street, 12";
       String address2 = new String("Г. МОСКВА, УЛ. АКАДЕМИКА КОРОЛЕВА, ДОМ 12");
       System.out.println(address1.equalsIgnoreCase(address2));
   }
}
במקרה זה, ברור שאנו מדברים על אותה כתובת, ולכן שימוש בשיטה equalsIgnoreCase()תהיה ההחלטה הנכונה.

שיטת String.intern()

למחלקה Stringיש שיטה מסובכת נוספת - intern(); השיטה intern()פועלת ישירות עם String Pool'אומ. אם אתה קורא למתודה intern()על מחרוזת, זה:
  • מחפש לראות אם יש מחרוזת עם הטקסט הזה במאגר המחרוזות
  • אם יש, מחזיר קישור אליו בבריכה
  • אם לא, הוא מציב שורה עם הטקסט הזה במאגר המחרוזות ומחזיר קישור אליו.
על ידי החלת השיטה intern()על הפניה למחרוזת שנוצרה באמצעות new, נוכל להשוות אותה להפניה למחרוזת מ- String Pool'a דרך ה- ==.
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush is the best site to learn Java!";
       String s2 = new String("JavaRush is the best site to learn Java!");
       System.out.println(s1 == s2.intern());
   }
}
פלט מסוף:

true
בעבר, כשהשווינו אותם ללא intern(), התוצאה הייתה שקרית. כעת השיטה intern()בדקה האם יש שורה עם הטקסט "JavaRush - האתר הטוב ביותר ללימוד ג'אווה!" בבריכת המיתרים. כמובן שזה שם: יצרנו את זה כשכתבנו
String s1 = "JavaRush is the best site to learn Java!";
נבדק שההפניה s1וההפניה המוחזרת בשיטה s2.intern()מצביעות על אותו אזור בזיכרון, וכמובן שכן :) לסיכום, זכרו והשתמשו בכלל העיקרי: כדי להשוות מחרוזות, השתמשו תמיד ב-equals() שיטה! כאשר משווים מחרוזות, אתה מתכוון כמעט תמיד להשוואת הטקסט שלהם, לא לקישורים, אזורי זיכרון וכו'. השיטה equals() עושה בדיוק את מה שאתה צריך. הנה כמה קישורים שתוכלו ללמוד בעצמכם:
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION