1. ホーム
  2. Android開発

コンパレータの例外です。比較メソッドは一般契約に違反する!

2022-02-25 02:35:25
<パス

最近、ソート用のコンパレータを書いていて、次のような例外に遭遇しました。

    java.lang.IllegalArgumentException: Comparison method violates its general contract!  
    at java.util.TimSort.mergeHi(TimSort.java:868)  
    at java.util.TimSort.mergeAt(TimSort.java:485)  
    at java.util.TimSort.mergeCollapse(TimSort.java:408)  
    at java.util.TimSort.sort(TimSort.java:214)  
    at java.util.TimSort.sort(TimSort.java:173)  
    at java.util.Arrays.sort(Arrays.java:659)  
    at java.util.Collections.sort(Collections.java:217)  

自作のコードは大まかに以下のように示され、主な機能は取得したファイルを時間順に並べることです。

public ArrayList<File> getAllLogFiles(int flag) {
    ArrayList<File> fileList = new ArrayList<File>();

    //Omit the logic to get File
    ........

    if (fileList.size() > 1) {
        //Comparator, determines result based on time
        Collections.sort(fileList, new Comparator<File>() {
            @Override
            public int compare(File lFile, File rFile) {
                long val = lFile.lastModified() - rFile.lastModified();
                if (val > 0) {
                    return -1;
                } else if (val == 0L) {
                    return 0;
                }
                return 1;
            }
        });
    }

    return fileList;
}

その例外メッセージを検索してみると、コンパレータの書き方が悪いことが原因であることがわかりました。
Javaのコンパレータには、"consistency"の原則と呼ばれるものがあり、おそらくこのような仕組みになっているのでしょう。

したがって、コンパレータを記述する際には、すべてのケースを考慮する必要がある。

上記のコードが例外を投げる理由は、lFileまたはrFileがNULLの場合を考慮していないためです。
この2つのパラメーターがNULLでないことは保証できたのですが、JVMはそれを知らないので、コンパレーターのロジックに、以下のような厳密さが要求されました。
の大きさをその都度判断する原則を厳密に考慮した。

この問題では、以下のコードを以下のように変更することができます。

public ArrayList<File> getAllLogFiles(int flag) {
    ArrayList<File> fileList = new ArrayList<File>();

    //Omit the logic to get File
    ........

    if (fileList.size() > 1) {
        ArrayList<File> tmpList = new ArrayList<File>(fileList);
        try {
            Collections.sort(tmpList, new Comparator<File>() {
                @Override
                public int compare(File lFile, File rFile) {
                    boolean lInValid = (lFile == null || !lFile.exists());
                    boolean rInValid = (rFile == null || !rFile.exists());
                    boolean bothInValid = lInValid && rInValid;

                    if (bothInValid) {
                        return 0;
                    }

                    if (lInValid) {
                        return 1;
                    }

                    if (rInValid) {
                        return -1;
                    }

                    Long lTime = lFile.lastModified();
                    Long rTime = rFile.lastModified();
                    return lTime.compareTo(rTime) * (-1);
                }
            });
            fileList = tmpList;
            //lastModified may meet ErrnoException and return 0
            //which may cause "Comparison method violates its general contract!"
        } catch (IllegalArgumentException e) {
            .........
            //active caught the exception
            //mainly when getting the file modification time, it may cause ErrnoException, which makes the comparator violates "Consistency" principle
        }
    }

    return fileList;
}