SortedList, identical elements + re-arranging
John Hendrikx
john.hendrikx at gmail.com
Thu Jan 29 12:52:20 UTC 2026
The ListChangeBuilder implements set(int, T) as:
publicvoidnextSet(intidx, E old) {
nextRemove(idx, old);
nextAdd(idx, idx + 1);
}
The Change interface has a `wasReplaced` method which I think would be
applicable for this case. SortedList however does not check this case
(it only checks add and remove), so it may be possible to make an
adjusment inside SortedList to be smarter about how to handle this.
--John
On 28/01/2026 04:50, Cormac Redmond wrote:
> Hi,
>
> I have noticed a troublesome quirk with SortedList which causes
> unexpected re-positioning of elements in its list.
>
> This is a noticeable problem when you are, for example, sorting a
> TableView on a column and re-insert an identical element at the same
> index in the source list (i.e., nothings changing). E.g., if your goal
> is to refresh a single row, even when there's no difference in the
> underlying object, SortedList can still shift its place in its arrays
> and hence the row shifts visibly in the table. On large tables, this
> can be a huge jump, outside of view. This occurs when the sort column
> value shares the same value with other rows (i.e., in my code sample
> below, multiple people are aged 62 and sorting is by age).
>
> Sample below code and output below. The bit in green represents what I
> would expect on each subsequent printout, but the bit in red shows the
> random re-arrangement of elements.
>
> There's no need to include a full TableView example to show this. But
> I've included a small gif too, for example.
>
> ---- Source Initial ----
> Person item [0]: Person[name=Bob, age=60, id=0]
> Person item [1]: Person[name=Alice, age=70, id=1]
> Person item [2]: Person[name=Diana, age=30, id=2]
> Person item [3]: Person[name=Frank1, age=62, id=3]
> Person item [4]: Person[name=Eve, age=62, id=4]
> Person item [5]: Person[name=Frank2, age=62, id=5]
> Person item [6]: Person[name=Jim, age=62, id=6]
> Person item [7]: Person[name=Ivy, age=62, id=7]
> Person item [8]: Person[name=Jack, age=53, id=8]
>
> ---- Sorted Initial (Subsequent Prints Should Match This, But Don't) ----
> Person item [0]: Person[name=Diana, age=30, id=2]
> Person item [1]: Person[name=Jack, age=53, id=8]
> Person item [2]: Person[name=Bob, age=60, id=0]
> Person item [3]: Person[name=Frank1, age=62, id=3]
> Person item [4]: Person[name=Eve, age=62, id=4]
> Person item [5]: Person[name=Frank2, age=62, id=5]
> Person item [6]: Person[name=Jim, age=62, id=6]
> Person item [7]: Person[name=Ivy, age=62, id=7]
> Person item [8]: Person[name=Alice, age=70, id=1]
>
> ---- Sorted After Identical Replace Index 5 ----
> Person item [0]: Person[name=Diana, age=30, id=2]
> Person item [1]: Person[name=Jack, age=53, id=8]
> Person item [2]: Person[name=Bob, age=60, id=0]
> Person item [3]: Person[name=Frank2, age=62, id=5]
> Person item [4]: Person[name=Frank1, age=62, id=3]
> Person item [5]: Person[name=Eve, age=62, id=4]
> Person item [6]: Person[name=Jim, age=62, id=6]
> Person item [7]: Person[name=Ivy, age=62, id=7]
> Person item [8]: Person[name=Alice, age=70, id=1]
>
> ---- Sorted After Identical Replace Index 4 ----
> Person item [0]: Person[name=Diana, age=30, id=2]
> Person item [1]: Person[name=Jack, age=53, id=8]
> Person item [2]: Person[name=Bob, age=60, id=0]
> Person item [3]: Person[name=Eve, age=62, id=4]
> Person item [4]: Person[name=Frank2, age=62, id=5]
> Person item [5]: Person[name=Frank1, age=62, id=3]
> Person item [6]: Person[name=Jim, age=62, id=6]
> Person item [7]: Person[name=Ivy, age=62, id=7]
> Person item [8]: Person[name=Alice, age=70, id=1]
>
> ---- Sorted After Identical Replace Index 3 ----
> Person item [0]: Person[name=Diana, age=30, id=2]
> Person item [1]: Person[name=Jack, age=53, id=8]
> Person item [2]: Person[name=Bob, age=60, id=0]
> Person item [3]: Person[name=Frank1, age=62, id=3]
> Person item [4]: Person[name=Eve, age=62, id=4]
> Person item [5]: Person[name=Frank2, age=62, id=5]
> Person item [6]: Person[name=Jim, age=62, id=6]
> Person item [7]: Person[name=Ivy, age=62, id=7]
> Person item [8]: Person[name=Alice, age=70, id=1]
>
> ...etc.
>
>
> Code:
> public class SortedListBehaviour {
>
> public static void main(String[] args) {
>
> ObservableList<Person> personList =
> FXCollections.observableArrayList();
>
> personList.addAll(
> new Person("Bob", 60, 0),
> new Person("Alice", 70, 1),
> new Person("Diana", 30, 2),
> new Person("Frank1", 62, 3),
> new Person("Eve", 62, 4),
> new Person("Frank2", 62, 5),
> new Person("Jim", 62, 6),
> new Person("Ivy", 62, 7),
> new Person("Jack", 53, 8)
> );
>
> SortedList<Person> sortedList = new SortedList<>(personList,
> Comparator.comparing(Person::age));
>
> // Starting out
> printList(personList, "Source Initial");
>
> // This is sorted by age, all subsequent prints should look
> like this, but they don't
> printList(sortedList, "Sorted Initial (Subsequent Prints
> Should Match This, But Don't)");
>
> personList.set(5, new Person("Frank2", 62, 5)); // Replace
> with identical, at same index
> printList(sortedList, "Sorted After Identical Replace Index 5");
>
> personList.set(4, new Person("Eve", 62, 4)); // Replace with
> identical, at same index
> printList(sortedList, "Sorted After Identical Replace Index 4");
>
> personList.set(3, new Person("Frank1", 62, 3)); // Replace
> with identical, at same index
> printList(sortedList, "Sorted After Identical Replace Index 3");
> }
>
> private static void printList(final List<Person> list, final
> String source) {
> System.out.println("\n---- " + source + " ----");
> for (int i = 0; i < list.size(); i++) {
> final Person next = list.get(i);
> System.out.println("Person item [" + i + "]: " + next);
> }
> }
>
> public record Person(String name, int age, int id) {
> @Override
> public boolean equals(final Object o) {
> if (o == null || getClass() != o.getClass()) {
> return false;
> }
> final Person person = (Person) o;
> return id == person.id <http://person.id> && age ==
> person.age && Objects.equals(name, person.name <http://person.name>);
> }
>
> @Override
> public int hashCode() {
> return Objects.hash(name, age, id);
> }
> }
> }
>
>
> UI example also; the "Refresh" item here replaces the object with an
> identical object and the same index, yet it will often jump around the
> table as you can see, and selection can change too.
>
> sl.gif
>
> I understand that strictly speaking the list is still sorted correctly
> (by age), but SortedList could make some effort to reduce re-arranging
> already-present elements.
>
>
>
> Regards,
> *
> *
> *Cormac Redmond*
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20260129/32bb7aed/attachment-0001.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sl.gif
Type: image/gif
Size: 58742 bytes
Desc: not available
URL: <https://mail.openjdk.org/pipermail/openjfx-dev/attachments/20260129/32bb7aed/sl-0001.gif>
More information about the openjfx-dev
mailing list