JavaRush /Blog Java /Random-VI /Bộ so sánh trong Java
Viacheslav
Mức độ

Bộ so sánh trong Java

Xuất bản trong nhóm
Chỉ những người lười biếng mới không viết về Comparator và so sánh trong Java. Tôi không lười biếng - vì vậy tôi yêu cầu bạn yêu thích và ưu ái thêm một biến thể nữa. Tôi hy vọng nó sẽ không thừa. Và vâng, bài viết này chính là câu trả lời cho câu hỏi: “Bạn có thể viết một bộ so sánh từ trí nhớ không?” Tôi hy vọng rằng sau khi đọc bài viết này, mọi người sẽ có thể viết một bộ so sánh từ trí nhớ.
Trình so sánh trong Java - 1
Giới thiệu Java được biết đến là một ngôn ngữ hướng đối tượng. Kết quả là, trong Java, việc thao tác với các đối tượng là điều phổ biến. Nhưng sớm hay muộn nhiệm vụ so sánh các đối tượng theo một nguyên tắc nào đó cũng nảy sinh. Vì vậy, đã cho: Chúng ta có một số thông báo được mô tả bởi lớp Message:
public static class Message {
    private String message;
    private int id;

    public Message(String message) {
        this.message = message;
        this.id = new Random().nextInt(1000);
    }
    public String getMessage() {
        return message;
    }
    public Integer getId() {
        return id;
    }
    public String toString() {
        return "[" + id + "] " + message;
    }
}
Hãy thêm lớp này vào trình biên dịch Java Tutorialspoint . Chúng ta cũng hãy nhớ thêm nhập khẩu:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Trong phương thức chính, chúng ta sẽ tạo một số thông báo:
public static void main(String[] args){
    List<Message> messages = new ArrayList();
    messages.add(new Message("Hello, World!"));
    messages.add(new Message("Hello, Sun!"));
    System.out.println(messages);
}
Chúng ta hãy nghĩ xem chúng ta nên làm gì nếu muốn so sánh chúng? Ví dụ: chúng tôi muốn sắp xếp theo id. Và để tạo trật tự, bạn cần bằng cách nào đó so sánh các đối tượng để hiểu đối tượng nào trước (nghĩa là nhỏ hơn) và đối tượng nào tiếp theo (nghĩa là lớn hơn). Hãy bắt đầu với một lớp như java.lang.Object . Như chúng ta đã biết, tất cả các lớp đều kế thừa ngầm từ lớp Object này. Và điều này là hợp lý, bởi vì Về cơ bản, điều này thể hiện khái niệm: "Mọi thứ đều là một đối tượng" và cung cấp hành vi chung cho tất cả các lớp. Và lớp này định nghĩa rằng mỗi lớp có hai phương thức: → hashCode Phương thức hashCode trả về một số biểu diễn số (int) của đối tượng như một thể hiện của lớp. Nó có nghĩa là gì? Điều này có nghĩa là nếu bạn đã tạo hai phiên bản khác nhau của một lớp thì vì các phiên bản này khác nhau nên Mã băm của chúng sẽ khác nhau. Đây là những gì nó nói trong phần mô tả phương thức: “Càng thực tế một cách hợp lý, phương thức hashCode được định nghĩa bởi lớp Đối tượng sẽ trả về các số nguyên riêng biệt cho các đối tượng riêng biệt”. Nghĩa là, nếu đây là hai trường hợp khác nhau thì chúng phải có các số nguyên khác nhau. mã băm. Đó là, phương pháp này không phù hợp để chúng tôi so sánh. → bằng Phương thức bằng trả lời câu hỏi “các đối tượng có bằng nhau không” và trả về một giá trị boolean. Phương pháp này có mã mặc định:
public boolean equals(Object obj) {
    return (this == obj);
}
Nghĩa là, không ghi đè phương thức này trên một đối tượng, về cơ bản phương thức này cho biết liệu các tham chiếu đến đối tượng có khớp hay không. Điều này không phù hợp với tin nhắn của chúng tôi, vì chúng tôi không quan tâm đến các liên kết đến đối tượng, chúng tôi quan tâm đến id tin nhắn. Và ngay cả khi chúng ta ghi đè phương thức bằng, giá trị tối đa chúng ta nhận được là: “Chúng bằng nhau” hoặc “Chúng không bằng nhau”. Nhưng điều này là không đủ để chúng tôi xác định thứ tự.

Trình so sánh và so sánh trong Java

Điều gì phù hợp với chúng tôi? Nếu dịch từ “so sánh” sang tiếng Anh trong trình dịch, chúng ta sẽ được dịch “so sánh”. Tuyệt vời, vậy thì chúng ta cần một người sẽ so sánh. Nếu so sánh cái so sánh này thì người so sánh chính là Comparator. Hãy mở Java Api và tìm Comparator ở đó . Và quả thực, có một giao diện như vậy - java.util.Comparator java.util.Comparator và java.lang.Comparable Như bạn thấy, có một giao diện như vậy. Lớp triển khai nó nói rằng “Tôi đang triển khai một hàm để so sánh các đối tượng”. Điều duy nhất cần thực sự nhớ là hợp đồng so sánh, được thể hiện như sau:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
Bây giờ hãy viết một bộ so sánh. Chúng ta sẽ cần import java.util.Comparator . Sau khi nhập, hãy thêm một phương thức vào main: Comparator<Message> comparator = new Comparator<Message>(); Đương nhiên, điều này sẽ không hoạt động, bởi vì Bộ so sánh là một giao diện. Vì vậy, sau dấu ngoặc đơn chúng ta sẽ thêm những dấu ngoặc nhọn { }. Trong các dấu ngoặc này chúng ta sẽ viết phương thức:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Bạn thậm chí không cần phải nhớ để viết điều này. Người so sánh là người thực hiện so sánh, nghĩa là thực hiện so sánh. Để trả lời câu hỏi thứ tự của các đối tượng được so sánh là gì, chúng ta trả về int. Thực ra đó là tất cả. Đơn giản và dễ dàng. Như chúng ta có thể thấy từ ví dụ, ngoài Comparator, còn có một giao diện khác - java.lang.Comparable , việc triển khai giao diện này chúng ta phải xác định phương thức so sánh . Giao diện này cho biết rằng "Một lớp triển khai giao diện cho phép so sánh các phiên bản của lớp đó." Ví dụ: việc triển khai so sánh của Integer trông như thế này:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Làm thế nào để nhớ tất cả các giao diện này? Để làm gì? Mọi thứ đều bắt nguồn từ tiếng Anh. So sánh - để so sánh, người so sánh là Comparator (với tư cách là nhà đăng ký chẳng hạn. Tức là người đăng ký) và tính từ “so sánh” là Comparable. Vâng, “So sánh với” được dịch không chỉ là so sánh với mà còn là so sánh với. Nó đơn giản. Ngôn ngữ Java được viết bởi những người nói tiếng Anh và khi đặt tên cho mọi thứ bằng Java, họ được hướng dẫn đơn giản bằng tiếng Anh và có một số loại logic trong cách đặt tên. Và phương thức so sánh mô tả cách so sánh một thể hiện của một lớp với các thể hiện khác. Ví dụ: các chuỗi được so sánh theo từ điển và các số được so sánh theo giá trị.
Bộ so sánh trong Java - 2
Java 8 mang lại một số thay đổi thú vị. Nếu nhìn kỹ vào giao diện Comparator, chúng ta sẽ thấy có chú thích phía trên nó @FunctionalInterface. Trên thực tế, chú thích này là để cung cấp thông tin và có nghĩa là giao diện này hoạt động bình thường. Điều này có nghĩa là giao diện này chỉ có 1 phương thức trừu tượng mà không cần triển khai. Điều này mang lại cho chúng ta điều gì? Bây giờ chúng ta có thể viết mã so sánh như thế này:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Trong ngoặc đơn là cách chúng ta đặt tên cho các biến. Bản thân Java sẽ thấy điều đó bởi vì. Nếu chỉ có một phương thức thì sẽ rõ ràng những tham số đầu vào nào là cần thiết, bao nhiêu và loại nào. Tiếp theo, chúng tôi nói bằng một mũi tên rằng chúng tôi muốn chuyển chúng sang phần mã này. Ngoài ra, nhờ Java 8, các phương thức mặc định đã xuất hiện trong các giao diện - đây là những phương thức xuất hiện theo mặc định (theo mặc định) khi chúng ta triển khai một giao diện. Có một số tính năng này trong giao diện Comparator, ví dụ:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Có một phương pháp khác sẽ làm cho mã của bạn sạch hơn. Hãy xem ví dụ trên, trong đó chúng tôi đã mô tả bộ so sánh của mình. Anh ta đang làm gì vậy? Nó khá nguyên thủy. Nó chỉ đơn giản lấy một đối tượng và trích xuất một số giá trị có thể so sánh được từ nó. Ví dụ: Integer triển khai tính năng so sánh, vì vậy chúng tôi có thể thực hiện so sánh trên các giá trị id thông báo. Hàm so sánh đơn giản này cũng có thể được viết như thế này:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Nghĩa đen là: “Chúng tôi có một Bộ so sánh so sánh như thế này: nó lấy các đối tượng, lấy các đối tượng có thể so sánh được từ chúng bằng phương thức getId(), so sánh bằng cách sử dụng so sánh”. Và không có thiết kế khủng khiếp hơn. Và cuối cùng, tôi muốn lưu ý thêm một tính năng nữa. Bộ so sánh có thể được nối với nhau. Ví dụ:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Ứng dụng

Khai báo bộ so sánh hóa ra khá logic phải không? Bây giờ chúng ta cần xem cách sử dụng nó và ở những nơi nào. → Collections.sort (java.util.Collections) Tất nhiên, chúng ta có thể sắp xếp các bộ sưu tập theo cách này. Nhưng không phải tất cả mọi thứ, chỉ là danh sách. Và không có gì bất thường ở đây, bởi vì... Đây là danh sách yêu cầu quyền truy cập vào một phần tử theo chỉ mục. Và điều này cho phép phần tử số hai được hoán đổi với phần tử số ba. Do đó, việc sắp xếp theo cách này chỉ có thể thực hiện được đối với các danh sách:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) Mảng cũng rất thuận tiện để sắp xếp. Một lần nữa, với cùng lý do truy cập các phần tử theo chỉ mục. → Hậu duệ của java.util.SortedSet và java.util.SortedMap Như chúng ta đã nhớ, Set và Map không đảm bảo thứ tự lưu trữ các bản ghi. NHƯNG chúng tôi có những triển khai đặc biệt để đảm bảo trật tự. Và nếu các thành phần của bộ sưu tập không triển khai java.lang.Comparable, thì chúng ta có thể chuyển Comparator cho hàm tạo của các bộ sưu tập đó:
Set<Message> msgSet = new TreeSet(comparator);
API luồng Trong Stream Api, xuất hiện trong Java 8, một bộ so sánh cho phép bạn đơn giản hóa công việc trên các phần tử luồng. Ví dụ: chúng ta cần một chuỗi các số ngẫu nhiên từ 0 đến 999:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Chúng ta có thể dừng lại, nhưng có nhiều vấn đề thú vị hơn. Ví dụ: bạn cần chuẩn bị Bản đồ, trong đó khóa là id tin nhắn. Đồng thời, chúng ta muốn sắp xếp các phím này sao cho các phím theo thứ tự từ nhỏ nhất đến lớn nhất. Hãy bắt đầu với mã này:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Những gì chúng ta sẽ nhận được ở đây thực chất là một HashMap. Và như chúng ta biết, nó không đảm bảo bất kỳ trật tự nào. Do đó, hồ sơ của chúng tôi được sắp xếp theo ID đã không còn đúng thứ tự. Không tốt. Chúng ta sẽ phải thay đổi bộ sưu tập của mình một chút:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
Mã trông có vẻ phức tạp hơn một chút nhưng vấn đề hiện đã được giải quyết chính xác nhờ triển khai TreeMap một cách rõ ràng. Bạn có thể đọc thêm về các nhóm khác nhau ở đây: Bạn có thể tự tạo bộ sưu tập. Bạn có thể đọc thêm tại đây: "Tạo bộ sưu tập tùy chỉnh trong Java 8" . Và thật hữu ích khi đọc cuộc thảo luận ở đây: "Danh sách Java 8 để ánh xạ với luồng" .
Trình so sánh trong Java - 3
Cào so sánh và cào có thể so sánh là tốt. Nhưng có một sắc thái liên quan đến chúng đáng được ghi nhớ. Khi một lớp thực hiện sắp xếp, nó sẽ tính toán rằng nó có thể chuyển lớp của bạn thành Có thể so sánh được. Nếu không, bạn sẽ gặp lỗi khi thực hiện. Hãy xem một ví dụ:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Có vẻ như không có gì sai ở đây. Nhưng trên thực tế, trong ví dụ của chúng tôi, nó sẽ gặp lỗi: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable Và tất cả là do nó đã cố gắng sắp xếp các phần tử (Xét cho cùng thì nó là một SortedSet). Và tôi không thể. Bạn nên nhớ điều này khi làm việc với SortedMap và SortedSet. Ngoài ra được đề xuất để xem: Yuri Tkach: HashSet và TreeSet - Bộ sưu tập số 1 - Java nâng cao
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION