CodeGym/Java Blog/무작위의/자바의 Comparator 인터페이스
John Squirrels
레벨 41
San Francisco

자바의 Comparator 인터페이스

무작위의 그룹에 게시되었습니다
회원
게으른 사람만이 비교자와 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;
메서드 에서 main여러 메시지를 생성합니다.
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클래스를 상속한다는 것을 알고 있습니다. 그리고 이것은 "모든 것이 객체"라는 개념을 반영하고 모든 클래스에 공통된 동작을 제공하기 때문에 의미가 있습니다. 이 클래스는 모든 클래스에 두 가지 메서드가 있음을 나타냅니다. → hashCodehashCode메서드는 일부 숫자(int) 객체의 표현. 그게 무슨 뜻이야? 즉, 클래스의 서로 다른 두 인스턴스를 만들면 서로 다른 hashCodes를 가져야 합니다. 메소드의 설명은 다음과 같이 말합니다. "합리적으로 실용적인 만큼 Object 클래스에 의해 정의된 hashCode 메소드는 개별 개체에 대해 개별 정수를 반환합니다." 즉, 두 개의 다른 instances에 대해 서로 다른 s가 있어야 합니다 hashCode. 즉, 이 방법은 비교에 적합하지 않습니다. → equals. 메서드 equals는 "이 개체가 같은가요?"라는 질문에 답합니다. 그리고 를 반환합니다 boolean." 기본적으로 이 메서드에는 다음 코드가 있습니다.
public boolean equals(Object obj) {
    return (this == obj);
}
즉, 이 메서드를 재정의하지 않으면 본질적으로 개체 참조가 일치하는지 여부를 나타냅니다. 우리는 개체 참조가 아니라 메시지 ID에 관심이 있기 때문에 메시지에 대해 원하는 것이 아닙니다. 그리고 우리가 equals방법을 재정의하더라도 우리가 바랄 수 있는 가장 큰 것은 그것들이 같은지 배우는 것입니다. 그리고 이것은 우리가 순서를 결정하기에 충분하지 않습니다. 그렇다면 우리에게 필요한 것은 무엇일까요? 비교 대상이 필요합니다. 비교하는 사람은 Comparator. Java API를 열고 Comparator 를 찾으십시오 . java.util.Comparator실제로 인터페이스 가 있습니다.java.util.Comparator and java.util.Comparable 보시다시피 이러한 인터페이스가 존재합니다. 이를 구현하는 클래스는 "객체를 비교하는 메서드를 구현합니다."라고 말합니다. 정말로 기억해야 할 유일한 것은 다음과 같이 표현되는 비교기 계약입니다.
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
이제 비교기를 작성해 봅시다. 우리는 수입해야 할 것입니다 java.util.Comparator. import 문 다음에 메서드에 다음을 추가합니다 main. Comparator<Message> comparator = new Comparator<Message>(); 물론 는 Comparator인터페이스이므로 작동하지 않습니다. {}따라서 괄호 뒤에 중괄호를 추가합니다 . 중괄호 안에 다음 메서드를 작성합니다.
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
철자를 기억할 필요조차 없습니다. 비교기는 비교를 수행하는 것, 즉 비교하는 것입니다. 객체의 상대적인 순서를 나타내기 위해 를 반환합니다 int. 그게 기본입니다. 좋고 쉽습니다. 예제에서 볼 수 있듯이 Comparator 외에도 메서드를 java.lang.Comparable구현해야 하는 또 다른 인터페이스 — 가 있습니다 compareTo. 이 인터페이스는 "나를 구현하는 클래스는 클래스의 인스턴스를 비교할 수 있게 합니다."라고 말합니다. 예를 들어 의 To Integer구현은 compare다음과 같습니다.
(x < y) ? -1 : ((x == y) ? 0 : 1)
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들어 를 구현 comparable하므로 메시지 ID 필드의 값에 대해 compareTo 작업을 수행할 수 있습니다. 이 간단한 비교기 함수는 다음과 같이 작성할 수 있습니다.
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
즉, Comparator다음과 같이 비교하는 a가 있습니다. 개체를 가져오고 getId()메서드를 사용하여 개체에서 가져온 Comparable다음 compareTo비교하는 데 사용합니다. 그리고 더 이상 끔찍한 구조는 없습니다. 마지막으로 한 가지 더 기능에 주목하고 싶습니다. 비교기는 연결될 수 있습니다. 예를 들어:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

애플리케이션

비교자를 선언하는 것이 매우 논리적이라고 생각하지 않습니까? 이제 우리는 그것을 어디에 어떻게 사용하는지 알아야 합니다. → Collections.sort(java.util.Collections) 물론 컬렉션을 이런 식으로 정렬할 수 있습니다. 그러나 모든 컬렉션이 아니라 목록만 있습니다. 목록은 인덱스로 요소에 액세스하는 일종의 컬렉션이기 때문에 여기에 특이한 것은 없습니다. 이렇게 하면 두 번째 요소를 세 번째 요소로 바꿀 수 있습니다. 이것이 다음 정렬 방법이 목록에만 적용되는 이유입니다.
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) 배열도 정렬하기 쉽습니다. 다시 말하지만, 같은 이유로 해당 요소는 인덱스로 액세스됩니다. → 요소가 저장되는 순서를 Descendants of java.util.SortedSet and java.util.SortedMap 기억 Set하고 보장하지 않습니다. Map그러나 주문을 보장하는 특별한 구현이 있습니다. 그리고 컬렉션의 요소가 를 구현하지 않으면 생성자에 java.util.Comparablea를 전달할 수 있습니다 .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));
여기서 멈출 수도 있지만 더 흥미로운 문제가 있습니다. Map예를 들어 키가 메시지 ID인 a를 준비해야 한다고 가정합니다 . 또한 이러한 키를 정렬하려고 하므로 다음 코드로 시작하겠습니다.
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));
코드가 조금 더 무섭게 보이기 시작했지만 이제 문제가 올바르게 해결되었습니다. 여기에서 다양한 그룹화에 대해 자세히 알아보세요. 나만의 컬렉터를 만들 수 있습니다. 자세한 내용은 "Creating a custom collector in Java 8"을 참조하십시오 . "Java 8 list to map with stream" 토론을 읽으면 도움이 될 것입니다 .

추락 함정

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.

더 읽어보기:

코멘트
  • 인기
  • 신규
  • 이전
코멘트를 남기려면 로그인 해야 합니다
이 페이지에는 아직 코멘트가 없습니다