היי! היום נדבר על השוואת חפצים. הממ... אבל האם לא דיברנו כבר על הנושא הזה יותר מפעם אחת? :/ אנחנו יודעים איך
==
המפעיל עובד, כמו גם את שיטות 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}]
המכוניות ממוינות כמו שאנחנו רוצים! :) מתי עלי להשתמש Comparable
? שיטת ההשוואה המיושמת במכונה Comparable
נקראת סדר טבעי. הסיבה לכך היא שבשיטה compareTo()
אתה מגדיר את הדרך הנפוצה ביותר, או הטבעית, להשוואת אובייקטים מהמחלקה הזו. לג'אווה יש כבר סדר טבעי. לדוגמה, Java יודעת שמחרוזות ממוינות לרוב בסדר אלפביתי, ומספרים על ידי הגדלת ערך מספרי. לכן, אם תקרא לשיטה sort()
ברשימת מספרים או מחרוזות, הם ימוינו. אם התוכנית שלנו בדרך כלל תשווה ותמיין מכוניות לפי שנת ייצור, אזי עלינו להגדיר את המיון הטבעי למכוניות באמצעות הממשק Comparable<Car>
והשיטה compareTo()
. אבל מה אם זה לא מספיק לנו? בואו נדמיין שהתוכנית שלנו לא כל כך פשוטה. ברוב המקרים מתאים לנו המיון הטבעי של המכוניות (שקבענו שיבוצע לפי שנת ייצור). אבל לפעמים הלקוחות שלנו חובבי נהיגה מהירה. אם אנחנו מכינים להם קטלוג רכבים לעיון, יש למיין את המכוניות לפי מהירות מרבית. לדוגמה, נניח שאנחנו צריכים למיין כך 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
. זהו זה! היום למדת שני מנגנונים חשובים מאוד שתשתמש בהם לעתים קרובות בפרויקטים אמיתיים בעבודה. אבל, כידוע, תיאוריה ללא תרגול היא כלום. עכשיו הגיע הזמן לגבש את הידע שלך ולהשלים כמה משימות!
GO TO FULL VERSION