简体   繁体   English

为什么此compareTo()方法导致排序时违反合同?

[英]Why does this compareTo() method lead to a contract violation while sorting?

I have a list of filenames and want to compare these in the following order: 我有一个文件名列表,并想按以下顺序进行比较:

  • all names ending with ".rar" should come before files with ".r01", ".r02", ... 以“.rar程序”结尾的所有名字都应该有“以R01" ,” .r02" ,文件之前 ...
  • all names ending with ".par2" should come after files with with any other suffix 所有以“ .par2”结尾的名称应位于带有任何其他后缀的文件之后

So I am using the following compareTo method for one of my Java classes: 因此,我对我的一个Java类使用了以下compareTo方法:

public class DownloadFile implements Comparable<DownloadFile>
{
    // custom code ...

    @Override
    public int compareTo(DownloadFile other)
    {
        if(other == null)
            throw new NullPointerException("Object other must not be null");

        // special cases -- .rar vs .par2 etc.
        String thisStr = filename.toLowerCase();
        String oStr = other.getFilename().toLowerCase();
        if(thisStr.endsWith(".rar") && oStr.matches(".*\\.r[0-9]{2,}$"))
            return -1;
        if(thisStr.matches(".*\\.r[0-9]{2,}$") && oStr.endsWith(".rar"))
            return 1;
        if(!thisStr.endsWith(".par2") && oStr.endsWith(".par2"))
            return -1;
        if(thisStr.endsWith(".par2") && !oStr.endsWith(".par2"))
            return 1;

        // normal comparison based on filename strings
        return thisStr.compareTo(oStr);
    }
}

However, on some data this leads to the following execption: 但是,在某些数据上,这导致以下执行:

Exception in thread "Thread-12" java.lang.IllegalArgumentException: Comparison method violates its general contract!

I tried to understand what I am missing here, but I can't find the issue. 我试图了解我在这里缺少的内容,但是找不到问题。
Can you spot where I am violating the contract? 你能发现我违反合同的地方吗?

PS: If I comment out the second two if s, then the exeception is still thrown. PS:如果我将后两个if注释掉,那么这种理解仍然会抛出。 So the problem lies with the first two if s. 因此,问题出在前两个if s上。

It is not transitive. 它不是可传递的。
Linear ordering of elements is not possible. 元素的线性排序是不可能的。

Proof by counterexample. 通过反例证明。

Say you have got 3 DownloadFile s ( c , b , a ) with names in lowercase: 假设您有3个DownloadFile s( cba ),它们的名称均小写:

c.par2
b.notpar2
a.par2

To simplify I will use < for linear ordering and names in lowercase. 为了简化,我将使用<进行线性排序并以小写形式命名。

c.par2 < b.notpar2 and b.notpar2 < a.par2 , but it is not true that c.par2 < a.par2 . c.par2 < b.notpar2b.notpar2 < a.par2 ,但它是不正确的c.par2 < a.par2
This relation is not transitive . 这种关系不是传递的

In logic... it would be like: 从逻辑上来说……

cRb and bRa , but it is not true that cRa . cRbbRa ,但它是不正确的cRa

All you have to do is to answer how to order your files linearly... 您所要做的就是回答如何线性排列文件...
I would go for something like this: 我会去这样的事情:

if(aMethodOnThis < aMethodOnOther) {
    return -1; //or 1
}
if(aCompletelyDifferentCriterium) {
    //...
}
return 0; //or return thisFileName.compareTo(otherFileName);

The return 0 at the end is quite important, because it returned for indistinguishable files. 最后的return 0非常重要,因为对于无法区分的文件, return 0

In that case: 在这种情况下:

public class DownloadFile implements Comparable<DownloadFile>{

    String filename;

    DownloadFile(String filename) {
        this.filename = filename;
    }

    public String getFilename() {
        return this.filename;
    }

    @Override
    public String toString() {
        return this.getFilename();
    }

    @Override
    public int compareTo(DownloadFile downloadFile) {
        String thisStr = this.filename.toLowerCase();
        String oStr = downloadFile.getFilename().toLowerCase();
        if(thisStr.endsWith(".rar")) {
            if(!oStr.endsWith(".rar"))
                return -1;
        }
        if(oStr.endsWith(".rar")) {
            if(!thisStr.endsWith(".rar"))
                return 1;
        }
        if(thisStr.matches(".*\\.r[0-9]{2,}$")) {
            if(!oStr.matches(".*\\.r[0-9]{2,}$"))
                return -1;
        }
        if(oStr.matches(".*\\.r[0-9]{2,}$")) {
            if(!thisStr.matches(".*\\.r[0-9]{2,}$"))
                return 1;
        }
        if(thisStr.endsWith(".par2")) {
            if(!oStr.endsWith(".par2"))
                return -1;
        }
        if(oStr.endsWith(".par2")) {
            if(!thisStr.endsWith(".par2"))
                return 1;
        }
        return thisStr.compareTo(oStr);
    }

    public static void main(String[] args) {
        List<DownloadFile> fileList = new ArrayList<>();
        fileList.add(new DownloadFile("a.rar"));
        fileList.add(new DownloadFile("b.rar"));
        fileList.add(new DownloadFile("a.r01"));
        fileList.add(new DownloadFile("b.r01"));
        fileList.add(new DownloadFile("a.r10"));
        fileList.add(new DownloadFile("b.r10"));
        fileList.add(new DownloadFile("a.par2"));
        fileList.add(new DownloadFile("b.par2"));
        fileList.add(new DownloadFile("a.other"));
        fileList.add(new DownloadFile("b.other"));
        Collections.shuffle(fileList);
        Collections.sort(fileList);
        System.out.println(fileList);
    }
}

To make it shorter Predicate<String> from Java 8 comes in handy ;) 为了使它更短,来自Java 8的Predicate<String>派上用场了;)

@Override
public int compareTo(DownloadFile downloadFile) {
    String thisStr = this.filename.toLowerCase();
    String oStr = downloadFile.getFilename().toLowerCase();
    List<Predicate<String>> conditionList = new ArrayList<>();
    conditionList.add(s -> s.endsWith(".rar"));
    conditionList.add(s -> s.matches(".*\\.r[0-9]{2,}$"));
    conditionList.add(s -> s.endsWith(".par2"));
    for(Predicate<String> condition : conditionList) {
        int orderForCondition =
                conditionHelper(thisStr, oStr, condition);
        if(orderForCondition != 0)
            return orderForCondition;
    }
    return thisStr.compareTo(oStr);
}

private int conditionHelper(String firstStr, String secondStr,
                            Predicate<String> condition) {
    if(condition.test(firstStr))
        if(!condition.test(secondStr))
            return -1;
    if(condition.test(secondStr))
        if(!condition.test(firstStr))
            return 1;
    return 0;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 自定义 CompareTo(),为什么 compareTo 的顺序对排序顺序很重要? - Self Define CompareTo(), why does the order of compareTo matters to the sorting order? 为什么compareTo方法在排序对象数组时这么慢 - Why is compareTo method so slow when sorting an array of objects compareTo比较方法违反了其一般合同 - compareTo comparison method violates its general contract 为什么这种方法会导致并发问题? - Why does this method lead to concurrency issues? 为什么我不能在保留compareTo契约的同时使用新的值组件扩展可实例化的类? - Why can't I extend an instantiable class with a new value component while preserving the compareTo contract? 为什么Ordered [A]使用比较方法而不是重用compareTo? - Why does Ordered[A] use a compare method instead of reusing compareTo? 比较方法在排序时违反了其一般约定 - Comparison method violates its general contract while sorting 多维数组排序时“比较法违反一般契约” - "Comparison method violates general contract " while sorting multi dimensional array 使用compareTo()方法按字母顺序对列表进行排序 - Sorting a List alphabetically using compareTo() method 比较方法违反了其一般合同和方法比较 - Comparison method violates its general contract and method compareTo
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM