Java7 改变排序算法,这样它就会抛出
java.lang.IllegalArgumentException:“比较法违反了它的一般合同!”
在某些情况下,当使用的比较器是错误的。有没有可能知道比较器中是哪种bug造成了这种情况呢?在我的实验中,x != x不重要,如果x
(如果对此有一条一般规则,那么在比较器中查找bug可能会更容易。当然,最好是修复所有的bug。*-)
特别是,以下两个比较国没有使TimSort抱怨:
final Random rnd = new Random(52);
Comparator<Integer> brokenButNoProblem1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 < o2) {
return Compare.LESSER;
} else if (o1 > o2) {
return Compare.GREATER;
}
return rnd.nextBoolean() ? Compare.LESSER : Compare.GREATER;
}
};
Comparator<Integer> brokenButNoProblem2 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 == o2) {
return Compare.EQUAL;
}
return rnd.nextBoolean() ? Compare.LESSER : Compare.GREATER;
}
};
但是,下面的比较器确实让它呕吐了:
Comparator<Integer> brokenAndThrowsUp = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (Math.abs(o1 - o2) < 10) {
return Compare.EQUAL; // WRONG and does matter
}
return Ordering.natural().compare(o1, o2);
}
};
更新:在一些真实的数据中,当x=y和y=z不存在x,y,z但x
发布于 2014-07-25 08:48:17
在查看了ComparableTimSort的代码之后,我不太确定。我们来分析一下。这里是抛出它的唯一方法(有一个类似的方法只对交换的角色执行相同的操作,所以分析其中一个角色就足够了)。
private void mergeLo(int base1, int len1, int base2, int len2) {
assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
// Copy first run into temp array
Object[] a = this.a; // For performance
Object[] tmp = ensureCapacity(len1);
int cursor1 = tmpBase; // Indexes into tmp array
int cursor2 = base2; // Indexes int a
int dest = base1; // Indexes int a
System.arraycopy(a, base1, tmp, cursor1, len1);
// Move first element of second run and deal with degenerate cases
a[dest++] = a[cursor2++];
if (--len2 == 0) {
System.arraycopy(tmp, cursor1, a, dest, len1);
return;
}
if (len1 == 1) {
System.arraycopy(a, cursor2, a, dest, len2);
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
return;
}
int minGallop = this.minGallop; // Use local variable for performance
outer:
while (true) {
int count1 = 0; // Number of times in a row that first run won
int count2 = 0; // Number of times in a row that second run won
/*
* Do the straightforward thing until (if ever) one run starts
* winning consistently.
*/
// ------------------ USUAL MERGE
do {
assert len1 > 1 && len2 > 0;
if (((Comparable) a[cursor2]).compareTo(tmp[cursor1]) < 0) {
a[dest++] = a[cursor2++];
count2++;
count1 = 0;
if (--len2 == 0)
break outer;
} else {
a[dest++] = tmp[cursor1++];
count1++;
count2 = 0;
if (--len1 == 1)
break outer;
}
} while ((count1 | count2) < minGallop);
// ------------------ GALLOP
/*
* One run is winning so consistently that galloping may be a
* huge win. So try that, and continue galloping until (if ever)
* neither run appears to be winning consistently anymore.
*/
do {
assert len1 > 1 && len2 > 0;
count1 = gallopRight((Comparable) a[cursor2], tmp, cursor1, len1, 0);
if (count1 != 0) {
System.arraycopy(tmp, cursor1, a, dest, count1);
dest += count1;
cursor1 += count1;
len1 -= count1;
// -->>>>>>>> HERE IS WHERE GALLOPPING TOO FAR WILL TRIGGER THE EXCEPTION
if (len1 <= 1) // len1 == 1 || len1 == 0
break outer;
}
a[dest++] = a[cursor2++];
if (--len2 == 0)
break outer;
count2 = gallopLeft((Comparable) tmp[cursor1], a, cursor2, len2, 0);
if (count2 != 0) {
System.arraycopy(a, cursor2, a, dest, count2);
dest += count2;
cursor2 += count2;
len2 -= count2;
if (len2 == 0)
break outer;
}
a[dest++] = tmp[cursor1++];
if (--len1 == 1)
break outer;
minGallop--;
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
if (minGallop < 0)
minGallop = 0;
minGallop += 2; // Penalize for leaving gallop mode
} // End of "outer" loop
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
if (len1 == 1) {
assert len2 > 0;
System.arraycopy(a, cursor2, a, dest, len2);
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
} else if (len1 == 0) {
throw new IllegalArgumentException(
"Comparison method violates its general contract!");
} else {
assert len2 == 0;
assert len1 > 1;
System.arraycopy(tmp, cursor1, a, dest, len1);
}
}该方法执行两次排序运行的合并。它通常进行合并,但一旦遇到一方就开始“大快朵颐”(即总是比另一方少)。跳跃式尝试通过向前看更多的元素,而不是一次比较一个元素,使事情变得更快。因为跑动应该被分类,所以向前看是很好的。
您可以看到,只有当len1在末尾为0时才会抛出异常。第一个观察是:在通常的合并过程中,不能抛出异常,因为循环一旦len这个1就会直接中止。因此,异常只能作为疾驰的结果抛出。
这已经有力地提示了异常行为是不可靠的:只要您有小数据集(生成的运行可能永远不会飞快,比如MIN_GALLOP是7),或者生成的运行总是巧合地生成一个永远不会飞驰的合并,那么您就永远不会收到异常。因此,无需进一步回顾gallopRight方法,我们就可以得出结论,您不能依赖异常:无论您的比较器有多么错误,它都可能永远不会抛出。
发布于 2014-07-25 08:53:08
来自文档
IllegalArgumentException -(可选)如果发现数组元素的自然排序违反了可比契约
我在提到的合同上没有找到多少,但是IMHO应该表示一个总订货 (即compareTo方法定义的关系必须是传递性、反对称和合计)。如果不满足这一要求,sort可能会抛出一个IllegalArgumentException。(我说可能是因为不符合这一要求可能会被忽视。)
编辑:添加到属性的链接,使关系成为一个总的顺序。
https://stackoverflow.com/questions/24951257
复制相似问题