HOWTO · Java

수정 비교 방법이 Java의 일반 계약 오류를 위반함

이 튜토리얼은 비교 규칙에 대해 교육하여 비교 방법이 일반 계약 오류를 위반하는 가능한 이유를 강조합니다. 또한 비교기와 유사한 인터페이스를 사용하는 솔루션을 제공합니다.

이 페이지의 내용

오늘은 Comparator 및 Comparable 인터페이스에서 사용하는 비교 규칙에 대해 알아보겠습니다. 이로 인해 Java에서 comparison 메서드가 일반 계약을 위반함 오류가 발생할 수 있습니다. 그런 다음 Comparator 및 Comparable 인터페이스를 사용하는 두 가지 솔루션을 이해합니다.

Comparator와 Comparable 인터페이스의 차이점

이들은 Java 핵심 라이브러리의 두 인터페이스입니다. 비교기는 서로 다른 두 개체를 비교하는 기능이며 다른 것과는 독립적입니다.

두 개의 입력만 찾고 프로세스를 계속 진행한 다음 결과를 표시합니다.

반면 Comparable은 데이터 클래스와 혼합되는 인터페이스입니다. 예를 들어, 일부 데이터가 있는 클래스가 있습니다. Comparable 인터페이스는 이 클래스의 첫 번째 개체를 같은 클래스의 두 번째 개체와 비교하는 데 사용됩니다.

이는 Comparable 인터페이스가 이 인스턴스를 동일한 클래스의 다른 인스턴스와 비교할 수 있음을 나타냅니다. Comparable은 클래스의 natural 순서를 정의합니다.

다른 측면에서는 Comparator와 같은 비교 함수이기도 하며 반환 값, 전이적, 재귀적 등에 대해 동일한 규칙을 가집니다.

Comparator 및 Comparable에 대한 비교 규칙

비교 방법이 일반 계약을 위반함은 비교기 또는 비교 가능(우리가 사용하는 것에 따라)에 버그가 있고 일관성 규칙 중 하나를 위반함을 의미합니다. 일관성 규칙은 무엇입니까?

아래에서 배워봅시다.

정수 유형 값에 대한 Java 프로그램을 작성했다고 가정합니다. 따라서 비교 함수는 다음 규칙을 준수해야 합니다.

  • 임의의 두 정수 a 및 b가 주어지면 삼분법이 충족되어야 합니다. 즉, 다음 관계 중 정확히 하나가 참이어야 합니다.

    • a는 b보다 작습니다(a < b인 경우 -ve 값 반환)
    • a는 b와 같습니다(a == b인 경우 0을 반환함)
    • a는 b보다 큽니다(a > b인 경우 +ve 값 반환)
  • 전이성을 만족해야 합니다. 즉, a < b 및 b < c인 경우 세 숫자 a, b, c에 대해 a < c를 의미합니다.

  • 세 번째 규칙은 Antisymmetry에 관한 것입니다. 여기서 a < b는 ~b < a를 의미합니다.

  • 대체 가능성은 a == b 및 a < c라고 가정하는 비교 규칙이기도 합니다. 이것은 b < c를 의미합니다.

  • 최종 비교 규칙은 반사성입니다. 여기서 a == a입니다. 또한 ~a < a.

이러한 규칙 중 하나라도 위반되면 비교 방법이 일반 계약을 위반함이라는 오류가 발생합니다. 아래 코드 예제를 통해 배워봅시다.

비교 방법이 일반 계약을 위반함 오류가 있는 Java 코드

예제 코드:

// import libraries
import static java.util.stream.Collectors.toCollection;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

// Main
public class Main {
  public static void main(String[] args) {
    // generate random numbers
    List<Integer> list = new Random(209).ints(32L).boxed().collect(toCollection(ArrayList::new));
    // sort using lambda expression
    list.sort(logging((a, b) -> a - b));
  } // end main()

  // logging the comparisons
  static Comparator<Integer> logging(Comparator<Integer> c) {
    return (a, b) -> {
      int r = c.compare(a, b);
      System.err.printf("%,14d %,14d => %,14d\n", a, b, r);
      return r;
    };
  } // end logging()
} // end class

이 코드에서는 난수 스트림을 생성하는 데 사용되는 Random 클래스의 ints 인스턴스를 사용하여 몇 가지 난수를 생성합니다.

이 코드의 경우 list.sort((a, b) -> a - b);로 정렬하면 그러면 문제가 무엇이며 어디에서 발생하는지 확인할 수 없습니다. 이것이 우리가 그것을 식별하는 데 도움이 될 로깅을 통해 정렬하는 이유입니다.

그것은 우리에게 오류를 주지만, 그것은 또한 많은 수의 비교를 제공합니다. 모두 다루지는 않겠지만 그 중 몇 가지는 오류를 찾기에 충분합니다.

출력:

수정 비교 방법이 java에서 일반 계약 오류를 위반함 - 오류

보시다시피 프로그램은 여기서 일관성 규칙을 위반하여 이 오류가 발생합니다. 다음 섹션에서는 Comparator 및 Comparable을 사용하여 솔루션을 살펴보겠습니다.

Comparator 및 Comparable 인터페이스를 사용하는 Java 솔루션

난수가 적은 Comparator 인터페이스를 사용해 봅시다.

예제 코드:

// import libraries
import static java.util.stream.Collectors.toCollection;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

// Main
public class Main {
  public static void main(String[] args) {
    // generate random numbers
    List<Integer> list = new Random(5).ints(32L).boxed().collect(toCollection(ArrayList::new));
    // sort using lambda expression
    list.sort(logging((a, b) -> a - b));
  } // end main()

  // logging the comparisons
  static Comparator<Integer> logging(Comparator<Integer> c) {
    return (a, b) -> {
      int r = c.compare(a, b);
      System.err.printf("%,14d %,14d => %,14d\n", a, b, r);
      return r;
    };
  } // end logging()
} // end class

이 코드는 성공적으로 실행됩니다. 우리는 모든 비교를 거치지는 않지만 확인을 위해 몇 가지 비교를 볼 것입니다.

다음 스크린샷을 확인하십시오.

출력:

수정 비교 메서드는 java에서 일반 계약 오류를 위반함 - 솔루션 1

비교를 위한 Comparable 인터페이스를 사용하여 오류가 없는 Java 코드에 대해 알아봅시다. 이 인터페이스를 사용하려면 데이터를 포함하는 클래스를 만들어야 합니다.

아래에서 해봅시다.

예제 코드(Students.java 클래스):

public class Student implements Comparable<Student> {
  private String firstName;
  private String lastName;

  public Student(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public int compareTo(Student other) {
    /*
    compare the last names and save the result
    in `compareResult` variable
    */
    int compareResult = this.lastName.compareTo(other.lastName);

    /*
    If both last names match, then it would be true means 0. So,
    dive into the `if` condition and check if the count of their
    first name matches.
    if this.count == other.count return 0
    if this.count > other.count return 1
    if this.count < other.count return -1
    */

    if (compareResult == 0) {
      if (this.firstName.chars().count() == other.firstName.chars().count()) {
        compareResult = 0;
        return compareResult;
      } else if (this.firstName.chars().count() > other.firstName.chars().count()) {
        compareResult = 1;
        return compareResult;
      } else {
        compareResult = -1;
        return compareResult;
      }
    } else {
      return compareResult;
    }
  }
}

예제 코드(Main.java 클래스):

public class Main {
  public static void main(String[] args) {
    Student std1 = new Student("Mehvish", "Ashiq");
    Student std2 = new Student("Mehvish", "Ashiq");
    System.out.println("Result of Comparison 1: " + std1.compareTo(std2));

    Student std3 = new Student("Aftab", "Ashiq");
    Student std4 = new Student("Mehvish", "Ashiq");
    System.out.println("Result of Comparison 2: " + std3.compareTo(std4));

    Student std5 = new Student("Mehr-un-nissa", "Ashiq");
    Student std6 = new Student("Mehvish", "Ashiq");
    System.out.println("Result of Comparison 3: " + std5.compareTo(std6));
  }
}

출력:

Result of Comparison 1: 0
Result of Comparison 2: -1
Result of Comparison 3: 1

이 코드에서는 두 개체의 lastName이 동일한 경우 firstName의 문자 수를 비교합니다.

보시다시피 결과는 일관됩니다. a==b, a<b 및 a>b인 경우 각각 0, -1 및 1을 반환합니다.