自定义方法,实现按指定属性进行去重

实例:根据age、sex、name对user对象集合进行去重

//构建user对象
public class User {
    private int id;
    private String name;
    private int age;
    private String sex;
    private double height;

    // 构造方法、getter和setter略
}

//创建对象集合
List<User> users = new ArrayList<>();
users.add(new User( 1, "张三", 20, "男", 170));
users.add(new User( 2, "李四", 22, "男", 175));
users.add(new User( 3, "张三", 20, "男", 170));
users.add(new User( 4, "王五", 21, "女", 162));
users.add(new User( 5, "赵六", 20, "女", 168));

//调用自定义方法
List<User> distinctUsers = users.stream()
    .filter(distinctByKey(user -> user.getName() + user.getAge() + user.getSex()))
    .collect(Collectors.toList());


// 自定义distinctByKey()方法,实现按指定属性进行去重
public 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;
}

上述代码中,distinctByKey()方法使用Map实现对元素去重的逻辑,filter()方法使用distinctByKey()方法返回的Predicate过滤重复元素,最终将结果收集到List集合中。

请注意,上述代码中的distinctByKey()方法是自定义的函数式接口,用于支持Stream API提供的所有接口方法。

t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null
这行代码使用Java 8的Lambda表达式实现了一个Predicate接口的匿名函数;

这个函数的作用是:对于List中的每个元素t,使用keyExtractor函数提取t对象的指定属性(这里是根据name、age和sex进行去重),并将该属性作为key放入一个ConcurrentHashMap实例中,如果这个key不存在,则返回null,表示元素t没有重复,该元素将被Stream API保留,如果这个key已经存在于ConcurrentHashMap中,则说明该元素已经出现过,将被Stream API去重丢弃。

具体而言,putIfAbsent(K key, V value)方法是ConcurrentHashMap中用于添加元素的原子方法,如果指定的key存在,则方法直接返回该key关联的value值;如果不存在,则将key和value添加到map中,并返回null。根据该方法的特性,我们可以利用putIfAbsent()方法进行去重操作。在上述Lambda表达式中,我们使用Boolean.TRUE表示ConcurrentHashMap中每个key对应的value值都为true,这个值对我们去重没有实际影响。

综上所述,该Lambda表达式的返回值是一个Predicate实例,用于对Stream API中的元素执行去重操作。

使用ConcurrentHashMap主要是为了保证多线程下的正确性。

在Stream API中,可能会出现并行stream的情况,也就是多线程处理集合中的元素。在这种情况下,如果使用非线程安全的HashMap,那么在出现线程竞争的情况下,可能会导致putIfAbsent()方法的返回值出现问题,多线程之间可能会出现覆盖的情况。这样就会导致去重操作失效,出现重复的元素。

因此,针对多线程的情况,使用线程安全的ConcurrentHashMap来实现去重操作,可以保证程序的正确性。注意,虽然Stream API中的filter()方法等是线程安全的,但内部实现会根据使用情况而选择串行执行或者并行执行,因此,无法完全保证多线程的正确性,因此,使用线程安全的容器类可以起到一个保险作用,将程序的正确性保障得更加充分。

posted @ 2023-05-30 17:39  德华。  阅读(25)  评论(0编辑  收藏  举报