HOWTO · Java

Java の一般契約エラーに違反する比較メソッドの修正

このチュートリアルでは、比較ルールについて説明し、比較方法が一般契約エラーに違反する考えられる理由を強調します。 また、コンパレータと同等のインターフェイスを使用したソリューションも提供します。

このページの内容

今日は、Comparator および Comparable インターフェースで使用される比較規則について学びます。これは、Java で 比較メソッドが一般的な契約に違反しています エラーの考えられる原因につながります。 その後、Comparator および Comparable インターフェイスを使用した 2つのソリューションを理解します。

Comparator インターフェイスと Comparable インターフェイスの違い

これらは、Java コア ライブラリの 2つのインターフェイスです。 Comparator は、2つの異なるオブジェクトを比較する関数であり、他のものから独立しています。

2つの入力のみを検索し、それらを使用してプロセスを続行し、結果を表示します。

一方、Comparable は、データ クラスと混合するインターフェイスです。 たとえば、いくつかのデータを持つクラスがあります。 Comparable インターフェイスは、このクラスの最初のオブジェクトを同じクラスの 2 番目のオブジェクトと比較するために使用されます。

これは、Comparable インターフェースが、this インスタンスを同じクラスの別のインスタンスと比較できることを示していることを意味します。 Comparable は、クラスの 自然な 順序を定義することを思い出してください。

他の点では、これは Comparator と同様の比較関数でもあり、戻り値、推移的、再帰的などについて同じ規則があります。

ComparatorComparable の比較規則

comparison method violations its general contract は、Comparator または Comparable (使用しているものに基づく) にバグがあり、整合性規則の 1つに違反していることを意味します。 整合性ルールとは何ですか?

以下でそれらを学びましょう。

整数型の値用の Java プログラムを作成したと仮定します。 したがって、比較関数は次の規則に従う必要があります。

  • 任意の 2つの整数 ab が与えられた場合、三分法を満たす必要があります。これは、次の関係のいずれかが真でなければならないことを意味します。

    • ab より小さい (a < b の場合は -ve 値を返す)
    • ab に等しい (a == b の場合は 0 を返す)
    • ab より大きい (a > b の場合は +ve 値を返す)
  • 推移性を満たす必要があります。つまり、a < b および b < c の場合、任意の 3つの数値 abc について、a < c を意味します。

  • 3 番目の規則は反対称性に関するもので、a < b~b < a を意味します。

  • 代用可能性は、a == b および a < c と仮定する比較規則でもあります。 これは b < c を意味します。

  • 最後の比較規則は Reflexivity です。ここで、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); のように並べ替えると、 そうすると、何が問題で、どこで発生しているのかを特定できなくなります。 そのため、識別しやすくするために logging で並べ替えています。

エラーが発生しますが、数値の比較も多数提供されます。 すべてを説明するわけではありませんが、そのうちのいくつかはエラーを見つけるのに十分です.

出力:

修正比較メソッドが Java の一般契約エラーに違反しています - エラー

ご覧のとおり、プログラムはここで一貫性ルールに違反しており、このエラーが発生しています。 次のセクションでは、ComparatorComparable を使用して解決策を考えてみましょう。

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

このコードでは、2つのオブジェクトの lastName が同じ場合、firstName の文字数を比較します。

ご覧のとおり、結果は一貫しています。 a==ba<b、および a>b の場合、それぞれ 0-1、および 1 を返します。