我正在尝试编写一个表格分类器,它将-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
== B
和B
== C
那么我认为我的也会返回A
== {{1但是我正试图思考它。
无论如何,我的问题是:
C
的过渡性合同吗?或者是否有必须遵守的另一份合同,我不知道这是什么引发错误?我写了一个方法来测试我的行排序器,我无法重现错误:
compareTo
一切似乎都很顺利。
我错过了什么?
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);
}
答案 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))
和x
都y
。
使用您的代码(假设ascending == true
),我们有compare(x, y) == 1
。但是compare(y, x) == 1
。自sgn(1) != -sgn(1)
起,这违反了Comparator
接口的合同。
将空值排序到开头或结尾的总体目标很好,实际上它很常见。但是你错过了很多使用库代码的机会。
在Java中,使用Comparator
类中的静态辅助方法几乎总是比手动编写自己的compare()
方法更容易。实际上,有一个专门为您的用例量身定制的:nullsLast()
。
不要编写自己的包装类来处理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类型,你的代码似乎接受了不属于这种类型的其他类型,这在这里可能不重要,但肯定会导致你的错误