Comparable và Comparator, đều là các giao diện (interface) và có thể được sử dụng để sắp xếp các phần tử của Collection. Hôm nay chúng ta sẽ cùng nhau xét tới các tính chất, cách sử dụng và sự khác nhau giữa hai Interface này.

Trong Java, lớp Collections cung cấp các phương thức static để sắp xếp các phần tử của collection. Nếu các phần tử collection thuộc kiểu Set hoặc Map, chúng ta có thể sử dụng TreeSet hoặc TreeMap để lưu trữ các phần tử, tuy nhiên chúng ta lại không thể sắp xếp các phần tử của List. Do đó, Collections cung cấp cho lập trình viên phương thức sort() để sắp xếp các phần tử của List.

Comparable

Interface Comparable trong Java được sử dụng để sắp xếp các đối tượng của lớp do người dùng định nghĩa (user-defined). Giao diện này thuộc về gói java.lang. Để sử dụng được Comparable, ta phải implements interface Comparable cho lớp đối tượng cần được so sánh. Trong interface Comparable, ta được cung cấp một phương thức được sử dụng để so sánh:

Phương thức compareTo() được sử dụng để so sánh đối tượng hiện tại với đối tượng được chỉ định.

Với interface Comparable và phương thức compareTo(), chúng ta có thể sắp xếp các phần tử của :

  • Các đối tượng String
  • Các đối tượng của lớp Wrapper
  • Các đối tượng của lớp do người dùng định nghĩa (User-defined)

Khi một class implement interface Comparable, Collections dựa vào đó mà biết cách sắp xếp các phần tử có kiểu class đó như thế nào. Nhưng cụ thể dựa vào cái gì?

Khi ta implement Comparable cho một class, bắt buộc ta phải override lại phương thức compareTo(Object obj) cho class đó, như là tiêu chuẩn mà Collections dựa vào đó mà sắp xếp các phần tử vào vị trí thích hợp. Khi gọi phương thức sort(List) từ Collections, Collections sẽ lần lượt gọi phương thức compareTo() của từng phần tử để so sánh phần tử này với phần tử khác và dựa vào giá trị trả về của compareTo() mà sắp xếp chúng.

Tùy vào tiêu chuẩn mà ta sử dụng để so sánh các phần tử mà ta sẽ override lại code vào trong phương thức compareTo(), và đa số là dựa vào giá trị của biến instance của chúng.

Phương thức compareTo() có kiểu trả về là int và có quy ước:

  • Giá trị trả về < 0: phần tử hiện tại < phần tử khác
  • Giá trị trả về = 0: phần tử hiện tại = phần tử khác
  • Giá trị trà về > 0: phần tử hiện tại > phần tử khác

Ta có thể dựa trên quy ước trên để điều chỉnh phương thức compareTo() cho phù hợp với tiêu chí so sánh đã đặt ra.

VD: Tạo một lớp sinh viên có tên Student inplement từ interface Comparable:

Tiếp tục,tạo một lớp ComparableExample chứa danh sách các sinh viên (Student) và sau đó sắp xếp danh sách theo tiêu chí tăng dần của Tuổi:

Sau khi chạy Class ComparableExample ta được kết quả:

Như vậy danh sách sinh viên đã được sắp xếp theo tiêu chí Tuổi và theo thứ tự tăng dần.

Với kết quả trên ta thấy có 2 sinh viên có cùng độ tuổi bằng 22. Trong trường hợp tiêu chí đang xét là bằng nhau, ta có thể đặt thêm điều kiện để xét đến các tiêu chí khác:

Với phương thức compareTo() được override như trên, trong trường hợp tiêu chí Tuổi của các đối tượng là bằng nhau thì compareTo() sẽ chuyển sang so sánh về tiêu chí ID theo giá trị giảm dần. Khi chạy chương trình ta có kết quả:

Comparator

Giao diện Comparator trong Java được sử dụng để sắp xếp các đối tượng của lớp do người dùng định nghĩa (user-defined). Để sử dụng Comparator ta không cần phải implements Comparator cho lớp đối tượng cần được so sánh. Giao diện này thuộc về gói java.util và chứa hai phương thức là compare(Object obj1, Object obj2) và equals(Object element):

Phương thức compare() được sử dụng để so sánh giữa hai đối tượng obj1 và obj2, cũng có kiểu trả về int, và quy ước giống như compareTo() của Comparable.

Phương thức equals được sử dụng để kiểm tra xem có hay không một đối tượng là cân bằng với comparator đang triệu hồi. Trong đó obj là đối tượng để được kiểm tra sự cân bằng. Phương thức trả về true nếu cả obj và đối tượng đang triệu hồi là các đối tượng Comparator và có cùng thứ tự, nếu không phải thì trả về false. Việc ghi đè phương thức equals() là không cần thiết, và các comparator đơn giản nhất sẽ không làm điều này.

Với interface Comparator và phương thức compare(), chúng ta có thể sắp xếp các phần tử của:

  • Các đối tượng String
  • Các đối tượng của lớp Wrapper
  • Các đối tượng của lớp do người dùng định nghĩa (User-defined)

Giả sử ngoài sắp xếp theo một tiêu chí nhất đinh, ta còn muốn sắp xếp theo nhiều các tiêu chí khác nữa thì làm thế nào? Interface Comparator chính là cách giải quyết vấn đề trên.

Tuy nhiên interface này không đi với phương thức sort(List list), mà là:

Ở phương thức này, Collections không yêu cầu các phần tử trong list phải implement Comparator mà thay vào đó, phải truyền thêm vào một biến tham chiếu kiểu Object có implement Comparator.

Ngoài ra khi implement Comparator thì ta phải override lại phương thức compare(Object obj1, Object obj2) để làm rõ tiêu chí sắp xếp.

VD: Với Class Student ở trên nhưng lần này ta không cần phải implement interface Comparator:

Ta tiếp tục tạo một Class ComparatorExample để sắp xếp các đối tượng bằng Comparator

Sau khi chạy Class ComparableExample ta được kết quả:

Như vậy danh sách sinh viên đã được sắp xếp theo tiêu chí Tên với thứ tự tăng dần theo vị trí bảng chữ cái.

Hoặc nếu ta muốn sắp xếp theo thứ tự giảm dần của ID:

Chạy chương trình ta có kết quả mới:

Như vậy danh sách sinh viên đã được sắp xếp theo tiêu chí ID và theo thứ tự giảm dần.

Comparator linh hoạt hơn Comparable ở chỗ, nếu phương thức compareTo() của Comparable chỉ được override lại một lần và chỉ có thể xét được 1 tiêu chí nhất định thì phương thức compare() của Comparator có thể được gọi nhiều lần và định nghĩa ngay tại vị trí cần so sánh để sắp xếp theo các tiêu chí tùy ý:

Khác nhau giữa Comparable và Comparator

Comparable Comparator
1 Phải implements giao tiếp Comparable cho lớp đối tượng cần được so sánh. Không phải implements giao tiếp Comparator cho lớp đối tượng cần được so sánh
2 Comparable cung cấp phương thức compareTo() để sắp xếp các phần tử. Comparator cung cấp phương thức compare() để sắp xếp các phần tử.
3 Comparable thuộc về java.lang package Comparator thuộc về java.util package
4 Có thể sắp xếp các phần tử của kiểu Comparable bởi phương thức Collections.sort(List). Có thể sắp xếp các phần tử của kiểu Comparator bởi phương thức Collections.sort(List, Comparator).

Như vậy chúng ta đã có cái nhìn rõ ràng về Comparable và Comparator trong Java. Tùy vào từng trường hợp và yêu cầu của bài toán mà chúng ta sẽ cân nhắc sử dụng Comparable hay Comparator một cách hợp lý và linh hoạt.

Link github tham khảo phần code sử dụng làm demo trong bài viết: https://github.com/linh-phamdang21/Comparable-Comparator

Author: Phạm Đăng Linh


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.