مقایسه اشیاء با استفاده از رابط های Comparable و Comparator

دو رابط مفید برای مقایسه اشیایی که توسط کاربر تعریف شده اند وجود دارد. این دو رابط Comparable و Comparator می باشند. رابط Comparable در یک کلاس پیاده سازی می شود و اجازه می دهد کلاس یا شی ایجاد شده از کلاس با اشیاء دیگر از همین کلاس مقایسه شوند. Comparator در یک کلاس جداگانه پیاده سازی می شود. همانطور که از نام این رابط پیداست، پیاده سازی آن باعث ایجاد یک کلاس مقایسه پذیر می شود. گاهی اوقات لازم است که بخواهید دو شیء را بر اساس یک فیلد یا خاصیت مقایسه کنید، این کار را می توانید با استفاده از پیاده سازی رابط Comparator انجام دهید. حال به نحوه استفاده از رابط Comparable می پردازیم.

رابط Comparable

در مثال زیر یک کلاس نشان داده شده است که رابط Comparable را پیاده سازی می کند:

   1: class Person implements Comparable<Person>
   2: {
   3:     public String FirstName ;
   4:     public String LastName ;
   5:     public int Age ;
   6: 
   7:     public Person(String FirstName, String LastName, int Age) 
   8:     {
   9:         this.FirstName = FirstName;
  10:         this.LastName = LastName;
  11:         this.Age = Age;
  12:     }
  13: 
  14:     @Override
  15:     public int compareTo(Person other)
  16:     {                                                                 
  17:         if (this.Age > other.Age)          
  18:         return 1;                                         
  19:         else if (this.Age < other.Age)
  20:         return -1;                                       
  21:         else                                                   
  22:         return 0;                                          
  23:     }                                                                  
  24: }
  25: 
  26: public class Program 
  27: {
  28:     public static void main(String[] args)
  29:     {
  30:         Person person1 = new Person("John", "Smith", 21);
  31:         Person person2 = new Person("Mark", "Logan", 19);
  32:         Person person3 = new Person("Luke", "Adams", 20);
  33: 
  34:         Person youngest = GetYoungest(person1, person2, person3);
  35:         Person oldest = GetOldest(person1, person2, person3);
  36: 
  37:         System.out.println(String.format("The youngest person is %s %s", youngest.FirstName, youngest.LastName));
  38:         System.out.println(String.format("The oldest person is %s %s", oldest.FirstName, oldest.LastName));
  39:     }
  40: 
  41:     private static Person GetYoungest(Person person1, Person person2, Person person3)
  42:     {
  43:         Person youngest = person1;
  44: 
  45:         if (person2.compareTo(youngest) == -1)
  46:         youngest = person2;
  47: 
  48:         if (person3.compareTo(youngest) == -1)
  49:         youngest = person3;
  50: 
  51:         return youngest;
  52:     }
  53: 
  54:     private static Person GetOldest(Person person1, Person person2, Person person3)
  55:     {
  56:         Person oldest = person1;
  57: 
  58:         if (person2.compareTo(oldest) == 1)
  59:         oldest = person2;
  60: 
  61:         if (person3.compareTo(oldest) == 1)
  62:         oldest = person3;
  63: 
  64:         return oldest;
  65:     }
  66: 
  67: }

وقتی که یک کلاس از رابط Comparable استفاده می کند (خط 1)، لازم است که تنها متد آن یعنی متد ()compareTo را نیز پیاده سازی کند (خطوط 23-15). متد ()compareTo یک مقدار صحیح را بر می گرداند. این متد همچنین یک آرگومان قبول می کند که همان شیی است که قرار است با شی جاری مقایسه شود. در داخل متد ()compareTo در مثال بالا ما سن (age) شخص فعلی را با سن شخص دیگر مقایسه کرده ایم. طبق قرارداد اگر سن شخص مورد نظر ما از سن شخص دیگر بیشتر بود مقداری بزرگتر از صفر، اگر کمتر بود مقداری کمتر از صفر و اگر مساوی بود مقدار صفر برگشت داده می شود. خطوط 67-26 پیاده سازی رابط Comparable توسط شی Person را نشان می دهد. برنامه جوانترین و پیرترین شخص را تشخیص می دهد.
در خطوط 32-30 سه شی Person با مقادیر کاملا اختیاری ایجاد شده است. در خطوط 35-34 متغیرهایی برای نگهداری جوانترین و پیرترین شخص تعریف شده اند. در خط 34 متد ()GetYoungest را فراخوانی کرده ایم. این متد در خطوط 52-41 تعریف شده است و سه شخص را که قرار است از لحاظ سنی با هم مقایسه شوند را قبول می کند.
در خط 43 فرض را بر این گذاشته ایم که اولین شخص (person1) جوانترین شخص است. سپس با استفاده از پیاده سازی متد ()compareTo تست می کنیم که آیا شخص دوم (person2) از شخص اول جوانتر است یا نه. در داخل متد مذکور سن شخص دوم و اول را با هم مقایسه می کنیم. اگر سن شخص دوم کمتر بود، باید مقدار 1- برگشت داده شده و در خط 46، person2 به عنوان جوانترین شخص معرفی شود. در خطوط 49-48 از تکنیکی مشابه برای شخص سوم استفاده کرده ایم. بعد از مقایسه جوانترین شخص در خط 51 به عنوان نتیجه برگشت داده می شود. در خط 10 متد ()GetOldest که در خطوط 65-54 تعریف شده است فراخوانی می شود. کد های داخل این مت شبیه به متد ()GetYoungest است با این تفاوت که تست می شود که آیا سن شخص دیگر بزرگتر از سن شخص مورد نظر ماست یا نه؟ بنابراین باید انتظار داشته باشیم که مقدار 1 به جای 1- توسط متد برگشت داده شود. در خطوط 38-37 نام جوانترین و پیرترین شخص چاپ می شود.

رابط Comparator

Comparator در یک کلاس جداگانه پیاده سازی می شود. همانطور که از نام این رابط پیداست، پیاده سازی آن باعث ایجاد یک کلاس مقایسه پذیر می شود. به وسیله این رابط می توان چندین مقایسه برای کلاس Person ایجاد کرد.یعنی می توان مقایسه های مختلفی بر روی اشیاء یک کلاس انجام داد. روش کار به صورت زیر است :

در مثال زیر، اشیاء ایجاد شده از کلاس Person بر اساس سن(age)، نام و یا نام خانوادگی مورد مقایسه قرار می گیرند.

   1: import java.util.ArrayList;
   2: import java.util.Collections;
   3: import java.util.Comparator;
   4: import java.util.List;
   5: import java.util.Scanner;
   6: 
   7: class Person implements Comparable<Person>
   8: {
   9:     public String FirstName ;
  10:     public String LastName ;
  11:     public int Age ;
  12:     
  13:     public Person(String FirstName, String LastName, int Age) 
  14:     {
  15:          this.FirstName = FirstName;
  16:          this.LastName = LastName;
  17:          this.Age = Age;
  18:     }
  19:         
  20:     @Override
  21:     public int compareTo(Person other)
  22:     {                                 
  23:         if (this.Age > other.Age)     
  24:         return 1;                     
  25:         else if (this.Age < other.Age)
  26:         return -1;                    
  27:         else                          
  28:         return 0;                     
  29:     }                                 
  30: }
  31: 
  32: class FirstNameComparer implements Comparator<Person>
  33: {
  34:     @Override
  35:     public int compare(Person person1, Person person2)
  36:     {
  37:         return person1.FirstName.compareTo(person2.FirstName);
  38:     }
  39: }
  40: 
  41: class LastNameComparer implements Comparator<Person>
  42: {
  43:     @Override
  44:     public int compare(Person person1, Person person2)
  45:     {
  46:         return person1.LastName.compareTo(person2.LastName);
  47:     }
  48: }
  49: 
  50: class AgeComparer implements Comparator<Person>
  51: {
  52:     @Override
  53:     public int compare(Person person1, Person person2)
  54:     {
  55:         return person1.compareTo(person2);
  56:     }
  57: }
  58: 
  59: public class Program 
  60: {
  61:     public static void main(String[] args)
  62:     {
  63:         ArrayList<Person> persons = new ArrayList<Person>();
  64:         persons.add(new Person ("John", "Smith", 21));
  65:         persons.add(new Person ("Mark", "Logan", 19));
  66:         persons.add(new Person ("Luke", "Adams", 20));
  67:                 
  68:         System.out.println("Original Order");
  69:         for(Person p : persons)
  70:             System.out.println(String.format("%s %s, Age: %s", p.FirstName, p.LastName, p.Age));
  71: 
  72:         System.out.println("\nSort persons based on their:");
  73:         System.out.println("[1] FirstNamen[2] LastNamen[3]Age");
  74: 
  75:         System.out.println("Enter your choice: ");
  76:         Scanner input = new Scanner(System.in);
  77:         int choice = input.nextInt();
  78: 
  79:         ReorderPersons(choice, persons);
  80: 
  81:         System.out.println("New Order");
  82:         for(Person p : persons)
  83:             System.out.println(String.format("%s %s, Age: %s", p.FirstName, p.LastName, p.Age));
  84:     }
  85: 
  86:     private static void ReorderPersons(int choice, List persons)
  87:     {
  88:         Comparator comparer;
  89: 
  90:         if (choice == 1)
  91:             comparer = new FirstNameComparer();
  92:         else if (choice == 2)
  93:             comparer = new LastNameComparer();
  94:         else
  95:             comparer = new AgeComparer();
  96: 
  97:         Collections.sort(persons,comparer);
  98:     }   
  99: }
Original Order
John Smith, Age: 21
Mark Logan, Age: 19
Luke Adams, Age: 20

Sort persons based on their:
[1] FirstName
[2] LastName
[3]Age
Enter your choice: 1
New Order
John Smith, Age: 21
Luke Adams, Age: 20
Mark Logan, Age: 19
Original Order
John Smith, Age: 21
Mark Logan, Age: 19
Luke Adams, Age: 20

Sort persons based on their:
[1] FirstName
[2] LastName
[3]Age
Enter your choice: 2
New Order
Luke Adams, Age: 20
Mark Logan, Age: 19
John Smith, Age: 21
Original Order
John Smith, Age: 21
Mark Logan, Age: 19
Luke Adams, Age: 20

Sort persons based on their:
[1] FirstName
[2] LastName
[3]Age
Enter your choice: 3
New Order
Mark Logan, Age: 19
Luke Adams, Age: 20
John Smith, Age: 21

هنگام استفاده از این رابط لازم است یک متد به نام ()compare که دو شیء قبول می کند و یک عدد صحیح را به عنوان نتیجه بر می گرداند را پیاده سازی کند. از آنجاییکه از رابط Comparator استفاده کرده ایم متد ()compare به طور خودکار دو شئ Person قبول می کند. همانطور که اشاره شد، این رابط به وسیله کلاس جداگانه پیاده سازی می شود و ما این کار روا در خطوط 57-32 انجام داده ایم. در این خطوط سه کلاس تعریف کرده ایم که این رابط را پیاده سازی کرده (خطوط 52، 41 و 32) و در نتیجه وظیفه دارند که متد ()compare این رابط را هم Override کنند. در داخل بدنه این متد هم مقایسه های مختلف را می نویسیم.

در مثال بالا با استفاده از کلاس FirstNameComparer که رابط Comparator را پیاده سازی می کند، دو شئ Person بر اساس نام مقایسه می شوند. در متد ()Compare به سادگی و با استفاده از متد ()compareTo از رابط Comparable استفاده کرده ایم (چون خاصیت FirstName یک رشته است) و یک مقدار را به عنوان نتیجه بر می گردانیم.

به روشی مشابه از کلاس های LastNameComparer و AgeComparer برای مقایسه اشیاء بر اساس نام خانوادگی و سن استفاده می کنیم. متد ()compare در صورتی که دو پارامتر با هم برابر باشند، مقدار 0 ، اگر پارامتر اول از پارامتر دوم بزرگتر باشد، مقداری بزرگتر از 0 و اگر پارامتر اول از پارامتر دوم کوچکتر باشد مقداری کوچکتر از 0 را بر می گرداند. در مثال بالا از کاربر سوال می شود که لیستی از اشیاء را قرار است بر اساس کدام خاصیت مرتب کند.در خطوط 66-64 اشیا با مقادیر از پیش تعریف شده ای برای هر یک از خاصیت هایشان ایجاد شده است.در خطوط 70-68 ترتیب عادی و اصلی این اشیاء نمایش داده شده است. در خطوط 73-72 لیستی از انتخاب هایی که کاربر بر اساس آنها می تواند عملیات مرتب سازی را انجام دهد آورده شده است.در خطوط 77-75 از کاربر در مورد انتخابش سوال می شود. در خط 79 متد ()ReorderPersons متد از پیش تعریف شده ی خطوط 98-86 را فراخوانی می کنیم. این متد انتخاب کاربر و لیستی از اشیا که قرار است بر اساس خاصیتی که کاربر انتخاب کرده است مرتب شوند را قبول می کند. در داخل متد یک متغیر تعریف کرده ایم که از نوع Comparator است و در نتیجه می تواند هر نوع کلاسی که رابط مذکور را پیاده سازی می کند را شامل شود. در خطوط 95-90 چک می کنیم که اگر کاربر یک مقدار عددی خاص را انتخاب کرد، چه کارهایی انجام شود. در خط 38 از متد ()sort کلاس Collections استفاده کرده ایم. این متد دارای یک نسخه سربارگذاری شده است که یک شی Comparator را قبول می کند. ما در خط 97 کلاس مقایسه کننده بر اساس نوع انتخاب کاربر را به این متد می دهیم و سپس متد ()sort، شی Person را بر اساس این کلاس مرتب می کند.

به طور خلاصه کلاسی که رابط Comparable را پیاده سازی کند، به کلاسی مقایسه پذیر تبدیل می شود و کلاسی که رابط Comparator را پیاده سازی کند، می تواند به وسیله متدهای ()Collections.sort یا ()Arrays.sort مرتب شود، کاری که ما در خط 97 کد بالا انجام داده ایم.