So, after serious testing and debugging, I copied the code for DefaultRowSorter and TableRowSorter to your FilterSSCCE and added some output controlling the modelToView field, which is used by DefaultRowSorter#convertRowIndexToView for matching between model and view ...
Change the Filter to: 1 createModelToView = [0, 0, 0, 0, 0] m0 : v0 m1 : v0 m2 : v0 m3 : v0 m4 : v0 initializeFilteredMapping.1 = [0, 1, 2, 3, 4] initializeFilteredMapping.2 = [0, 1, 2, 3, 4] Change the Filter to: 5 m0 : v0 m1 : v1 m2 : v2 m3 : v3 m4 : v4 initializeFilteredMapping.1 = [0, 1, 2, 3, 4] initializeFilteredMapping.2 = [0, 1, 2, 3, 4] Change the Filter to: 1 m0 : v0 m1 : v1 m2 : v2 m3 : v3 m4 : v4 initializeFilteredMapping.1 = [0, -1, -1, -1, -1] initializeFilteredMapping.2 = [0, -1, -1, -1, -1] Change the Filter to: 2 m0 : v0 m1 : v-1 m2 : v-1 m3 : v-1 m4 : v-1 initializeFilteredMapping.1 = [0, 1, 2, 3, 4] initializeFilteredMapping.2 = [0, 1, 2, 3, 4]
The interesting part here is at the end, between Change the Filter to: 1 and Change the Filter to: 2 . You can see that initializeFilteredMapping set the model indices out of range to -1 , but when we go to Change the Filter to: 2 , the same indices are still set, changing the filter has NOT reset them.
This seems to be a design choice to maintain the responsiveness of the table, and they probably never thought that someone could try and access the view from the filter, since you are assuming to use model data ...
How to get around this ??
You can create a "proxy" TableModel , but this eliminates the ability to sort the table.
You can write a βproxyβ TableModel that βknewβ about the sorted state of JTable (possibly through RowSorter ) and which could act as a filter to determine the visible number of rows, but this goes into murky water as the model begins to risk into the world of views. ..
Another option would be to change the way the setFilter method setFilter and the reset variables modelToView and viewToModel , but they are private , as it should be, well, we could use createModelToView , createViewToModel and setModelToViewFromViewToModel , available in DefaultRowSorter ... but they are private to ...
It would seem that some useful method that concerns a serious modification of these variables is private ... the story of my life ... (get torches and pitchforks, we will go in search)
Next choice, write it EVERYTHING ... what a great idea, expect this to run counter to the basic principles of OO ...
βWorkβ (and I use this term very, very easy), will use reflection and just call the methods we need ...
public class TestRowSorter<M extends TableModel> extends TableRowSorter<M> { public TestRowSorter() { } public TestRowSorter(M model) { super(model); } public Method findMethod(String name, Class... lstTypes) { return findMethod(getClass(), name, lstTypes); } public Method findMethod(Class parent, String name, Class... lstTypes) { Method method = null; try { method = parent.getDeclaredMethod(name, lstTypes); } catch (NoSuchMethodException noSuchMethodException) { try { method = parent.getMethod(name, lstTypes); } catch (NoSuchMethodException nsm) { if (parent.getSuperclass() != null) { method = findMethod(parent.getSuperclass(), name, lstTypes); } } } return method; } @Override public void setRowFilter(RowFilter<? super M, ? super Integer> filter) { try { Method method = findMethod("createModelToView", int.class); method.setAccessible(true); method.invoke(this, getModelWrapper().getRowCount()); method = findMethod("createViewToModel", int.class); method.setAccessible(true); method.invoke(this, getModelWrapper().getRowCount()); method = findMethod("setModelToViewFromViewToModel", boolean.class); method.setAccessible(true); method.invoke(this, true); } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException exp) { exp.printStackTrace(); } super.setRowFilter(filter); } }
Now, I'm sure you know, like me, this is a terrible, terrible idea that can break at any time. This is probably also very, very inefficient, as you drop indexes on bidirectional search each time.
So, the answer is, do not refer to the view from the filter.
Generally speaking, I try to replace RowSorter when I ever replace RowFilter as it avoids such problems: P