JavaRush /Java Blog /Random-KO /자바의 비교기
Viacheslav
레벨 3

자바의 비교기

Random-KO 그룹에 게시되었습니다
게으른 사람들만이 Java의 비교기와 비교에 대해 글을 쓰지 않았습니다. 저는 게으르지 않습니다. 따라서 한 가지 변형을 더 좋아하고 선호해 주시기 바랍니다. 나는 그것이 불필요하지 않기를 바랍니다. 그리고 그렇습니다. 이 기사는 "메모리에서 비교기를 작성할 수 있습니까?"라는 질문에 대한 답변입니다. 이 기사를 읽은 후에 모든 사람이 메모리에서 비교기를 작성할 수 있기를 바랍니다.
Java의 비교기 - 1
소개 Java는 객체 지향 언어로 알려져 있습니다. 결과적으로 Java에서는 객체를 사용하여 작업하는 것이 일반적입니다. 그러나 조만간 어떤 원칙에 따라 객체를 비교하는 작업이 발생합니다. 따라서 주어진 메시지는 다음과 같습니다. 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;
    }
}
이 클래스를 Tutorialspoint Java 컴파일러 에 추가해 보겠습니다 . import를 추가하는 것도 잊지 마세요:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
기본 메서드에서는 여러 메시지를 생성합니다.
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);
}
비교하고 싶다면 어떻게 해야 할지 생각해볼까요? 예를 들어, ID를 기준으로 정렬하려고 합니다. 그리고 순서를 만들려면 어떤 개체가 이전 개체인지(즉, 더 작은 개체) 다음 개체(즉, 큰 개체)인지 이해하기 위해 어떻게든 개체를 비교해야 합니다. java.lang.Object 와 같은 클래스부터 시작해 보겠습니다 . 우리가 알고 있듯이 모든 클래스는 이 Object 클래스에서 암시적으로 상속됩니다. 그리고 이것은 논리적입니다. 왜냐하면 이는 본질적으로 "모든 것이 객체이다"라는 개념을 표현하고 모든 클래스에 대한 공통 동작을 제공합니다. 그리고 이 클래스는 각 클래스에 두 개의 메소드가 있음을 정의합니다. → hashCode hashCode 메소드는 클래스의 인스턴스로서 객체의 숫자(int) 표현을 반환합니다. 무슨 뜻이에요? 이는 클래스의 서로 다른 두 인스턴스를 생성한 경우 인스턴스가 다르기 때문에 해당 hashCode도 달라야 함을 의미합니다. 메서드 설명에 다음과 같이 나와 있습니다. "합리적으로 실용적인 만큼 Object 클래스에 의해 정의된 hashCode 메서드는 개별 객체에 대해 개별 정수를 반환합니다." 즉, 두 개의 다른 인스턴스인 경우 서로 다른 인스턴스를 가져야 합니다. 해시코드. 즉, 이 방법은 비교에 적합하지 않습니다. → equals equals 메소드는 "객체는 동일한가?"라는 질문에 답하고 부울 값을 반환합니다. 이 메서드에는 기본 코드가 있습니다.
public boolean equals(Object obj) {
    return (this == obj);
}
즉, 개체에서 이 메서드를 재정의하지 않고 이 메서드는 본질적으로 개체에 대한 참조가 일치하는지 여부를 알려줍니다. 이는 우리 메시지에 적합하지 않습니다. 왜냐하면 우리는 객체에 대한 링크에 관심이 없고 메시지 ID에만 관심이 있기 때문입니다. 그리고 equals 메서드를 재정의하더라도 얻을 수 있는 최대값은 "그들은 동일합니다" 또는 "그들은 동일하지 않습니다."입니다. 그러나 이것만으로는 순서를 결정하기에 충분하지 않습니다.

Java의 비교기와 비교 가능

우리에게 어울리는 것은 무엇입니까? 번역기에서 "비교"라는 단어를 영어로 번역하면 "비교"라는 번역이 표시됩니다. 좋습니다. 그렇다면 비교할 사람이 필요합니다. 이 비교를 비교하면 비교하는 사람이 비교자입니다. Java Api를 열고 거기에서 Comparator를 찾아보겠습니다 . 그리고 실제로 그러한 인터페이스가 있습니다 - java.util.Comparator java.util.Comparator 및 java.lang.Comparable 보시다시피 그러한 인터페이스가 있습니다. 이를 구현한 클래스에서는 “객체를 비교하는 함수를 구현하고 있습니다.”라고 말합니다. 실제로 기억해야 할 유일한 것은 다음과 같이 표현되는 비교기 계약입니다.

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
이제 비교기를 작성해보자. java.util.Comparator 를 가져와야 합니다 . 가져온 후 main에 메소드를 추가하십시오. Comparator<Message> comparator = new Comparator<Message>(); 당연히 이것은 작동하지 않습니다. 비교기는 인터페이스입니다. 따라서 괄호 뒤에 곱슬곱슬한 괄호를 추가합니다 { }. 이 괄호 안에 메소드를 작성합니다:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
이 글을 쓰는 것을 기억할 필요도 없습니다. 비교자는 비교를 수행하는 것, 즉 비교하는 것입니다. 비교된 객체의 순서가 무엇인지 묻는 질문에 답하기 위해 int를 반환합니다. 실제로 그게 전부입니다. 간단하고 쉽게. 예제에서 볼 수 있듯이 Comparator 외에도 다른 인터페이스인 java.lang.Comparable 이 있으며 이를 구현하려면 CompareTo 메서드를 정의해야 합니다 . 이 인터페이스는 "인터페이스를 구현하는 클래스를 통해 해당 클래스의 인스턴스를 비교할 수 있습니다."라고 말합니다. 예를 들어 Integer의 CompareTo 구현은 다음과 같습니다.
(x < y) ? -1 : ((x == y) ? 0 : 1)
이 모든 인터페이스를 어떻게 기억하나요? 무엇 때문에? 모든 것은 영어에서 비롯됩니다. 비교 - 비교를 위해 비교하는 사람은 Comparator(예: 등록자, 즉 등록하는 사람)이고 형용사 "비교됨"은 Comparable입니다. 글쎄요, “비교하다”는 비교한다는 의미뿐만 아니라 비교라는 의미로도 번역됩니다. 간단 해. 자바라는 언어는 영어권 사람들이 썼는데, 자바로 모든 것을 명명할 때 단순히 영어를 기준으로 하고 명명에는 일종의 논리가 있었습니다. 그리고 CompareTo 메서드는 클래스의 인스턴스를 다른 인스턴스와 비교하는 방법을 설명합니다. 예를 들어 문자열은 사전식으로 비교되고 숫자는 값으로 비교됩니다.
Java 비교기 - 2
Java 8은 몇 가지 좋은 변화를 가져왔습니다. Comparator 인터페이스를 자세히 살펴보면 그 위에 주석이 있는 것을 볼 수 있습니다 @FunctionalInterface. 실제로 이 주석은 정보 제공을 위한 것이며 이 인터페이스가 작동한다는 것을 의미합니다. 즉, 이 인터페이스에는 구현 없이 추상 메서드가 하나만 있습니다. 이것이 우리에게 무엇을 주는가? 이제 다음과 같이 비교기 코드를 작성할 수 있습니다.
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
괄호 안에는 변수 이름을 지정하는 방법이 나와 있습니다. Java 자체가 그것을 볼 것이기 때문입니다. 메서드가 하나만 있는 경우 필요한 입력 매개 변수, 개수, 유형이 명확합니다. 다음으로 화살표를 사용하여 코드의 이 섹션으로 전송하고 싶다고 말합니다. 또한 Java 8 덕분에 인터페이스에 기본 메소드가 나타났습니다. 이는 인터페이스를 구현할 때 기본적으로(기본적으로) 나타나는 메소드입니다. Comparator 인터페이스에는 이러한 기능이 여러 개 있습니다. 예를 들면 다음과 같습니다.
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
코드를 더욱 깔끔하게 만드는 또 다른 방법이 있습니다. 비교기를 설명했던 위의 예를 살펴보겠습니다. 그는 무엇을 하고 있나요? 상당히 원시적입니다. 이는 단순히 개체를 가져와서 비교할 수 있는 값을 추출합니다. 예를 들어 Integer는 비교 기능을 구현하므로 메시지 ID 값에 대해 CompareTo를 수행할 수 있었습니다. 이 간단한 비교 함수는 다음과 같이 작성할 수도 있습니다.
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
즉, 문자 그대로 "우리는 다음과 같이 비교하는 Comparator를 가지고 있습니다. 객체를 가져와서 getId() 메서드를 사용하여 객체에서 Comparable을 가져오고, CompareTo를 사용하여 비교합니다." 그리고 더 이상 끔찍한 디자인은 없습니다. 마지막으로 한 가지 기능을 더 언급하고 싶습니다. 비교기는 서로 연결될 수 있습니다. 예를 들어:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

애플리케이션

비교기 선언은 상당히 논리적인 것으로 드러났습니다. 그렇지 않습니까? 이제 우리는 그것을 어떻게 사용하는지, 어떤 장소에서 사용하는지 살펴봐야 합니다. → Collections.sort (java.util.Collections) 물론 이 방법으로 컬렉션을 정렬할 수 있습니다. 하지만 전부는 아니고 목록만 나열합니다. 그리고 여기에는 특별한 것이 없습니다. 왜냐면... 인덱스별로 요소에 액세스해야 하는 목록입니다. 그리고 이를 통해 요소 번호 2를 요소 번호 3으로 교체할 수 있습니다. 따라서 이러한 방식의 정렬은 목록에만 가능합니다.
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) 배열은 정렬에도 편리합니다. 다시 말하지만, 인덱스로 요소에 액세스하는 것과 같은 이유입니다. → java.util.SortedSet과 java.util.SortedMap의 후손 우리가 기억하는 것처럼 Set과 Map은 레코드 저장 순서를 보장하지 않습니다. 그러나 순서를 보장하는 특별한 구현이 있습니다. 컬렉션 요소가 java.lang.Comparable을 구현하지 않으면 Comparator를 해당 컬렉션의 생성자에 전달할 수 있습니다.
Set<Message> msgSet = new TreeSet(comparator);
Stream API Java 8에 등장한 Stream Api에서는 비교기를 사용하여 스트림 요소에 대한 작업을 단순화할 수 있습니다. 예를 들어, 0부터 999까지의 난수 시퀀스가 ​​필요합니다.
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
멈출 수도 있지만 더 흥미로운 문제가 있습니다. 예를 들어 키가 메시지 ID인 맵을 준비해야 합니다. 동시에 우리는 키가 가장 작은 것부터 큰 것 순으로 정렬되도록 이러한 키를 정렬하려고 합니다. 다음 코드부터 시작해 보겠습니다.
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
여기서 다시 얻을 것은 실제로 HashMap입니다. 그리고 우리가 알고 있듯이 이는 주문을 보장하지 않습니다. 따라서 ID별로 정렬된 기록이 단순히 순서가 잘못되었습니다. 안좋다. 수집기를 약간 변경해야 합니다.
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));
코드가 조금 이상해 보였지만 이제 TreeMap의 명시적인 구현 덕분에 문제가 올바르게 해결되었습니다. 여기에서 다양한 그룹에 대해 자세히 알아볼 수 있습니다. 수집기를 직접 만들 수 있습니다. 자세한 내용은 "Java 8에서 사용자 정의 수집기 생성"을 참조하세요 . 그리고 여기에서 "스트림과 매핑할 Java 8 목록"에 대한 토론을 읽는 것이 유용합니다 .
Java의 비교기 - 3
비교기(Comparator)와 비교기(Comparable) 레이크 가 좋습니다. 그러나 기억할 가치가 있는 하나의 뉘앙스가 있습니다. 클래스가 정렬을 수행하면 클래스를 Comparable로 캐스팅할 수 있다고 계산합니다. 그렇지 않은 경우 실행 시 오류가 발생합니다. 예를 살펴보겠습니다:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
여기에는 아무런 문제가없는 것 같습니다. 그러나 실제로 우리 예에서는 다음 오류와 함께 충돌이 발생합니다. java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable 그리고 모든 것은 요소를 정렬하려고 시도했기 때문입니다(결국 SortedSet입니다). 그리고 나는 할 수 없었다. SortedMap 및 SortedSet을 사용할 때 이 점을 기억해야 합니다. 추가로 시청할 것을 권장합니다: Yuri Tkach: HashSet 및 TreeSet - 컬렉션 #1 - 고급 Java
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION