为什么Java中Comparator排序后id为3的对象出现在id为2的对象前面?

为什么这个id为3的对象会在id为2的对象的前面?

public static void main(String[] args) throws IOException {
    Person person = new Person("lili", 1, null, 1);
    Person person1 = new Person("aa", 2, null, 2);
    Person person3 = new Person("lili", -1, null, 3);
    Person person2= new Person("bb", -9, null, 4);
    Person person4= new Person("bb1", 5, null, -5);
    List<Person> collect = Stream.of(person2, person1, person, person3,person4).collect(Collectors.toList());
    List<Person> collect1 = collect.stream().sorted(new PersonComparator()).collect(Collectors.toList());
    collect1.forEach(System.out::println);
}

public class PersonComparator implements Comparator<Person> {

    @Override
    public int compare(Person o1, Person o2) {
        if (o1.name.equals("lili") && o2.name.equals("lili")) {
            return o1.age - o2.age;
        } else {
            return o1.id - o2.id;
        }
    }
}

比较结果:
Person{name='bb1', age=5, id=-5, pet=null}
Person{name='lili', age=-1, id=3, pet=null}
Person{name='lili', age=3, id=1, pet=null}
Person{name='aa', age=2, id=2, pet=null}
Person{name='bb', age=-9, id=4, pet=null}

阅读 1.7k
avatarAI BotBETA

问题分析:为什么Java中Comparator排序后id为3的对象出现在id为2的对象前面?

根本原因

自定义Comparator违反了排序的传递性要求,导致排序行为不一致。具体问题出在:

  • 当两个对象都是"lili"时,比较器使用age属性排序(升序)。
  • 其他情况(包括非"lili"对象的比较或混合比较)使用id属性排序(升序)。

这种混合逻辑会导致传递性冲突,使排序结果不可预测,不符合Comparator的约定(违反自反性、对称性和传递性)。Java的排序算法(如TimSort)在遇到不一致的比较器时仍会运行,但可能产生意外的顺序。


详细解释

1. 问题复现

输入列表:

Person{name='bb', age=-9, id=4, pet=null}   // person2
Person{name='aa', age=2, id=2, pet=null}    // person1
Person{name='lili', age=1, id=1, pet=null}   // person
Person{name='lili', age=-1, id=3, pet=null}  // person3
Person{name='bb1', age=5, id=-5, pet=null}   // person4

排序后实际输出:

Person{name='bb1', age=5, id=-5, pet=null}    // id=-5(非lili)
Person{name='lili', age=-1, id=3, pet=null}  // id=3(lili)
Person{name='lili', age=1, id=1, pet=null}    // id=1(lili)
Person{name='aa', age=2, id=2, pet=null}      // id=2(非lili)
Person{name='bb', age=-9, id=4, pet=null}     // id=4(非lili)

这里观察到:

  • id=3的对象(lili, age=-1)出现在id=2(非lili)之前。
2. 传递性冲突示例

以下三个对象比较时出现矛盾:

  1. person3 (lili, id=3) 与 person1 (aa, id=2):

    • 混合比较 → 用 id3 - 2 = 1 > 0person1应排在person3前。
  2. person1 (aa, id=2) 与 person (lili, id=1):

    • 混合比较 → 用 id2 - 1 = 1 > 0person应排在person1前。
  3. person3 (lili, id=3) 与 person (lili, id=1):

    • 都是lili → 用 age-1 - 1 = -2 < 0person3应排在person前。

冲突链:
person3 < person < person1 < person3(循环依赖),违反传递性。

3. 排序过程分析

原比较器逻辑:

public int compare(Person o1, Person o2) {
    if (o1.name.equals("lili") && o2.name.equals("lili")) {
        return o1.age - o2.age; // lili之间按age排序
    } else {
        return o1.id - o2.id; // 其他情况按id排序
    }
}
  • 非全局一致性lili对象在组内按age排序,但与组外对象比较时用id
  • 结果不可预测:Java排序算法(如TimSort)会尽力处理,但无法保证正确顺序。

修复方案

通过多级优先级排序修复传递性问题。例如,要求所有lili排在非lili前:

public class FixedPersonComparator implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        boolean isLili1 = "lili".equals(o1.name);
        boolean isLili2 = "lili".equals(o2.name);

        // 1. lili优先排在最前
        if (isLili1 && isLili2) {
            return o1.age - o2.age; // lili之间按age排序
        } else if (isLili1) {
            return -1; // o1是lili → 排前
        } else if (isLili2) {
            return 1; // o2是lili → o1排后
        } else {
            return o1.id - o2.id; // 非lili按id排序
        }
    }
}
修复后排序结果:
Person{name='lili', age=-1, id=3, pet=null}  // lili组:age最小
Person{name='lili', age=1, id=1, pet=null}   // lili组:age次小
Person{name='bb1', age=5, id=-5, pet=null}   // 非lili:id最小 (-5)
Person{name='aa', age=2, id=2, pet=null}     // 非lili:id次小 (2)
Person{name='bb', age=-9, id=4, pet=null}    // 非lili:id最大 (4)
关键点:
  1. 统一优先级:lili组始终在非lili组之前。
  2. 组内规则:lili组用age排序,非lili组用id排序。
  3. 传递性:消除了混合属性比较的冲突。

总结

  • 错误原因:自定义Comparator混合使用不同属性排序,违反了传递性。
  • 修复方法:改用多级优先级策略(如先分组,再组内排序)。
  • 编程建议:设计Comparator时确保逻辑一致,避免动态切换比较属性。使用Comparator.comparing().thenComparing()链式调用更安全:

    Comparator<Person> fixedComparator = Comparator
      .comparing((Person p) -> !"lili".equals(p.name)) // lili优先
      .thenComparing(p -> "lili".equals(p.name) ? p.age : p.id);
2 个回答

问题在于compare。根据提供的数据,能发现id是唯一的,根据id就能唯一排列出一个序列。

但是里面有涉及了对lili的单独处理。

这块就出现了一个问题。idname="lili"他们俩之间的排序关系怎么处理?

如果忽略他们的排序关系,你就能发现。name="lili"的数据排序没问题,不含liliid排序也没有问题。


一般这种排序场景是这种:比如按name排序,当name重复时,id小的排在前面

你给出的代码和你的结果对应不上,age这个属性

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进