首页 > 编程语言 >Java 根据指定字段实现对对象进行去重

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

时间:2024-10-13 17:20:14浏览次数:3  
标签:Java name HashSet 对象 age 指定 equals Person 方法

文章目录


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

引入问题

首先,我自定义了一个名为 Person 的 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 字段对目标集合进行去重:

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 方法,用于比较指定字段:

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 去重:

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

去重结果为:

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() 方法:

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 字段进行去重:

List<Person> collect = persons.stream()
                .distinct()
                .collect(Collectors.toList());

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

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 对象:

public static void main(String[] args) {

    ......

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

去重结果如下:

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

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 表达式,用于处理当存在重复键时的情况。此处选择保留第一个键对应的值,而忽略第二个键对应的值。

去重结果如下:

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()方法实现去重并返回去重后的结果集:

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>对象即为去重后的结果集。

输出去重后的结果:

Person{name='Tom', age=20}
Person{name='Jerry', age=18}
Person{name='Jim', age=23}

标签:Java,name,HashSet,对象,age,指定,equals,Person,方法
From: https://blog.csdn.net/qq_20185737/article/details/135311581

相关文章

  • 深拷贝与浅拷贝:JavaScript 里的“复制粘贴”大作战
    在JavaScript的世界里,复制对象就像是在玩“传声筒”游戏,听着听着就乱了套。今天,我们要聊聊两个“复制”大法:深拷贝和浅拷贝。他们就像是“有深度的朋友”和“表面交情的朋友”,那么到底有什么区别呢?让我们一起看看!1.浅拷贝:表面交情浅拷贝就像你和朋友一起去吃火锅,你们的......
  • java中HashMap扩容机制详解(扩容的背景、触发条件、扩容的过程、扩容前后的对比、性能
    在Java中,HashMap是一个非常常用的数据结构,基于哈希表实现,它通过键值对的形式存储数据。为了保证其操作的效率,HashMap采用了一种动态扩容机制。当HashMap中元素数量增长到一定程度时,会自动进行扩容。本文将详细讲解HashMap的扩容机制,包括其触发条件、过程、及扩容过程中可能......
  • Javascript笔试手撕题目大全
    1.如何使用JS模拟实现instanceof操作符?请写出具体代码方法描述优点缺点typeof 运算符返回变量的数据类型(对于基本类型很有效,但对于对象和数组返回 "object")简洁易用,适用于基本类型判断无法准确判断 null(返回 "object")和复杂对象/数组的类型instanceof 运算符检查对象是......
  • JAVA基础知识
    day01-继承&修饰符1.继承1.1继承的实现(掌握)继承的概念*继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法实现继承的格式*继承通过extends实现*格式:class子类extends父类{}*举例:classDogextendsAn......
  • Java_运算符
    一、运算符介绍运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。二、算术运算符1.讲解介绍算术运算符是对数值类型的变量进行运算的,在Java程序中使用的非常多。!注意上图中最后一行的字符串相加,结果是:hsped(不会有空格)注意!%公式是:a%b=a-a/b*b......
  • 【附源码】在线动漫信息平台(源码+数据库+毕业论文+ppt齐全),java语言springboot框架开
    ......
  • 【附源码】个人博客系统(源码+数据库+毕业论文齐全)java开发springboot框架vue javawe
    ......
  • 前端知识整理(全屏播放器 CSS JavaScript 轮转播放 jquery库 AJAX 画布 网页测试)
    currenttime在前端开发中,“currenttime”通常指的是获取用户设备的当前时间。这可以通过JavaScript来实现,下面是一个简单的示例代码,展示如何获取并显示当前时间:<!DOCTYPEhtml><html><head><title>显示当前时间</title></head><body><h1>当前时间:</h1><pid="d......
  • Java中的修饰符——类、方法、变量的修饰
            Java中的修饰符可以根据其作用对象进行细分,主要包括类的修饰符、方法的修饰符和变量的修饰符。不同的修饰符适用于不同的场景,以下是对它们的详细划分和解释。1.类的修饰符(ClassModifiers)        修饰符可以用于类声明,影响类的行为和可见性。访......
  • 浅谈Java之UDP通信
    一、基本介绍        Java提供了用于处理UDP(用户数据报协议)的类和方法。UDP是一种无连接的网络协议,它允许发送端和接收端之间无需建立连接即可发送数据。在Java中,你可以使用java.net包中的DatagramSocket和DatagramPacket类来实现UDP通信。二、简单用法以下是使用......