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

מחלקת ה-Comparator של Java

פורסם בקבוצה
היי! היום נדבר על השוואת חפצים. מחלקת Comparator של Java - 1 הממ... אבל האם לא דיברנו כבר על הנושא הזה יותר מפעם אחת? :/ אנחנו יודעים איך ==המפעיל עובד, כמו גם את שיטות equals()ו hashCode(). ההשוואה קצת שונה. בעבר, סביר להניח שהתכוונו ל"בדיקת חפצים לשוויון". אבל הסיבות להשוואת חפצים זה עם זה יכולות להיות שונות לחלוטין! הברור שבהם הוא המיון. אני חושב שאם היו אומרים לך למיין ArrayList<>מספרים או מחרוזות, תוכל להתמודד עם זה ללא בעיות:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       String name1 = "Masha";
       String name2 = "Sasha";
       String name3 = "Dasha";

       List<String> names = new ArrayList<>();
       names.add(name1);
       names.add(name2);
       names.add(name3);

       Collections.sort(names);
       System.out.println(names);
   }
}
פלט מסוף:

[Dasha, Masha, Sasha]
אם זכרתם את Collectionsהכיתה ואת sort()השיטה שלה, כל הכבוד! אני חושב שגם לא תהיה לך בעיה עם מספרים. הנה משימה מאתגרת יותר עבורך:
public class Car {

   private int manufactureYear;
   private String model;
   private int maxSpeed;

   public Car(int manufactureYear, String model, int maxSpeed) {
       this.manufactureYear = manufactureYear;
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   // ...getters, setters, toString()

}

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);
   }
}
המשימה למעשה פשוטה. יש לנו Carמחלקה ו-3 חפצי רכב. האם אתה מוכן למיין את המכוניות ברשימה? בוודאי תשאלו, "איך צריך למיין אותם?" לפי שם? לפי שנת ייצור? לפי מהירות מרבית? שאלה מצויינת. כרגע אנחנו לא יודעים איך למיין את Carהחפצים. ובאופן טבעי, גם ג'אווה לא יודעת את זה! כאשר אנו מנסים להעביר רשימה של Carאובייקטים לשיטה Collections.sort(), אנו מקבלים שגיאה:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(20012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);

       // Compilation error!
       Collections.sort(cars);
   }
}
ואכן, איך תדע השפה למיין אובייקטים של מחלקות שכתבת? זה תלוי במה שהתוכנית שלך צריכה לעשות. עלינו איכשהו ללמד את ג'אווה להשוות בין אובייקטים אלה. ולהשוות ביניהם בדיוק איך שאנחנו רוצים. לג'אווה יש מנגנון מיוחד לכך: Comparableהממשק. כדי איכשהו להשוות ולמיין את Carהאובייקטים שלנו, המחלקה חייבת ליישם את הממשק הזה, המורכב משיטה אחת: compareTo():
public class Car implements Comparable<Car> {

   private int manufactureYear;
   private String model;
   private int maxSpeed;

   public Car(int manufactureYear, String model, int maxSpeed) {
       this.manufactureYear = manufactureYear;
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   @Override
   public int compareTo(Car o) {
       return 0;
   }

   // ...getters, setters, toString()

}
שימו לבשציינו את Comparable<Car>הממשק, לא רק Comparable. זהו ממשק בעל פרמטרים, כלומר עלינו לציין את המחלקה הספציפית הקשורה. באופן עקרוני, ניתן להסיר <Car>מהממשק, אבל אז ההשוואה תתבסס על Objectאובייקטים כברירת מחדל. במקום השיטה compareTo(Car o), בכיתה שלנו יהיו:
@Override
   public int compareTo(Object o) {
       return 0;
   }
כמובן שהרבה יותר קל לנו לעבוד איתו Car. בתוך compareTo()השיטה, אנו מיישמים את ההיגיון שלנו להשוואת מכוניות. נניח שאנחנו צריכים למיין אותם לפי שנת ייצור. בטח שמתם לב שהשיטה compareTo()מחזירה int, ולא boolean. אל תתנו לזה להפתיע אתכם. כאשר אנו משווים שני אובייקטים, ישנן 3 אפשרויות:
  • а < b
  • a > b
  • a == b.
booleanיש רק 2 ערכים: true ו-false, מה שלא עובד טוב להשוואת אובייקטים. עם int, הכל הרבה יותר פשוט. אם ערך ההחזר הוא > 0, אז a > b. אם התוצאה של compareToהיא < 0, אז a < b. ואם התוצאה היא == 0, אז שני אובייקטים שווים: a == b. ללמד את הכיתה שלנו למיין מכוניות לפי שנת ייצור זה קל:
@Override
public int compareTo(Car o) {
   return this.getManufactureYear() - o.getManufactureYear();
}
אבל מה קורה כאן? אנחנו לוקחים חפץ מכונית אחד ( this), מקבלים את שנת הייצור של המכונית הזו ומפחיתים ממנה את שנת הייצור של מכונית אחרת (זו שאיתה מושווה החפץ). אם שנת הייצור של המכונית הראשונה גדולה יותר, השיטה תחזיר int > 0. זה אומר this car >שהמכונית o. לעומת זאת, אם שנת הייצור של המכונית השנייה ( о) גדולה יותר, השיטה תחזיר מספר שלילי, כלומר o > this. לבסוף, אם הם שווים, השיטה תחזור 0. המנגנון הפשוט הזה כבר מספיק לנו כדי למיין אוספים של Carחפצים! אתה לא צריך לעשות שום דבר אחר. תבדוק את זה:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);

       // There was previously an error here
       Collections.sort(cars);
       System.out.println(cars);
   }
}
פלט מסוף:

[Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310}, 
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350}, 
Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290}]
המכוניות ממוינות כמו שאנחנו רוצים! :) מחלקת Comparator של Java - 2מתי עלי להשתמש Comparable? שיטת ההשוואה המיושמת במכונה Comparableנקראת סדר טבעי. הסיבה לכך היא שבשיטה compareTo()אתה מגדיר את הדרך הנפוצה ביותר, או הטבעית, להשוואת אובייקטים מהמחלקה הזו. לג'אווה יש כבר סדר טבעי. לדוגמה, Java יודעת שמחרוזות ממוינות לרוב בסדר אלפביתי, ומספרים על ידי הגדלת ערך מספרי. לכן, אם תקרא לשיטה sort()ברשימת מספרים או מחרוזות, הם ימוינו. אם התוכנית שלנו בדרך כלל תשווה ותמיין מכוניות לפי שנת ייצור, אזי עלינו להגדיר את המיון הטבעי למכוניות באמצעות הממשק Comparable<Car>והשיטה compareTo(). אבל מה אם זה לא מספיק לנו? בואו נדמיין שהתוכנית שלנו לא כל כך פשוטה. ברוב המקרים מתאים לנו המיון הטבעי של המכוניות (שקבענו שיבוצע לפי שנת ייצור). אבל לפעמים הלקוחות שלנו חובבי נהיגה מהירה. אם אנחנו מכינים להם קטלוג רכבים לעיון, יש למיין את המכוניות לפי מהירות מרבית. מחלקת Comparator של Java - 3לדוגמה, נניח שאנחנו צריכים למיין כך 15% מהזמן. ברור שזה לא מספיק לנו כדי להגדיר את Carהמיון הטבעי של המחלקה לפי מהירות במקום לפי שנת ייצור. אבל אנחנו לא יכולים להתעלם מ-15% מהלקוחות שלנו. אז מה אנחנו עושים? ממשק נוסף בא לעזרתנו כאן: Comparator. בדיוק כמו Comparable, זהו ממשק בעל פרמטרים. מה ההבדל? Comparableהופך את האובייקטים שלנו ל"ניתנים להשוואה" ומגדיר את סדר המיון הטבעי ביותר שלהם, כלומר סדר המיון שישמש ברוב המקרים. Comparatorהוא ממשק "השוואה" נפרד. אם אנחנו צריכים ליישם איזשהו סדר מיון מיוחד, אנחנו לא צריכים להיכנס לכיתה Carולשנות את ההיגיון של compareTo(). במקום זאת, נוכל ליצור מחלקה נפרדת המיישמת Comparator וללמד אותה כיצד לבצע את המיון שאנו צריכים!
import java.util.Comparator;

public class MaxSpeedCarComparator implements Comparator<Car> {

   @Override
   public int compare(Car o1, Car o2) {
       return o1.getMaxSpeed() - o2.getMaxSpeed();
   }
}
כפי שאתה יכול לראות, שלנו Comparatorהוא די פשוט. עלינו ליישם רק שיטת ממשק אחת: compare(). זה לוקח שני Carאובייקטים כקלט ומשווה את המהירויות המקסימליות שלהם בדרך הרגילה (על ידי חיסור). כמו compareTo(), זה מחזיר int, ועקרון ההשוואה זהה. איך אנחנו משתמשים בזה? הכל פשוט:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);

       Comparator speedComparator = new MaxSpeedCarComparator();
       Collections.sort(cars, speedComparator);

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

[Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290}, 
Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310}, 
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350}]
אנחנו פשוט יוצרים אובייקט השוואה ומעבירים אותו לשיטה Collections.sort()יחד עם הרשימה שיש למיין. כאשר sort()השיטה מקבלת משווה, היא אינה משתמשת במיון הטבעי המוגדר בשיטת Carהמחלקה compareTo(). במקום זאת, הוא מיישם את אלגוריתם המיון שהוגדר על ידי המשווה שהועבר אליו. מה היתרונות של לעשות את זה? ראשית, תאימות לקוד קיים. יצרנו שיטת מיון חדשה ומיוחדת תוך שמירה על הקיימת שתשמש רוב הזמן. לא נגענו Carבכיתה בכלל. זה היה Comparable, וכך זה נשאר:
public class Car implements Comparable<Car> {

   private int manufactureYear;
   private String model;
   private int maxSpeed;

   public Car(int manufactureYear, String model, int maxSpeed) {
       this.manufactureYear = manufactureYear;
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   @Override
   public int compareTo(Car o) {
       return this.getManufactureYear() - o.getManufactureYear();
   }

   // ...getters, setters, toString()

}
שנית, גמישות. אנחנו יכולים להוסיף כמה אלגוריתמי מיון שנרצה. לדוגמה, אנחנו יכולים למיין מכוניות לפי צבע, מהירות, משקל, או לפי כמה פעמים נעשה שימוש במכונית בסרטי באטמן. כל מה שאנחנו צריכים לעשות הוא ליצור תוספת נוספת Comparator. זהו זה! היום למדת שני מנגנונים חשובים מאוד שתשתמש בהם לעתים קרובות בפרויקטים אמיתיים בעבודה. אבל, כידוע, תיאוריה ללא תרגול היא כלום. עכשיו הגיע הזמן לגבש את הידע שלך ולהשלים כמה משימות!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION