۳۰ آذر ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

مرتب سازی اشیاء جاوا با Comparable و Comparator

آیا باید بدانید که چگونه اشیاء جاوا را در یک مجموعه، آرایه یا نقشه مرتب کنید؟ در اینجا نحوه استفاده از رابط های Comparable و Comparator و اجتناب از ClassCastException ها آورده شده است.

آیا باید بدانید که چگونه اشیاء جاوا را در یک مجموعه، آرایه یا نقشه مرتب کنید؟ در اینجا نحوه استفاده از رابط های Comparable و Comparator و اجتناب از ClassCastException ها آورده شده است.

برنامه نویسان اغلب نیاز دارند عناصر را از پایگاه داده به مجموعه، آرایه یا نقشه مرتب کنند. در جاوا، می‌توانیم هر الگوریتم مرتب‌سازی را با هر نوع که بخواهیم پیاده‌سازی کنیم.

با استفاده از رابط Comparable و روش compareTo()، می‌توانیم با استفاده از ترتیب حروف الفبا، طول String، ترتیب حروف الفبا معکوس یا اعداد مرتب کنیم. . رابط Comparator به ما امکان می دهد همین کار را انجام دهیم اما به روشی انعطاف پذیرتر.

هر کاری که می‌خواهیم انجام دهیم، فقط باید بدانیم که چگونه منطق مرتب‌سازی صحیح را برای رابط و نوع داده‌شده پیاده‌سازی کنیم.

مرتب‌سازی با جاوا Comparable و Comparator

در اینجا چیزی است که در این مقاله درباره مرتب سازی اشیاء جاوا خواهید آموخت:

  • مرتب‌سازی با مقایسه:
    • مرتب سازی لیست
    • جاوا

    • نحوه عملکرد compareTo() جاوا
    • نحوه مرتب سازی آرایه ها در جاوا
    • مرتب‌سازی جاوا نقشه با TreeMap
    • مرتب کردن Set جاوا با TreeSet
    • نحوه اجتناب از ClassCastException هنگام مرتب‌سازی
    • استفاده از مقایسه با کلاس‌های اصلی جاوا
  • مرتب سازی لیست
  • جاوا

  • نحوه عملکرد compareTo() جاوا
  • نحوه مرتب سازی آرایه ها در جاوا
  • مرتب‌سازی جاوا نقشه با TreeMap
  • مرتب کردن Set جاوا با TreeSet
  • نحوه اجتناب از ClassCastException هنگام مرتب‌سازی
  • استفاده از مقایسه با کلاس‌های اصلی جاوا
  • مرتب سازی با مقایسه کننده:
    • استفاده از مقایسه کننده با کلاس های داخلی ناشناس
    • استفاده از مقایسه کننده با عبارات لامبدا
  • استفاده از مقایسه کننده با کلاس های داخلی ناشناس
  • استفاده از مقایسه کننده با عبارات لامبدا

مرتب‌سازی با Comparable

ما با نحوه مرتب‌سازی با استفاده از مقایسه. ما زمانی از مقایسه‌پذیر استفاده می‌کنیم که یک مقایسه پیش‌فرض برای شی مورد نظر وجود داشته باشد.

مرتب سازی فهرست جاوا

در این مثال اول، Comparable را در کلاس Simpson با استفاده از Simpson در نوع عمومی پیاده‌سازی می‌کنیم:


class Simpson implements Comparable<Simpson> {
    String name;

    Simpson(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(Simpson simpson) {
        return this.name.compareTo(simpson.name);
    }
}

public class SimpsonSorting {

     public static void main(String... sortingWithList) {
        List<SimpsonCharacter> simpsons = new ArrayList<>();
        simpsons.add(new SimpsonCharacter("Homer "));
        simpsons.add(new SimpsonCharacter("Marge "));
        simpsons.add(new SimpsonCharacter("Bart "));
        simpsons.add(new SimpsonCharacter("Lisa "));

        Collections.sort(simpsons);
        simpsons.stream().map(s -> s.name).forEach(System.out::print);

        Collections.reverse(simpsons);
        simpsons.stream().forEach(System.out::print);
    }

}

توجه داشته باشید که compareTo() و در یک شی Simpson دیگر ارسال شد. ما همچنین روش toString() را لغو کرده‌ایم، فقط برای آسان‌تر خواندن مثال.

چاپ با toString

روش toString تمام اطلاعات شی را نشان می دهد. وقتی شی را چاپ می کنیم، خروجی همان چیزی است که در toString() پیاده سازی شده است.

جاوا compareTo() چگونه کار می کند

روش compareTo() یک شی داده شده یا نمونه فعلی را با یک شی مشخص شده مقایسه می کند تا ترتیب اشیا را تعیین کند. در اینجا نگاهی گذرا به نحوه عملکرد compareTo() داریم.

روش compareTo()

اگر مقایسه برگردد …

سپس…

>= ۱

this.name > simpson.name

۰

this.name == simpson.name

<= -1

this.name < simpson.name

اگر مقایسه برگردد …

سپس…

>= ۱

this.name > simpson.name

۰

this.name == simpson.name

<= -1

this.name < simpson.name

ما فقط می توانیم از کلاس هایی استفاده کنیم که با متد sort() قابل مقایسه باشند. اگر سعی کنیم Simpson را ارسال کنیم که مقایسه را اجرا نمی کند، یک خطای کامپایل دریافت خواهیم کرد.

روش sort() از استفاده می کند چندشکلی با عبور از هر شیئی که مقایسه است. سپس اشیاء طبق انتظار مرتب خواهند شد.

خروجی کد قبلی این خواهد بود:


Bart Homer Lisa Marge 

اگر می‌خواهیم ترتیب را معکوس کنیم، می‌توانیم sort() را با reverse() مبادله کنیم. از:


Collections.sort(simpsons);

به:


Collections.reverse(simpsons);

استقرار روش reverse() خروجی قبلی را به:

تغییر می‌دهد


Marge Lisa Homer Bart 

نحوه مرتب سازی آرایه جاوا

در جاوا، ما می‌توانیم آرایه‌ای را با هر نوع که می‌خواهیم مرتب کنیم تا زمانی که رابط Comparable را پیاده‌سازی کند. این یک مثال است:


public class ArraySorting {

    public static void main(String... moeTavern) {
        int[] moesPints = new int[] {9, 8, 7, 6, 1};

        Arrays.sort(moesPints);

        Arrays.stream(moesPints).forEach(System.out::print);

        Simpson[] simpsons = new Simpson[]{new Simpson("Lisa"), new Simpson("Homer")};

        Arrays.sort(simpsons);
        Arrays.stream(simpsons).forEach(System.out::println);
    }
}

در اولین فراخوانی sort()، آرایه به ترتیب زیر طبقه بندی می شود:


۱ ۶ ۷ ۸ ۹

در دومین فراخوان sort()، به این صورت مرتب شده است:


Homer Lisa

به‌خاطر داشته باشید که اشیاء سفارشی باید مقایسه‌پذیر را پیاده‌سازی کنند تا حتی به‌عنوان یک آرایه مرتب شوند.

اجتناب از ClassCastExceptions هنگام مرتب کردن اشیاء جاوا

اگر شی سیمپسون مقایسه را اجرا نمی کرد، یک ClassCastException پرتاب می شود. اگر این را به عنوان آزمایش اجرا کنید، چیزی شبیه خروجی زیر خواهید دید:


Error:(16, 20) java: no suitable method found for sort(java.util.List<com.javaworld.javachallengers.sortingcomparable.Simpson>)
    method java.util.Collections.<T>sort(java.util.List<T>) is not applicable
      (inference variable T has incompatible bounds
        equality constraints: com.javaworld.javachallengers.sortingcomparable.Simpson
        lower bounds: java.lang.Comparable<? super T>)
    method java.util.Collections.<T>sort(java.util.List<T>,java.util.Comparator<? super T>) is not applicable
      (cannot infer type-variable(s) T
        (actual and formal argument lists differ in length))

این گزارش ممکن است گیج کننده باشد، اما نگران نباشید. فقط به خاطر داشته باشید که یک ClassCastException برای هر شی مرتب شده ای که رابط مقایسه را اجرا نمی کند پرتاب می شود.

مرتب سازی نقشه با TreeMap

جاوا API شامل کلاس های زیادی برای کمک به مرتب‌سازی، از جمله TreeMap< /a>. در مثال زیر، ما از TreeMap برای مرتب کردن کلیدها در Map استفاده می‌کنیم.


public class TreeMapExample {

    public static void main(String... barney) {
        Map<SimpsonCharacter, String> simpsonsCharacters = new TreeMap<>();
        simpsonsCharacters.put(new SimpsonCharacter("Moe"), "shotgun");
        simpsonsCharacters.put(new SimpsonCharacter("Lenny"), "Carl");
        simpsonsCharacters.put(new SimpsonCharacter("Homer"), "television");
        simpsonsCharacters.put(new SimpsonCharacter("Barney"), "beer");

        System.out.println(simpsonsCharacters);
    }
}

TreeMap از روش compareTo() استفاده می کند که توسط رابط Comparable پیاده سازی شده است. هر عنصر در Map حاصل بر اساس کلید خود مرتب شده است. در این حالت، خروجی این خواهد بود:


Barney=beer, Homer=television, Lenny=Carl, Moe=shotgun

به یاد داشته باشید: اگر شیء مقایسه را پیاده سازی نکند، یک ClassCastException دریافت خواهید کرد.

مرتب کردن مجموعه با TreeSet

واسط Set مسئول ذخیره مقادیر منحصر به فرد است، اما زمانی که از TreeSet پیاده سازی، عناصر درج شده به طور خودکار با اضافه کردن آنها مرتب می شوند:


public class TreeSetExample {

    public static void main(String... barney) {
        Set<SimpsonCharacter> simpsonsCharacters = new TreeSet<>();
        simpsonsCharacters.add(new SimpsonCharacter("Moe"));
        simpsonsCharacters.add(new SimpsonCharacter("Lenny"));
        simpsonsCharacters.add(new SimpsonCharacter("Homer"));
        simpsonsCharacters.add(new SimpsonCharacter("Barney"));

        System.out.println(simpsonsCharacters);
    }
}

خروجی این کد این است:


Barney, Homer, Lenny, Moe

دوباره، اگر از شیئی استفاده کنیم که قابل مقایسه نیست، یک ClassCastException دریافت خواهیم کرد.

در مرحله بعدی: استفاده از Comparable با کلاس های اصلی جاوا

استفاده از Comparable با کلاس های اصلی جاوا

بسیاری از کلاس‌ها و اشیاء اصلی جاوا رابط Comparable را پیاده‌سازی می‌کنند، به این معنی که ما مجبور نیستیم منطق compareTo() را برای آن کلاس‌ها پیاده‌سازی کنیم. در اینجا چند مثال آشنا آورده شده است:


public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence { ...

public final class Integer extends Number implements Comparable<Integer> { …

public final class Double extends Number implements Comparable<Double> {...

مرتب سازی با مقایسه

اگر نخواهیم از همان روش compareTo() از کلاس POJO استفاده کنیم، چه؟ آیا می‌توانیم روش مقایسه را برای استفاده از منطقی متفاوت لغو کنیم؟ در زیر یک مثال آمده است:


public class BadExampleOfComparable {

    public static void main(String... args) {
        List<SimpsonCharacter> characters = new ArrayList<>();

        SimpsonCharacter homer = new SimpsonCharacter("Homer") {
            @Override
            public int compareTo(SimpsonCharacter simpson) {
                return this.name.length() - (simpson.name.length());
            }
        };

        SimpsonCharacter moe = new SimpsonCharacter("Moe") {
            @Override
            public int compareTo(SimpsonCharacter simpson) {
                return this.name.length() - (simpson.name.length());
            }
        };

        characters.add(homer);
        characters.add(moe);

        Collections.sort(characters);

        System.out.println(characters);
    }

}

همانطور که می بینید، این کد پیچیده است و شامل تکرارهای زیادی است. ما مجبور شدیم برای یک منطق دو بار روش compareTo() را لغو کنیم. اگر عناصر بیشتری وجود داشت، باید منطق را برای هر شیء تکرار کنیم.

خوشبختانه، ما Comparator، که به ما امکان می دهد منطق compareTo() را از کلاس های جاوا جدا کنیم. همان مثال بالا را که با استفاده از Comparator:

بازنویسی شده است، در نظر بگیرید


public class GoodExampleOfComparator {

    public static void main(String... args) {
        List<SimpsonCharacter> characters = new ArrayList<>();

        SimpsonCharacter homer = new SimpsonCharacter("Homer");
        SimpsonCharacter moe = new SimpsonCharacter("Moe");

        characters.add(homer);
        characters.add(moe);

        Collections.sort(characters, (Comparator.<SimpsonCharacter>
                        comparingInt(character1 -> character1.name.length())
                        .thenComparingInt(character2 -> character2.name.length())));

        System.out.println(characters);
    }
}

تفاوت های مقایسه کننده و مقایسه کننده

این مثال ها تفاوت اصلی بین مقایسه و مقایسه را نشان می دهد. هنگامی که یک مقایسه پیش فرض برای شی شما وجود دارد، از مقایسه استفاده کنید. زمانی که باید در مورد compareTo() موجود کار کنید، یا زمانی که نیاز به استفاده از منطق خاص به روشی انعطاف‌پذیرتر دارید، از Comparator استفاده کنید. مقایسه کننده منطق مرتب سازی را از شی شما جدا می کند و منطق compareTo() را در روش sort() شما دارد.

استفاده از مقایسه کننده با کلاس های داخلی ناشناس

در این مثال بعدی، ما از یک ناشناس استفاده می کنیم کلاس داخلی برای مقایسه ارزش اشیا. یک کلاس داخلی ناشناس، در این مورد، هر کلاسی است که Comparator را پیاده‌سازی می‌کند. استفاده از آن به این معنی است که ما ملزم به نمونه سازی کلاس نامگذاری شده در پیاده سازی یک رابط نیستیم. در عوض، ما متد compareTo() را در داخل کلاس داخلی ناشناس پیاده‌سازی می‌کنیم.


public class MarvelComparator {

    public static void main(String... comparator) {
        List<String> marvelHeroes = new ArrayList<>();

        marvelHeroes.add("SpiderMan ");
        marvelHeroes.add("Wolverine ");
        marvelHeroes.add("Xavier ");
        marvelHeroes.add("Cyclops ");


        Collections.sort(marvelHeroes, new Comparator<String>() {
            @Override
            public int compare(String hero1, String hero2) {
                return hero1.compareTo(hero2);
            }
        });

        Collections.sort(marvelHeroes, (m1, m2) -> m1.compareTo(m2));

        Collections.sort(marvelHeroes, Comparator.naturalOrder());

        marvelHeroes.forEach(System.out::print);
    }
}

اطلاعات بیشتر در مورد کلاس های داخلی

یک کلاس داخلی ناشناس به سادگی هر کلاسی است که نام آن مهم نیست و رابطی را که ما اعلام می کنیم پیاده سازی می کند. بنابراین، در مثال، Comparator جدید در واقع نمونه‌سازی کلاسی است که نامی ندارد، که متد را با منطقی که ما می‌خواهیم پیاده‌سازی می‌کند.

استفاده از مقایسه کننده با عبارات لامبدا

کلاس‌های داخلی ناشناس پرحرف هستند، که می‌تواند در کد ما مشکل ایجاد کند. در رابط Comparator، می‌توانیم از عبارات لامبدا برای ساده‌سازی و خوانایی کد استفاده کنیم. برای مثال، می‌توانیم این را تغییر دهیم:


Collections.sort(marvel, new Comparator<String>() {
            @Override
            public int compare(String hero1, String hero2) {
                return hero1.compareTo(hero2);
            }
        });

به این:


Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));

کد کمتر و نتیجه یکسان!

خروجی این کد خواهد بود:


Cyclops SpiderMan Wolverine Xavier 

می‌توانیم با تغییر این کد را ساده‌تر کنیم:


Collections.sort(marvel, (m1, m2) -> m1.compareTo(m2));

به این:


Collections.sort(marvel, Comparator.naturalOrder());

عبارات لامبدا در جاوا

درباره عبارات لامبدا در جاوا بیشتر بیاموزید.

چالش رابط Comparable را انجام دهید!

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


public class SortComparableChallenge {

    public static void main(String... doYourBest) {
        Set<Simpson> set = new TreeSet<>();
        set.add(new Simpson("Homer"));
        set.add(new Simpson("Marge"));
        set.add(new Simpson("Lisa"));
        set.add(new Simpson("Bart"));
        set.add(new Simpson("Maggie"));

        List<Simpson> list = new ArrayList<>();
        list.addAll(set);
        Collections.reverse(list);
        list.forEach(System.out::println);
    }

    static class Simpson implements Comparable<Simpson> {
        String name;

        public Simpson(String name) {
            this.name = name;
        }

        public int compareTo(Simpson simpson) {
            return simpson.name.compareTo(this.name);
        }

        public String toString() {
            return this.name;
        }
    }
}

A)


Bart
Homer
Lisa
Maggie
Marge

B)


Maggie
Bart
Lisa
Marge
Homer

ج)


Marge
Maggie
Lisa
Homer
Bart

D)


Indeterminate

TreeSet and reverse()

با نگاهی به کد، اولین چیزی که باید متوجه شوید این است که ما از TreeSet استفاده می کنیم، بنابراین عناصر به طور خودکار مرتب می شوند. دومین مورد این است که ترتیب مقایسه معکوس است، بنابراین مرتب سازی به ترتیب معکوس انجام می شود.

وقتی برای اولین بار عناصر را به لیست اضافه می کنیم، TreeSet به طور خودکار آنها را به این صورت مرتب می کند:


Marge
Maggie
Lisa
Homer
Bart

سپس از روش reverse() استفاده می کنیم که ترتیب عناصر را معکوس می کند. بنابراین خروجی نهایی A):

خواهد بود


Bart
Homer
Lisa
Maggie
Marge

چالش ویدیویی! اشکال زدایی sort() و TreeSet

اشکال‌زدایی یکی از ساده‌ترین راه‌ها برای جذب کامل مفاهیم برنامه‌نویسی و در عین حال بهبود کد شماست. در این ویدیو می‌توانید در حین رفع اشکال و توضیح چالش مرتب‌سازی جاوا همراه باشید:

اشتباهات رایج با Comparable

  • تلاش برای مرتب کردن یک شی غیر قابل مقایسه در روش sort().
  • استفاده از مقایسه برای استراتژی‌های مرتب‌سازی مختلف در یک شی.
  • مقایسه معکوس در روش compareTo() به طوری که مرتب‌سازی به ترتیب معکوس باشد، مانند:
    ترتیب عادی:

    
    public int compareTo(Simpson simpson) {
    this.name.compareTo(simpson.name);
    }
    

    ترتیب معکوس:

    
    public int compareTo(Simpson simpson) {
    simpson.name.compareTo(this.name);
    }
    
  • افزودن یک شی غیر قابل مقایسه (هر شیئی که مقایسه را اجرا نمی کند) در یک شی TreeSet یا TreeMap.

public int compareTo(Simpson simpson) {
this.name.compareTo(simpson.name);
}

public int compareTo(Simpson simpson) {
simpson.name.compareTo(this.name);
}

چه چیزی را در مورد مرتب سازی با جاوا به خاطر بسپارید

  • از مقایسه هنگامی که مقایسه برای کلاس داده شده استاندارد است استفاده کنید.
  • از مقایسه کننده هنگامی که به انعطاف پذیری بیشتری نیاز دارید استفاده کنید.
  • امکان استفاده از لامبدا با مقایسه کننده وجود دارد.
  • بسیاری از کلاس های اصلی جاوا مقایسه را پیاده سازی می کنند.
  • از TreeMap یا TreeSet هنگام مرتب‌سازی Map یا Set استفاده کنید.
  • روش compareTo() با Comparable و Comparator کار می‌کند.
  • متد compareTo() اگر یک شی بزرگتر از دیگری باشد یک عدد مثبت، اگر کوچکتر باشد منفی و اگر یکسان باشند صفر را برمی‌گرداند.