collections stream语法 - Java 8独特的财产





stream使用 java8 (14)


我们也可以使用RxJava (非常强大的反应扩展库)

Observable.from(persons).distinct(Person::getName)

要么

Observable.from(persons).distinct(p -> p.getName())

在Java 8中,如何通过检查每个对象的属性的独特性来使用Stream API过滤集合?

例如,我有一个Person对象列表,我想删除具有相同名称的人员,

persons.stream().distinct();

将使用Person对象的默认相等检查,所以我需要类似的东西,

persons.stream().distinct(p -> p.getName());

不幸的是, distinct()方法没有这样的重载。 在不修改Person类中的相等性检查的情况下,可以简洁地做到这一点?




您可以编写的最简单的代码:

    persons.stream().map(x-> x.getName()).distinct().collect(Collectors.toList());



您可以使用groupingBy收集器:

persons.collect(groupingBy(p -> p.getName())).values().forEach(t -> System.out.println(t.get(0).getId()));

如果你想有另一个流,你可以使用这个:

persons.collect(groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));



您可以在Eclipse集合中使用distinct(HashingStrategy)方法。

List<Person> persons = ...;
MutableList<Person> distinct =
    ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));

如果您可以重构persons以实现Eclipse Collections界面,则可以直接在列表中调用方法。

MutableList<Person> persons = ...;
MutableList<Person> distinct =
    persons.distinct(HashingStrategies.fromFunction(Person::getName));

HashingStrategy只是一个策略接口,允许您定义equals和hashcode的自定义实现。

public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}

注意:我是Eclipse集合的提交者。




扩展斯图尔特马克斯的答案,这可以以更短的方式完成,没有并发映射(如果你不需要并行流):

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

然后打电话:

persons.stream().filter(distinctByKey(p -> p.getName());



实现这个最简单的方法是跳转排序功能,因为它已经提供了一个可选的Comparator ,它可以使用元素的属性创建。 然后你必须使用statefull Predicate来完成可以完成的重复项,这个Predicate使用了一个事实,对于一个已排序的流,所有相等的元素都是相邻的:

Comparator<Person> c=Comparator.comparing(Person::getName);
stream.sorted(c).filter(new Predicate<Person>() {
    Person previous;
    public boolean test(Person p) {
      if(previous!=null && c.compare(previous, p)==0)
        return false;
      previous=p;
      return true;
    }
})./* more stream operations here */;

当然,有状态Predicate不是线程安全的,但是如果这是您的需要,您可以将此逻辑移入Collector ,让流使用Collector时保证线程安全。 这取决于你想对你在问题中没有告诉我们的不同元素流做什么。




另一种解决方案,使用Set 。 可能不是理想的解决方案,但它的工作原理

Set<String> set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());

或者,如果您可以修改原始列表,则可以使用removeIf方法

persons.removeIf(p -> !set.add(p.getName()));



基于@ josketres的回答,我创建了一个通用实用程序方法:

你可以通过创建一个Collector来使这个Java 8更友好。

public static <T> Set<T> removeDuplicates(Collection<T> input, Comparator<T> comparer) {
    return input.stream()
            .collect(toCollection(() -> new TreeSet<>(comparer)));
}


@Test
public void removeDuplicatesWithDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(7), new C(42), new C(42));
    Collection<C> result = removeDuplicates(input, (c1, c2) -> Integer.compare(c1.value, c2.value));
    assertEquals(2, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 7));
    assertTrue(result.stream().anyMatch(c -> c.value == 42));
}

@Test
public void removeDuplicatesWithoutDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(1), new C(2), new C(3));
    Collection<C> result = removeDuplicates(input, (t1, t2) -> Integer.compare(t1.value, t2.value));
    assertEquals(3, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 1));
    assertTrue(result.stream().anyMatch(c -> c.value == 2));
    assertTrue(result.stream().anyMatch(c -> c.value == 3));
}

private class C {
    public final int value;

    private C(int value) {
        this.value = value;
    }
}



我做了一个通用版本:

private <T, R> Collector<T, ?, Stream<T>> distinctByKey(Function<T, R> keyExtractor) {
    return Collectors.collectingAndThen(
            toMap(
                    keyExtractor,
                    t -> t,
                    (t1, t2) -> t1
            ),
            (Map<R, T> map) -> map.values().stream()
    );
}

一个例子:

Stream.of(new Person("Jean"), 
          new Person("Jean"),
          new Person("Paul")
)
    .filter(...)
    .collect(distinctByKey(Person::getName)) // return a stream of Person with 2 elements, jean and Paul
    .map(...)
    .collect(toList())



考虑distinct的是一个有状态的过滤器 。 这是一个函数,它返回一个谓词,该谓词保持之前所看到的状态,并返回给定元素是否第一次被查看:

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

然后你可以写:

persons.stream().filter(distinctByKey(Person::getName))

请注意,如果流是有序的并且是并行运行的,则这将保留重复项中的任意元素,而不是第一个元素,就像distinct()一样。

(这与我对这个问题的回答基本相同: 在任意键上的Java Lambda Stream Distinct()?




Saeed Zarinfam使用了类似的方法,但更多的Java 8风格:)

persons.collect(groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(toList());



您可以将人物对象包装到另一个类中,该类仅比较人物的姓名。 之后,您可以打开包装的对象以再次获取人员流。 流操作可能如下所示:

persons.stream()
    .map(Wrapper::new)
    .distinct()
    .map(Wrapper::unwrap)
    ...;

Wrapper可能看起来如下所示:

class Wrapper {
    private final Person person;
    public Wrapper(Person person) {
        this.person = person;
    }
    public Person unwrap() {
        return person;
    }
    public boolean equals(Object other) {
        if (other instanceof Wrapper) {
            return ((Wrapper) other).person.getName().equals(person.getName());
        } else {
            return false;
        }
    }
    public int hashCode() {
        return person.getName().hashCode();
    }
}



另一种方法是将人员作为关键字放置在地图中:

persons.collect(toMap(Person::getName, p -> p, (p, q) -> p)).values();

请注意,如果名称重复,则保留的人员将成为第一名。




在java-8 Streams Reducer中的简单工作是一个函数,它将两个值作为输入并在计算后返回结果。 这个结果在下一次迭代中被提供。

在Math:max函数的情况下,方法不断返回传递的最大值两个值,最后您手中的数字最大。





java collections java-8 java-stream