<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Hi Daniel,</p>
<div class="moz-cite-prefix">On 04/02/2026 08:44, Daniel Peintner
wrote:<br>
</div>
<blockquote type="cite"
cite="mid:CAAoP7T6DhEN8pDxhTDguw53j_ApppbVYwDec1_MuNXxdp7X_RQ@mail.gmail.com">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<div dir="ltr">
<div dir="ltr">
<div>Hi John,</div>
<div><br>
</div>
<div>Thank you very much for your feedback.</div>
</div>
<div class="gmail_quote gmail_quote_container">
<blockquote class="gmail_quote"
style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div>
<p>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.<br>
</p>
</div>
</blockquote>
<div><br>
</div>
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.<br>
<br>
On the other hand, the sort method of Collections [1] states<br>
<br>
> This sort is guaranteed to be stable: equal elements
will not be reordered as a result of the sort.</div>
</div>
</blockquote>
<p>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:</p>
<p> A1, A2, A3, A4, A5</p>
<p>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:</p>
<p> A2, A4, A1, A5, A3<br>
</p>
<p>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):</p>
<p> A1, A2, A6, A3, A4, A5</p>
<p>Only after a resort does it ensure again that the sort is stable,
and it will remain in the same order:<br>
</p>
<p> A1, A2, A6, A3, A4, A5</p>
<p>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:</p>
<p> A1, A2, A6, A3, A5</p>
<p>I hope that clarifies it a bit.<br>
</p>
<p></p>
<blockquote type="cite"
cite="mid:CAAoP7T6DhEN8pDxhTDguw53j_ApppbVYwDec1_MuNXxdp7X_RQ@mail.gmail.com">
<div dir="ltr"><br>
<div class="gmail_quote gmail_quote_container">I am not saying
it is a *real* conflict, but my assumption/expectation was
that the order of a SortedList behaves somewhat the same.</div>
</div>
</blockquote>
<p>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.<br>
</p>
<p>--John<br>
</p>
<blockquote type="cite"
cite="mid:CAAoP7T6DhEN8pDxhTDguw53j_ApppbVYwDec1_MuNXxdp7X_RQ@mail.gmail.com">
<div dir="ltr">
<div class="gmail_quote gmail_quote_container">
<div><br>
</div>
<div>Thanks,</div>
<div><br>
</div>
<div>-- Daniel</div>
<div><br>
</div>
<div>[1] <a
href="https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Collections.html#sort(java.util.List)"
moz-do-not-send="true" class="moz-txt-link-freetext">https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/Collections.html#sort(java.util.List)</a></div>
<div><br>
</div>
<div> </div>
<blockquote class="gmail_quote"
style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div>
<p> </p>
--John<br>
<br>
<div>On 29/01/2026 11:09, Daniel Peintner wrote:<br>
</div>
<blockquote type="cite">
<div dir="ltr">Hi,<br>
<br>
I had and still have the same problem in a similar
situation. <br>
<br>
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).<br>
<div>In this situation, the copied entry is identical
by nature (Comparator returns 0).</div>
<div><br>
</div>
<div>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.</div>
<br>
This is confusing for a user.<br>
<br>
<div>If there is a good solution for this, it would be
helpful.</div>
<div>However, I agree with Cormac Redmond that,
ideally, the order should not change if the
Comparator returns 0 (zero).</div>
<br>
Thanks,<br>
<br>
<div>-- Daniel</div>
<div><br>
</div>
<div><br>
</div>
</div>
<br>
<div class="gmail_quote">
<div dir="ltr" class="gmail_attr">On Wed, Jan 28, 2026
at 4:51 AM Cormac Redmond <<a
href="mailto:credmond@certak.com" target="_blank"
moz-do-not-send="true"
class="moz-txt-link-freetext">credmond@certak.com</a>>
wrote:<br>
</div>
<blockquote class="gmail_quote"
style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div dir="ltr">
<div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)">Hi,</div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)"><br>
</div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)">I
have noticed a troublesome quirk with
SortedList which causes unexpected
re-positioning of elements in its list.</div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)"><br>
</div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)">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).</div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)"><br>
</div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)">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.</div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)"><br>
</div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)">There's
no need to include a full TableView example to
show this. But I've included a small gif too,
for example.</div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)"><br>
</div>
<div style="font-size:small;color:rgb(0,0,0)"><font
face="monospace"><span
style="color:rgb(34,34,34)">---- Source
Initial ----</span></font></div>
<font face="monospace">Person item [0]:
Person[name=Bob, age=60, id=0]<br>
Person item [1]: Person[name=Alice, age=70,
id=1]<br>
Person item [2]: Person[name=Diana, age=30,
id=2]<br>
Person item [3]: Person[name=Frank1, age=62,
id=3]<br>
Person item [4]: Person[name=Eve, age=62,
id=4]<br>
Person item [5]: Person[name=Frank2, age=62,
id=5]<br>
Person item [6]: Person[name=Jim, age=62,
id=6]<br>
Person item [7]: Person[name=Ivy, age=62,
id=7]<br>
Person item [8]: Person[name=Jack, age=53,
id=8]<br>
<br>
---- Sorted Initial (Subsequent Prints Should
Match This, But Don't) ----<br>
Person item [0]: Person[name=Diana, age=30,
id=2]<br>
Person item [1]: Person[name=Jack, age=53,
id=8]<br>
Person item [2]: Person[name=Bob, age=60,
id=0]<br>
<span style="background-color:rgb(0,255,0)"><span
class="gmail_default"
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)"></span>Person
item [3]: Person[name=Frank1, age=62, id=3]<br>
Person item [4]: Person[name=Eve, age=62,
id=4]<br>
Person item [5]: Person[name=Frank2, age=62,
id=5]</span><br>
Person item [6]: Person[name=Jim, age=62,
id=6]<br>
Person item [7]: Person[name=Ivy, age=62,
id=7]<br>
Person item [8]: Person[name=Alice, age=70,
id=1]<br>
<br>
---- Sorted After Identical Replace Index 5
----<br>
Person item [0]: Person[name=Diana, age=30,
id=2]<br>
Person item [1]: Person[name=Jack, age=53,
id=8]<br>
Person item [2]: Person[name=Bob, age=60,
id=0]<br>
<span
style="background-color:rgb(234,153,153)">Person
item [3]: Person[name=Frank2, age=62, id=5]<br>
Person item [4]: Person[name=Frank1, age=62,
id=3]<br>
Person item [5]: Person[name=Eve, age=62,
id=4]</span><br>
Person item [6]: Person[name=Jim, age=62,
id=6]<br>
Person item [7]: Person[name=Ivy, age=62,
id=7]<br>
Person item [8]: Person[name=Alice, age=70,
id=1]<br>
<br>
---- Sorted After Identical Replace Index 4
----<br>
Person item [0]: Person[name=Diana, age=30,
id=2]<br>
Person item [1]: Person[name=Jack, age=53,
id=8]<br>
Person item [2]: Person[name=Bob, age=60,
id=0]<br>
<span
style="background-color:rgb(234,153,153)">Person
item [3]: Person[name=Eve, age=62, id=4]<br>
Person item [4]: Person[name=Frank2, age=62,
id=5]<br>
Person item [5]: Person[name=Frank1, age=62,
id=3]</span><br>
Person item [6]: Person[name=Jim, age=62,
id=6]<br>
Person item [7]: Person[name=Ivy, age=62,
id=7]<br>
Person item [8]: Person[name=Alice, age=70,
id=1]<br>
<br>
---- Sorted After Identical Replace Index 3
----<br>
Person item [0]: Person[name=Diana, age=30,
id=2]<br>
Person item [1]: Person[name=Jack, age=53,
id=8]<br>
Person item [2]: Person[name=Bob, age=60,
id=0]<br>
<span
style="background-color:rgb(234,153,153)">Person
item [3]: Person[name=Frank1, age=62, id=3]<br>
Person item [4]: Person[name=Eve, age=62,
id=4]<br>
Person item [5]: Person[name=Frank2, age=62,
id=5]</span><br>
Person item [6]: Person[name=Jim, age=62,
id=6]<br>
Person item [7]: Person[name=Ivy, age=62,
id=7]<br>
Person item [8]: Person[name=Alice, age=70,
id=1]</font><br>
<br clear="all">
</div>
<div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)">...etc.</div>
<br>
</div>
<div><br>
</div>
<div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)">Code:</div>
<div style="font-size:small;color:rgb(0,0,0)"><font
face="monospace">public class
SortedListBehaviour {<br>
<br>
public static void main(String[] args) {<br>
<br>
ObservableList<Person>
personList =
FXCollections.observableArrayList();<br>
<br>
personList.addAll(<br>
new Person("Bob", 60, 0),<br>
new Person("Alice", 70, 1),<br>
new Person("Diana", 30, 2),<br>
new Person("Frank1", 62, 3),<br>
new Person("Eve", 62, 4),<br>
new Person("Frank2", 62, 5),<br>
new Person("Jim", 62, 6),<br>
new Person("Ivy", 62, 7),<br>
new Person("Jack", 53, 8)<br>
);<br>
<br>
SortedList<Person> sortedList
= new SortedList<>(personList,
Comparator.comparing(Person::age));<br>
<br>
// Starting out<br>
printList(personList, "Source
Initial");<br>
<br>
// This is sorted by age, all
subsequent prints should look like this, but
they don't<br>
printList(sortedList, "Sorted
Initial (Subsequent Prints Should Match
This, But Don't)");<br>
<br>
personList.set(5, new
Person("Frank2", 62, 5)); // Replace with
identical, at same index<br>
printList(sortedList, "Sorted After
Identical Replace Index 5");<br>
<br>
personList.set(4, new Person("Eve",
62, 4)); // Replace with identical, at same
index<br>
printList(sortedList, "Sorted After
Identical Replace Index 4");<br>
<br>
personList.set(3, new
Person("Frank1", 62, 3)); // Replace with
identical, at same index<br>
printList(sortedList, "Sorted After
Identical Replace Index 3");<br>
}<br>
<br>
private static void printList(final
List<Person> list, final String
source) {<br>
System.out.println("\n---- " +
source + " ----");<br>
for (int i = 0; i < list.size();
i++) {<br>
final Person next = list.get(i);<br>
System.out.println("Person item
[" + i + "]: " + next);<br>
}<br>
}<br>
<br>
public record Person(String name, int
age, int id) {<br>
@Override<br>
public boolean equals(final Object
o) {<br>
if (o == null || getClass() !=
o.getClass()) {<br>
return false;<br>
}<br>
final Person person = (Person)
o;<br>
return id == <a
href="http://person.id" target="_blank"
moz-do-not-send="true">person.id</a>
&& age == person.age &&
Objects.equals(name, <a
href="http://person.name" target="_blank"
moz-do-not-send="true">person.name</a>);<br>
}<br>
<br>
@Override<br>
public int hashCode() {<br>
return Objects.hash(name, age,
id);<br>
}<br>
}<br>
}</font><br>
</div>
<div style="font-size:small;color:rgb(0,0,0)"><font
face="monospace"><br>
</font></div>
<div style="font-size:small;color:rgb(0,0,0)"><font
face="monospace"><br>
</font></div>
<div style="font-size:small;color:rgb(0,0,0)"><font
face="verdana, sans-serif">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.</font></div>
<div style="font-size:small;color:rgb(0,0,0)"><font
face="monospace"><br>
</font></div>
<div style="font-size:small;color:rgb(0,0,0)"><img
src="cid:part1.e9bHUM0X.JsPe3oIw@gmail.com"
alt="sl.gif" class="" width="562"
height="422"><br>
</div>
<br>
</div>
<div>
<div
style="font-family:verdana,sans-serif;font-size:small;color:rgb(0,0,0)">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.</div>
<br>
</div>
<div><br>
</div>
<div>
<div dir="ltr" class="gmail_signature">
<div dir="ltr">
<div><font face="verdana, sans-serif"
color="#000000"><br>
</font></div>
<div><font face="verdana, sans-serif"
color="#000000">Regards,</font></div>
<div><font face="verdana, sans-serif"
color="#000000"><b><br>
</b></font></div>
<font face="verdana, sans-serif"
color="#000000"><b>Cormac Redmond</b></font>
<div><br>
</div>
<div><br>
</div>
<div><br>
</div>
</div>
</div>
</div>
</div>
</blockquote>
</div>
</blockquote>
</div>
</blockquote>
</div>
</div>
</blockquote>
</body>
</html>