List集合去重的一些方法(常规遍历、Set去重、java8 stream去重、重写equals和hashCode方法)

2019-10-11 20:16:45

参考地址 List集合去重的一些方法(常规遍历、Set去重、java8 stream去重、重写equals和hashCode方法)

1. 常规元素去重(对象去重在下面)

碰到List去重的问题,除了遍历去重,我们常常想到利用Set集合不允许重复元素的特点,通过List和Set互转,来去掉重复元素。

复制代码

// 遍历后判断赋给另一个list集合,保持原来顺序
    public static void ridRepeat1(List<String> list) {
        System.out.println("list = [" + list + "]");
        List<String> listNew = new ArrayList<String>();        for (String str : list) {            if (!listNew.contains(str)) {
                listNew.add(str);
            }
        }
        System.out.println("listNew = [" + listNew + "]");
    }    // set集合去重,保持原来顺序
    public static void ridRepeat2(List<String> list) {
        System.out.println("list = [" + list + "]");
        List<String> listNew = new ArrayList<String>();
        Set set = new HashSet();        for (String str : list) {            if (set.add(str)) {
                listNew.add(str);
            }
        }
        System.out.println("listNew = [" + listNew + "]");
    }    // Set去重     由于Set的无序性,不会保持原来顺序
    public static void ridRepeat3(List<String> list) {
        System.out.println("list = [" + list + "]");
        Set set = new HashSet();
        List<String> listNew = new ArrayList<String>();
        set.addAll(list);
        listNew.addAll(set);
        System.out.println("listNew = [" + listNew + "]");
    }    // Set去重(将ridRepeat3方法缩减为一行) 无序
    public static void ridRepeat4(List<String> list) {
        System.out.println("list = [" + list + "]");
        List<String> listNew = new ArrayList<String>(new HashSet(list));
        System.out.println("listNew = [" + listNew + "]");
    }    // Set去重并保持原先顺序的两种方法
    public static void ridRepeat5(List<String> list) {
        System.out.println("list = [" + list + "]");
        List<String> listNew = new ArrayList<String>(new TreeSet<String>(list));
        List<String> listNew2 = new ArrayList<String>(new LinkedHashSet<String>(list));
        System.out.println("listNew = [" + listNew + "]");
        System.out.println("listNew2 = [" + listNew2 + "]");
    }

复制代码

除此之外,可以利用java8的stream来实现去重

  //利用java8的stream去重
  List uniqueList = list.stream().distinct().collect(Collectors.toList());
  System.out.println(uniqueList.toString());

上面的方法在List元素为基本数据类型及String类型时是可以的,但是如果List集合元素为对象,却不会奏效

复制代码

public class ObjectRidRepeat {    
    public static void main(String[] args) {
        List<User> userList = new ArrayList<User>();
        userList.add(new User("小黄",10));
        userList.add(new User("小红",23));
        userList.add(new User("小黄",78));
        userList.add(new User("小黄",10));        
        //使用HashSet,无序
        Set<User> userSet = new HashSet<User>();
        userSet.addAll(userList);
        System.out.println(userSet);        
        //使用LinkedHashSet,有序
        List<User> listNew = new ArrayList<User>(new LinkedHashSet(userList));
        System.out.println(listNew.toString());
    }
}

复制代码

User类结构如下:

输出如下:(没有去重)

2. 对象去重

 解决对象去重,可以利用for循环遍历的方式进行判断去重,但今天我不准备探究这种方法,要使用的是如下两种:

2.1 使用Java8新特性stream去重

复制代码

        //根据name属性去重
        List<User> unique1 = userList.stream().collect(
                collectingAndThen(
                        toCollection(() -> new TreeSet<>(comparing(User::getName))), ArrayList::new));

        System.out.println(unique1.toString());        //根据name,age属性去重
        List<User> unique2 = userList.stream().collect(
                collectingAndThen(
                        toCollection(() -> new TreeSet<>(comparing(o -> o.getName() + ";" + o.getAge()))), ArrayList::new)
        );

        System.out.println(unique2.toString());

复制代码

输出如下:

2.2 对象中重写equals()方法和hashCode()方法

 在User类中重写equals()方法和hashCode()方法:

复制代码

 //重写equals方法 @Override    public boolean equals(Object obj) {
        User user = (User) obj;        return name.equals(user.getName()) && (age==user.getAge());
    }//重写hashCode方法    @Override    public int hashCode() {
        String str = name + age;        return str.hashCode();
    }

复制代码

当再次执行通过Set去重的方法时,输出如下:

3. equals()方法和hashCode()方法探究

通过最具代表的的String中的equals()方法和hashCode()方法源码来探究两个方法的实现

3.1 equals()方法

复制代码

/**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)     */
    public boolean equals(Object anObject) {        if (this == anObject) {            return true;
        }        if (anObject instanceof String) {
            String anotherString = (String)anObject;            int n = value.length;            if (n == anotherString.value.length) {                char v1[] = value;                char v2[] = anotherString.value;                int i = 0;                while (n-- != 0) {                    if (v1[i] != v2[i])                        return false;
                    i++;
                }                return true;
            }
        }        return false;
    }

复制代码

比较两个对象时,首先先去判断两个对象是否具有相同的地址,如果是同一个对象的引用,则直接放回true;如果地址不一样,则证明不是引用同一个对象,接下来就是挨个去比较两个字符串对象的内容是否一致,完全相等返回true,否则false。

3.2 hashCode()方法

复制代码

 /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.     */
    public int hashCode() {        int h = hash;        if (h == 0 && value.length > 0) {            char val[] = value;            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }        return h;
    }

复制代码

当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

根据《Effective Java》第二版的第九条:覆盖equals时总要覆盖hashCode 中的内容,总结如下:

  • 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。

  • 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。

  • 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。

《Java编程思想》中也有类似总结:

  设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码。


  • 2019-12-10 16:21:05

    display:flex的子元素无法设置宽度

    子元素有个flex-shrink属性,表示在父元素宽度不够的情况下是自动收缩不?0表示不自动收缩,1表示自动收缩;所以将子元素(图片)添加属性:flex-shrink:0;即

  • 2019-12-10 21:14:11

    axios文件上传功能+formData

    在项目中使用axios上传文件,记得new一个纯净的axios或者考虑用ajax请求。因为axios在项目估计已经用了全局配置请求头等信息,这里的配置可能被全局请求头拦截,导致请求失败。 2.1构造formData 作者:exmexm 链接:https://www.jianshu.com/p/9c708a47d8a5 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 2019-12-11 16:04:15

    CSS中的 “var()” 和 “:root”

    var() var()函数可以代替元素中任何属性中的值的任何部分。var()函数不能作为属性名、选择器或者其他除了属性值之外的值。(这样做通常会产生无效的语法或者一个没有关联到变量的值。)

  • 2019-12-11 16:18:51

    npm发布vue组件

    开发之前先看看官网的 开发规范 我们开发的之后期望的结果是支持 import、require 或者直接使用 script 标签的形式引入,就像这样

  • 2019-12-11 16:21:00

    .vue文件 加scoped 样式不起作用

    在vue组件中,为了使样式私有化(模块化),不对全局造成污染,在style标签上添加scoped属性,以表示它只属于当下的模块。但是要慎用,因为在我们需要修改公共组件(第三方库或者项目中定制的组件)的样式的时候,scoped会造成很多困难,组要增加额外的复杂度。

  • 2019-12-11 16:22:04

    Vue中的scoped和scoped穿透,scoped原理

    在Vue文件中的style标签上有一个特殊的属性,scoped。当一个style标签拥有scoped属性时候,它的css样式只能用于当前的Vue组件,可以使组件的样式不相互污染。如果一个项目的所有style标签都加上了scoped属性,相当于实现了样式的模块化。

  • 2019-12-12 14:19:32

    laravel自定义分页LengthAwarePaginator

    有时候我们使用larave提供的后台分页数据库查询,有时候限制太多,我们需要自己定制分页功能。 下面是我给大家一个例子,我们可以根据例子,制作自己的分页功能。