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

时间:2018-06-06 01:24:44

标签: java comparator comparable compareto

我正在尝试编写一个表格分类器,它将-always-将空值排序到底部。所以我写了一个“包装”类implements 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);
    }

}

你会注意到compareTo方法只进行空检查,然后按照包装值类的compareTo方法。

然后我写了一个RowSorter Comparator检查SortOrder

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 violates its general contract),我认为我的问题是我的比较不是传递性的。虽然我不确定,因为如果A == BB == C那么我认为我的也会返回A == {{1但是我正试图思考它。

无论如何,我的问题是:

  1. 故意使行排序器以这种方式排在底部是否存在潜在的危险?
  2. 这个SEEMS传递给我。我确实违反了C的过渡性合同吗?或者是否有必须遵守的另一份合同,我不知道这是什么引发错误?
  3. 我写了一个方法来测试我的行排序器,我无法重现错误:

    compareTo

    一切似乎都很顺利。

    我错过了什么?

    enter image description here

    EDIT(7/11/18):试图摆脱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); } 解决方案并实施Matt McHenry的解决方案提出了另一个问题。我将WrappedBigDecimal简化为:

    RowSorter

    用以下方法测试:

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

2 个答案:

答案 0 :(得分:2)

合同违规

我看到违反了两件合同:

  • WrappedBigDecimal.compareTo(null)

javadoc for Comparable表示compareTo(null)必须始终抛出NullPointerException。你的方法不遵守这一点。

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

考虑以下代码:

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

...以及来自javadoc for Comparator

的引用
  

实施者必须确保所有sgn(compare(x, y)) == -sgn(compare(y, x))xy

使用您的代码(假设ascending == true),我们有compare(x, y) == 1。但是compare(y, x) == 1。自sgn(1) != -sgn(1)起,这违反了Comparator接口的合同。

一般评论

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

比较器辅助方法

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

Optional

不要编写自己的包装类来处理null的{​​{1}}值,而只需使用BigDecimal。这正是它的意思。

完整解决方案

由于Optional<BigDecimal>是一个非常通用的容器类,它本身不能实现Optional<T>(不能保证它包装的类型是Comparable<Optional<T>>)。但Comparable的静态辅助方法使我们自己很容易做到。

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

然后,当我们有一对其“存在”相等的选项(两者都存在或两者都不存在)时,我们给出一个简单的lambda来提取要比较的值。 Comparator正是我们所需要的:如果存在一个值,那就得到它并进行比较;如果没有值,则提供后备。 (请记住,最后一种情况只发生在两个比较的Optionals都是空的时候,所以我们使用什么值作为后备并不重要,它总是与自身相比,给出我们想要的结果:两个空的选项是等价的。)

orElse()

答案 1 :(得分:0)

你可以,摆脱包装类。在排序方法中,检查每个对象的值并相应地对它们进行排序。例如,您希望将所有空实例放在底部。如果其中一个或两个都为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);
        }
    };
}

此外,看起来你期待&#34;非BigDecimal&#34;要在BigDecimal可比较对象中进行比较的对象。

A)如果BigDecimal已经实现了Comparable,则无需重新实现该接口。只需覆盖超类中的现有方法。

B)WrappedBigDecimal是Generic类型,你的代码似乎接受了不属于这种类型的其他类型,这在这里可能不重要,但肯定会导致你的错误