问题
In trying to figure out the answer to another question I asked (How to sort a jtable with null values always at the end), I ran into another problem.
I am implementing a custom TableRowSorter
that creates a custom Comparator
. However, the Comparator
always seems to read every Object
as a type String
. This one has me baffled.
If you'll notice in the SSCCE below, the line
System.out.println(o1.getClass() + " - " + o2.getClass());
always yeilds the output
class java.lang.String - class java.lang.String
Even though the items in the Object[][]
data array are varied types.
import java.awt.Component;
import java.util.Comparator;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
public class Test {
public static void main(String args[]) {
JFrame frame = new JFrame();
JTable table = new JTable();
Object[][] data = new Object[8][3];
data[0][0] = 6.5d; data[0][1] = "Name1";
data[1][0] = new NullClassFiller(); data[1][1] = "Name2";
data[2][0] = 2.6d; data[2][1] = "Name3";
data[3][0] = 0d; data[3][1] = "Name4";
data[4][0] = new NullClassFiller(); data[4][1] = "Name5";
data[5][0] = -4d; data[5][1] = "Name6";
data[6][0] = 0d; data[6][1] = "Name7";
data[7][0] = -4.3d; data[7][1] = "Name8";
table.setModel(new DefaultTableModel(data, new String[]{"One", "Two"}));
TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel()) {
@Override
public Comparator<?> getComparator(final int column) {
Comparator c = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
System.out.println(o1.getClass() + " - " + o2.getClass());
if (o1 instanceof NullClassFiller) {
return -1;
} else if (o2 instanceof NullClassFiller) {
return -1;
} else {
return ((Comparable<Object>) o1).compareTo(o2);
}
}
};
return c;
}
};
table.setRowSorter(sorter);
table.getColumnModel().getColumn(0).setCellRenderer(new CustomRenderer());
JScrollPane pane = new JScrollPane(table);
frame.add(pane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setSize(500, 500);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
static class NullClassFiller {}
static class CustomRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if(value instanceof NullClassFiller)
renderer.setText("");
return renderer;
}
}
}
回答1:
There are number of compound problems...
The first is with the DefaultTableModel
. DefaultTableModel#getColumnClass
returns Object.class
.
The second is with the TableRowSorter
. TableRowSorter
checks to see if the Class
returned from the model is Comparable
, if it is not, it is automatically converted to String
, because Object
is not Comparable
...
So, the basic solution is to override the getColumnClass
of the DefaultTableModel
to return the appropriate type of Class
of a particular column
TableModel model = new DefaultTableModel(data, new String[]{"One", "Two"}) {
@Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? Double.class : String.class;
}
};
table.setModel(model);
When the TableRowSorter
checks the column's Class
it will now find Comparable
values and will use the actual value from the table model and not convert it to String
first.
Now when you try and sort the first column, you should be seeing something more like...
class testtablesort.TestTableSort$NullClassFiller) - class java.lang.Double
class java.lang.Double) - class testtablesort.TestTableSort$NullClassFiller
class java.lang.Double) - class java.lang.Double
class testtablesort.TestTableSort$NullClassFiller) - class java.lang.Double
class java.lang.Double) - class testtablesort.TestTableSort$NullClassFiller
class java.lang.Double) - class java.lang.Double
class java.lang.Double) - class java.lang.Double
class java.lang.Double) - class testtablesort.TestTableSort$NullClassFiller
class java.lang.Double) - class java.lang.Double
class java.lang.Double) - class java.lang.Double
class java.lang.Double) - class java.lang.Double
class java.lang.Double) - class java.lang.Double
回答2:
There are several problematic aspects, both with the question and some answers:
- the real requirement is to have null values sorted at end of the table, irrespective of sortOrder
- a clean solution of that requirement requires a near-to complete re-write of DefaultRowSorter and its subclasses because nulls are handled "early" in the comparing algorithem and is buried deep inside its private bowels. Simply no way a custom comparator could jump in.
- a dirty hack around is to not add nulls, but a dedicated null-substitute and then let a custom comparator do its job. Part of the dirtyness is that the comparator must know the sortOrder, that is ultimately needs a handle to the calling RowSorter.
- hacks are ... evil: even if we go for them (don't, don't, don't ...) they are brittle and we have to take extreme care to get them right at least in very controlled environments!
Here the hack is implemented incorrectly in several respects, the comparator
- is incomplete, not handling the comparison of two nullFillers
- doesn't even has the meat of the hack (that is, toggle the returned value based on the sortOrder if a nullFiller is one of the values to compare)
- collides with the other implementation details of the DefaultRowSorter by overriding
getComparator(int)
vs. callingsetComparator(int, Comparator)
So if anybody decides to apply a hack s/he finds somewhere, at least copy it correctly ;-)
回答3:
The problem is here:
table.setModel(new DefaultTableModel(data, new String[]{"One", "Two"}));
According to http://docs.oracle.com/javase/tutorial/uiswing/components/table.html,
There are two JTable constructors that directly accept data (SimpleTableDemo uses the first):
JTable(Object[][] rowData, Object[] columnNames) JTable(Vector rowData, Vector columnNames) The advantage of these constructors is
that they are easy to use. However, these constructors also have disadvantages:
They automatically make every cell editable. They treat all data types the same (as strings).
来源:https://stackoverflow.com/questions/18680682/java-comparator-always-reading-values-as-strings