为什么我的比较methd有时抛出IllegalArgumentException?

时间:2015-05-26 12:29:55

标签: java android sorting comparator

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

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

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

2 个答案:

答案 0 :(得分:11)

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

    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与文件名不为null的另一个FileItem(o3)进行比较,则再次返回0。

但是如果将o1与o3进行比较,因为它们都具有非空文件名,比较将返回-1或1(假设文件名不同)。

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

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

例如:

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

答案 1 :(得分:3)

这是比较器的一个常见错误 - 您没有始终如一地处理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);
}