RowFilter.NumberFilter:无法处理“混合";具体数字类型

2022-01-17 00:00:00 numbers compare java swing rowfilter

如果至少有一个值(RowFilter 中的值 == 值,条目中的值)是小数,则会发生这种情况.这是一个失败的测试:

@Test公共无效 testRowFilterNumberMixCore() {TestEntry 条目 = 新的 TestEntry(1.2f);RowFilter 过滤器 = RowFilter.numberFilter(ComparisonType.AFTER, 1, 0);assertTrue(entry + "必须包含" + filter, filter.include(entry));}

输出是:

<上一页>junit.framework.AssertionFailedError:[entry: 1.2] 必须包含 [RowFilter: ComparisonType = AFTER, compatibleValue: 1, compatibleClass: class java.lang.Integer]

原因是 NumberFilter 回退到通过它们的 number.longValue() 比较数字,如果它们不是同一个类(并且可以相互比较)

知道这个细节,测试失败并不令人惊讶(事后看来,不会想到这是一个问题 ;-) 一个级别的防御是确保 - 在客户端代码中 - 要比较的数字 是同一类.这并不总是可能的(想想 f.i.: a tableColumn with columnClass Number)所以我想知道是否/如何改进后备.比如:

if (one instanceof Comparable && one.getClass() == other.getClass()) {//同一个类,使用比较器return ((Comparable) one).compareTo(other);}if (areIntegers(one, other)) {//所有整数,使用 longValue返回longCompare(一个,其他);}if (areDecimals(one, other)) {//这里有什么可做的吗?}//最后转换为 BigDecimal 并比较它们:BigDecimal bigOne = new BigDecimal(one.toString());BigDecimal bigOther = new BigDecimal(other.toString());返回 bigOne.compareTo(bigOther);

这样做可以使测试通过 - 我对隐藏的(阅读:我不知道 :) 的陷阱有点警惕.任何警告/替代方案都非常欢迎!

仅供参考:交叉发布到 OTN 的 Swing 论坛

跟进

如上所述实施,现在等待客户投诉 - 在这种情况下,将指责所有没有在这里警告我的人 :-)

解决方案

我没有更好的答案,但下面的例子说明了效果.特别是,基于 double 原语的 RowFilter 是 boxed as Double,产生具有 values > 的预期 tableau1.相反,基于 float 的那个被装箱为 Float.因为类文字不匹配,include() 比较 long 值,意外过滤所有小数 values <2.

import java.awt.BorderLayout;导入 java.awt.Dimension;导入 java.awt.event.ActionEvent;导入 java.util.Arrays;导入 javax.swing.AbstractAction;导入 javax.swing.JFrame;导入 javax.swing.JScrollPane;导入 javax.swing.JTable;导入 javax.swing.JToggleButton;导入 javax.swing.RowFilter;导入 javax.swing.RowFilter.ComparisonType;导入 javax.swing.table.AbstractTableModel;导入 javax.swing.table.TableRowSorter;/** @see http://stackoverflow.com/questions/7993546 */公共类FilterTest {私有静态 TableRowSorter<TableModel>分拣机;私有静态 RowFilter<TableModel, Integer>d过滤器;私有静态 RowFilter<TableModel, Integer>f过滤器;私有静态布尔值 b;公共静态无效主要(字符串[]参数){表模型模型 = 新表模型();JTable 表 = 新 JTable(模型);sorter = new TableRowSorter(model);dFilter = RowFilter.numberFilter(ComparisonType.AFTER, 1d, 0);fFilter = RowFilter.numberFilter(ComparisonType.AFTER, 1f, 0);sorter.setRowFilter(dFilter);table.setRowSorter(排序器);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") {@覆盖公共无效actionPerformed(ActionEvent e){b = !b;如果 (b) {sorter.setRowFilter(fFilter);} 别的 {sorter.setRowFilter(dFilter);}}}), 边框布局.SOUTH);f.pack();f.setVisible(true);}私有静态类 TableModel 扩展 AbstractTableModel {私有静态最终 int ROWS = 16;私有静态最终 int COLS = 4;私有 Double[][] 矩阵 = new Double[ROWS][COLS];公共表模型(){双 v = 0;对于(对象 [] 行:矩阵){Arrays.fill(row, Double.valueOf(v += 0.25));}}@覆盖公共 int getRowCount() {返回行;}@覆盖公共 int getColumnCount() {返回 COLS;}@覆盖公共对象 getValueAt(int row, int col) {返回矩阵[行][列];}@覆盖公共类<?>getColumnClass(int col) {返回数字.class;}}}

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 :-)

解决方案

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

相关文章