CodeGym /Blog Java /Ngẫu nhiên /Giao diện so sánh của Java

Giao diện so sánh của Java

Xuất bản trong nhóm
Những người lười biếng không phải là những người duy nhất viết về Bộ so sánh và phép so sánh trong Java. Tôi không lười biếng, vì vậy hãy yêu thích và phàn nàn về một lời giải thích khác. Tôi hy vọng nó sẽ không thừa. Và vâng, bài viết này là câu trả lời cho câu hỏi: " Bạn có thể viết một bộ so sánh từ bộ nhớ không? " Tôi hy vọng mọi người sẽ có thể viết một bộ so sánh từ bộ nhớ sau khi đọc bài viết này. Giao diện so sánh Javas - 1

Giới thiệu

Như các bạn đã biết, Java là một ngôn ngữ hướng đối tượng. Kết quả là thao tác với các đối tượng trong Java là thông lệ. Nhưng sớm hay muộn, bạn phải đối mặt với nhiệm vụ so sánh các đối tượng dựa trên một số đặc điểm. Ví dụ : Giả sử chúng ta có một số thông báo được mô tả bởi Messagelớp:

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;
    }
}
Đặt lớp này trong trình biên dịch Tutorialspoint Java . Đừng quên thêm các câu lệnh nhập:

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Trong mainphương thức này, hãy 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);
}
Hãy nghĩ xem chúng ta sẽ 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 một trật tự, chúng ta cần bằng cách nào đó so sánh các đối tượng để hiểu đối tượng nào nên đến trước (tức là đối tượng nhỏ hơn) và đối tượng nào nên theo sau (tức là đối tượng lớn hơn). Hãy bắt đầu với một lớp như java.lang.Object . Chúng ta biết rằng tất cả các lớp đều ngầm kế thừa Objectlớp đó. Và điều này có ý nghĩa vì nó phản ánh khái niệm "mọi thứ đều là đối tượng" và cung cấp hành vi chung cho tất cả các lớp. Lớp này quy định rằng mọi lớp đều có hai phương thức: → Phương thức hashCode này hashCodetrả về một số (int) đại diện của đối tượng. Điều đó nghĩa là gì? Điều đó có nghĩa là nếu bạn tạo hai thể hiện khác nhau của một lớp, thì chúng phải có hashCodecác s khác nhau. Mô tả của phương thức nói rất nhiều: "Trong thực tế hợp lý, phương thức hashCode được xác định bởi lớp Object sẽ trả về các số nguyên riêng biệt cho các đối tượng riêng biệt". Nói cách khác, đối với hai instances khác nhau, sẽ có hashCodenhững s khác nhau. Đó là, phương pháp này không phù hợp để so sánh của chúng tôi. → equals. Phương equalsthức trả lời câu hỏi "các đối tượng này có bằng nhau không?" và trả về một boolean." Theo mặc định, phương thức này có mã sau:

public boolean equals(Object obj) {
    return (this == obj);
}
Nghĩa là, nếu phương thức này không bị ghi đè, thì về cơ bản, nó cho biết liệu các tham chiếu đối tượng có khớp hay không. Đây không phải là điều chúng tôi muốn cho thư của mình, bởi vì chúng tôi quan tâm đến id thư chứ không phải tham chiếu đối tượng. Và ngay cả khi chúng ta ghi đè equalsphương thức, điều chúng ta có thể hy vọng nhất là tìm hiểu xem chúng có bằng nhau hay không. Và điều này là không đủ để chúng tôi xác định thứ tự. Vậy chúng ta cần gì sau đó? Chúng ta cần một cái gì đó để so sánh. Người so sánh là a Comparator. Mở API Java và tìm Bộ so sánh . Thật vậy, có một java.util.Comparatorgiao diện java.util.Comparator and java.util.Comparable Như bạn có thể thấy, một giao diện như vậy tồn tại. Một lớp triển khai nó nói, "Tôi triển khai một phương thức so sánh các đối tượng." Điều duy nhất bạn thực sự cần nhớ là hợp đồng so sánh, được thể hiện như sau:

Comparator returns an int according to the following rules: 
  • It returns a negative int if the first object is smaller
  • It returns a positive int if the first object is larger
  • It returns zero if the objects are equal
Bây giờ hãy viết một bộ so sánh. Chúng tôi sẽ cần phải nhập khẩu java.util.Comparator. Sau câu lệnh nhập, hãy thêm phần sau vào mainphương thức: Comparator<Message> comparator = new Comparator<Message>(); Tất nhiên, điều này sẽ không hoạt động, vì Comparatorlà một giao diện. Vì vậy, chúng tôi thêm dấu ngoặc nhọn {}sau dấu ngoặc đơn. Viết phương thức sau bên trong dấu ngoặc nhọn:

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ớ chính tả. Người so sánh là người thực hiện so sánh, tức là so sánh. Để chỉ ra thứ tự tương đối của các đối tượng, chúng tôi trả về một int. Về cơ bản là vậy. Tốt đẹp và dễ dàng. Như bạn có thể thấy từ ví dụ, ngoài Bộ so sánh, còn có một giao diện khác — java.lang.Comparable, giao diện này yêu cầu chúng tôi triển khai compareTophương thức. Giao diện này cho biết, "một lớp triển khai tôi giúp so sánh các thể hiện của lớp đó." Ví dụ: Integerviệc triển khai compareTo như sau:

(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 đã giới thiệu một số thay đổi thú vị. Nếu bạn nhìn kỹ vào Comparatorgiao diện, bạn sẽ thấy @FunctionalInterfacechú thích phía trên nó. Chú thích này dành cho mục đích thông tin và cho chúng tôi biết rằng giao diện này đang hoạt độ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, là phương thức không có 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ã của bộ so sánh như thế này:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Chúng tôi đặt tên cho các biến trong ngoặc đơn. Java sẽ thấy rằng vì chỉ có một phương thức nên số lượng và loại tham số đầu vào cần thiết là rõ ràng. Sau đó, chúng tôi sử dụng toán tử mũi tên để chuyển chúng đến phần mã này. Hơn nữa, nhờ có Java 8, giờ đây chúng ta có các phương thức mặc định trong các giao diện. Các phương thức này xuất hiện theo mặc định khi chúng ta triển khai một giao diện. Giao Comparatordiện có một số. 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, nơi chúng tôi đã xác định bộ so sánh của mình. Nó làm gì? Nó khá nguyên thủy. Nó chỉ đơn giản là lấy một đối tượng và trích xuất một số giá trị "có thể so sánh được". Ví dụ: Integerimplements comparable, vì vậy chúng tôi có thể thực hiện thao tác so sánh trên các giá trị của trường id thông báo. Hàm so sánh đơn giản này có thể được viết như sau:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Nói cách khác, chúng ta có một Comparatorphép so sánh như sau: nó lấy các đối tượng, sử dụng getId()phương thức để lấy một Comparabletừ chúng, sau đó sử dụng compareTođể so sánh. Và không có cấu trúc khủng khiếp hơn. Và cuối cùng, tôi muốn lưu ý một tính năng nữa. Bộ so sánh có thể được xâu chuỗi. Ví dụ:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Ứng dụng

Tuyên bố một bộ so sánh hóa ra khá hợp lý, bạn có nghĩ vậy không? Bây giờ chúng ta cần xem làm thế nào và ở đâu để sử dụng nó. → Collections.sort(java.util.Collections) Tất nhiên, chúng ta có thể sắp xếp các tập hợp theo cách này. Nhưng không phải mọi bộ sưu tập, chỉ danh sách. Không có gì bất thường ở đây, bởi vì danh sách là loại bộ sưu tập nơi bạn truy cập các phần tử theo chỉ mục của chúng. Điều này cho phép hoán đổi phần tử thứ hai với phần tử thứ ba. Đó là lý do tại sao phương pháp sắp xếp sau đây chỉ dành cho danh sách:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) Mảng cũng dễ sắp xếp. Một lần nữa, vì lý do tương tự - các phần tử của chúng được truy cập theo chỉ mục. → Descendants of java.util.SortedSet and java.util.SortedMap Bạn sẽ nhớ lại điều đó SetMapkhông đảm bảo thứ tự các phần tử được lưu trữ. NHƯNG, chúng tôi có các triển khai đặc biệt đảm bảo thứ tự. Và nếu các phần tử của bộ sưu tập không triển khai java.util.Comparable, thì chúng ta có thể chuyển a Comparatortới hàm tạo của nó:

Set<Message> msgSet = new TreeSet(comparator);
Stream API Trong API luồng, xuất hiện trong Java 8, bộ so sánh cho phép bạn đơn giản hóa công việc với các phần tử luồng. Ví dụ: giả sử chúng ta cần một dãy số ngẫu nhiên từ 0 đến 999, bao gồm:

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 ở đây, nhưng còn nhiều vấn đề thú vị hơn nữa. Ví dụ: giả sử bạn cần chuẩn bị một Map, trong đó khóa là id thư. Ngoài ra, chúng tôi muốn sắp xếp các khóa này, vì vậy chúng tôi sẽ bắt đầu với đoạn mã sau:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Chúng tôi thực sự nhận được một HashMapở đây. Và như chúng ta biết, nó không đảm bảo bất kỳ thứ tự nào. Do đó, các phần tử của chúng tôi, được sắp xếp theo id, chỉ đơn giản là mất 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ã đã bắt đầu trông đáng sợ hơn một chút, nhưng bây giờ vấn đề đã được giải quyết chính xác. Đọc thêm về các nhóm khác nhau ở đây: Bạn có thể tạo bộ sưu tập của riêng bạn. Đọc thêm tại đây: "Tạo trình thu thập tùy chỉnh trong Java 8" . Và bạn sẽ được lợi khi đọc phần thảo luận tại đây: "Danh sách Java 8 để ánh xạ với luồng" .

bẫy rơi

ComparatorComparablelà tốt. Nhưng có một sắc thái bạn nên nhớ. Khi một lớp thực hiện sắp xếp, nó hy vọng rằng lớp của bạn có thể được chuyển đổi thành tệp Comparable. Nếu đây không phải là trường hợp, thì bạn sẽ gặp lỗi trong thời gian chạy. Hãy xem xét một ví dụ:

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Có vẻ như không có gì là sai ở đây. Nhưng trên thực tế, trong ví dụ của chúng ta, nó sẽ thất bại với lỗi: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable Và tất cả chỉ vì nó đã cố gắng sắp xếp các phần tử ( SortedSetrốt cuộc nó là một )...nhưng không thể. Đừng quên điều này khi làm việc với SortedMapSortedSet.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION