Java 根据指定字段实现对对象进行去重

文章目录

    • 引入问题
    • [方法一:使用 HashSet 数据结构](#方法一:使用 HashSet 数据结构)
    • [方法二:使用 Java 8 的 Stream API 的 distinct() 去重](#方法二:使用 Java 8 的 Stream API 的 distinct() 去重)
    • [方法三:使用 Map 数据结构](#方法三:使用 Map 数据结构)
    • [方法四:使用 Collectors.toMap() 方法](#方法四:使用 Collectors.toMap() 方法)
    • [方法五:使用 Collectors.collectingAndThen() 方法](#方法五:使用 Collectors.collectingAndThen() 方法)

注: 该文中的多种方法实现涉及 Java 8 Stream API 特性,若没有接触过,建议先阅读另一篇文章进行了解: Java8 Stream API全面解析------高效流式编程的秘诀

引入问题

首先,我自定义了一个名为 Person 的 Java 类:

java 复制代码
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public int hashCode() {
        return super.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • Person 类中有两个属性:nameage 和一个全参构造方法:

    • name 是一个字符串类型的变量,用于表示人的姓名;age 是一个整数类型的变量,用于表示人的年龄。
    • 构造方法用于创建 Person 类的对象。
  • 并且重写了三个方法:hashCode()equals()toString()

    • hashCode() 方法返回对象的哈希码,此处直接调用了父类 ObjecthashCode() 方法。
    • equals() 方法用于比较对象是否相等,此处直接调用了父类 Objectequals() 方法。
    • toString() 方法返回一个描述该对象内容的字符串,格式为 "Person{name='姓名', age=年龄}"

最终,我们需要根据 Person 类的 name 字段对目标集合进行去重:

java 复制代码
public static void main(String[] args) {
    List<Person> persons = new ArrayList<>();
    persons.add(new Person("Tom", 20));
    persons.add(new Person("Jerry", 18));
    persons.add(new Person("Tom", 22));
    persons.add(new Person("Jim", 23));
    persons.add(new Person("Tom", 22));

    persons.forEach(System.out::println);
}

方法一:使用 HashSet 数据结构

根据 Java 对象某个字段进行去重,可以使用 HashSet 数据结构。HashSet 内部实现了哈希表,能够快速判断元素是否已存在,从而实现去重。

Tips: HashSet 是如何实现元素去重的,或者说如何判断元素是否重复?

在 Java 中,HashSet 是一种基于哈希表实现的集合类,它内部维护了一个存储元素的哈希表。HashSet 通过元素的哈希码(hashcode)来判断元素是否重复的。

当我们向 HashSet 中添加元素时,HashSet 会首先计算该元素的哈希码,并根据哈希码将元素放入对应的桶中。如果该桶中已经有了相同哈希码的元素,则会调用元素的 equals() 方法,比较元素是否相等。如果相等,则认为该元素已经存在于 HashSet 中,不进行重复添加;否则将该元素添加到集合中。

如果我们通过 HashSet 进行去重,就需要正确地实现 hashCode()equals() 方法。hashCode() 方法应该返回与元素属性相关的哈希码,而 equals() 方法应该根据元素属性判断元素是否相等。只有这样才能保证在 HashSet 中正确地去重和查找元素。

使用 HashSet 去重的具体步骤如下:

  1. 重写对象的 equalshashCode 方法。在这两个方法中,分别比较对象的指定字段,并返回相应的哈希值。
  2. 创建一个 HashSet 对象,并将所有要去重的对象添加到该 HashSet 中。
  3. 遍历该 HashSet,处理去重后的结果。

重写 Person 类的 equalshashCode 方法,用于比较指定字段:

java 复制代码
public class Person {
    
    ......
        
	// 重写 hashCode 方法
    @Override
    public int hashCode() {
        // 哈希值只与 name 字段有关
        return name.hashCode(); 
    }
    
    // 重写 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        // 比较 name 字段
        return name.equals(person.name); 
    }
}

在重写的 equals() 方法中,首先使用 this == o 来判断两个对象是否为同一个对象(即内存地址是否相同),如果是,则直接返回 true。如果不是同一个对象,则继续比较其他属性。

接着,使用 o == null 判断传入的参数是否为 null,如果是 null,则两个对象肯定不相等,直接返回 false。然后使用 getClass() 方法来获取传入对象的类,判断其是否与当前对象的类相同,如果不同,则两个对象肯定不相等,直接返回 false

最后,将参数对象强制转换成 Person 类型,并比较两个对象的 name 属性是否相等。如果相等,则认为两个对象相等,返回 true,否则返回 false

同时重写 hashCode() 方法,以确保两个对象相等时它们的哈希码也相等。

使用 HashSet 去重:

java 复制代码
public static void main(String[] args) {
    
	......
        
    HashSet<Person> personHashSet = new HashSet<>(persons);
    personHashSet.forEach(System.out::println);
}

去重结果为:

json 复制代码
Person{name='Tom', age=20}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}

方法二:使用 Java 8 的 Stream API 的 distinct() 去重

Java 8 增加的 Stream API 提供了 distinct() 方法去重。

Stream 流的 distinct() 方法是基于对象的 equals() 方法来判断对象是否相等,因此我们需要重写 Person 类的 equals() 方法:

java 复制代码
public class Person {
    
    ......
    
    // 重写 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        // 比较 name 字段
        return name.equals(person.name); 
    }
}

之后调用 stream() 方法将列表转换为流,并且使用 distinct() 方法基于 name 字段进行去重:

java 复制代码
List<Person> collect = persons.stream()
                .distinct()
                .collect(Collectors.toList());

最后打印去重后的 Person 集合:

json 复制代码
Person{name='Tom', age=20}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}

方法三:使用 Map 数据结构

Java 中的 Map 是一种用于存储键值对的集合。我们可以利用 Map 中的键唯一的特性实现去重。

我们只需要遍历 List 中的 Person 对象,将 name 作为 key,Person 对象作为 value 存入 Map 中,这样就可以去除重复的 name 对应的 Person 对象:

java 复制代码
public static void main(String[] args) {

    ......

    Map<String, Person> map = new HashMap<>();
    for (Person person : persons) {
        map.put(person.getName(), person);
    }
}

去重结果如下:

json 复制代码
Person{name='Tom', age=22}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}

方法四:使用 Collectors.toMap() 方法

Collectors.toMap()是 Java 8 中的一个收集器(Collector),它可以将 Stream 中的元素收集到一个 Map 中,其中每个元素都是一个键值对。该方法有多个重载形式:

  1. toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)

    将 Stream 中的元素转换为键值对,并存储到一个Map中。其中,keyMapper用于指定如何从元素中提取键,valueMapper用于指定如何从元素中提取值。

    如果存在重复的键,则会抛出IllegalStateException异常。

  2. toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)

    与第一种形式类似,但当存在重复的键时,会使用mergeFunction函数来处理冲突。例如,可以使用mergeFunction来选择较小或较大的值,或将两个值合并成一个新值。

  3. toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)

    与第二种形式类似,但允许指定用于创建 Map 的具体实现类。

使用第二种重载形式将包含Person对象的List转换为一个以Person对象的姓名作为键的Map

java 复制代码
public static void main(String[] args) {

    ......

   Map<String, Person> collect = persons.stream()
        .collect(Collectors.toMap(Person::getName, p -> p, (p1, p2) -> p1));
}
  • Person::getName:函数式接口Function类型的方法引用,用于将Person对象的姓名作为键。
  • person -> person:Lambda 表达式,用于将Person对象本身作为值。
  • (p1, p2) -> p1:Lambda 表达式,用于处理当存在重复键时的情况。此处选择保留第一个键对应的值,而忽略第二个键对应的值。

去重结果如下:

json 复制代码
Person{name='Tom', age=20}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}

方法五:使用 Collectors.collectingAndThen() 方法

Collectors.collectingAndThen()是 Java 8 中的一个收集器(Collector)方法,它允许在收集元素后应用一个最终转换函数。在使用collectingAndThen()时,先通过一个初始的收集器将元素收集起来,然后再应用一个最终转换函数对收集结果进行处理。

以下是collectingAndThen()方法的常用重载形式:

collectingAndThen(Collector<T, A, R> downstream, Function<R, RR> finisher)

  • downstream:初始的收集器,用于将元素收集起来并生成一个中间结果。
  • finisher:最终转换函数,用于对中间结果进行处理,并返回最终结果。

使用collectingAndThen()方法实现去重并返回去重后的结果集:

java 复制代码
public static void main(String[] args) {

    ......

    ArrayList<Person> collect = persons.stream()
        .collect(Collectors.collectingAndThen(
            Collectors.toMap(Person::getName, person -> person, (p1, p2) -> p1),
            map -> new ArrayList<>(map.values())
        ));
}
  • 使用Collectors.toMap()persons流中的元素转换为一个以 name 作为键的Map
  • 通过 map -> new ArrayList<>(map.values())Map的值部分提取出来,并使用ArrayList的构造函数将其包装为一个新的ArrayList<Person>对象。最终得到的ArrayList<Person>对象即为去重后的结果集。

输出去重后的结果:

json 复制代码
Person{name='Tom', age=20}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}
相关推荐
考虑考虑2 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261352 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊3 小时前
Java学习第22天 - 云原生与容器化
java
渣哥5 小时前
原来 Java 里线程安全集合有这么多种
java
间彧5 小时前
Spring Boot集成Spring Security完整指南
java
间彧6 小时前
Spring Secutiy基本原理及工作流程
java
Java水解7 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆9 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学9 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole9 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端