SortedList, identical elements + re-arranging

John Hendrikx john.hendrikx at gmail.com
Wed Feb 4 08:43:03 UTC 2026


Hi Daniel,

On 04/02/2026 08:44, Daniel Peintner wrote:
> Hi John,
>
> Thank you very much for your feedback.
>
>     The solution IMHO is to make this simply crystal clear in your
>     Comparator.  If equal items should be ordered in insertion order,
>     then the index should be part of the comparator.  Any decision
>     that SortedList makes could be correct for some use cases, and
>     incorrect for others.
>
>
> You might be right that one could (should?) of course take into
> account the original order of the underlying list in the comparator to
> be sure.
>
> On the other hand, the sort method of Collections [1] states
>
> >  This sort is guaranteed to be stable: equal elements will not be
> reordered as a result of the sort.

This statement is about the sort algorithm used.  It means that if you
have two equal elements (already present at the time of sorting) that
the algorithm won't decide to swap them around.  Some algorithms may
reorder equal elements unintentionally due to the way they are
implemented.  This re-arranging for such algorithms could occur on each
call to `sort`, even if nothing else changed.  Stable sort algorithms
take extra care to ensure unnecessary re-arranging of equal
elements doesn't happen. For example, given 5 equal elements:

      A1, A2, A3, A4, A5

A stable sort when re-applied will never re-arrange these, however, an
unstable sort may re-arrange these in some random order each time the
list is sorted, for example to:

      A2, A4, A1, A5, A3

The stability statement however provides no guarantees when you add a
new element (even if it was the same as one that was just removed, or
duplicated).  In that case the new element gets a random spot among its
peers.  Only in subsequent sorts does the algorithm guarantee that it
doesn't move relative to other equal elements; so if I add A6, it may
end up anywhere (the rest of the elements however remain in the same
relative order):

     A1, A2, A6, A3, A4, A5

Only after a resort does it ensure again that the sort is stable, and it
will remain in the same order:

     A1, A2, A6, A3, A4, A5

If I remove any of the elements, a stable sort will also ensure that the
remaining equal elements stay in the same order, so if I remove A4, it
will become:

     A1, A2, A6, A3, A5

I hope that clarifies it a bit.

>
> I am not saying it is a *real* conflict, but my assumption/expectation
> was that the order of a SortedList behaves somewhat the same.

Unfortunately, it can't guarantee this, and the stability guarantee
doesn't apply in your case.  So if you really want the new / duplicate
item to appear in a specific location relative to other equal elements,
you must add another sort key to the comparator to remove any ambiguity.

--John

>
> Thanks,
>
> -- Daniel
>
> [1] https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Collections.html#sort(java.util.List)
>
>  
>
>     --John
>
>     On 29/01/2026 11:09, Daniel Peintner wrote:
>>     Hi,
>>
>>     I had and still have the same problem in a similar situation.
>>
>>     The situation is as follows. A table contains a list of entries.
>>     A user selects an entry and performs a copy and paste operation
>>     (in order to modify the entry later).
>>     In this situation, the copied entry is identical by nature
>>     (Comparator returns 0).
>>
>>     Even if I insert the *new* entry into the underlying list after
>>     the existing entry, SortedList decides to display the entry
>>     *before* the existing entry.
>>
>>     This is confusing for a user.
>>
>>     If there is a good solution for this, it would be helpful.
>>     However, I agree with Cormac Redmond that, ideally, the order
>>     should not change if the Comparator returns 0 (zero).
>>
>>     Thanks,
>>
>>     -- Daniel
>>
>>
>>
>>     On Wed, Jan 28, 2026 at 4:51 AM Cormac Redmond
>>     <credmond at certak.com> 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/20260204/f53304f1/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/20260204/f53304f1/sl-0001.gif>


More information about the openjfx-dev mailing list