2017-03-04 27 views
1

我有文件名列表,并希望按照以下顺序比较这些:为什么compareTo()方法在排序时导致契约违规?

  • 结尾的所有名称“.RAR”应该有“以R01" ,” .r02" 以前文件。 ..
  • 与” .par2" 结尾的所有名字应该来文件与任何其他后缀

所以我用我的一个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); 
    } 
} 

然而,在一些数据,这导致以下execption:

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

我想明白我是缺少在这里,但我不能找到问题。
你能发现我违反合同的地方吗? PS:如果我注释掉第二个if s,那么仍然会抛出异议。所以问题在于前两个if s。

+2

首先,你知道这个错误的最可能的原因之一? http://stackoverflow.com/a/8327575/1743880 – Tunaki

+0

所有带有“.r02”结尾的文件都必须在最后带有“.par”的文件之前出现? –

+0

@GrzegorzGórkiewicz你的意思是在最后那些带有“.par2”的人之前?对,那是正确的。 – Matthias

回答

5

这不是传递性的。
元素的线性排序是不可能的。

通过反例证明。

假设你已经在小写拿到3个DownloadFile S(cba)与名称:

c.par2 
b.notpar2 
a.par2 

为了简化,我将使用<的线性排序和小写名称。

c.par2 < b.notpar2b.notpar2 < a.par2,但它不是真的c.par2 < a.par2
这种关系不是transitive

在逻辑...它会像:

cRbbRa,但它是不正确的cRa

所有你需要做的是回答如何线性订购您的文件...
我会去这样的事情:

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

return 0末是非常重要的,因为它返回为不可区分的文件。

在这种情况下:

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); 
    } 
} 

把它缩短Predicate<String>从Java 8就派上用场了;)

@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; 
} 
+0

但是你推荐的if(aMethodOnThis Matthias

+0

是的,你这样做。但是你的条件并没有那么不同。在你的情况下,它归结为决定...你希望带有.par2结尾的文件在其他文件之前先出现吗?我认为如果最后4个条件中有两个是多余的,那么我认为两个。 –

+0

我已经添加了对我原来的问题的理想排序逻辑的解释。 – Matthias

相关问题