JavaRush /وبلاگ جاوا /Random-FA /مقایسه کننده در جاوا
Viacheslav
مرحله

مقایسه کننده در جاوا

در گروه منتشر شد
فقط افراد تنبل درباره Comparator و Comparator در جاوا چیزی ننوشته اند. من تنبل نیستم - بنابراین از شما می خواهم که یک تغییر دیگر را دوست داشته باشید و از آن حمایت کنید. امیدوارم زائد نباشه و بله، این مقاله پاسخ به این سوال است: "آیا می توانید مقایسه ای از حفظ بنویسید؟" امیدوارم پس از خواندن این مقاله، همه بتوانند از حفظ یک مقایسه بنویسند.
مقایسه کننده در جاوا - 1
مقدمه جاوا به عنوان یک زبان شی گرا شناخته شده است. در نتیجه، در جاوا کار با اشیاء رایج است. اما دیر یا زود وظیفه مقایسه اشیاء بر اساس برخی اصول مطرح می شود. بنابراین، با توجه به: ما پیامی داریم که توسط کلاس 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 java.util.Random;
import java.util.ArrayList;
import java.util.List;
در روش اصلی ما چندین پیام ایجاد خواهیم کرد:
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 که توسط کلاس Object تعریف شده است، اعداد صحیح متمایز را برای اشیاء مجزا برمی‌گرداند» یعنی اگر این دو نمونه متفاوت هستند، پس باید متفاوت باشند. هش کدها یعنی این روش برای مقایسه ما مناسب نیست. ← مساوی متد برابر به سوال «آیا اشیاء برابر هستند» پاسخ می دهد و یک بولی برمی گرداند. این روش دارای کد پیش فرض است:
public boolean equals(Object obj) {
    return (this == obj);
}
یعنی بدون اینکه این متد را روی یک شی رد کند، این روش اساساً می گوید که آیا ارجاعات به شی مطابقت دارند یا خیر. این برای پیام‌های ما مناسب نیست، زیرا ما علاقه‌ای به پیوندهای شیء نداریم، ما به شناسه پیام علاقه‌مندیم. و حتی اگر روش برابر را نادیده بگیریم، حداکثر چیزی که به دست می‌آوریم این است: «مساوی هستند» یا «مساوی نیستند». اما این برای تعیین ترتیب کافی نیست.

مقایسه و مقایسه در جاوا

چه چیزی برای ما مناسب است؟ اگر کلمه Compare را در مترجم به انگلیسی ترجمه کنیم، ترجمه Compare را دریافت خواهیم کرد. عالی است، پس ما به کسی نیاز داریم که مقایسه کند. اگر این مقایسه را با هم مقایسه کنید، آن کس که مقایسه می کند مقایسه کننده است. بیایید Java Api را باز کنیم و Comparator را در آنجا پیدا کنیم . و در واقع، چنین رابطی وجود دارد - java.util.Comparator java.util.Comparator و java.lang.Comparable همانطور که می بینید، چنین رابطی وجود دارد. کلاسی که آن را پیاده‌سازی می‌کند می‌گوید: «من یک تابع برای مقایسه اشیاء پیاده‌سازی می‌کنم». تنها چیزی که واقعاً باید به خاطر بسپارید قرارداد مقایسه است که به شرح زیر بیان می شود:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
حالا بیایید یک مقایسه بنویسیم. ما باید java.util.Comparator را وارد کنیم . پس از وارد کردن، یک روش به main اضافه کنید: Comparator<Message> comparator = new Comparator<Message>(); طبیعتاً این کار نمی کند، زیرا مقایسه کننده یک رابط است. بنابراین، پس از پرانتز، فرفری ها را اضافه می کنیم { }. در این پرانتز روش را می نویسیم:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
حتی لازم نیست این را به خاطر بسپارید. مقایسه کننده کسی است که مقایسه می کند، یعنی مقایسه می کند. برای پاسخ به این سؤال که اشیاء مقایسه شده در چه ترتیبی هستند، int را برمی‌گردانیم. این همه، در واقع. به سادگی و به راحتی. همانطور که از مثال می بینیم، علاوه بر Comparator، یک رابط دیگر وجود دارد - java.lang.Comparable که برای پیاده سازی آن باید متد compareTo را تعریف کنیم . این رابط می گوید که "کلاسی که یک رابط را پیاده سازی می کند، امکان مقایسه نمونه های کلاس را فراهم می کند." به عنوان مثال، پیاده سازی CompareTo توسط Integer به شکل زیر است:
(x < y) ? -1 : ((x == y) ? 0 : 1)
چگونه تمام این رابط ها را به خاطر بسپاریم؟ برای چی؟ همه چیز از زبان انگلیسی است. مقایسه - برای مقایسه، مقایسه کننده، مقایسه کننده است (مثلاً به عنوان ثبت کننده. یعنی کسی که ثبت می کند) و صفت "مقایسه" قابل مقایسه است. خوب، "مقایسه با" نه تنها به عنوان مقایسه با، بلکه به عنوان مقایسه با ترجمه شده است. ساده است. زبان جاوا توسط افراد انگلیسی زبان نوشته می شد و در نامگذاری همه چیز در جاوا به سادگی با زبان انگلیسی هدایت می شدند و نوعی منطق در نامگذاری وجود داشت. و متد compareTo توضیح می دهد که چگونه یک نمونه از یک کلاس باید با نمونه های دیگر مقایسه شود. به عنوان مثال، رشته ها از نظر لغوی با هم مقایسه می شوند و اعداد با مقدار مقایسه می شوند.
مقایسه کننده در جاوا - 2
جاوا 8 تغییرات خوبی به ارمغان آورد. اگر به رابط مقایسه کننده دقت کنیم، خواهیم دید که یک حاشیه در بالای آن وجود دارد @FunctionalInterface. در واقع این حاشیه برای اطلاع رسانی است و به معنای کاربردی بودن این رابط است. این بدان معناست که این رابط تنها 1 روش انتزاعی بدون پیاده سازی دارد. این چه چیزی به ما می دهد؟ اکنون می توانیم کد مقایسه کننده را به صورت زیر بنویسیم:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
در پرانتز نحوه نامگذاری متغیرها آمده است. خود جاوا این را خواهد دید زیرا ... اگر فقط یک روش وجود داشته باشد، مشخص است که چه پارامترهای ورودی، چه تعداد و چه نوع مورد نیاز است. بعد با یک فلش می گوییم که می خواهیم آنها را به این قسمت از کد منتقل کنیم. علاوه بر این، به لطف جاوا 8، روش‌های پیش‌فرض در رابط‌ها ظاهر می‌شوند - اینها روش‌هایی هستند که به‌طور پیش‌فرض (به‌طور پیش‌فرض) هنگام پیاده‌سازی یک رابط ظاهر می‌شوند. چندین مورد از اینها در رابط Comparator وجود دارد. برای مثال:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
روش دیگری وجود دارد که کد شما را پاک‌تر می‌کند. بیایید به مثال بالا نگاه کنیم، جایی که مقایسه کننده خود را توضیح دادیم. داره چیکار میکنه؟ این کاملا ابتدایی است. به سادگی یک شی را می گیرد و مقداری از آن را استخراج می کند که قابل مقایسه است. به عنوان مثال، Integer پیاده‌سازی‌های قابل مقایسه را انجام می‌دهد، بنابراین ما توانستیم compareTo را روی مقادیر شناسه پیام انجام دهیم. این تابع مقایسه کننده ساده را نیز می توان به صورت زیر نوشت:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
یعنی به معنای واقعی کلمه، "ما یک مقایسه داریم که به این صورت مقایسه می کند: اشیا را می گیرد، با استفاده از متد getId() از آنها قابل مقایسه می شود، با استفاده از 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) آرایه ها نیز برای مرتب سازی راحت هستند. باز هم به همان دلیل دسترسی به عناصر با شاخص. → فرزندان java.util.SortedSet و java.util.SortedMap همانطور که به یاد داریم، Set و Map ترتیب ذخیره رکوردها را تضمین نمی کنند. اما ما پیاده سازی های ویژه ای داریم که نظم را تضمین می کند. و اگر عناصر مجموعه java.lang.Comparable را پیاده سازی نکنند، می توانیم 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<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
آنچه در اینجا به دست خواهیم آورد در واقع یک نقشه HashMap است. و همانطور که می دانیم هیچ گونه سفارشی را تضمین نمی کند. بنابراین، سوابق ما مرتب شده بر اساس شناسه به سادگی از کار افتاد. خوب نیست. ما باید کلکسیونر خود را کمی تغییر دهیم:
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));
کد کمی ترسناک تر به نظر می رسید، اما به لطف اجرای صریح TreeMap، مشکل اکنون به درستی حل شد. در اینجا می توانید اطلاعات بیشتری در مورد گروه های مختلف بخوانید: شما می توانید مجموعه را خودتان ایجاد کنید. شما می توانید در اینجا بیشتر بخوانید: "ایجاد یک کلکتور سفارشی در جاوا 8" . و خواندن بحث در اینجا مفید است: "لیست جاوا 8 برای نقشه با جریان" .
مقایسه کننده در جاوا - 3
چنگک های مقایسه و مقایسه خوب هستند. اما یک تفاوت ظریف در ارتباط با آنها وجود دارد که ارزش یادآوری را دارد. هنگامی که یک کلاس مرتب سازی را انجام می دهد، محاسبه می کند که می تواند کلاس شما را به 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 باید این را به خاطر بسپارید. علاوه بر این برای مشاهده توصیه می شود: Yuri Tkach: HashSet و TreeSet - مجموعه شماره 1 - جاوا پیشرفته
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION