代码之家  ›  专栏  ›  技术社区  ›  ryvantage

比较法违反了它的一般合同-但我可能想?

  •  0
  • ryvantage  · 技术社区  · 6 年前

    我正在尝试编写一个表排序器,它总是将空值排序到底部。所以我写了一个“包装器”类 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 方法只执行空检查,然后遵从于 比较函数

    然后我写了 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 B C 我想我的也会回来 一个 C类 但我想起来了。

    不管怎样,我的问题是:

    1. 故意 用这种方法使行分拣机将空分拣到底部?
    2. 这对我来说似乎是过渡的。我真的违反了 比较函数 在这里?或者还有一个合同必须遵守,而我不知道,这就是导致错误的原因?

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

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

    一切似乎进展顺利。

    enter image description here


    编辑(7/11/18):试图摆脱 WrappedBigDecimal 解决和实现Matt McHenry的解决方案提出了另一个问题。我简化了 划船机 对此:

    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 回复  |  直到 6 年前
        1
  •  2
  •   Matt McHenry    6 年前

    违反合同

    • 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)) x y .

    ascending == true ),我们有 compare(x, y) == 1 . 但是 compare(y, x) == 1 . 自从 sgn(1) != -sgn(1) 比较仪 接口。

    一般性意见

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

    比较器助手方法

    在Java中,使用 比较仪 上课不如自己写 compare() nullsLast() .

    Optional

    而不是编写自己的包装类来处理 null 你的价值观 BigDecimal ,使用 Optional<BigDecimal> . 这正是它的本意。

    自从 Optional<T> 是一个非常通用的容器类,它不能实现 Comparable<Optional<T>> 可比 比较仪 的静态助手方法使我们自己做起来很容易。

    然后,当我们有一对“存在”相等的选项时(既存在也不存在),我们给出一个简单的lambda来提取要比较的值。 orElse() 正是我们所需要的:如果一个值存在,获取它并进行比较;如果一个值不存在,则提供回退。(记住,最后一种情况只有在 二者都 什么 我们用作回退的值,它总是与自身进行比较,得到我们想要的结果:两个空选项是等价的。)

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

    i、 (英)

    @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可比较对象中比较“非BigDecimal”对象。

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

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