Java8 Collections.sort (sometimes) does not sort JPA returned lists
Well, this is a perfect didactic play telling you why programmers shouldn’t extend classes not designed for being subclassed. Books like “Effective Java” tell you why: the attempt to intercept every method to alter its behavior will fail when the superclass evolves.
Here, IndirectList
extends Vector
and overrides almost all methods to modify its behavior, a clear anti-pattern. Now, with Java 8 the base class has evolved.
Since Java 8, interfaces can have default
methods and so methods like sort
were added which have the advantage that, unlike Collections.sort
, implementations can override the method and provide an implementation more suitable to the particular interface
implementation. Vector
does this, for two reasons: now the contract that all methods are synchronized
expands to sorting as well and the optimized implementation can pass its internal array to the Arrays.sort
method skipping the copying operation known from previous implementations (ArrayList
does the same).
To get this benefit immediately even for existing code, Collections.sort
has been retrofitted. It delegates to List.sort
which will by default delegate to another method implementing the old behavior of copying via toArray
and using TimSort
. But if a List
implementation overrides List.sort
it will affect the behavior of Collections.sort
as well.
interface method using internal
List.sort array w/o copying
Collections.sort ─────────────────> Vector.sort ─────────────────> Arrays.sort
Wait for the bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=446236 to be fixed. Use the below dependency when it get's available or a snapshot.
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.0</version>
</dependency>
Until then use the workaround from the question:
if (docs instanceof IndirectList) {
IndirectList iList = (IndirectList)docs;
Object sortTargetObject = iList.getDelegateObject();
if (sortTargetObject instanceof List<?>) {
List<Document> sortTarget=(List<Document>) sortTargetObject;
Collections.sort(sortTarget,comparator);
}
} else {
Collections.sort(docs,comparator);
}
or specify eager fetching where possible:
// http://stackoverflow.com/questions/8301820/onetomany-relationship-is-not-working
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parentFolder", fetch=FetchType.EAGER)
The issue you are having is not with sort.
TimSort is called via Arrays.sort
which does the following:
TimSort.sort(a, 0, a.length, c, null, 0, 0);
So you can see the size of the array TimSort is getting is either 0 or 1.
Arrays.sort
is called from Collections.sort
, which does the following.
Object[] a = list.toArray();
Arrays.sort(a, (Comparator)c);
So the reason your collection is not getting sorted is that it is returning an empty array. So the collection that is being used is not conforming to the collections API by returning an empty array.
You say you have a persistence layer. So it sounds like the problem is that the library you are using retrieves entities in a lazy way and does not populate its backing array unless it has to. Have a closer look at the collection you are trying to sort and see how it works. Your original unit test didn't show anything as it was not trying to sort the same collection that is used in production.