Ngày 06 tháng 05 năm 2024 - Lĩnh vực: Công nghệ thông tin
Bài viết này sẽ tập trung vào việc giải thích cách sử dụng và bối cảnh ứng dụng của hai giao diện Comparable
và Comparator
trong ngôn ngữ lập trình Java. Chúng ta đều biết rằng để cho phép đối tượng của một lớp có thể được sắp xếp, lớp đó cần phải triển khai giao diện Comparable
. Tuy nhiên, nếu bạn muốn định nghĩa nhiều quy tắc sắp xếp khác nhau mà không cần thay đổi chính cấu trúc của lớp, thì giao diện Comparator
sẽ là lựa chọn phù hợp. Vì vậy, cả hai đều phục vụ mục đích sắp xếp nhưng cách sử dụng lại khác nhau.
1. Giao diện B88bet Win Comparable
Giao diện Comparable
được định nghĩa như sau:
package java.lang;
public interface Comparable<T> {
public int compareTo(T o);
}
Phương thức compareTo()
dùng để so sánh thứ tự giữa đối tượng hiện tại và một đối tượng chỉ định. Kết quả trả về có thể là số nguyên dương, 0 hoặc số nguyên âm, tương ứng với việc đối tượng hiện tại lớn hơn, bằng hoặc nhỏ hơn đối tượng chỉ định. Nếu một lớp không triển khai giao diện Comparable
, khi sử dụng các phương thức như Arrays.sort()
hoặc Collections.sort()
để sắp xếp tập hợp các đối tượng của lớp đó, sẽ nảy sinh lỗi ClassCastException
.
Khi triển khai phương thức compareTo()
, cần đảm bảo thỏa mãn những điều kiện chung sau (trong đó sgn(biểu_thức)
là hàm ký hiệu, biểu thị giá trị dương, 0 hoặc âm):
- Cần đảm bảo rằng
sgn(a.compareTo(b)) == -sgn(b.compareTo(a))
(và nếua.compareTo(b)
ném ra ngoại lệ, thìb.compareTo(a)
cũng phải ném ngoại lệ tương tự). - Cần đảm bảo tính chuyển tiếp trong so sánh, tức là nếu
a.compareTo(b) > 0 && b.compareTo(c) > 0
, thìa.compareTo(c) > 0
. - Cần đảm bảo tính tương đương, tức là nếu
a.compareTo(b) == 0
, thì với mọi c,sgn(a.compareTo(c)) == sgn(b.compareTo(c))
. - Nên cố gắng giữ kết quả nhất quán với phương thức
equals()
, tức là nếua.compareTo(b) == 0
, thì tốt nhất nên đảm bảo rằnga.equals(b)
.
Dưới đây là ví dụ về cách sử dụng giao diện Comparable
. Chúng ta sẽ tạo một lớp Telephone
và triển khai giao diện Comparable
:
// src/test/java/Telephone.java
public class Telephone implements Comparable<Telephone> {
private final int countryCode;
private final String areaCode;
private final int number;
public Telephone(int countryCode, String areaCode, int number) {
this.countryCode = countryCode;
this.areaCode = areaCode;
this.number = number;
}
@Override
public int compareTo(Telephone o) {
int result = Integer.compare(countryCode, o.countryCode);
if (result == 0) {
result = String.CASE_INSENSITIVE_ORDER.compare(areaCode, o.areaCode);
if (result == 0) {
result = Integer.compare(number, o.number);
}
}
return result;
}
@Override
public String toString() {
return "PhoneNumber{" +
"countryCode=" + countryCode +
", areaCode=" + areaCode +
", number=" + number + '}';
}
}
Trong lớp Telephone
, chúng ta có ba thuộc tính: countryCode
, areaCode
và number
, lần lượt thuộc kiểu int
, String
và int
. Lớp này đã triển khai giao diện Comparable
, và phương thức compareTo()
được viết theo logic so sánh tuần tự giữa countryCode
, areaCode
và number
bằng cách sử dụng các phương thức compare
từ Integer
và String
.
Tiếp theo, chúng ta sẽ viết một trường hợp kiểm thử đơn vị ComparableTest
. Chuẩn bị một mảng các đối tượng Telephone
, sử dụng Arrays.sort()
để sắp xếp và in kết quả:
// src/test/java/ComparableTest.java
import org.junit.jupiter.api.Test;
import java.util.Arrays;
public class ComparableTest {
@Test
public void testArraySort() {
Telephone[] telephones = new Telephone[]{
new Telephone(86, "010", 89150405),
new Telephone(86, "010", 56249829),
new Telephone(86, "0411", 66177118),
new Telephone(86, "0411", 39966686)
};
// Sắp xếp mảng
Arrays.sort(telephones);
// In kết quả
Arrays.stream(telephones).forEach(System.out::println);
}
}
Kết quả in ra như sau:
PhoneNumber{countryCode=86, areaCode=010, number=56249829}
PhoneNumber{countryCode=86, areaCode=010, number=89150405}
PhoneNumber{countryCode=86, areaCode=0411, number=39966686}
PhoneNumber{countryCode=86, areaCode=0411, number=66177118}
Như chúng ta thấy, kết quả in ra hoàn toàn tuân theo quy tắc sắp xếp đã được viết trong phương thức compareTo()
. Cụ thể, trước tải game 789win tiên nó sắp xếp theo countryCode
, sau đó là areaCode
và cuối cùng là number
.
Tuy nhiên, nếu bạn muốn áp dụng nhiều quy tắc sắp xếp khác nhau mà không cần sửa đổi chính lớp, thì giao diện Comparator
sẽ phát huy tác dụng.
2. Giao diện Comparator
Giao diện Comparator
được định nghĩa như sau:
package java.util;
public interface Comparator<T> {
int compare(T o1, T o2);
}
Các điều kiện chung cần thỏa mãn khi triển khai phương thức compare()
giống hệt như khi triển khai phương thức Comparable.compareTo()
. Khi sử dụng giao diện Comparator
, lớp tương ứng không cần triển khai bất kỳ giao diện nào. Do đó, Telephone
có thể chỉ là một lớp POJO đơn giản:
// src/test/java/Telephone.java
public class Telephone {
private final int countryCode;
private final String areaCode;
private final int number;
public Telephone(int countryCode, String areaCode, int number) {
this.countryCode = countryCode;
this.areaCode = areaCode;
this.number = number;
}
public int getCountryCode() {
return countryCode;
}
public String getAreaCode() {
return areaCode;
}
public int getNumber() {
return number;
}
@Override
public String toString() {
return "PhoneNumber{" +
"countryCode=" + countryCode +
", areaCode=" + areaCode +
", number=" + number + '}';
}
}
Sau đây là ví dụ về cách viết một trường hợp kiểm thử đơn vị ComparatorTest
. Cũng chuẩn bị một mảng các đối tượng Telephone
, sử dụng Arrays.sort()
để sắp xếp, nhưng lần này cần truyền vào một triển khai của giao diện Comparator
để chỉ định quy tắc sắp xếp (ở đây chúng ta sắp xếp ngược theo thứ tự của countryCode
, areaCode
và number
), và cuối cùng in kết quả đã sắp xếp:
// src/test/java/ComparatorTest.java
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Comparator;
public class ComparatorTest {
@Test
public void testArraySort() {
Telephone[] telephones = new Telephone[]{
new Telephone(86, "010", 89150405),
new Telephone(86, "010", 56249829),
new Telephone(86, "0411", 66177118),
new Telephone(86, "0411", 39966686)
};
// Sắp xếp mảng
Arrays.sort(telephones, new Comparator<Telephone>() {
@Override
public int compare(Telephone o1, Telephone o2) {
return Comparator.comparingInt(Telephone::getCountryCode)
.thenComparing(Telephone::getAreaCode)
.thenComparingInt(Telephone::getNumber)
.reversed()
.compare(o1, o2);
}
});
// In kết quả
Arrays.stream(telephones).forEach(System.out::println);
}
}
Kết quả sau khi sắp xếp như sau, đáp ứng đúng mong đợi:
PhoneNumber{countryCode=86, areaCode=0411, number=66177118}
PhoneNumber{countryCode=86, areaCode=0411, number=39966686}
PhoneNumber{countryCode=86, areaCode=010, number=89150405}
PhoneNumber{countryCode=86, areaCode=010, number=56249829}
3. Tổng kết
Giao diện Comparable
nằm trong gói java.lang
, trong khi Comparator
nằm trong gói java.util
. Điều này cho thấy Comparable
là một giao diện cơ bản của ngôn ngữ Java, còn Comparator
đóng vai trò như một công cụ hỗ trợ. Bằng cách triển khai giao diện Comparable
cho lớp tương ứng, bạn có thể cung cấp một cách sắp xếp mặc định. Mặt khác, giao diện Comparator
ngoài việc định nghĩa phương thức compare()
, còn cung cấp một loạt các phương thức tĩnh để hỗ trợ so sánh, thường được sử dụng khi bạn muốn sắp xếp theo yêu cầu mà không cần thay đổi cấu trúc lớp gốc.
Mã nguồn đầy đủ của bài viết này đã được tải lên GitHub cá nhân của tôi. Mời bạn theo dõi hoặc Fork dự án.
[1] Effective Java (Ấn bản thứ 3): Hãy cân nhắc triển khai giao diện Comparable - [2] DigitalOcean: Ví dụ về Comparable và Comparator trong Java - [3] Jenkov: Java Comparable - [4] Jenkov: Java Comparator - [5] Blog Garden: So sánh giữa Comparable và Comparator trong Java -

#Java