gpt4 book ai didi

java - 比较方法违反了它的一般契约——但我可能想这样做?

转载 作者:行者123 更新时间:2023-12-02 02:06:28 24 4
gpt4 key购买 nike

我正在尝试编写一个表排序器,它总是将空值排序到底部。所以我编写了一个“包装”类,它实现了 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 方法仅执行 null 检查,然后遵循包装值类的 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 == C 但我在努力思考它。

无论如何,我的问题是:

  1. 故意让行排序器以这种方式将空值排序到底部是否存在潜在危险?
  2. 这对我来说似乎是及物的。我确实违反了 compareTo 的传递契约吗?或者是否还有另一个必须遵守的契约(Contract),而我不知道,这就是引发错误的原因?

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

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

<小时/>

编辑(2018 年 7 月 11 日):尝试摆脱 WrappedBigDecimal 解决方案并实现 Matt McHenry 的解决方案会带来另一个问题。我将 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);
}

现在当我尝试排序时出现错误:

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)

最佳答案

违反契约(Contract)

我发现契约(Contract)的两部分被违反:

  • 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 中的这句话:

The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y.

使用您的代码(假设 ascending == true ),我们有 compare(x, y) == 1 。但是compare(y, x) == 1 。自 sgn(1) != -sgn(1) ,这是违反了Comparator的契约(Contract)界面。

一般评论

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

比较器辅助方法

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

Optional

而不是编写自己的包装类来处理 null您的值BigDecimal ,只需使用 Optional<BigDecimal> 。这正是它的用途。

完整的解决方案

Optional<T>是一个非常通用的容器类,它无法实现 Comparable<Optional<T>>本身(不能保证它包装的类型是 Comparable )。但是Comparator的静态帮助器方法使我们自己可以轻松完成。

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

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

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

关于java - 比较方法违反了它的一般契约——但我可能想这样做?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50711025/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com