【问题标题】:Remove duplicates in list based upon multiple conditions根据多个条件删除列表中的重复项
【发布时间】:2026-02-22 08:30:01
【问题描述】:

我有一个列表,其中包含要清理的重复 Student 对象。如果列表中的对象具有相同的名称和 ID,则应删除它们。

有人可以帮助下面的 Comparator 实现吗?

public class RemoveDuplicate {
public static void main(String[] args) {
    final ArrayList students = new ArrayList();
    students.add(new Student("Student1", "1000", "1"));
    students.add(new Student("Student2", "1001", "2"));
    students.add(new Student("Student3", "1002", "3"));
    students.add(new Student("Student4", "1001", "4"));
    students.add(new Student("Student1", "1003", "45"));
    students.add(new Student("Student1", "1000", "46"));
    students.add(new Student("Student4", "1001", "47"));
    Set set = new TreeSet(new StudentsComparator());
    set.addAll(students);
    final ArrayList newList = new ArrayList(set);
    System.out.println(newList);
}
}
class StudentsComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
    System.out.println(s1 + "," + s2);
    if (s1.getId().equalsIgnoreCase(s2.getId())) {
        if (s1.getNum().equalsIgnoreCase(s2.getNum())) {
            System.out.println("return0");
            return 0;
        }
    }
    return 1;
}
}

实际输出:

Name=Student1   Id=1000   Num=1, 
Name=Student2   Id=1001   Num=2, 
Name=Student3   Id=1002   Num=3, 
Name=Student4   Id=1001   Num=4, 
Name=Student1   Id=1003   Num=45, 
Name=Student1   Id=1000   Num=46, 
Name=Student4   Id=1001   Num=47]

预期输出:

Name=Student1   Id=1000   Num=1, 
Name=Student2   Id=1001   Num=2, 
Name=Student3   Id=1002   Num=3, 
Name=Student4   Id=1001   Num=4, 
Name=Student1   Id=1003   Num=45]

谢谢

【问题讨论】:

  • getIdgetNumStudent 返回哪些值?为什么没有compare返回负数表示s1
  • 那不是一个有效的比较器。它可以返回 1,但永远不能返回负数,因此它不是反对称的。
  • 为了可读性重写了描述。

标签: java list collections


【解决方案1】:

您的Comparator 实现不正确,因为当前对象始终等于或大于比较对象(您永远不会返回负值)。
所以不遵守对称规则:

实施者必须确保 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 用于所有 x 和 y。

无论如何,如果您不想对它们进行排序,则删除重复项 TreeSet 是不合适的。

Student 类中覆盖 equals()hashCode() 并在 LinkedHashSet 中添加元素(最初在 ArrayList 中)以保持实际顺序。

final List<Student> students = new ArrayList<>();
students.add(new Student("Student1", "1000", "1"));
students.add(new Student("Student2", "1001", "2"));
...
LinkedHashSet<Student> set = new LinkedHashSet<>(students);

【讨论】:

  • 为什么使用 TreeSet 对删除重复项毫无用处?
  • @cpp 初学者我已经改写了:用例“不合适”。
【解决方案2】:

您的Comparator 违反了比较器合同:

比较它的两个参数的顺序。返回负整数、零或正整数,因为第一个参数小于、等于或大于第二个参数。

另外,TreeSet 中的 add 需要 log(n)(这还不错,特别是对于不是那么大的集合),如果您可以覆盖 equalshashcode 这通常是一个更好的选择.

但是,如果您想要动态生成自定义比较器,或者不能/想要覆盖 equalshashcode,您可以使用提供 java Comparator 的函数 comparingthenComparing

Comparator<Student> studentComparator = Comparator
        .comparing((Student student) -> student.getId().toLowerCase())
        .thenComparing((Student student) -> student.getNum().toLowerCase());

这个比较器函数一般更容易使用,并且也许不容易出错。您可以照常在TreeSet 中使用它。

然而,这个比较器不是 null 安全的,您需要传递 Comparator.nullsFirst(Comparator.naturalOrder()) 作为第二个参数以允许比较 null。并在调用toLowerCase()之前检查字符串。

你可以使用一些辅助方法(或者创建一个自定义比较器来定义类似的东西):

public static String handleCase(String str) {
    return str != null ? str.toLowerCase() : null;
}
public static <T extends Comparable<? super T>> Comparator<T> nullComparator() {
    return Comparator.nullsFirst(Comparator.naturalOrder());
}

例如,如果num 是一个可为空的属性,您可以使用:

Comparator<Student> studentComparator = Comparator
    .comparing((Student student) -> handleCase(student.getId()))
    .thenComparing((Student student) -> handleCase(student.getNum()), nullComparator());

最后,如果您想从“任何地方”访问此比较器,您可以将定义包装在比较器中,作为final 字段,或static 用于单例,或者只是作为final static 字段自定义比较器的辅助类。

【讨论】:

    【解决方案3】:

    这是针对您的特定问题的运行代码。

    学生班

    public class Student {
    
    private String name;
    private String id;
    private String num;
    
    
    
    public Student(String name, String id, String num) {
        super();
        this.name = name;
        this.id = id;
        this.num = num;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getNum() {
        return num;
    }
    public void setNum(String num) {
        this.num = num;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", id=" + id + ", num=" + num + "]";
    }
    

    主类

    public class RemoveDuplicate {
    public static void main(String[] args) {
        final ArrayList students = new ArrayList();
        students.add(new Student("Student1", "1000", "1"));
        students.add(new Student("Student2", "1001", "2"));
        students.add(new Student("Student3", "1002", "3"));
        students.add(new Student("Student4", "1001", "4"));
        students.add(new Student("Student1", "1003", "45"));
        students.add(new Student("Student1", "1000", "46"));
        students.add(new Student("Student4", "1001", "47"));
    
        Set set = new TreeSet(new StudentsComparator());
        set.addAll(students);
        final ArrayList newList = new ArrayList(set);
        System.out.println(newList);
    }
    }
    class StudentsComparator implements Comparator<Student> {
    @Override
        public int compare(Student s1, Student s2) {
          if (s1.getId().compareTo(s2.getId()) == 0) 
             return s1.getName().compareTo(s2.getName());
          else 
             return s1.getId().compareTo(s2.getId());
        }
    }
    

    【讨论】: