为啥我的比较方法有时会抛出 IllegalArgumentException?

Posted

技术标签:

【中文标题】为啥我的比较方法有时会抛出 IllegalArgumentException?【英文标题】:Why does my compare methd throw IllegalArgumentException sometimes?为什么我的比较方法有时会抛出 IllegalArgumentException? 【发布时间】:2015-08-08 03:10:36 【问题描述】:

我遇到这个问题有一段时间了,已经搜索了很多 *** 问题但无法解决我的问题。

我之前也问过类似的问题,得到了使用建议,

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");

它没有解决我的问题。我从来没有在我的任何测试设备上遇到过这个异常,但我的一些用户一直在定期报告它。我真的不知道如何解决它。

例外

这是我得到的例外,

java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:743)
at java.util.TimSort.mergeAt(TimSort.java:479)
at java.util.TimSort.mergeCollapse(TimSort.java:404)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.TimSort.sort(TimSort.java:169)
at java.util.Arrays.sort(Arrays.java:2023)
at java.util.Collections.sort(Collections.java:1883)

有时是这样,

java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:864)
at java.util.TimSort.mergeAt(TimSort.java:481)
at java.util.TimSort.mergeCollapse(TimSort.java:406)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.TimSort.sort(TimSort.java:169)
at java.util.Arrays.sort(Arrays.java:2010)
at java.util.Collections.sort(Collections.java:1883)

我做了什么

enum FileItemComparator implements Comparator<FileItem> 

    //Using ENUM
    NAME_SORT 
        public int compare(FileItem o1, FileItem o2) 

            int result = 0;
            if (o1 != null && o2 != null) 

                String n1 = o1.getFileName();
                String n2 = o2.getFileName();

                if (n1 != null && n2 != null)
                    result = n1.compareTo(n2);
            

            return result;
        
    ,
    DATE_SORT 
        public int compare(FileItem o1, FileItem o2) 

            int result = 0;
            if (o1 != null && o2 != null) 

                String d1 = o1.getFileDate();
                String d2 = o2.getFileDate();

                if (d1 != null && d2 != null) 

                    Long l1 = Long.valueOf(d1);
                    Long l2 = Long.valueOf(d2);

                    if (l1 != null && l2 != null) 
                        result = l1.compareTo(l2);
                    
                

            

            return result;
        
    ,
    SIZE_SORT 
        public int compare(FileItem o1, FileItem o2) 

            int result = 0;
            if (o1 != null && o2 != null) 

                File f1 = o1.getItem();
                File f2 = o2.getItem();

                if (f1 != null && f2 != null) 

                    result = Long.valueOf(f1.length()).compareTo(Long.valueOf(f2.length()));
                
            

            return result;
        
    ;

    public static Comparator<FileItem> descending(final Comparator<FileItem> other) 

        return new Comparator<FileItem>() 
            public int compare(FileItem o1, FileItem o2) 
                return -1 * other.compare(o1, o2);
            
        ;
    

    public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) 
        return new Comparator<FileItem>() 
            public int compare(FileItem o1, FileItem o2) 
                for (FileItemComparator option : multipleOptions) 
                    int result = option.compare(o1, o2);
                    if (result != 0) 
                        return result;
                    
                
                return 0;
            
        ;
    

这就是我的排序方式,

Collections.sort(dirs, FileItemComparator.getComparator(FileItemComparator.NAME_SORT));

问题

我确信带有传递依赖的比较方法有问题。我已经尝试了很多,似乎无法修复它。实际上,我从未在我的任何测试设备中遇到过这个问题,但我的用户不断报告它。

我希望这里的任何人都能够发现问题并帮助我一劳永逸地解决它。

更新代码(感谢@Eran)

我认为最好通过发布完整的更新代码来帮助他人。它将帮助很多面临同样问题的人。

enum FileItemComparator implements Comparator<FileItem> 

    //Using ENUM
    NAME_SORT 
        public int compare(FileItem o1, FileItem o2) 

            if (o1 == null) 
                if (o2 == null) 
                    return 0;
                 else 
                    return 1; // this will put null in the end
                
             else if (o2 == null) 
                return -1;
            

            String n1 = o1.getFileName();
            String n2 = o2.getFileName();

            if (n1 == null) 
                if (n2 == null) 
                    return 0;
                 else 
                    return 1; // this will put null names after non null names
                
             else if (n2 == null) 
                return -1;
            
            return n1.compareTo(n2);
        
    ,
    DATE_SORT 
        public int compare(FileItem o1, FileItem o2) 

            if (o1 == null) 
                if (o2 == null) 
                    return 0;
                 else 
                    return 1; // this will put null in the end
                
             else if (o2 == null) 
                return -1;
            

            String d1 = o1.getFileDate();
            String d2 = o2.getFileDate();

            if (d1 == null) 
                if (d2 == null) 
                    return 0;
                 else 
                    return 1; // this will put null names after non null names
                
             else if (d2 == null) 
                return -1;
            

            Long l1 = Long.valueOf(d1);
            Long l2 = Long.valueOf(d2);

            if (l1 == null) 
                if (l2 == null) 
                    return 0;
                 else 
                    return 1; // this will put null names after non null names
                
             else if (l2 == null) 
                return -1;
            

            return l1.compareTo(l2);
        
    ,
    SIZE_SORT 
        public int compare(FileItem o1, FileItem o2) 

            if (o1 == null) 
                if (o2 == null) 
                    return 0;
                 else 
                    return 1; // this will put null in the end
                
             else if (o2 == null) 
                return -1;
            

            File f1 = o1.getItem();
            File f2 = o2.getItem();

            if (f1 == null) 
                if (f2 == null) 
                    return 0;
                 else 
                    return 1; // this will put null in the end
                
             else if (f2 == null) 
                return -1;
            

            Long l1 = Long.valueOf(f1.length());
            Long l2 = Long.valueOf(f2.length());

            if (l1 == null) 
                if (l2 == null) 
                    return 0;
                 else 
                    return 1; // this will put null names after non null names
                
             else if (l2 == null) 
                return -1;
            

            return l1.compareTo(l2);
        
    ;

    public static Comparator<FileItem> descending(final Comparator<FileItem> other) 

        return new Comparator<FileItem>() 
            public int compare(FileItem o1, FileItem o2) 
                return -1 * other.compare(o1, o2);
            
        ;
    

    public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) 
        return new Comparator<FileItem>() 
            public int compare(FileItem o1, FileItem o2) 
                for (FileItemComparator option : multipleOptions) 
                    int result = option.compare(o1, o2);
                    if (result != 0) 
                        return result;
                    
                
                return 0;
            
        ;
    

【问题讨论】:

它是否与 null 中的一个参数有关,但它会返回 `0`? @Mena 是的,这是可能的。很好的发现。你能帮我提供一个工作代码吗? 看起来Eran 刚刚做了:) 【参考方案1】:

让我们看看你的第一个比较方法:

    public int compare(FileItem o1, FileItem o2) 

        int result = 0;
        if (o1 != null && o2 != null) 

            String n1 = o1.getFileName();
            String n2 = o2.getFileName();

            if (n1 != null && n2 != null)
                result = n1.compareTo(n2);
        

        return result;
    

假设您正在比较两个 FileItem(我们称它们为 o1 和 o2),一个有文件名,另一个没有(即空文件名)。您的方法将返回 0。

现在,如果您将 o2 与另一个文件名不为空的 FileItem (o3) 进行比较,您将再次返回 0。

但是如果你比较 o1 和 o3,由于它们都有非空文件名,比较返回 -1 或 1(假设文件名不同)。

因此,您的比较是不一致的,因为它不具有传递性。

如果一个元素缺少比较所需的属性而另一个没有,则不应返回 0。您应决定返回 1 还是 -1(例如,取决于名称为 null 的 FileItems 是否应该返回在具有非空名称的 FileItems 之前或之后排序)。

例如:

public int compare(FileItem o1, FileItem o2) 

    if (o1 == null) 
        if (o2 == null) 
            return 0;
         else 
            return 1; // this will put null in the end
        
     else if (o2 == null) 
        return -1;
    
    String n1 = o1.getFileName();
    String n2 = o2.getFileName();
    if (n1 == null) 
        if (n2 == null) 
            return 0;
         else 
            return 1; // this will put null names after non null names 
        
     else if (n2 == null) 
        return -1;
    
    return n1.compareTo(n2);

【讨论】:

这是一个很棒的解释。您能否发布比较方法的工作代码?我不想再犯任何错误。 @Aritra 查看编辑。没测试过,但感觉是对的。 @Aritra 只要使用一致,两者都是正确的。我交换了 1 和 -1,因为我在 cmets 中写道,空值将在非空值之后排序(任意决定),这意味着 compare(null,not null) 应该返回 1 并且 compare(not null,null) 应该返回 - 1.我的原始代码将空值放在非空值之前。 @Aritra 看起来不错,尽管可能有些多余。 Long.valueOf(d1) 中的 d1 可以为 null 不为 null 吗?您不必在降序方法中检查 null。顺便说一句,您可以返回 other.compare(o2, o1) 来获得相反的顺序,而不是乘以 -1。 @Aritra 如果第一个对象比第二个对象“小”,则返回 -1(或者换句话说,应该在第二个对象之前排序)。因此,如果您希望空值位于最后,这意味着您认为它们大于非空值,因此 compare(null,not null) 应该返回 1。正如 Javadoc 所说,比较 Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second【参考方案2】:

这是比较器的常见错误 - 您没有始终如一地处理 null。通常的模式如下所示:

public int compare(FileItem o1, FileItem o2) 
    // null == null
    if (o1 == null && o2 == null) 
        return 0;
    
    // null < not null
    if (o1 == null || o2 == null) 
        return -1;
    
    // Neither can be null now so this is safe.
    String n1 = o1.getFileName();
    String n2 = o2.getFileName();
    // Same logic again.
    if (n1 == null && n2 == null) 
        return 0;
    
    if (n1 == null || n2 == null) 
        return -1;
    
    return n1.compareTo(n2);

已添加

请注意,此实现也是一个常见错误,因为我允许 compare(null,not_null) 等于 compare(not_null,null),这也违反了合同 - 请使用 @Eran's solution 或类似的东西。

public int compare(FileItem o1, FileItem o2) 
    // null == null
    if (o1 == null && o2 == null) 
        return 0;
    
    // null != not null
    if (o1 == null || o2 == null) 
        // Swap these around if you want 'null' at the other end.
        return o1 == null ? -1: 1;
    
    // Neither can be null now so this is safe.
    String n1 = o1.getFileName();
    String n2 = o2.getFileName();
    // Same logic again.
    if (n1 == null && n2 == null) 
        return 0;
    
    if (n1 == null || n2 == null) 
        // Swap these around if you want 'null' at the other end.
        return n1 == null ? -1: 1;
    
    return n1.compareTo(n2);

【讨论】:

我也不认为这种比较是一致的。 compare(null,not null) 和 compare(not null, null) 都将返回 -1 与此代码。 @Eran - 这是一个很好的观点 - 我今天过得不好。 :)

以上是关于为啥我的比较方法有时会抛出 IllegalArgumentException?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的线程池有时会抛出 `std::bad_function_call` 或 `double free or corruption (!prev)`

为啥有时会抛出 FileNotFoundException

为啥这个 Linq 方法会抛出空引用异常

为啥我的数组列表即使在初始化后也会抛出空指针异常?

为啥 collections.sort 在 Java 中按比较器排序时会抛出不支持的操作异常?

为啥我的存储过程在包含在事务块中时会抛出错误?