当前位置: 首页 > 知识库问答 >
问题:

比较方法违反了其总合同 - 但我可能想要?

霍书
2023-03-14

我正在尝试编写一个表排序器,该排序器将始终将空值排序到底部。所以我写了一个实现 Comparable 的“包装器”类:

public class WrappedBigDecimal implements Comparable<WrappedBigDecimal> {
    public final BigDecimal value;

    public WrappedBigDecimal(BigDecimal value) {
        this.value = value;
    }

    @Override
    public int compareTo(WrappedBigDecimal o) {
        if (value == null && (o == null || o.value == null)) { // both are null, thus equal
            return 0;
        } else if (value == null && (o != null && o.value != null)) { // value is null and compared value isn't
            return -1;
        } else if (value != null && (o == null || o.value == null)) {
            return 1;
        } else {
            return value.compareTo(o.value);
        }
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }

}

您会注意到comareTo方法只执行空检查,然后服从包装值类的comareTo方法。

然后我编写了一个< code >行排序器,它的< code >比较器检查< code >排序顺序

public class WrappedNumberSorter extends TableRowSorter<TableModel> {

    public WrappedNumberSorter(TableModel model) {
        super(model);
    }

    @Override
    public Comparator<?> getComparator(final int column) {
        Comparator c = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                boolean ascending = getSortKeys().get(0).getSortOrder() == SortOrder.ASCENDING;

                if (o1 instanceof WrappedBigDecimal && ((WrappedBigDecimal)o1).value == null) {
                    if(ascending)
                        return 1;
                    else
                        return -1;
                } else if (o2 instanceof WrappedBigDecimal && ((WrappedBigDecimal)o2).value == null) {
                    if(ascending)
                        return -1;
                    else
                        return 1;
                } else {
                    return ((Comparable<Object>) o1).compareTo(o2);
                }

            }
        };
        return c;
    }
}  

但这会引发一个错误(无法找出原因,无法重现-继续阅读):

java.lang.IllegalArgumentException: Comparison method violates its general contract!

幸运的是,这个错误似乎没有影响任何事情,因为一切都按预期进行。

看到这个问题(Java . lang . illegalargumentexception:Comparison method违背了它的通用契约)我觉得我的问题是我的比较是不可传递的。虽然我不确定,因为如果< code>A == B和< code>B == C,那么我想我的也会返回< code>A == C,但是我一直在思考这个问题。

无论如何,我的问题是:

    < li >故意让行排序器以这种方式将空值排序到底是否有潜在的危险? < li >这对我来说似乎是可传递的。我真的违反了< code>compareTo的传递契约吗?还是有另一个它必须遵守的合同,而我不知道,这就是引发错误的原因?

我写了一个方法来测试我的行排序器,我无法重现错误:

public static void main(String[] args) {
    JFrame frame = new JFrame();
    JTable table = new JTable();
    JScrollPane jsp = new JScrollPane(table);

    String[] headers = new String[] {"h1", "h2"};

    Object[][] data = new Object[10][2];
    for(int i = 0; i < 7; i++) {
        data[i][0] = new WrappedBigDecimal(BigDecimal.TEN.multiply(BigDecimal.valueOf(i % 3 == 0 ? (i*i*-1) : (i*i))));
    }

    data[7][0] = new WrappedBigDecimal(null);
    data[8][0] = new WrappedBigDecimal(null);
    data[9][0] = new WrappedBigDecimal(null);

    for(int i = 10; i < 17; i++) {
        data[i-10][1] = new WrappedBigDecimal(BigDecimal.TEN.multiply(BigDecimal.valueOf(i % 3 == 0 ? (i*i*-1) : (i*i))));
    }

    data[7][1] = new WrappedBigDecimal(null);
    data[8][1] = new WrappedBigDecimal(null);
    data[9][1] = new WrappedBigDecimal(null);

    table.setModel(new DefaultTableModel(data, headers) {
        @Override
        public boolean isCellEditable(int row, int column) {
            return false;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return BigDecimal.class;
        }
    });

    table.setRowSorter(new WrappedNumberSorter(table.getModel()));

    frame.add(jsp);
    frame.setSize(200,400);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

一切似乎都很顺利。

我错过了什么?

EDIT (7/11/18):试图摆脱< code>WrappedBigDecimal解决方案并实现Matt McHenry的解决方案带来了另一个问题。我将< code >行排序器简化为:

static class NullsLastSorter extends TableRowSorter<TableModel> {

    public NullsLastSorter(TableModel model) {
        super(model);
    }

    @Override
    public Comparator<?> getComparator(int column) {
        return Comparator.<Optional<BigDecimal>, Boolean>comparing(Optional::isPresent).reversed().thenComparing(o -> o.orElse(BigDecimal.ONE));
    }
}

并对其进行了测试:

public static void main(String[] args) {
    JFrame frame = new JFrame();
    JTable table = new JTable();
    JScrollPane jsp = new JScrollPane(table);

    String[] headers = new String[] {"h1", "h2"};

    Object[][] data = new Object[10][2];
    for(int i = 0; i < 7; i++) {
        data[i][0] = BigDecimal.TEN.multiply(BigDecimal.valueOf(i % 3 == 0 ? (i*i*-1) : (i*i)));
    }

    data[7][0] = null;
    data[8][0] = null;
    data[9][0] = null;

    for(int i = 10; i < 17; i++) {
        data[i-10][1] = BigDecimal.TEN.multiply(BigDecimal.valueOf(i % 3 == 0 ? (i*i*-1) : (i*i)));
    }

    data[7][1] = null;
    data[8][1] = null;
    data[9][1] = null;



    table.setModel(new DefaultTableModel(data, headers) {
        @Override
        public boolean isCellEditable(int row, int column) {
            return false;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return BigDecimal.class;
        }
    });

    table.setRowSorter(new NullsLastSorter(table.getModel()));

    frame.add(jsp);
    frame.setSize(200,400);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

现在,当我尝试排序时,我得到一个错误:

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.math.BigDecimal cannot be cast to java.util.Optional
at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
at java.util.Collections$ReverseComparator2.compare(Collections.java:5178)
at java.util.Comparator.lambda$thenComparing$36697e65$1(Comparator.java:216)
at javax.swing.DefaultRowSorter.compare(DefaultRowSorter.java:968)
at javax.swing.DefaultRowSorter.access$100(DefaultRowSorter.java:112)
at javax.swing.DefaultRowSorter$Row.compareTo(DefaultRowSorter.java:1376)
at javax.swing.DefaultRowSorter$Row.compareTo(DefaultRowSorter.java:1366)
at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:320)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:188)
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:6536)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
at java.awt.Component.processEvent(Component.java:6298)
at java.awt.Container.processEvent(Container.java:2236)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2294)
at java.awt.Component.dispatchEvent(Component.java:4711)
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:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
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个答案

唐和洽
2023-03-14

你可以,摆脱包装类。在排序方法中,检查每个对象的值并相应地对其进行排序。例如,您希望将所有空实例置于底部。如果其中一个或两个都为 null,则将它们视为值 -1

即)

@Override
public Comparator<?> getComparator(final int column) {
    return new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            if (o1 == null || o2 == null)
                return -1;
            /**
             * Do the rest of your checks here
             */
            return o1.compareTo(o2);
        }
    };
}

此外,看起来您希望在BigDecimal可比较对象中比较“非BigDeximal”对象。

A) 如果 BigDecimal 已经实现了 Comparable,则无需重新实现该接口。只需从超类重写现有方法即可。

B) WrappedBigDecimal是泛型类型,您的代码似乎正在接受其他不属于该类型的类型,这在这里可能并不重要,但肯定会导致错误

江建明
2023-03-14

我看到违反了合同的两个部分:

  • WrappedBigDecimal.compare(null)

可比较的javadoc说comareTo(null)必须始终抛出NullPointerExc的。您的方法不遵守这一点。

  • WrappedNumberSorter.getComparator()WrappedBigDecimal(null) 的处理

考虑这段代码:

WrappedBigDecimal x = new WrappedBigDecimal(null);
WrappedBigDecimal y = new WrappedBigDecimal(null);

...以及来自 Javadoc for Comparator 的这句话:

实施者必须确保< code>sgn(compare(x,y)) == -sgn(compare(y,x))对于所有< code>x和< code>y。

使用你的代码(假设升序 == true),我们有 compare(x, y) == 1。但是比较(y, x) == 1。由于 sgn(1) != -sgn(1),这违反了 Comparator 接口的合约。

将 null 排序到开头或结尾的总体目标很好,事实上这很常见。但是你错过了很多使用库代码的机会。

在Java中,使用< code>Comparator类中的静态助手方法几乎总是比手动编写自己的< code>compare()方法更容易。事实上,有一个专门为您的用例定制的方法:< code>nullsLast()。

无需编写自己的包装类来处理BigDecimalnull值,只需使用可选

由于<code>可选

首先,我们根据是否存在值进行排序,确保空选项进入末尾而不是开头。

然后,当我们有一对“存在”是等价的(要么都存在,要么都不存在)选项时,我们给出一个简单的lambda来提取要比较的值。orElse()正是我们需要的:如果一个值存在,获取它并进行比较;如果一个值不存在,提供一个后备。(记住,最后一种情况仅发生在两个被比较的选项都为空的情况下,所以我们使用什么值作为后备并不重要,它总是与自身进行比较,给出我们想要的结果:两个空选项是等价的。)

Comparator.<Optional<BigDecimal>,Boolean>comparing(Optional::isPresent)
          .reversed() //default ordering of booleans is false before true
          .thenComparing(o -> o.orElse(BigDecimal.ONE))

 类似资料:
  • 我已经在类上实现了Comaprable,它给了我比较方法违反了它的总合同!,由于有一些值返回为 null,代码如下 公共静态比较器名称比较器 = 新比较器() {

  • 我收到以下错误:“比较方法违反了它的一般合同!”当使用下面的比较器时,我无法使用jUnit复制异常。我想知道是什么导致了这个问题,以及如何复制它。有其他人也有同样的问题,但不知道如何复制。 使用以下方法调用该代码: 感谢任何帮助。 额外信息:该错误似乎发生在Java utils中的TimSort类中,并来自一个名为mergeLo的方法。链接:http://grepcode.com/file/rep

  • 我试图使用比较器基于两个字符串的比较对数组列表进行排序,但我最终使用的比较方法违反了它的一般契约错误。如果字符串中出现空值比较,我该如何处理? 密码 错误

  • 我看到我的应用程序在一些中国 Android 手机上发生了很多崩溃,并出现错误:比较方法违反了其总合同! 我读过这与Collections.sort有关。 我不太确定的是,这是否是因为我的自定义比较器。 以下是错误发生的地方: 比较器是这样的: 所以我不太确定比较器是否搞砸了什么,或者我是否需要以不同的方式进行collections.sort调用 感谢任何帮助

  • 问题内容: 有人可以简单地向我解释一下,为什么此代码会引发异常,“比较方法违反了它的一般约定!”,我该如何解决? 问题答案: 你的比较器不是可传递的。 让是的父,并成为母公司。既然和,那一定是这样。但是,如果在和上调用比较器,它将返回零,即。这违反了合同,因此引发异常。 该库可以很好地检测到这一点并让你知道,而不是行为不规律。 满足传递性要求的一种方法是遍历整个链,而不仅仅是查看直接祖先。

  • 我有一个类 ,它看起来像这样: 接下来,我有<代码>列表 其中和是当前位置的纬度和经度。有时,代码会抛出异常 我知道,如果方法不满足传递条件,则会抛出此异常。我在这里找到了一些信息:比较方法违反了其总合同!我理解为什么在引用的问题中抛出异常,但我仍然不知道为什么在我的代码中抛出异常。 你能帮我一下吗?谢了。 更新 的列表用于的适配器。如果有新的GPS位置,则对中的数据进行排序。代码如下所示: 和方