誰かが私に簡単な言葉で説明することができますか、なぜこのコードは例外を投げます、「比較方法はその一般的な契約に違反します!」そしてどうやってそれを直すのですか?
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
あなたの比較器は推移的ではありません。
A
をB
の親とし、B
をC
の親とする。 A > B
とB > C
なので、それはA > C
の場合でなければなりません。ただし、A
およびC
に対してコンパレータが呼び出されると、ゼロが返されます。つまり、A == C
です。これは契約に違反しているため、例外がスローされます。
不規則に振る舞うのではなく、これを検出してあなたに知らせるのはライブラリのほうがいいです。
compareParents()
の推移性要件を満たす1つの方法は、直接の先祖だけを見るのではなくgetParent()
チェーンをトラバースすることです。
私がこのエラーをグーグルしたときにこれが私が得たものであるという理由だけで、私の問題は私が持っていたということでした
if (value < other.value)
return -1;
else if (value >= other.value)
return 1;
else
return 0;
value >= other.value
は(明らかに)実際にはvalue > other.value
であるべきです。そうすれば、等しいオブジェクトで実際に0を返すことができます。
契約違反は、オブジェクトを比較するときに比較子が正しい値または一貫した値を提供していないことを意味します。たとえば、文字列比較を実行して、空の文字列を強制的に最後までソートすることができます。
if ( one.length() == 0 ) {
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
しかし、これは1と2の両方が空の場合を見落としています - そしてその場合、間違った値が返され(一致を示すために0ではなく1)、そして比較器はそれを違反として報告します。それは次のように書かれているはずです。
if ( one.length() == 0 ) {
if ( two.length() == 0 ) {
return 0; // BOth empty - so indicate
}
return 1; // empty string sorts last
}
if ( two.length() == 0 ) {
return -1; // empty string sorts last
}
return one.compareToIgnoreCase( two );
たとえあなたのcompareToが理論的に推移性を持っていたとしても、時々微妙なバグが物を台無しにします...浮動小数点演算エラーのような。それは私に起こりました。これは私のコードでした:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
else
return 0;
}
推移的なプロパティは明らかに成り立ちますが、何らかの理由でIllegalArgumentExceptionを受け取りました。そして、浮動小数点演算の小さなエラー、丸め誤差が原因で推移的なプロパティが壊れてはならないところでそれが破綻することがわかりました。それで、私はコードを書き直して、ほんのわずかな違い0を考慮しました、そしてそれはうまくいきました:
public int compareTo(tfidfContainer compareTfidf) {
//descending order
if ((this.tfidf - compareTfidf.tfidf) < .000000001)
return 0;
if (this.tfidf > compareTfidf.tfidf)
return -1;
else if (this.tfidf < compareTfidf.tfidf)
return 1;
return 0;
}
私たちの場合は、誤ってs1とs2の比較順序を逆にしたため、このエラーが発生していました。だから気をつけろ。それは明らかに以下よりもずっと複雑でしたが、これは実例です:
s1 == s2
return 0;
s2 > s1
return 1;
s1 < s2
return -1;
私の場合は、次のようなことをしていました。
if (a.someField == null) {
return 1;
}
if (b.someField == null) {
return -1;
}
if (a.someField.equals(b.someField)) {
return a.someOtherField.compareTo(b.someOtherField);
}
return a.someField.compareTo(b.someField);
私が確認し忘れたのは、a.someFieldとb.someFieldの両方がnullの場合です。
Javaは厳密な意味で整合性をチェックしません。重大な問題が発生した場合にのみ通知します。また、エラーから多くの情報を得ることはありません。
私は私のソーターで何が起こっているのか困惑していて、厳密な一貫性チェックをしました、多分これはあなたを助けるでしょう:
/**
* @param dailyReports
* @param comparator
*/
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();
iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
/**
* @param o1
* @param o2
*/
@Override
public void pair(T o1, T o2) {
final int diff = comparator.compare(o1, o2);
if (diff < Compare.EQUAL) {
checkConsistency(objectMapSmallerOnes, o1, o2);
getListSafely(objectMapSmallerOnes, o2).add(o1);
} else if (Compare.EQUAL < diff) {
checkConsistency(objectMapSmallerOnes, o2, o1);
getListSafely(objectMapSmallerOnes, o1).add(o2);
} else {
throw new IllegalStateException("Equals not expected?");
}
}
});
}
/**
* @param objectMapSmallerOnes
* @param o1
* @param o2
*/
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
final List<T> smallerThan = objectMapSmallerOnes.get(o1);
if (smallerThan != null) {
for (final T o : smallerThan) {
if (o == o2) {
throw new IllegalStateException(o2 + " cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
}
checkConsistency(objectMapSmallerOnes, o, o2);
}
}
}
/**
* @param keyMapValues
* @param key
* @param <Key>
* @param <Value>
* @return List<Value>
*/
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
List<Value> values = keyMapValues.get(key);
if (values == null) {
keyMapValues.put(key, values = new LinkedList<Value>());
}
return values;
}
/**
* @author Oku
*
* @param <T>
*/
public interface IPairIteratorCallback<T> {
/**
* @param o1
* @param o2
*/
void pair(T o1, T o2);
}
/**
*
* Iterates through each distinct unordered pair formed by the elements of a given iterator
*
* @param it
* @param callback
*/
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return it;
}
});
for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
callback.pair(list.get(outerIndex), list.get(innerIndex));
}
}
}
私はこれがヌル値の頻繁に繰り返されるチェックが実行されたコードの一部で起こるのを見ました:
if(( A==null ) && ( B==null )
return +1;//WRONG: two null values should return 0!!!
compareParents(s1, s2) == -1
の場合、compareParents(s2, s1) == 1
が期待されます。あなたのコードでは、必ずしもそうとは限りません。
特にs1.getParent() == s2 && s2.getParent() == s1
の場合。考えられる問題の1つにすぎません。
VM構成の編集はうまくいきました。
-Djava.util.Arrays.useLegacyMergeSort=true
このようにオブジェクトデータを比較することはできません。s1.getParent() == s2
- これはオブジェクト参照を比較します。 Fooクラスのequals function
をオーバーライドしてから、次のように比較してください。s1.getParent().equals(s2)