[Java] Tận dụng Lambda Expression trong bài toán sắp xếp

imicrosoft

Trong bài viết này chúng ta sẽ tận dụng một tính năng mới của Java 8 - Lambda Expression làm đòn bẩy phục vụ cho bài toán sắp xếp các phần tử thuộc Collection bất kỳ.

Hãy bắt đầu với việc define class mô tả một Entity cụ thể:
 

public class Human {
    private String name;
    private int age;
 
    public Human() {
        super();
    }
 
    public Human(String name, int age) {
        super();
 
        this.name = name;
        this.age = age;
    }
 
// các bạn hãy tự thêm setter, getter, hashCode() như mặc định
}


1. Sắp xếp kiểu thông thường, không sử dụng Lambda Expression

Trước khi Lambda Expression chính thức có mặt trong Java 8, để sắp xếp một collection, người ta sẽ tạo một anonymous inner class cho Comparator như sau:
 
new Comparator<Human>() {
    @Override
    public int compare(Human h1, Human h2) {
        return h1.getName().compareTo(h2.getName());
    }
}

Và khi áp dụng nó để sắp xếp một Collection chứa các object kiểu Human (đã được define thông qua entity class ở trên), ta có:
 
@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());
        }
    });
//phần này phục vụ test
    Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

2. Sắp xếp với Lambda Expression(mức cơ bản)

Ta sẽ tiết kiệm được khá nhiều diện tích và thời gian code khi áp dụng Lambda vào bài toán trên. Cụ thể, cú pháp tạo anonymous inner class sẽ được thay thế bằng:
(final Human h1, final Human h2) -> h1.getName().compareTo(h2.getName());

Và khi áp dụng, ta có:
@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()));

  // phần này phục vụ test
    assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

Ở trường hợp này ta cũng áp dụng method sort() mới được bổ sung vào java.util.List trong bản Java 8 thay vì sử dụng Collections.sort() như truyền thống.

3. Sắp xếp mà không cần dùng Type Definition

Ta có thể thu gọn cú pháp sắp xếp một lần nữa bằng cách bỏ qua type definition, Compiler sẽ làm việc đó.
Thay vì:
(final Human h1, final Human h2) -> h1.getName().compareTo(h2.getName());

Thì ta sẽ gõ:
	
(h1, h2) -> h1.getName().compareTo(h2.getName())

Và áp dụng:
@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()));
  
//phần này phục vụ test
    assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

4. Sắp xếp bằng cách sử dụng tham chiếu tới static method

Cách này vẫn xoay quanh lambda expression.

Đầu tiên, chúng ta vẫn define ra một method mới là compareByNameThenAge(). Phần method signature của nó cũng tương tự như method compare() thuộc 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);
    }
}

Và đây là cú pháp để gọi hàm này:
humans.sort(Human::compareByNameThenAge);

Kết quả khi áp dụng:
@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)));
}

5. Sắp xếp thông qua trích xuất Comparator

Với cách này, chúng ta không cần define lại logic sắp xếp bằng cách sử dụng instance method reference và method Comparator.comparing().
@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));


// phần này liên quan đến test.
    assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

6. Reversed Sort

JDK 8 có cung cấp một hàm tiện ích để đảo ngược lại logic sắp xếp của comparator, ta có thể dùng nó trong bài toán này:
@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());

  

// có lẽ phần này không cần chú thích nữa 
    Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}

7. Sắp xếp theo nhiều điều kiện khác nhau

Ta hoàn toàn có thể sử dụng lambda expression để thiết kế các phép toán sắp xếp phức tạp hơn. Cụ thể với bài toán trong ví dụ này, hãy thử sắp xếp các thực thể theo tên trước, nếu tên trùng nhau, sắp xếp theo tuổi:
@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)));
}

8 . Sắp xếp theo nhiều điều kiện khác nhau - ứng dụng Composition

Từ bản JDK 8, chúng ta có thể liên kết nhiều comparator (chaining multiple comparator) để biểu diễn các logic sắp xếp phức tạp hơn.
Nghe thì rất... phức tạp, nhưng mọi thứ chỉ xoay quanh 1 dòng code, hãy đọc và hiểu:
@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)
    );
     

// không đọc dòng này 
    Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}
BTV. Phạm Thị Mỹ Phương
Phòng Truyền Thông ImicroSoft Hồ Chí Minh
Hotline: 0916 878 224
Email: phuongptm@imicrosoft.edu.vn
Học CNTT/CNPM để biến ý tưởng kinh doanh thành hiện thực ngay

Với đội ngũ nhân sự có trình độ năng lực chuyên môn cao, trong đó có những Chuyên gia hàng đầu sở hữu nhiều Chứng chỉ Quốc Tế từ MicroSoft, Google, Oracle, Java, IBM. IMicroSoft luôn đi đầu trong việc ứng dụng công nghệ mới vào các dự án phần mềm cũng như nỗ lực trau dồi kỹ năng, kiến thức chuyên môn để nâng cao hơn nữa chất lượng Đào tạo nguồn nhân sự đáp ứng tốt các yêu cầu thực tế tại Doanh nghiệp. Chúng tôi cam kết sẽ mang đến chất lượng dịch vụ hoàn hảo vượt trên cả mong đợi của khách hàng.

Bài viết liên quan

Nghiên cứu đủ rồi, đăng ký tham gia khóa học thôi

ĐỐI TÁC LIÊN KẾT TUYỂN DỤNG NHÂN SỰ CỦA IMICROSOFT VIET NAM

IMicroSoft

IMicroSoft tự hào là doanh nghiệp đầu tiên tại Việt Nam triển khai các Chương trình Đào tạo chuyên môn thực tế cho Học viên ngành CNTT/CNPM. Cũng là một trong những doanh nghiệp đạt được nhiều giải thưởng lớn trong lĩnh vực này. Góp phần phát triển mạnh ngành CNTT/CNPM tại nước ta hiện nay.