Comparable và Comparator trong Java: Bạn nên lựa chọn cái nào?
Sắp xếp là một thao tác cơ bản trong lập trình, cần thiết để tổ chức dữ liệu theo một thứ tự cụ thể. Trong Java, các phương thức sắp xếp tích hợp cung cấp những cách hiệu quả để sắp xếp các kiểu dữ liệu nguyên thủy và mảng, giúp dễ dàng quản lý và thao tác với các tập hợp dữ liệu. Ví dụ: bạn có thể nhanh chóng sắp xếp một mảng số nguyên hoặc một danh sách chuỗi bằng cách sử dụng các phương thức như Arrays.sort() và Collections.sort().
Tuy nhiên, khi nói đến việc sắp xếp các đối tượng tùy chỉnh, chẳng hạn như các thể hiện của các lớp do người dùng định nghĩa, thì các phương thức sắp xếp tích hợp sẽ không còn phù hợp. Các phương thức này không biết cách sắp xếp các đối tượng dựa trên tiêu chí tùy chỉnh. Đây là lúc các giao diện Comparable và Comparator của Java phát huy tác dụng, cho phép các nhà phát triển xác định và triển khai logic sắp xếp tùy chỉnh phù hợp với các yêu cầu cụ thể.
Trong bài đăng trên blog này, chúng ta sẽ khám phá cách sử dụng các giao diện Comparable và Comparator để sắp xếp các đối tượng tùy chỉnh trong Java. Tôi sẽ cung cấp các ví dụ để minh họa sự khác biệt và trường hợp sử dụng cho từng phương pháp, giúp bạn nắm vững cách sắp xếp tùy chỉnh trong các ứng dụng Java của mình.
Phương thức sắp xếp cho kiểu dữ liệu nguyên thủy
Java cung cấp nhiều phương thức sắp xếp tích hợp giúp dễ dàng sắp xếp các kiểu dữ liệu nguyên thủy. Các phương thức này được tối ưu hóa cao và hiệu quả, cho phép bạn sắp xếp các mảng và tập hợp với mã tối thiểu. Đối với các kiểu nguyên thủy, chẳng hạn như số nguyên, số dấu phẩy động và ký tự, phương thức Arrays.sort() thường được sử dụng.
Cách sử dụng phương thức Arrays.sort()
Phương thức Arrays.sort() sắp xếp mảng được chỉ định thành thứ tự số tăng dần. Phương thức này sử dụng thuật toán quicksort hai trục, nhanh hơn và hiệu quả hơn đối với hầu hết các tập dữ liệu.
Hãy xem một ví dụ về cách sắp xếp một mảng số nguyên và ký tự bằng cách sử dụng Arrays.sort():
package tutorial;
import java.util.Arrays;
public class PrimitiveSorting
public static void main(String[] args)
int[] numbers = 5, 3, 8, 2, 1 ;
System.out.println("Original array: " + Arrays.toString(numbers));
Arrays.sort(numbers);
System.out.println("Sorted array: " + Arrays.toString(numbers));
char[] characters = 'o', 'i', 'e', 'u', 'a' ;
System.out.println("Original array: " + Arrays.toString(characters));
Arrays.sort(characters);
System.out.println("Sorted array: " + Arrays.toString(characters));
Kết quả:
Original array: [5, 3, 8, 2, 1]
Sorted array: [1, 2, 3, 5, 8]
Original array: [o, i, e, u, a]
Sorted array: [a, e, i, o, u]
Cách sử dụng phương thức Collections.sort()
Phương thức Collections.sort() được sử dụng để sắp xếp các tập hợp như ArrayList. Phương thức này cũng dựa trên thứ tự tự nhiên của các phần tử hoặc một trình so sánh tùy chỉnh.
package tutorial;
import java.util.ArrayList;
import java.util.Collections;
public class CollectionsSorting
public static void main(String[] args)
ArrayList<String> wordsList = new ArrayList<>();
wordsList.add("banana");
wordsList.add("apple");
wordsList.add("cherry");
wordsList.add("date");
System.out.println("Original list: " + wordsList);
Collections.sort(wordsList);
System.out.println("Sorted list: " + wordsList);
Kết quả:
Original list: [banana, apple, cherry, date]
Sorted list: [apple, banana, cherry, date]
Hạn chế với các lớp tùy chỉnh
Mặc dù các phương thức sắp xếp tích hợp sẵn của Java, chẳng hạn như Arrays.sort() và Collections.sort(), rất mạnh mẽ và hiệu quả để sắp xếp các kiểu nguyên thủy và các đối tượng có thứ tự tự nhiên (như String), nhưng chúng lại có nhược điểm khi sắp xếp các đối tượng tùy chỉnh. Các phương thức này không biết cách sắp xếp các đối tượng do người dùng định nghĩa vì không có cách tự nhiên nào để chúng so sánh các đối tượng này.
Ví dụ: hãy xem xét một lớp Person đơn giản có các thuộc tính name, age và weight:
package tutorial;
public class Person
String name;
int age;
double weight;
public Person(String name, int age, double weight)
this.name = name;
this.age = age;
this.weight = weight;
@Override
public String toString()
return "Person [name=" + name + ", age=" + age + ", weight=" + weight + " kgs]";
Nếu chúng ta cố gắng sắp xếp danh sách các đối tượng Person bằng cách sử dụng Arrays.sort() hoặc Collections.sort(), chúng ta sẽ gặp lỗi biên dịch vì các phương thức này không biết cách so sánh các đối tượng Person:
package tutorial;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CustomClassSorting
public static void main(String[] args)
List<Person> people = new ArrayList<>(Arrays.asList(
new Person("Alice", 30, 65.5),
new Person("Bob", 25, 75.0),
new Person("Charlie", 35, 80.0)
));
System.out.println("Original people list: " + people);
Collections.sort(people);
System.out.println("Sorted people list: " + people);
Lỗi biên dịch:
java: no suitable method found for sort(java.util.List<tutorial.Person>)
method java.util.Collections.<T>sort(java.util.List<T>) is not applicable
(inference variable T has incompatible bounds
equality constraints: tutorial.Person
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))
Lỗi xảy ra vì lớp Person không triển khai giao diện Comparable và phương thức sắp xếp không có cách nào biết cách so sánh hai đối tượng Person.
Để sắp xếp các đối tượng tùy chỉnh như Person, chúng ta cần cung cấp một cách để so sánh các đối tượng này. Java cung cấp hai phương pháp chính để đạt được điều này:
- Triển khai giao diện Comparable: Điều này cho phép một lớp xác định thứ tự tự nhiên của nó bằng cách triển khai phương thức compareTo.
- Sử dụng giao diện Comparator: Điều này cho phép chúng ta tạo các lớp riêng biệt hoặc biểu thức lambda để xác định nhiều cách so sánh các đối tượng.
Chúng ta sẽ khám phá cả hai phương pháp trong các phần sắp tới, bắt đầu bằng giao diện Comparable.
Cách sử dụng giao diện Comparable
Java cung cấp một giao diện Comparable để xác định thứ tự tự nhiên cho các đối tượng của một lớp do người dùng định nghĩa. Bằng cách triển khai giao diện Comparable, một lớp có thể cung cấp một thứ tự tự nhiên duy nhất có thể được sử dụng để sắp xếp các thể hiện của nó. Điều này đặc biệt hữu ích khi bạn cần một cách mặc định để so sánh và sắp xếp các đối tượng.
Tổng quan
Giao diện Comparable chứa một phương thức duy nhất, compareTo(), phương thức này so sánh đối tượng hiện tại với đối tượng được chỉ định cho thứ tự. Phương pháp trả về:
- Một số nguyên âm nếu đối tượng hiện tại nhỏ hơn đối tượng được chỉ định.
- 0 nếu đối tượng hiện tại bằng đối tượng được chỉ định.
- Một số nguyên dương nếu đối tượng hiện tại lớn hơn đối tượng được chỉ định.
Cách Comparable cho phép sắp xếp tự nhiên duy nhất cho các đối tượng
Bằng cách triển khai giao diện Comparable, một lớp có thể đảm bảo rằng các đối tượng của nó có thứ tự tự nhiên. Điều này cho phép các đối tượng được sắp xếp bằng cách sử dụng các phương thức như Arrays.sort() hoặc Collections.sort() mà không cần trình so sánh riêng biệt.
Hãy triển khai giao diện Comparable trong một lớp PersonV2 mới, so sánh theo tuổi.
package tutorial;
public class PersonV2 implements Comparable<PersonV2>
String name;
int age;
double weight;
public PersonV2(String name, int age, double weight)
this.name = name;
this.age = age;
this.weight = weight;
@Override
public String toString()
return "PersonV2 [name=" + name + ", age=" + age + ", weight=" + weight + " kgs]";
@Override
public int compareTo(PersonV2 other)
return this.age - other.age;
Trong triển khai này, phương thức compareTo() so sánh thuộc tính age của đối tượng PersonV2 hiện tại với thuộc tính age của đối tượng PersonV2 được chỉ định bằng cách trừ một tuổi này cho tuổi kia. Bằng cách sử dụng biểu thức this.age - other.age, chúng ta đang triển khai logic này một cách hiệu quả như sau:
- Nếu this.age nhỏ hơn other.age, kết quả sẽ là số âm.
- Nếu this.age bằng other.age, kết quả sẽ là 0.
- Nếu this.age lớn hơn other.age, kết quả sẽ là số dương.
Lưu ý: Chúng ta cũng có thể sử dụng Integer.compare(this.age, other.age) thay vì thực hiện phép toán số học theo cách thủ công.
Bây giờ lớp PersonV2 đã triển khai giao diện Comparable, chúng ta có thể sắp xếp danh sách các đối tượng PersonV2 bằng cách sử dụng Collections.sort():
package tutorial;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CustomClassSortingV2
public static void main(String[] args)
List<PersonV2> people = new ArrayList<>(Arrays.asList(
new PersonV2("Alice", 30, 65.5),
new PersonV2("Bob", 25, 75.0),
new PersonV2("Charlie", 35, 80.0)
));
System.out.println("Original people list: " + people);
Collections.sort(people);
System.out.println("Sorted people list: " + people);
Kết quả:
Original people list: [PersonV2 [name=Alice, age=30, weight=65.5 kgs], PersonV2 [name=Bob, age=25, weight=75.0 kgs], PersonV2 [name=Charlie, age=35, weight=80.0 kgs]]
Sorted people list: [PersonV2 [name=Bob, age=25, weight=75.0 kgs], PersonV2 [name=Alice, age=30, weight=65.5 kgs], PersonV2 [name=Charlie, age=35, weight=80.0 kgs]]
Trong ví dụ này, các đối tượng PersonV2 được sắp xếp theo thứ tự tăng dần của tuổi bằng cách sử dụng phương thức Collections.sort(), phương thức này dựa trên thứ tự tự nhiên được xác định bởi phương thức compareTo() trong lớp PersonV2.
Hạn chế của Comparable
Mặc dù giao diện Comparable cung cấp một cách để xác định thứ tự tự nhiên cho các đối tượng, nhưng nó có một số hạn chế có thể hạn chế việc sử dụng nó trong các ứng dụng thực tế. Hiểu được những hạn chế này có thể giúp chúng ta xác định thời điểm sử dụng các cơ chế khác, chẳng hạn như giao diện Comparator, để đạt được cách sắp xếp linh hoạt hơn.
- Thứ tự tự nhiên duy nhất: Hạn chế chính của Comparable là nó chỉ cho phép một thứ tự tự nhiên cho các đối tượng của một lớp. Khi bạn triển khai Comparable, bạn xác định một cách duy nhất để so sánh các đối tượng, cách này được sử dụng bất cứ khi nào các đối tượng được sắp xếp hoặc so sánh. Điều này có thể gây hạn chế nếu bạn cần sắp xếp các đối tượng theo nhiều cách.
- Không linh hoạt: Nếu bạn cần sắp xếp các đối tượng theo các thuộc tính khác nhau hoặc theo thứ tự khác nhau, bạn sẽ phải sửa đổi lớp hoặc tạo các triển khai mới của Comparable. Sự không linh hoạt này có thể dẫn đến sự gia tăng các phương thức so sánh và có thể khiến mã khó bảo trì hơn.
- Không thể thích ứng: Khi một lớp triển khai Comparable, thứ tự tự nhiên được cố định và không thể dễ dàng thay đổi. Ví dụ: nếu lớp PersonV2 của bạn ban đầu sắp xếp theo tuổi nhưng sau đó bạn cần sắp xếp theo cân nặng hoặc tên, bạn phải thay đổi phương thức compareTo() hoặc tạo phiên bản mới của lớp.
Đây là lúc giao diện Comparator phát huy tác dụng. Để xác định nhiều cách so sánh các đối tượng, chúng ta có thể sử dụng giao diện Comparator, điều mà chúng ta sẽ khám phá trong phần tiếp theo.
Cách sử dụng giao diện Comparator
Giao diện Comparator trong Java cung cấp một cách để xác định nhiều cách so sánh và sắp xếp các đối tượng. Không giống như giao diện Comparable, chỉ cho phép một thứ tự tự nhiên duy nhất, Comparator được thiết kế để mang đến sự linh hoạt bằng cách cho phép nhiều chiến lược sắp xếp. Điều này làm cho nó đặc biệt hữu ích cho các trường hợp mà các đối tượng cần được sắp xếp theo nhiều cách khác nhau.
Tổng quan
Giao diện Comparator xác định một phương thức duy nhất, compare(), phương thức này so sánh hai đối tượng và trả về:
- Một số nguyên âm nếu đối tượng đầu tiên nhỏ hơn đối tượng thứ hai.
- 0 nếu đối tượng đầu tiên bằng đối tượng thứ hai.
- Một số nguyên dương nếu đối tượng đầu tiên lớn hơn đối tượng thứ hai.
Phương thức này cung cấp một cách để xác định thứ tự tùy chỉnh cho các đối tượng mà không cần sửa đổi chính lớp đó.
Cách Comparator cho phép nhiều cách sắp xếp các đối tượng
Giao diện Comparator cho phép bạn tạo nhiều thể hiện Comparator, mỗi thể hiện xác định một thứ tự khác nhau cho các đối tượng. Sự linh hoạt này có nghĩa là bạn có thể sắp xếp các đối tượng theo nhiều thuộc tính khác nhau hoặc theo thứ tự khác nhau mà không cần thay đổi lớp của đối tượng.
Hãy triển khai nhiều thể hiện Comparator cho lớp Person. Chúng ta sẽ xác định các trình so sánh để sắp xếp theo tên, theo tuổi và theo cân nặng. Đầu tiên, chúng ta cần cập nhật lớp Person để bao gồm các trình getter và đảm bảo rằng các thuộc tính có thể truy cập được.
package tutorial;
public class Person
String name;
int age;
double weight;
public Person(String name, int age, double weight)
this.name = name;
this.age = age;
this.weight = weight;
public String getName()
return name;
public int getAge()
return age;
public double getWeight()
return weight;
@Override
public String toString()
return "Person [name=" + name + ", age=" + age + ", weight=" + weight + " kgs]";
Comparator theo Name
Trình so sánh này sắp xếp các đối tượng Person theo thứ tự bảng chữ cái theo name của chúng.
package tutorial.comparator;
import tutorial.Person;
import java.util.Comparator;
public class PersonNameComparator implements Comparator<Person>
@Override
public int compare(Person p1, Person p2)
return p1.getName().compareTo(p2.getName());
Comparator theo Age
Trình so sánh này sắp xếp các đối tượng Person theo age của chúng, theo thứ tự tăng dần.
package tutorial.comparator;
import tutorial.Person;
import java.util.Comparator;
public class PersonAgeComparator implements Comparator<Person>
@Override
public int compare(Person p1, Person p2)
return p1.getAge() - p2.getAge();
Comparator theo Weight
Trình so sánh này sắp xếp các đối tượng Person theo weight của chúng, theo thứ tự tăng dần.
package tutorial.comparator;
import tutorial.Person;
import java.util.Comparator;
public class PersonWeightComparator implements Comparator<Person>
@Override
public int compare(Person p1, Person p2)
return (int) (p1.getWeight() - p2.getWeight());
Bây giờ, đây là cách bạn có thể sử dụng các thể hiện Comparator này để sắp xếp danh sách các đối tượng Person:
package tutorial;
import tutorial.comparator.PersonAgeComparator;
import tutorial.comparator.PersonNameComparator;
import tutorial.comparator.PersonWeightComparator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CustomClassSortingV3
public static void main(String[] args)
List<Person> people = new ArrayList<>(Arrays.asList(
new Person("Alice", 30, 65.5),
new Person("Bob", 25, 75.0),
new Person("Charlie", 35, 80.0)
));
System.out.println("Original people list: " + people);
Collections.sort(people, new PersonNameComparator());
System.out.println("Sorted people list by name: " + people);
Collections.sort(people, new PersonAgeComparator());
System.out.println("Sorted people list by age: " + people);
Collections.sort(people, new PersonWeightComparator());
System.out.println("Sorted people list by weight: " + people);
Kết quả:
Original people list: [Person [name=Alice, age=30, weight=65.5 kgs], Person [name=Bob, age=25, weight=75.0 kgs], Person [name=Charlie, age=35, weight=80.0 kgs]]
Sorted people list by name: [Person [name=Alice, age=30, weight=65.5 kgs], Person [name=Bob, age=25, weight=75.0 kgs], Person [name=Charlie, age=35, weight=80.0 kgs]]
Sorted people list by age: [Person [name=Bob, age=25, weight=75.0 kgs], Person [name=Alice, age=30, weight=65.5 kgs], Person [name=Charlie, age=35, weight=80.0 kgs]]
Sorted people list by weight: [Person [name=Alice, age=30, weight=65.5 kgs], Person [name=Bob, age=25, weight=75.0 kgs], Person [name=Charlie, age=35, weight=80.0 kgs]]
Trong ví dụ này, các thể hiện Comparator cho phép sắp xếp các đối tượng Person theo các thuộc tính khác nhau: tên, tuổi và cân nặng. Điều này chứng minh cách giao diện Comparator cho phép các chiến lược sắp xếp linh hoạt và linh hoạt cho một lớp.
Comparable so với Comparator
Khi sắp xếp các đối tượng trong Java, bạn có hai tùy chọn chính: giao diện Comparable và Comparator. Hiểu được sự khác biệt giữa hai giao diện này có thể giúp bạn chọn đúng phương pháp cho nhu cầu của mình. Xin lưu ý rằng đây cũng là một câu hỏi phỏng vấn rất quan trọng.
So sánh
Dưới đây là bảng so sánh và đối chiếu các giao diện Comparable và Comparator trong Java:
Ưu điểm và nhược điểm của từng phương pháp
Toán tử Comparable
Ưu điểm:
- Đơn giản: Cung cấp một thứ tự sắp xếp mặc định dễ thực hiện và sử dụng.
- Tích hợp sẵn: Thứ tự tự nhiên là một phần của chính lớp đó, vì vậy nó luôn khả dụng và được sử dụng theo mặc định trong các phương thức sắp xếp.
Nhược điểm:
- Sắp xếp đơn lẻ: Chỉ có thể xác định một cách để so sánh các đối tượng. Nếu cần các thứ tự sắp xếp khác nhau, lớp phải được sửa đổi hoặc phải sử dụng các thể hiện Comparator bổ sung.
- Sửa đổi lớp: Yêu cầu thay đổi lớp để triển khai Comparable, điều này có thể không khả thi nếu lớp là một phần của thư viện hoặc nếu thứ tự tự nhiên của nó không rõ ràng.
Comparator
Ưu điểm:
- Linh hoạt: Cho phép nhiều thứ tự và tiêu chí sắp xếp, có thể được xác định bên ngoài và sử dụng khi cần thiết.
- Không xâm lấn: Không yêu cầu sửa đổi chính lớp đó, làm cho nó phù hợp với các lớp mà bạn không kiểm soát hoặc khi bạn cần các tùy chọn sắp xếp khác nhau.
Nhược điểm:
- Phức tạp: Yêu cầu tạo và quản lý nhiều thể hiện Comparator, điều này có thể làm tăng thêm độ phức tạp cho mã.
- Chi phí: Có thể gây ra chi phí bổ sung nếu nhiều trình so sánh được sử dụng, đặc biệt nếu chúng được tạo khi đang di chuyển.
Tóm lại, Comparable được sử dụng tốt nhất khi một lớp có thứ tự tự nhiên phù hợp với hầu hết các trường hợp sử dụng.
Comparator, mặt khác, cung cấp tính linh hoạt để sắp xếp theo nhiều tiêu chí và hữu ích khi lớp không có thứ tự tự nhiên hoặc khi cần các thứ tự sắp xếp khác nhau.
Việc lựa chọn giữa Comparable và Comparator phụ thuộc vào nhu cầu sắp xếp cụ thể của bạn và bạn có cần một thứ tự mặc định duy nhất hay nhiều tùy chọn sắp xếp linh hoạt hay không.
Kết luận
Việc hiểu và sử dụng cả Comparable và Comparator có thể nâng cao đáng kể khả năng quản lý và thao tác với các tập hợp đối tượng trong Java của bạn. Bằng cách áp dụng những khái niệm này, bạn có thể tạo ra các cơ chế sắp xếp linh hoạt và mạnh mẽ hơn.
Để củng cố sự hiểu biết của bạn, hãy thử triển khai cả Comparable và Comparator trong các tình huống thực tế. Thử nghiệm với các lớp và tiêu chí sắp xếp khác nhau để xem cách tiếp cận hoạt động trong thực tế. Cảm ơn các bạn đã theo dõi bài viết vừa rồi.
All rights reserved