Java中对List去重, Stream去重


要排序的对象User

@Data
@Builder
@AllArgsConstructor
public class User {
  private Integer id;
  private String name;
}

List<User> users = Lists.newArrayList(
        new User(1, "a"),
        new User(1, "b"),
        new User(2, "b"),
        new User(1, "a"));

1.用一个空list存放遍历后的数据

@Test
public void dis1() {
    List<User> result = new LinkedList<>();
    for (User user : users) {
      boolean b = result.stream().anyMatch(u -> u.getId().equals(user.getId()));
      if (!b) {
        result.add(user);
      }
    }

    System.out.println(result);
}

2.使用HashSet去重

HashSet是由HashMap来实现的

public HashSet() {
    map = new HashMap<>();
}

/**
* 显然,存在则返回false,不存在的返回true
*/
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

HashSet的去重就是根据HashMap实现的,而HashMap的实现又完全依赖于hashcode和equals方法

@Override
public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    User user = (User) o;
    return Objects.equals(id, user.id);
}

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


//hashcode
result = 31 * result + (element == null ? 0 : element.hashCode());

最终实现如下:

@Test
public void dis2() {
    Set<User> result = new HashSet<>(users);
    System.out.println(result);
}

3.使用JavaStream去重

对于大数据,采用Stream相关函数是最简单的了。正好Stream也提供了distinct函数

users.parallelStream().distinct().forEach(System.out::println);

但是没有到用lambda当作参数,也就是没有提供自定义条件。只有Javadoc标注了去重标准:

Returns a stream consisting of the distinct elements
(according to {@link Object#equals(Object)}) of this stream.

equals返回true的时候,hashcode的返回值必须相同。HashMap先根据hashcode方法定位,再比较equals方法。

所以,要使用distinct来实现去重,必须重写hashcode和equals方法,除非你使用默认的。

查看实现源码

<P_IN> Node<T> reduce(PipelineHelper<T> helper, Spliterator<P_IN> spliterator) {
    // If the stream is SORTED then it should also be ORDERED so the following will also
    // preserve the sort order
    TerminalOp<T, LinkedHashSet<T>> reduceOp
            = ReduceOps.<T, LinkedHashSet<T>>makeRef(LinkedHashSet::new, LinkedHashSet::add,
                                                     LinkedHashSet::addAll);
    return Nodes.node(reduceOp.evaluateParallel(helper, spliterator));
}

内部是用reduce实现,可以把Stream的元素拿出来和我自己内置的一个HashMap比较,有则跳过,没有则放进去。其实,思路与方法1相同

@Test
public void dis3() {
    users.parallelStream().filter(distinctByKey(User::getId))
        .forEach(System.out::println);
}

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}

声明:Wayen|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Java中对List去重, Stream去重


-当你感到最无助困难的时候,那就是离成功最近的时候。-Martin Frohm