一、distinct去重

1、distinct简单去重

Stream提供的distinct()方法除了去除重复的对象

private static void testDistinct() {
    int[] ints = {1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4};
    Arrays.stream(ints).distinct().forEach(number -> {
        System.out.println("number ->" + number);
    });
}

结果如下:

2、distinct根据指定的对象属性进行去重

注意:必须重写hashcode和equals方法

实体类

@Data
@AllArgsConstructor
public class UserInfo {
    private Integer Id;
    private String name;
    private Integer age;
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof UserInfo) {
            UserInfo tmp = (UserInfo) obj;
            if (this.getName().equals(tmp.getName())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

测试类

@SpringBootTest
public class distinctTest {
    @Test
    public void test() {
        List<UserInfo> list = new ArrayList();
        list.add(new UserInfo(1,"小明",1));
        list.add(new UserInfo(2,"小s",2));
        list.add(new UserInfo(1,"小明",2));
        list.add(new UserInfo(3,"小明",3));
        List<UserInfo> collect = list.stream().distinct().collect(Collectors.toList());
        System.out.println(collect);
    }
}

结果:

[UserInfo(Id=1, name=小明, age=1), UserInfo(Id=2, name=小s, age=2)]

有两种方式根据对象的属性进行去重,如下:

二、将对象的属性放入set再转为List(不建议)

List<Book> unique = books.stream().collect(collectingAndThen(toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getId()))), ArrayList::new));

使用上述代码可以根据指定元素去重(book的id属性),原理是把id作为key放入set中,然后在转换为list。这种方式能够去重,但不优雅,因为需要执行流的终端操作,把流转换为List。这样的话,流就不能继续使用了,不优雅,不好。但网上大多是这种方法。所以第三种方法才是我们要推荐的。

三、使用自定义的distinctByKey方法去重(建议)

unique = books.stream().filter(distinctByKey(o -> o.getId())).collect(Collectors.toList());

这种方法是不是看着优雅多了?非常的java8了?是的。但这个distinctByKey()这个方法是自定义的。定义如下:

private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

首先是filter方法,返回一个流,需要一个Predicate类型的参数(多嘴介绍下Predicate这个函数式接口,接受一个参数,返回布尔值)。

Stream<T> filter(Predicate<? super T> predicate)

filter根据Predicate返回的布尔值来判断是否要过滤掉,会过滤掉返回值为false的数据。而我们自己定义的distinctByKey返回值就是Predicate,所以可以作为参数传入filter。

distinctByKey也需要一个Function的参数。distinctByKey先是定义了一个线程安全的Map(相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,但同时降低了对读一致性的要求),因为在流计算中是多线程处理的,需要线程安全

然后将值作为key,TRUE作为value put到map中。这里的put方法使用的是putIfAbsent()。putIfAbsent()方法是如果key不存在则put如map中,并返回null。若key存在,则直接返回key所对应的value值。

其中putIfAbsent方法如下:

default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }

        return v;
    }

所以

seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;

这里用 == null判断是否存在于map了,若存在则返回false,不存在则为true。以用来达到去重的目的。

注:putIfAbsent()定义在Map接口中,是默认方法。

项目代码:

List<MaterialsTemplate> intelDrugStoreTemplateList = ExcelImportUtil.importExcel(file.getInputStream(), MaterialsTemplate.class, params);
List<MaterialsTemplate> distinct = intelDrugStoreTemplateList.stream().filter(distinctByKey(MaterialsTemplate::getMmNo)).collect(Collectors.toList());

案例:

实体类

@Data
@AllArgsConstructor
public class UserInfo {
    private Integer Id;
    private String name;
    private Integer age;
}

测试类

@SpringBootTest
public class distinctTest {
    @Test
    public void test() {
        List<UserInfo> list = new ArrayList();
        list.add(new UserInfo(1,"小明",1));
        list.add(new UserInfo(2,"小s",2));
        list.add(new UserInfo(1,"小明",2));
        list.add(new UserInfo(3,"小明",3));
        List<UserInfo> collect = list.stream().filter(distinctByKey(UserInfo::getName)).collect(Collectors.toList());
        System.out.println(collect);
    }

    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }
}

结果:

[UserInfo(Id=1, name=小明, age=1), UserInfo(Id=2, name=小s, age=2)]

四、Collectors.toMap方法去重

该方法只适合转成map后对key去重,在之前的distinct和filter中默认重复的数据去重只能保留之前的,没法做到后面重复的数据覆盖前面的,那Collectors.toMap方法就可以随意定义这个操作动作。

实体类

@Data
@AllArgsConstructor
public class UserInfo {
    private Integer Id;
    private String name;
    private Integer age;
}

测试类

@SpringBootTest
public class distinctTest {
    @Test
    public void test() {
        List<UserInfo> list = new ArrayList();
        list.add(new UserInfo(1,"小明",1));
        list.add(new UserInfo(2,"小s",2));
        list.add(new UserInfo(1,"小明",2));
        list.add(new UserInfo(3,"小明",3));
        Map<Integer, UserInfo> map4 = list.stream().collect(Collectors.toMap(UserInfo::getId, item -> item,(key1,key2) -> key2));
        System.out.println(map4);
    }
}

结果:

{1=UserInfo(Id=1, name=小明, age=2), 2=UserInfo(Id=2, name=小s, age=2), 3=UserInfo(Id=3, name=小明, age=3)}

其中(key1,key2) -> key1)就是定义如果出现重复,以前面key为准,如果要以后面数据为准,也就是让重复的数据用后面的覆盖,那么就可以写成:(key1,key2) -> key2)。

@SpringBootTest
public class distinctTest {
    @Test
    public void test() {
        List<UserInfo> list = new ArrayList();
        list.add(new UserInfo(1,"小明",1));
        list.add(new UserInfo(2,"小s",2));
        list.add(new UserInfo(1,"小明",2));
        list.add(new UserInfo(3,"小明",3));
        Map<Integer, UserInfo> map4 = list.stream().collect(Collectors.toMap(UserInfo::getId, item -> item,(key1,key2) -> key1));
        System.out.println(map4);
    }
}

结果:

{1=UserInfo(Id=1, name=小明, age=1), 2=UserInfo(Id=2, name=小s, age=2), 3=UserInfo(Id=3, name=小明, age=3)}

这种去重的方式好处是不需要重写equals方法,也不需要定义工具类,随时可以用,但仅限于转Map的场景。

 

posted on 2022-02-09 22:52  周文豪  阅读(8356)  评论(0编辑  收藏  举报