دانستنی‌ها

مرتب‌سازی با استفاده از عبارات لامبدا در جاوا 8

1- بررسی اجمالی

در این مقاله قصد داریم نگاهی بیندازیم به ویژگی لامبدا (Lambda) در جاوا 8 و به طور خاص نحوه استفاده از آن در ایجاد یک Comprator جهت مرتب‌سازی یک مجموعه.
ابتدا بیایید یک کلاس entity ساده تعریف کنیم:
public class Human {
    private String name;
    private int age;

    // standard constructors, getters/setters, equals and hashcode
}

2- مرتب‌سازی پایه بدون استفاده از لامبدا

تا قبل از جاوا 8، برای مرتب‌سازی یک مجموعه، باید یک کلاس داخلی بی‌نام برای Comprator ایجاد می‌کردیم تا در فرآیند مرتب‌سازی از آن استفاده کنیم:
	
new Comparator<Human>() {
    @Override
    public int compare(Human h1, Human h2) {
        return h1.getName().compareTo(h2.getName());
    }
}

این Comprator برای مرتب‌سازی لیست نمونه‌های Human به سادگی مورد استفاده قرار می‌گیرد:

@Test
public void givenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted() {
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10),
      new Human("Jack", 12)
    );
     
    Collections.sort(humans, new Comparator<Human>() {
        @Override
        public int compare(Human h1, Human h2) {
            return h1.getName().compareTo(h2.getName());
        }
    });
    Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

3- مرتب‌سازی پایه با کمک لامبدا

با معرفی لامبداها، می‌توانیم از ایجاد کلاس داخلی بی‌نام صرف نظر کرده و با استفاده‌ از semanticهای تابعی و ساده، به نتایج مشابهی دست یابیم:

(final Human h1, final Human h2) -> h1.getName().compareTo(h2.getName());

هم‌چنین مانند قبل می‌توانیم نتیجه را تست کنیم:

@Test
public void whenSortingEntitiesByName_thenCorrectlySorted() {
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10),
      new Human("Jack", 12)
    );
     
    humans.sort(
      (Human h1, Human h2) -> h1.getName().compareTo(h2.getName()));
  
    assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

توجه داشته باشید که به جای API قدیمی Collections.sort، از API جدید مرتب‌سازی که به java.util.List در جاوا 8 اضافه شده استفاده می‌کنیم.

4- مرتب‌سازی پایه بدون تعریف نوع

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

	
(h1, h2) -> h1.getName().compareTo(h2.getName())

و مجددا، می‌توانیم نتیجه را تست کنیم:

@Test
public void
  givenLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted() {
     
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10),
      new Human("Jack", 12)
    );
     
    humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
  
    assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

5- مرتب‌سازی با استفاده از «ارجاع به متدهای استاتیک»

در قدم بعدی، با استفاده از عبارت لامبدا و ارجاع به یک متد استاتیک، قصد داریم عمل مرتب‌سازی را انجام دهیم.

ابتدا، متد compareByNameThenAge را با امضایی کاملا مشابه با متد compare در شی <Comparator<Human تعریف می‌کنیم:

	
public static int compareByNameThenAge(Human lhs, Human rhs) {
    if (lhs.name.equals(rhs.name)) {
        return lhs.age - rhs.age;
    } else {
        return lhs.name.compareTo(rhs.name);
    }
}

حالا متد humans.sort را با این ارجاع فراخوانی می‌کنیم:

humans.sort(Human::compareByNameThenAge);

نتیجه نهایی، مرتب‌سازی یک مجموعه با استفاده از متد استاتیک به عنوان Comparator است.

@Test
public void
  givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
     
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10),
      new Human("Jack", 12)
    );
     
    humans.sort(Human::compareByNameThenAge);
    Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

6- مرتب‌سازی با استفاده از «ارجاع به متدهای شی»

حتی می‌توانیم با استفاده از ارجاع به متد شی (instance method reference) و متد Comparator.comparing، از تعریف منطق مقایسه هم خودداری کنیم.

از ()getName برای ساخت عبارت لامبدا و مرتب‌سازی لیست بر اساس نام استفاده می‌کنیم:

@Test
public void
  givenInstanceMethod_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
     
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10),
      new Human("Jack", 12)
    );
     
    Collections.sort(
      humans, Comparator.comparing(Human::getName));
    assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

7- مرتب‌سازی وارونه

JDK 8 یک متد کمک‌کننده (helper) برای وارونه‌سازی comparator معرفی کرده است. می‌توانیم خیلی سریع با استفاده از آن، مرتب‌سازی وارونه را انجام دهیم:

@Test
public void whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 10),
      new Human("Jack", 12)
    );
     
    Comparator<Human> comparator
      = (h1, h2) -> h1.getName().compareTo(h2.getName());
     
    humans.sort(comparator.reversed());
  
    Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}

8- مرتب‌سازی بر اساس چند شرط مختلف

عبارت‌های لامبدایی لزوما نباید خیلی ساده باشند و می‌توانیم عبارات پیچیده‌تر هم بنویسیم. برای مثال، مرتب‌سازی اشیا ابتدا بر اساس نام و سپس بر اساس سن:

@Test
public void whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 12),
      new Human("Sarah", 10),
      new Human("Zack", 12)
    );
     
    humans.sort((lhs, rhs) -> {
        if (lhs.getName().equals(rhs.getName())) {
            return lhs.getAge() - rhs.getAge();
        } else {
            return lhs.getName().compareTo(rhs.getName());
        }
    });
    Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}

9- مرتب‌سازی بر اساس چند شرط مختلف – composition

همین منطق، یعنی ابتدا مرتب‌سازی بر اساس نام و سپس بر اساس سن، می‌تواند با کمک composition هم انجام شود.

پس از جاوا 8، می‌توانیم برای ساخت منطق‌های مقایسه‌ای پیچیده‌تر، چندین comparator را به هم زنجیر کنیم: 

@Test
public void
  givenComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
     
    List<Human> humans = Lists.newArrayList(
      new Human("Sarah", 12),
      new Human("Sarah", 10),
      new Human("Zack", 12)
    );


    humans.sort(
      Comparator.comparing(Human::getName).thenComparing(Human::getAge)
    );
     
    Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}

10- مرتب‌سازی یک لیست با استفاده از ()Stream.sorted

هم‌چنین، می‌توانیم یک مجموعه را با استفاده از Stream.sorted() API مرتب کنیم.

می‌توانیم استریم را بر اساس ترتیب طبیعی و یا ترتیبی حاصل از یک Comparator، مرتب کنیم. برای این منظور، دو نوع مختلف overloadشده از sorted() API داریم:

  • ()sorted: آیتم‌های استریم را با ترتیب طبیعی مرتب می‌کند. البته کلاس مربوط به اشیای داخل استریم، باید واسط Comparable را پیاده‌سازی کرده باشد.
  • (sorted(Comparator<? superT> comparator: آیتم‌ها را بر اساس یک نمونه comparator مرتب می‌کند.

یک مثال از نحوه استفاده از متد ()sorted با ترتیب طبیعی ببینیم:

@Test
public final void givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
    List<String> letters = Lists.newArrayList("B", "A", "C");
     
    List<String> sortedLetters = letters.stream().sorted().collect(Collectors.toList());
    assertThat(sortedLetters.get(0), equalTo("A"));
}

حالا، ببینیم که چطور می‌شود از یک Comparator سفارشی در sorted() API استفاده کرد:

@Test
public final void givenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {  
    List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
    Comparator<Human> nameComparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
     
    List<Human> sortedHumans = humans.stream().sorted(nameComparator).collect(Collectors.toList());
    assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));
}

اگر از متد ()Comparator.comparing استفاده کنیم، می‌توانیم مثال بالا را ساده‌تر هم بکنیم:

@Test
public final void givenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
    List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
  
    List<Human> sortedHumans = humans.stream()
      .sorted(Comparator.comparing(Human::getName))
      .collect(Collectors.toList());
       
    assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));
}

11- مرتب‌سازی یک لیست به صورت وارونه با استفاده از ()Stream.sorted

هم‌چنین می‌توانیم از ()Stream.sorted برای مرتب‌سازی یک مجموعه به صورت وارونه استفاده کنیم.

ابتدا، بیایید مثالی از نحوه ترکیب متد ()sorted با ()Comparator.reverse برای مرتب‌سازی وارونه یک لیست بر اساس ترتیب طبیعی ببینیم:

@Test
public final void givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
    List<String> letters = Lists.newArrayList("B", "A", "C");


    List<String> reverseSortedLetters = letters.stream()
      .sorted(Comparator.reverseOrder())
      .collect(Collectors.toList());
       
    assertThat(reverseSortedLetters.get(0), equalTo("C"));
}

حالا، ببینیم که چطور می‌توانیم از متد ()sorted و یک Comparator سفارشی استفاده کنیم:

@Test
public final void givenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
    List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
    Comparator<Human> reverseNameComparator = (h1, h2) -> h2.getName().compareTo(h1.getName());


    List<Human> reverseSortedHumans = humans.stream().sorted(reverseNameComparator)
      .collect(Collectors.toList());
    assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));
}

توجه کنید که compareTo فراخوانی شده است و همین است که کار وارونه‌ کردن مرتب‌سازی را انجام می‌دهد.

نهایتا، بیایید مثال بالا را با استفاده از متد ()Comparator.comparing ساده‌سازی کنیم:

@Test
public final void givenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
    List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));


    List<Human> reverseSortedHumans = humans.stream()
      .sorted(Comparator.comparing(Human::getName, Comparator.reverseOrder()))
      .collect(Collectors.toList());
     
    assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));
}

12- جمع‌بندی

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

منبع

.

.

.

با ما همراه باشید

آدرس کانال تلگرام: JavaCupIR@

آدرس اکانت توییتر: JavaCupIR@

آدرس صفحه اینستاگرام: javacup.ir

آدرس گروه لینکدین: Iranian Java Developers

نوشته های مشابه

‫3 دیدگاه ها

    1. سلام
      بهتر و بدتری در این زمینه وجود نداره.
      شما باید با توجه به کاری که قرار هست یا دوست دارید انجام بدید، تصمیم بگیرید که جاوا یاد بگیرید یا php

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا