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)));
}

Trải nghiệm chương trình thực tập Full Stack - đảm bảo việc làm tại Techmaster

#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)));
}

Method signature là 4 thành phần có mặt trong cú pháp khai báo của tất cả method: return type, parameter type, số lượng parameter, thứ tự parameter

#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)));
}

#9 Tổng kết

Bài viết hôm nay khá ngắn gọn nhưng đã giới thiệu được 8 cách để biểu diễn logic sắp xếp các thực thể trong Java. Các bạn có thể tham khảo full source code và ví dụ mẫu tại repo này.

Techmaster via baeldung.com