问题
Happens if at least one of the values (values == value in RowFilter, value in entry) is a decimal. Here's a failing test:
@Test
public void testRowFilterNumberMixCore() {
TestEntry entry = new TestEntry(1.2f);
RowFilter filter = RowFilter.numberFilter(ComparisonType.AFTER, 1, 0);
assertTrue(entry + "must be included " + filter, filter.include(entry));
}
The output is:
junit.framework.AssertionFailedError: [entry: 1.2] must be included [RowFilter: ComparisonType = AFTER, comparableValue: 1, comparableClass: class java.lang.Integer]
The reason is that NumberFilter falls back to comparing the numbers by their number.longValue() if they are not the same class (and by that comparable to each other)
Knowing that detail, the test failure is not astonishing (in hind-sight, would have never thought of that being an issue ;-) One level of defense is to make sure - in client code - that the numbers to compare are of the same class. That's not always possible (think f.i.: a tableColumn with columnClass Number) So I'm wondering if/how to improve on the fallback. Something like:
if (one instanceof Comparable && one.getClass() == other.getClass()) {
// same class, use comparator
return ((Comparable) one).compareTo(other);
}
if (areIntegers(one, other)) {
// all integers, use longValue
return longCompare(one, other);
}
if (areDecimals(one, other)) {
// anything to do here?
}
// at last resort convert to BigDecimal and compare those:
BigDecimal bigOne = new BigDecimal(one.toString());
BigDecimal bigOther = new BigDecimal(other.toString());
return bigOne.compareTo(bigOther);
Doing so, makes the test pass - I'm a bit wary about hidden (read: unknown to me :) pitfalls. Any warnings/alternatives highly welcome!
FYI: cross-posted to OTN's Swing forum
Follow-up
implemented as outlined above, now waiting for clients to complain - in that case will point fingers to everybody who didn't warn me here :-)
回答1:
I don't have a better answer, but the example below illustrates the effect. In particular, a RowFilter
based on a double
primitive is boxed as Double
, producing the expected tableau having values > 1
. In contrast, the one based on a float
is boxed as Float
. Because the class literals do not match, include()
compares the long
values, unexpectedly filtering all fractional values < 2
.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.util.Arrays;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.RowFilter;
import javax.swing.RowFilter.ComparisonType;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableRowSorter;
/** @see http://stackoverflow.com/questions/7993546 */
public class FilterTest {
private static TableRowSorter<TableModel> sorter;
private static RowFilter<TableModel, Integer> dFilter;
private static RowFilter<TableModel, Integer> fFilter;
private static boolean b;
public static void main(String[] args) {
TableModel model = new TableModel();
JTable table = new JTable(model);
sorter = new TableRowSorter<TableModel>(model);
dFilter = RowFilter.numberFilter(ComparisonType.AFTER, 1d, 0);
fFilter = RowFilter.numberFilter(ComparisonType.AFTER, 1f, 0);
sorter.setRowFilter(dFilter);
table.setRowSorter(sorter);
JScrollPane scrollPane = new JScrollPane(table);
table.setPreferredScrollableViewportSize(new Dimension(320, 240));
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(scrollPane, BorderLayout.CENTER);
f.add(new JToggleButton(new AbstractAction("Toggle") {
@Override
public void actionPerformed(ActionEvent e) {
b = !b;
if (b) {
sorter.setRowFilter(fFilter);
} else {
sorter.setRowFilter(dFilter);
}
}
}), BorderLayout.SOUTH);
f.pack();
f.setVisible(true);
}
private static class TableModel extends AbstractTableModel {
private static final int ROWS = 16;
private static final int COLS = 4;
private Double[][] matrix = new Double[ROWS][COLS];
public TableModel() {
double v = 0;
for (Object[] row : matrix) {
Arrays.fill(row, Double.valueOf(v += 0.25));
}
}
@Override
public int getRowCount() {
return ROWS;
}
@Override
public int getColumnCount() {
return COLS;
}
@Override
public Object getValueAt(int row, int col) {
return matrix[row][col];
}
@Override
public Class<?> getColumnClass(int col) {
return Number.class;
}
}
}
来源:https://stackoverflow.com/questions/7993546/rowfilter-numberfilter-cant-handle-mixed-concrete-number-types