比较者违反一般合同

时间:2016-07-09 21:58:19

标签: java sorting comparator

以下代码是Dave Koelle's AlphanumComparator的编辑版本。编辑包含将空字符串排序到列表末尾的代码,或者在我的情况下将JTable的底部排序。问题是java.lang.IllegalArgumentException: Comparison method violates its general contract!发生。

为了解决我的问题,我调查了一下,发现比较器在正确的位置没有return 0;的原因。我还在Java bug database中找到了一条评论

  

java.util.Arrays.sort和(间接)java.util.Collections.sort使用的排序算法已被替换。如果新的排序实现检测到违反Comparable合同的Comparable,则可能抛出IllegalArgumentException。以前的实现默默地忽略了这种情况。   如果需要以前的行为,则可以使用新的系统属性java.util.Arrays.useLegacyMergeSort来恢复先前的mergesort行为

import java.util.Comparator;
import javax.swing.JTable;
import javax.swing.SortOrder;

public class AlphanumComparator implements Comparator<String> {
    JTable table;

    public AlphanumComparator(JTable table) {
        this.table = table;
    }

    private final boolean isDigit(char ch) {
        return ch >= 48 && ch <= 57;
    }

    private final String getChunk(String s, int slength, int marker) {
        StringBuilder chunk = new StringBuilder();
        char c = s.charAt(marker);
        chunk.append(c);
        marker++;
        if (isDigit(c)) {
            while (marker < slength) {
                c = s.charAt(marker);
                if (!isDigit(c))
                    break;
                chunk.append(c);
                marker++;
            }
        } else {
            while (marker < slength) {
                c = s.charAt(marker);
                if (isDigit(c))
                    break;
                chunk.append(c);
                marker++;
            }
        }
        return chunk.toString();
    }

    public int compare(String s1, String s2) {
        boolean swapInt = table.getRowSorter().getSortKeys().get(0).getSortOrder() == SortOrder.ASCENDING;

        int thisMarker = 0;
        int thatMarker = 0;
        int s1Length = s1.length();
        int s2Length = s2.length();

        if(s1Length != 0 && s2Length != 0) {
            while (thisMarker < s1Length && thatMarker < s2Length) {
                String thisChunk = getChunk(s1, s1Length, thisMarker);
                thisMarker += thisChunk.length();

                String thatChunk = getChunk(s2, s2Length, thatMarker);
                thatMarker += thatChunk.length();

                int result = 0;
                if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) {
                    int thisChunkLength = thisChunk.length();
                    result = thisChunkLength - thatChunk.length();
                    if (result == 0) {
                        for (int i = 0; i < thisChunkLength; i++) {
                            result = thisChunk.charAt(i) - thatChunk.charAt(i);
                            if (result != 0) {
                                return result;
                            }
                        }
                    }
                } else {
                    result = thisChunk.compareTo(thatChunk);
                }

                if (result != 0)
                    return result;
            }

            return s1Length - s2Length;
        } else {
            if(swapInt) {
                if(s1Length == 0) {
                    return 1;
                } else {
                    return -1;
                }
            } else {
                if(s1Length == 0) {
                    return -1;
                } else {
                    return 1;
                }
            }
        }
    }
}

是否有人能够帮助解决我的问题,并解释为什么这个比较器违反了可比较合同

如果需要,可以使用异常堆栈跟踪

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.ComparableTimSort.mergeLo(ComparableTimSort.java:744)
    at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:481)
    at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:422)
    at java.util.ComparableTimSort.sort(ComparableTimSort.java:222)
    at java.util.Arrays.sort(Arrays.java:1246)
    at javax.swing.DefaultRowSorter.sort(DefaultRowSorter.java:607)
    at javax.swing.DefaultRowSorter.setSortKeys(DefaultRowSorter.java:319)
    at javax.swing.DefaultRowSorter.toggleSortOrder(DefaultRowSorter.java:480)
    at javax.swing.plaf.basic.BasicTableHeaderUI$MouseInputHandler.mouseClicked(BasicTableHeaderUI.java:112)
    at java.awt.AWTEventMulticaster.mouseClicked(AWTEventMulticaster.java:270)
    at java.awt.Component.processMouseEvent(Component.java:6538)
    at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
    at java.awt.Component.processEvent(Component.java:6300)
    at java.awt.Container.processEvent(Container.java:2236)
    at java.awt.Component.dispatchEventImpl(Component.java:4891)
    at java.awt.Container.dispatchEventImpl(Container.java:2294)
    at java.awt.Component.dispatchEvent(Component.java:4713)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4888)
    at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4534)
    at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4466)
    at java.awt.Container.dispatchEventImpl(Container.java:2280)
    at java.awt.Window.dispatchEventImpl(Window.java:2750)
    at java.awt.Component.dispatchEvent(Component.java:4713)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
    at java.awt.EventQueue.access$500(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:709)
    at java.awt.EventQueue$3.run(EventQueue.java:703)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.awt.EventQueue$4.run(EventQueue.java:731)
    at java.awt.EventQueue$4.run(EventQueue.java:729)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

2 个答案:

答案 0 :(得分:6)

我认为问题是,当s2Length为零时,您的代码永远不会检查s1Length。您需要添加另一个检查以查看两个字符串是否为空,如下所示:

if(swapInt) {
    if(s1Length == 0 && s2Length != 0) {
        return 1;
    } else if (s2Length == 0 && s1Length != 0) {
        return -1;
    } else {
        return 0;
    }
} else {
    if(s1Length == 0 && s2Length != 0) {
        return -1;
    } else if (s2Length == 0 && s1Length != 0) {
        return 1;
    } else {
        return 0;
    }
}

您的当前实现返回1-1,即使两个字符串为空(意味着它们必须比较为相等并返回零)。新的排序算法检测到此问题,并抛出异常。

注意:

您应该能够通过swapInt int 1-1 getSortOrder进一步简化此代码,具体取决于if(s1Length == 0 && s2Length != 0) { return swapInt; } else if (s2Length == 0 && s1Length != 0) { return -swapInt; } else { return 0; } 结果:

dplyr

答案 1 :(得分:2)

您的比较器确实:

if (s1Length != 0 && s2Length != 0) {
    ...
} else {
    if (swapInt) {
        if(s1Length == 0) {
            return 1;
        } else {
            return -1;
        }
    } else {
        if(s1Length == 0) {
            return -1;
        } else {
            return 1;
        }
     }
}

因此,如果它没有进入if块,则意味着至少有一个字符串为空。但两者都可能是空的。但在这种情况下,您的比较器仅返回-1或1。这意味着如果A和B都是空的,并且比较A和B导致-1,那么将B与A比较也将导致-1,因此A同时小于B且大于B.

启动你的else块
if (s1Length == 0 && s2Length == 0) {
    return 0;
}