تنبل ها تنها کسانی نیستند که در مورد مقایسه کننده ها و مقایسه ها در جاوا می نویسند. من تنبل نیستم، پس لطفاً از توضیح دیگری لذت ببرید. امیدوارم زائد نباشد. و بله، این مقاله پاسخ به این سوال است: " آیا می توانید یک مقایسه کننده از حفظ بنویسید؟ " امیدوارم همه بتوانند پس از خواندن این مقاله یک مقایسه کننده از حفظ بنویسند.

معرفی
همانطور که می دانید جاوا یک زبان شی گرا است. در نتیجه، دستکاری اشیا در جاوا مرسوم است. اما دیر یا زود، شما با وظیفه مقایسه اشیاء بر اساس برخی ویژگی ها روبرو می شوید. به عنوان مثال : فرض کنید پیامی داریم که توسطMessage
کلاس توضیح داده شده است:
public static class Message {
private String message;
private int id;
public Message(String message) {
this.message = message;
this.id = new Random().nextInt(1000);
}
public String getMessage() {
return message;
}
public Integer getId() {
return id;
}
public String toString() {
return "[" + id + "] " + message;
}
}
این کلاس را در کامپایلر جاوا Tutorialspoint
قرار دهید . فراموش نکنید که دستورهای import را نیز اضافه کنید:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
در main
روش، چندین پیام ایجاد کنید:
public static void main(String[] args){
List<Message> messages = new ArrayList();
messages.add(new Message("Hello, World!"));
messages.add(new Message("Hello, Sun!"));
System.out.println(messages);
}
بیایید به این فکر کنیم که اگر بخواهیم آنها را با هم مقایسه کنیم چه کار می کنیم؟ به عنوان مثال، می خواهیم بر اساس id مرتب کنیم. و برای ایجاد نظم باید به نحوی اشیا را با هم مقایسه کنیم تا بفهمیم کدام شی باید اول بیاید (یعنی کوچکتر) و کدام یک باید دنبال شود (یعنی بزرگتر). بیایید با کلاسی مانند java.lang.Object
شروع کنیم . می دانیم که همه کلاس ها به طور ضمنی Object
کلاس را به ارث می برند. و این منطقی است زیرا این مفهوم را منعکس می کند که "همه چیز یک شی است" و رفتار مشترک را برای همه طبقات ارائه می دهد. این کلاس حکم می کند که هر کلاس دو متد داشته باشد: → hashCode
متد hashCode
مقداری int
نمایش عددی ( ) از شی را برمی گرداند. معنی آن چیست؟ به این معنی که اگر شما دو نمونه متفاوت از یک کلاس ایجاد کنید، باید hashCode
s های متفاوتی داشته باشند. توضیحات متد به همان اندازه میگوید: «تا آنجایی که به طور منطقی عملی است، متد hashCode که توسط کلاس Object تعریف شده است، اعداد صحیح مجزا را برای اشیاء مجزا برمیگرداند». به عبارت دیگر، برای دو instance
s مختلف، باید hashCode
s های مختلف وجود داشته باشد. یعنی این روش برای مقایسه ما مناسب نیست. → equals
_ این equals
روش به این سوال پاسخ می دهد که "آیا این اشیاء برابر هستند؟" و a را برمی گرداند boolean
." به طور پیش فرض، این متد دارای کد زیر است:
public boolean equals(Object obj) {
return (this == obj);
}
یعنی اگر این روش overrid نشود، اساساً می گوید که آیا ارجاعات شی مطابقت دارند یا خیر. این چیزی نیست که ما برای پیامهایمان میخواهیم، زیرا به شناسههای پیام علاقهمندیم، نه مراجع شی. و حتی اگر روش را نادیده بگیریم equals
، بیشترین امیدی که می توانیم به آن داشته باشیم این است که بفهمیم آیا آنها برابر هستند یا خیر. و این برای تعیین ترتیب کافی نیست. پس ما چه نیازی داریم؟ ما به چیزی نیاز داریم که قابل مقایسه باشد. کسی که مقایسه می کند یک Comparator
. Java API را
باز کنید و Comparator را
پیدا کنید . java.util.Comparator
در واقع، یک رابط وجود دارد java.util.Comparator and java.util.Comparable
همانطور که می بینید، چنین رابطی وجود دارد. کلاسی که آن را پیادهسازی میکند، میگوید: «من متدی را پیادهسازی میکنم که اشیاء را با هم مقایسه میکند». تنها چیزی که واقعاً باید به خاطر بسپارید قرارداد مقایسه کننده است که به صورت زیر بیان می شود:
Comparator returns an int according to the following rules:
- It returns a negative int if the first object is smaller
- It returns a positive int if the first object is larger
- It returns zero if the objects are equal
حالا بیایید یک مقایسه بنویسیم. ما نیاز به واردات خواهیم داشت java.util.Comparator
. بعد از دستور import، موارد زیر را به main
روش اضافه کنید: Comparator<Message> comparator = new Comparator<Message>();
البته، این کار نمی کند، زیرا Comparator
یک رابط است. بنابراین بریس های فرفری را {}
بعد از پرانتز اضافه می کنیم. روش زیر را داخل بریس ها بنویسید:
public int compare(Message o1, Message o2) {
return o1.getId().compareTo(o2.getId());
}
حتی لازم نیست املا را به خاطر بسپارید. مقايسه کننده کسي است که مقايسه مي کند، يعني مقايسه مي کند. برای نشان دادن ترتیب نسبی اشیا، یک عدد را برمی گردانیم int
. اساساً همین است. خوب و ساده. همانطور که از مثال می بینید، علاوه بر Comparator، یک رابط دیگر وجود دارد - java.lang.Comparable
که ما را ملزم به پیاده سازی compareTo
روش می کند. این رابط می گوید، "کلاسی که من را پیاده سازی می کند، مقایسه نمونه های کلاس را ممکن می کند." به عنوان مثال، Integer
پیاده سازی To compare
به شرح زیر است:
(x < y) ? -1 : ((x == y) ? 0 : 1)
جاوا 8 تغییرات خوبی را ارائه کرد. اگر نگاه دقیقتری به رابط بیندازید Comparator
، حاشیهنویسی را در بالای آن خواهید دید @FunctionalInterface
. این حاشیه نویسی برای اهداف اطلاعاتی است و به ما می گوید که این رابط کاربردی است. این بدان معناست که این رابط تنها 1 متد انتزاعی دارد که روشی بدون پیاده سازی است. این چه چیزی به ما می دهد؟ اکنون می توانیم کد مقایسه کننده را به صورت زیر بنویسیم:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
متغیرهای داخل پرانتز را نام می بریم. جاوا خواهد دید که چون تنها یک روش وجود دارد، تعداد و انواع پارامترهای ورودی مورد نیاز مشخص است. سپس از عملگر arrow برای ارسال آنها به این قسمت از کد استفاده می کنیم. علاوه بر این، به لطف جاوا 8، ما اکنون روش های پیش فرض را در رابط ها داریم. این روش ها به طور پیش فرض زمانی که ما یک رابط را پیاده سازی می کنیم ظاهر می شوند. رابط Comparator
دارای چندین است. مثلا:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
روش دیگری وجود دارد که کد شما را پاکتر میکند. به مثال بالا نگاه کنید، جایی که ما مقایسه کننده خود را تعریف کردیم. چه کار میکند؟ کاملا ابتدایی است. به سادگی یک شی را می گیرد و مقداری را استخراج می کند که "قابل مقایسه" است. به عنوان مثال، Integer
پیادهسازی comparable
، بنابراین میتوانیم یک عملیات compareTo را روی مقادیر فیلدهای شناسه پیام انجام دهیم. این تابع مقایسه کننده ساده را می توان به صورت زیر نوشت:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
به عبارت دیگر، یک داریم Comparator
که به این صورت مقایسه میکند: اشیا را میگیرد، از getId()
روش برای گرفتن a Comparable
از آنها استفاده میکند و سپس compareTo
برای مقایسه استفاده میکند. و هیچ سازه وحشتناک دیگری وجود ندارد. و در نهایت، من می خواهم به یک ویژگی دیگر توجه کنم. مقایسه کننده ها را می توان زنجیر کرد. مثلا:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());
کاربرد
اعلام مقایسه کننده کاملاً منطقی است، فکر نمی کنید؟ حال باید ببینیم چگونه و کجا از آن استفاده کنیم. ←Collections.sort(java.util.Collections)
البته میتوانیم مجموعهها را از این طریق مرتب کنیم. اما نه هر مجموعه، فقط فهرستها. هیچ چیز غیرعادی در اینجا وجود ندارد، زیرا لیست ها مجموعه هایی هستند که در آنها به عناصر با فهرست آنها دسترسی پیدا می کنید. این اجازه می دهد تا عنصر دوم با عنصر سوم جایگزین شود. به همین دلیل است که روش مرتب سازی زیر فقط برای لیست ها است:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
→ Arrays.sort(java.util.Arrays)
آرایه ها نیز به راحتی مرتب می شوند. باز هم، به همین دلیل - عناصر آنها توسط شاخص قابل دسترسی هستند. → Descendants of java.util.SortedSet and java.util.SortedMap
شما آن را به خاطر خواهید آورد Set
و Map
ترتیب ذخیره عناصر را تضمین نمی کنید. اما، ما پیاده سازی های ویژه ای داریم که سفارش را تضمین می کنند. و اگر عناصر یک مجموعه اجرا نمی شوند java.util.Comparable
، می توانیم a را Comparator
به سازنده آن ارسال کنیم:
Set<Message> msgSet = new TreeSet(comparator);
← Stream API
در Stream API، که در جاوا 8 ظاهر شد، مقایسهکنندهها به شما اجازه میدهند کار با عناصر جریان را ساده کنید. برای مثال، فرض کنید به دنباله ای از اعداد تصادفی از 0 تا 999 نیاز داریم که شامل:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
.limit(10)
.sorted(Comparator.naturalOrder())
.forEach(e -> System.out.println(e));
میتوانیم در اینجا توقف کنیم، اما مشکلات جالبتری هم وجود دارد. به عنوان مثال، فرض کنید باید یک را تهیه کنید Map
، که در آن کلید یک شناسه پیام است. علاوه بر این، ما می خواهیم این کلیدها را مرتب کنیم، بنابراین با کد زیر شروع می کنیم:
Map<Integer, Message> collected = Arrays.stream(messages)
.sorted(Comparator.comparing(msg -> msg.getId()))
.collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
ما در واقع یک HashMap
اینجا دریافت می کنیم. و همانطور که می دانیم، هیچ سفارشی را تضمین نمی کند. در نتیجه، عناصر ما، که بر اساس id مرتب شده اند، به سادگی ترتیب خود را از دست می دهند. خوب نیست. ما باید کلکسیونرمان را کمی تغییر دهیم:
Map<Integer, Message> collected = Arrays.stream(messages)
.sorted(Comparator.comparing(msg -> msg.getId()))
.collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
کد کمی ترسناک تر به نظر می رسد، اما اکنون مشکل به درستی حل شده است. اطلاعات بیشتر در مورد گروه بندی های مختلف را اینجا بخوانید:
شما می توانید کلکسیونر خود را ایجاد کنید. در اینجا بیشتر بخوانید: "ایجاد یک کلکتور سفارشی در جاوا 8"
. و از خواندن بحث در اینجا سود خواهید برد: "لیست جاوا 8 برای نقشه با جریان"
.
تله سقوط
Comparator
و Comparable
خوب هستند. اما یک نکته ظریف وجود دارد که باید به خاطر بسپارید. هنگامی که یک کلاس مرتب سازی را انجام می دهد، انتظار دارد که کلاس شما بتواند به یک تبدیل شود Comparable
. اگر اینطور نیست، در زمان اجرا یک خطا دریافت خواهید کرد. بیایید به یک مثال نگاه کنیم:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
به نظر می رسد که اینجا هیچ مشکلی وجود ندارد. اما در واقع، در مثال ما، با یک خطا شکست خواهد خورد: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable
و همه به این دلیل است که سعی کرده است عناصر را مرتب کند ( SortedSet
به هر حال یک است) ... اما نتوانست. SortedMap
این را هنگام کار با و فراموش نکنید SortedSet
.
ادامه مطلب: |
---|
GO TO FULL VERSION