Java 오류 : 비교 방법이 일반 계약을 위반 함
이것에 대한 많은 질문을보고 문제를 해결하려고 노력했지만 한 시간 동안 인터넷 검색과 많은 시행 착오 끝에 여전히 고칠 수 없습니다. 여러분 중 일부가 문제를 파악하기를 바랍니다.
이것이 내가 얻는 것입니다.
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:835)
at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:453)
at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:392)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:191)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
at java.util.Arrays.sort(Arrays.java:472)
at java.util.Collections.sort(Collections.java:155)
...
그리고 이것은 내 비교기입니다.
@Override
public int compareTo(Object o) {
if(this == o){
return 0;
}
CollectionItem item = (CollectionItem) o;
Card card1 = CardCache.getInstance().getCard(cardId);
Card card2 = CardCache.getInstance().getCard(item.getCardId());
if (card1.getSet() < card2.getSet()) {
return -1;
} else {
if (card1.getSet() == card2.getSet()) {
if (card1.getRarity() < card2.getRarity()) {
return 1;
} else {
if (card1.getId() == card2.getId()) {
if (cardType > item.getCardType()) {
return 1;
} else {
if (cardType == item.getCardType()) {
return 0;
}
return -1;
}
}
return -1;
}
}
return 1;
}
}
어떤 생각?
예외 메시지는 실제로 매우 설명 적입니다. 그것이 언급하는 계약은 전이성입니다 : if A > B
and B > C
then for any A
, B
and C
: A > C
. 종이와 연필로 확인했는데 코드에 구멍이 거의없는 것 같습니다.
if (card1.getRarity() < card2.getRarity()) {
return 1;
-1
이면 반환하지 않습니다 card1.getRarity() > card2.getRarity()
.
if (card1.getId() == card2.getId()) {
//...
}
return -1;
-1
ID가 같지 않으면 반환 합니다. 당신은 반환해야 -1
또는 1
에 따라 ID가 더 크다.
이것 좀보세요. 훨씬 더 읽기 쉬운 것 외에도 실제로 작동해야한다고 생각합니다.
if (card1.getSet() > card2.getSet()) {
return 1;
}
if (card1.getSet() < card2.getSet()) {
return -1;
};
if (card1.getRarity() < card2.getRarity()) {
return 1;
}
if (card1.getRarity() > card2.getRarity()) {
return -1;
}
if (card1.getId() > card2.getId()) {
return 1;
}
if (card1.getId() < card2.getId()) {
return -1;
}
return cardType - item.getCardType(); //watch out for overflow!
다음 클래스를 사용하여 비교기에서 전이성 버그를 찾을 수 있습니다.
/**
* @author Gili Tzabari
*/
public final class Comparators
{
/**
* Verify that a comparator is transitive.
*
* @param <T> the type being compared
* @param comparator the comparator to test
* @param elements the elements to test against
* @throws AssertionError if the comparator is not transitive
*/
public static <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> elements)
{
for (T first: elements)
{
for (T second: elements)
{
int result1 = comparator.compare(first, second);
int result2 = comparator.compare(second, first);
if (result1 != -result2)
{
// Uncomment the following line to step through the failed case
//comparator.compare(first, second);
throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 +
" but swapping the parameters returns " + result2);
}
}
}
for (T first: elements)
{
for (T second: elements)
{
int firstGreaterThanSecond = comparator.compare(first, second);
if (firstGreaterThanSecond <= 0)
continue;
for (T third: elements)
{
int secondGreaterThanThird = comparator.compare(second, third);
if (secondGreaterThanThird <= 0)
continue;
int firstGreaterThanThird = comparator.compare(first, third);
if (firstGreaterThanThird <= 0)
{
// Uncomment the following line to step through the failed case
//comparator.compare(first, third);
throw new AssertionError("compare(" + first + ", " + second + ") > 0, " +
"compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " +
firstGreaterThanThird);
}
}
}
}
}
/**
* Prevent construction.
*/
private Comparators()
{
}
}
Comparators.verifyTransitivity(myComparator, myCollection)
실패한 코드 앞에서 호출하기 만하면 됩니다.
또한 JDK 버전과 관련이 있습니다. JDK6에서 잘 작동한다면 jdk 7의 구현 방법이 변경 되었기 때문에 JDK 7에서 설명한 문제가있을 수 있습니다.
이것 좀봐:
Description: The sorting algorithm used by java.util.Arrays.sort
and (indirectly) by java.util.Collections.sort
has been replaced. The new sort implementation may throw an IllegalArgumentException
if it detects a Comparable
that violates the Comparable
contract. The previous implementation silently ignored such a situation. If the previous behavior is desired, you can use the new system property, java.util.Arrays.useLegacyMergeSort
, to restore previous mergesort behaviour.
I don't know the exact reason. However, if you add the code before you use sort. It will be OK.
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
Consider the following case:
First, o1.compareTo(o2)
is called. card1.getSet() == card2.getSet()
happens to be true and so is card1.getRarity() < card2.getRarity()
, so you return 1.
Then, o2.compareTo(o1)
is called. Again, card1.getSet() == card2.getSet()
is true. Then, you skip to the following else
, then card1.getId() == card2.getId()
happens to be true, and so is cardType > item.getCardType()
. You return 1 again.
From that, o1 > o2
, and o2 > o1
. You broke the contract.
if (card1.getRarity() < card2.getRarity()) {
return 1;
However, if card2.getRarity()
is less than card1.getRarity()
you might not return -1.
You similarly miss other cases. I would do this, you can change around depending on your intent:
public int compareTo(Object o) {
if(this == o){
return 0;
}
CollectionItem item = (CollectionItem) o;
Card card1 = CardCache.getInstance().getCard(cardId);
Card card2 = CardCache.getInstance().getCard(item.getCardId());
int comp=card1.getSet() - card2.getSet();
if (comp!=0){
return comp;
}
comp=card1.getRarity() - card2.getRarity();
if (comp!=0){
return comp;
}
comp=card1.getSet() - card2.getSet();
if (comp!=0){
return comp;
}
comp=card1.getId() - card2.getId();
if (comp!=0){
return comp;
}
comp=card1.getCardType() - card2.getCardType();
return comp;
}
}
I had to sort on several criterion (date, and, if same date; other things...). What was working on Eclipse with an older version of Java, did not worked any more on Android : comparison method violates contract ...
After reading on StackOverflow, I wrote a separate function that I called from compare() if the dates are the same. This function calculates the priority, according to the criteria, and returns -1, 0, or 1 to compare(). It seems to work now.
I got the same error with a class like the following StockPickBean
. Called from this code:
List<StockPickBean> beansListcatMap.getValue();
beansList.sort(StockPickBean.Comparators.VALUE);
public class StockPickBean implements Comparable<StockPickBean> {
private double value;
public double getValue() { return value; }
public void setValue(double value) { this.value = value; }
@Override
public int compareTo(StockPickBean view) {
return Comparators.VALUE.compare(this,view); //return
Comparators.SYMBOL.compare(this,view);
}
public static class Comparators {
public static Comparator<StockPickBean> VALUE = (val1, val2) ->
(int)
(val1.value - val2.value);
}
}
After getting the same error:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
I changed this line:
public static Comparator<StockPickBean> VALUE = (val1, val2) -> (int)
(val1.value - val2.value);
to:
public static Comparator<StockPickBean> VALUE = (StockPickBean spb1,
StockPickBean spb2) -> Double.compare(spb2.value,spb1.value);
That fixes the error.
'Programing' 카테고리의 다른 글
`find -perm`을 사용하여 권한이 설정되지 않은 경우 찾기 (0) | 2020.11.15 |
---|---|
.gitmodules에서 git 하위 모듈 복원 (0) | 2020.11.15 |
권장 ServiceStack API 구조 (0) | 2020.11.14 |
Visual Studio Code의 기본 어두운 테마를 편집하는 방법은 무엇입니까? (0) | 2020.11.14 |
LIKE 연산자에서 여러 조건을 어떻게 도입 할 수 있습니까? (0) | 2020.11.14 |