CodeGym /وبلاگ جاوا /Random-FA /رابط مقایسه کننده جاوا
John Squirrels
مرحله
San Francisco

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

در گروه منتشر شد
تنبل ها تنها کسانی نیستند که در مورد مقایسه کننده ها و مقایسه ها در جاوا می نویسند. من تنبل نیستم، پس لطفاً از توضیح دیگری لذت ببرید. امیدوارم زائد نباشد. و بله، این مقاله پاسخ به این سوال است: " آیا می توانید یک مقایسه کننده از حفظ بنویسید؟ " امیدوارم همه بتوانند پس از خواندن این مقاله یک مقایسه کننده از حفظ بنویسند. رابط مقایسه جاوا - 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 را نیز اضافه کنید:
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نمایش عددی ( ) از شی را برمی گرداند. معنی آن چیست؟ به این معنی که اگر شما دو نمونه متفاوت از یک کلاس ایجاد کنید، باید hashCodes های متفاوتی داشته باشند. توضیحات متد به همان اندازه می‌گوید: «تا آنجایی که به طور منطقی عملی است، متد hashCode که توسط کلاس Object تعریف شده است، اعداد صحیح مجزا را برای اشیاء مجزا برمی‌گرداند». به عبارت دیگر، برای دو instances مختلف، باید hashCodes های مختلف وجود داشته باشد. یعنی این روش برای مقایسه ما مناسب نیست. → 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.

ادامه مطلب:

نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION