ArrayLists, похоже, сортируются с помощью TimSort, где базовый список не всегда согласован при сортировке. Может случиться, что записи списка исчезают или появляются дважды при вызове компаратора.
В нашем компараторе мы сравниваем ключи, для которых мы используем функцию, чтобы получить значение для сравнения для этого ключа. Поскольку эта функция используется в других контекстах, у нас есть тест, присутствует ли ключ в списке (что не нужно для сортировки):
if (keys.contains(itemId)) {
...
Поскольку keys - это список, который мы сортируем, это может произойти в компараторе, если ключ не найден в списке из-за внутренней механики TimSort.
Вопрос: упоминается ли где-нибудь в Javadoc (не удалось его найти), что вы не должны получать доступ к базовому списку в Comparator? Это плохая реализация TimSort, которая должна сортировать копию? Или это была глупая идея, прежде всего, получить доступ к базовому списку в компараторе?
Программа, приведенная ниже, предоставлена T.J. Crowder демонстрирует, что содержимое базового списка может быть непоследовательным во время обращений к Comparator. (Эта программа демонстрирует рассматриваемое явление, но не отражает фактическое приложение, на которое повлияла проблема.)
import java.util.*;
public class Example {
private static String[] chars = {
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
};
private List<String> list;
private String[] entries;
private Example() {
this.entries = new String[1000];
for (int n = 0; n < 1000; ++n) {
this.entries[n] = chars[n % chars.length] + n;
}
// Ensure it's an ArrayList, specifically
this.list = new ArrayList<String>(Arrays.asList(this.entries));
}
public static void main(String[] args) {
(new Example()).run();
}
class ListComparator implements Comparator<String> {
public int compare(String a, String b) {
for (String s : entries) {
int i1 = Example.this.list.indexOf(s);
if (i1 == -1) {
System.out.println(s + ": Missing");
} else {
int i2 = Example.this.list.lastIndexOf(s);
if (i2 != i1) {
System.out.println(s + ": Duplicated, at " + i1 + " and " + i2);
}
}
}
return a.compareTo(b);
}
}
private void run() {
this.list.sort(new ListComparator());
}
}
Вот первые несколько строк вывода из цикла:
b1: Missing
a52: Duplicated, at 2 and 32
b27: Missing
a52: Duplicated, at 2 and 32
c2: Missing
a52: Duplicated, at 2 and 32
c2: Missing
c28: Missing
a52: Duplicated, at 2 and 32
b53: Duplicated, at 5 and 33
c28: Missing
d29: Missing
a52: Duplicated, at 2 and 32
b53: Duplicated, at 5 and 33
d3: Missing
d29: Missing
a52: Duplicated, at 2 and 32
b53: Duplicated, at 5 and 33
d3: Missing
d29: Missing
e30: Missing